Repository navigation
Drain closing handles after the exit event - #160
Closed
bavulapati wants to merge 3 commits into
Closed
bavulapati wants to merge 3 commits into
bavulapati wants to merge 3 commits into
Conversation
Pump the loop once (non-blocking) after emitting 'exit' so handles closed during the exit event release before bare_run() returns, rather than lingering until bare_teardown(). This lets resources whose lifetime is tied to the process - e.g. an IPC channel an embedder closes on exit - be seen as closed by the peer without waiting for teardown.
kasperisager
requested changes
Jun 30, 2026
kasperisager
left a comment
Contributor
There was a problem hiding this comment.
This provides an escape hatch for I/O callbacks and microtasks to run between exit firing and teardown handlers running, which is strictly forbidden. That's also why exit event handlers are forbidden from queuing additional I/O.
Whatever fix we need must be made in Bare Kit, breaking the teardown contract is not the solution.
Contributor
|
The tests introduced in #161 show why this is problematic. |
bavulapati
added a commit
to holepunchto/bare-kit
that referenced
this pull request
Aug 13, 2026
Bare Kit is in-process (worklets are threads), so the pipe-based IPC with its async close-on-exit was the wrong tool and the root of the EOF and win32 problems (holepunchto/bare#160, #92). Move the transport onto the in-process queue primitive: - Bare.IPC becomes a bare-stream Duplex over native queue ops that worklet.c injects (read/write/close/ref/unref); end() closes the queue so the host reaches EOF. - The host bare_ipc runs over the queue's thread port; the apple poll is a manual dispatch source fired by the queue signal, reporting readiness via bare_queue_readable/writable. - The worklet closes its queue end synchronously after bare_run, so the host reaches EOF on every exit path (loop drain, Bare.exit, IPC.end, terminate) with no loop pump and no async scheduled from exit. - bare_worklet_destroy joins the worklet thread (terminating and waking it first) so the runtime and queue finish tearing down before the host returns. - Delete the pipe posix/ipc.c. Darwin only so far: android/linux/win32 polls are not yet ported. All 14 darwin worklet and queue tests pass.
bavulapati
added a commit
to holepunchto/bare-kit
that referenced
this pull request
Aug 14, 2026
Bare Kit is in-process - worklets are threads - so the pipe transport, with its async close on exit, was the wrong tool and the root of the EOF and win32 problems (holepunchto/bare#160, #92). Bare.IPC becomes a bare-stream Duplex over the queue, and the worklet closes its end synchronously after bare_run, so the host reaches EOF on every exit path with no loop pump and nothing scheduled from the exit event. The host polls readiness rather than reading a pipe: apple keeps its dispatch source, linux and win32 now share one uv implementation instead of a hand-rolled epoll thread and a pipe loop, and android keeps its looper with the ipc owning the eventfd. bare_ipc_get_incoming and bare_ipc_get_outgoing go with the pipes; the win32 exports follow. The two EOF tests now run on win32, which is what this was for.
bavulapati
added a commit
to holepunchto/bare-kit
that referenced
this pull request
Aug 14, 2026
Bare Kit is in-process - worklets are threads - so the pipe transport, with its async close on exit, was the wrong tool and the root of the EOF and win32 problems (holepunchto/bare#160, #92). Bare.IPC becomes a bare-stream Duplex over the queue, and the worklet closes its end synchronously after bare_run, so the host reaches EOF on every exit path with no loop pump and nothing scheduled from the exit event. The host polls readiness rather than reading a pipe: apple keeps its dispatch source, linux and win32 now share one uv implementation instead of a hand-rolled epoll thread and a pipe loop, and android keeps its looper with the ipc owning the eventfd. The two poll tests closed their loop while their poll was still armed. Pipes hid that: their EOF arrived asynchronously at thread teardown, while the queue closes synchronously, so the wake lands in the window and the callback posts to a closed loop - a segfault on linux-x64. The poll is retired first now. bare_ipc_get_incoming and bare_ipc_get_outgoing go with the pipes; the win32 exports follow. Both EOF tests now run on win32, which is what this was for.
bavulapati
added a commit
to holepunchto/bare-kit
that referenced
this pull request
Aug 14, 2026
Bare Kit is in-process - worklets are threads - so the pipe transport, with its async close on exit, was the wrong tool and the root of the EOF and win32 problems (holepunchto/bare#160, #92). Bare.IPC becomes a bare-stream Duplex over the queue, and the worklet closes its end synchronously after bare_run, so the host reaches EOF on every exit path with no loop pump and nothing scheduled from the exit event. The host polls readiness rather than reading a pipe: apple keeps its dispatch source, linux and win32 now share one uv implementation instead of a hand-rolled epoll thread and a pipe loop, and android keeps its looper with the ipc owning the eventfd. The two poll tests closed their loop while their poll was still armed. Pipes hid that: their EOF arrived asynchronously at thread teardown, while the queue closes synchronously, so the wake lands in the window and the callback posts to a closed loop - a segfault on linux-x64. The poll is retired first now. bare_ipc_get_incoming and bare_ipc_get_outgoing go with the pipes; the win32 exports follow. Both EOF tests now run on win32, which is what this was for.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Comes out of holepunchto/bare-kit#83 / holepunchto/bare-kit#92. A bare-kit worklet ties
Bare.IPCto the process lifetime: when the worklet exits, it closesBare.IPCso the host's read reaches EOF and the host can detect a stopped worklet.The problem is a chicken-and-egg around teardown. Closing a pipe is async (
uv_closeneeds a loop turn). Theexitevent fires after the loop has already stopped (bare_runtime_runexits itsuv_runloop, then callsbare_runtime__on_exit), so a close issued fromexitis queued but never completes untilbare_teardownpumps the loop. An embedder that parks betweenbare_runreturning andbare_teardown(waiting for the host to tear it down) therefore never flushes the close - and the host never sees EOF, so it never tears down. Deadlock.Change
After
bare_runtime__on_exit, pump the loop once, non-blocking:This drains handles closed during the
exitevent so they release beforebare_run()returns. It's bounded - a single non-blocking iteration - and is the same pumpbare_teardownalready does (bare_runtime_teardownrunsuv_run(loop, UV_RUN_NOWAIT)), just early enough that process-scoped resources release as the process exits.Validation
Verified end to end in bare-kit: with this flush applied (and bare-kit's own loop-pump workaround removed), the IPC EOF tests pass on real Windows (VMware Fusion VM) and the equivalent pump is green on CI across win32-x64, win32-arm64, linux, darwin, ios, android.
Note on tests
I didn't add a bare-side test: the behavior is close-timing relative to
bare_runreturning, which isn't cleanly observable from a JS test (you can't run JS afterexit). Happy to add a C test in whatever shape you prefer if you'd like one here - the downstream bare-kit EOF tests exercise it.Refs holepunchto/bare-kit#92, holepunchto/bare-kit#83