Skip to content

ccan: stale polling - #9457

Open
niftynei wants to merge 2 commits into
ElementsProject:masterfrom
niftynei:review/ccan-stale-poll
Open

ccan: stale polling #9457
niftynei wants to merge 2 commits into
ElementsProject:masterfrom
niftynei:review/ccan-stale-poll

Conversation

@niftynei

Copy link
Copy Markdown
Collaborator

Adds a new mode to ccan's io, io_poll_protect_stale_fds which adds another layer of accounting for file descriptors.

We aim to protect daemons which replace connections inside readiness callbacks from delivering an old poll result to a newly registered fd object.

Not sure if these should also be upstreamed in ccan?

@niftynei
niftynei force-pushed the review/ccan-stale-poll branch from 792abec to aedb145 Compare August 31, 2026 17:12
A callback can close a connection while readiness from the current poll call
is still being dispatched.  Deleting that connection compacts the fd table,
and registering a replacement can restore its previous length with different
occupants.  A snapshot tied to mutable table positions can consequently skip
an original event or deliver stale readiness to the replacement.

Give each fd a stable registration object and retain that registration while a
poll result refers to it.  Deletion clears the registration's fd pointer, so
pending readiness for a retired connection is ignored, while a replacement
receives a distinct registration and cannot inherit the old event.

Snapshot only entries with nonzero revents and preserve the IO_ALWAYS marker
in the existing fairness order.  Reuse a growable snapshot buffer between loop
iterations and dispatch directly through the retained registration, avoiding
both a fresh allocation on every poll and the repeated linear lookup required
by the earlier generation-based approach.

Register one process-exit cleanup for that reusable buffer.  Retaining it for
the daemon lifetime preserves allocation reuse, while releasing it after event
dispatch can no longer be active avoids a reachable allocation in Valgrind and
LeakSanitizer runs.

Changelog-Fixed: io: replacing a file descriptor during poll dispatch no longer delivers stale readiness events to the replacement connection.
Closing a connection while dispatching poll results compacts the fd table.  If
the callback immediately registers a replacement, the table can return to its
original length with a different connection occupying a slot represented in
the current poll result.  Dispatching readiness by mutable table position can
then deliver an old event to the replacement or skip another ready connection.

Use a fake poll implementation to make three original connections ready.  The
connection selected first closes itself and installs a replacement while the
other readiness events remain pending.  Verify that every original event is
delivered exactly once, that the replacement does not inherit stale readiness,
and that it is handled normally by the following poll call.

Register an exit assertion before enabling protection so atexit LIFO ordering
checks that the backend releases its reusable snapshot buffer at process exit.
This keeps leak-sensitive unit runs from accepting a permanently reachable
allocation.

Add the regression to check-units so this CCAN behavior is covered by the
project's normal unit-test suite.

Changelog-None
@niftynei
niftynei force-pushed the review/ccan-stale-poll branch from aedb145 to e80ded2 Compare September 3, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant