Skip to content

fix(iocp): share Async state across copies - #251

Open
Pyre909 wants to merge 1 commit into
mitchellh:mainfrom
Pyre909:pr/async-iocp-copy
Open

Pyre909 wants to merge 1 commit into
mitchellh:mainfrom
Pyre909:pr/async-iocp-copy

Conversation

@Pyre909

@Pyre909 Pyre909 commented Oct 9, 2026

Copy link
Copy Markdown

On Windows, xev.Async breaks once you copy it. The IOCP version keeps its state (the lock, the “a notify came in early” flag, and the waiting completion) inside the struct itself. So if you copy an Async before calling wait(), the copy has its own private state, and notify() on it never wakes the loop. The eventfd and mach port versions don’t have this problem, because every copy points at the same kernel object.

Ghostty runs into this: it gives its IO thread a copy of the renderer’s Async. On Windows that means new terminal output only shows up on the next cursor blink, up to ~600 ms late. You mentioned in ghostty-org/ghostty#12050 that the fix should be making Async copy-by-value in libxev on Windows, so here’s a go at that.

The fix moves the state into a small heap allocation made in init() and freed in deinit(), so all copies share it, like the other backends share their fd or port. As with those, only one copy should call deinit().

I know libxev doesn’t really allocate anywhere else, so I wanted to flag it. The no-allocation option I looked at was a Windows event that copies share, tied to the loop’s completion port with NtAssociateWaitCompletionPacket. But that’s an undocumented ntdll call and would need a new completion type in the IOCP backend, so it felt like too much for this fix. Happy to go that way if you’d prefer it.

Testing:

Added a test that notifies through a copy taken before wait(). It fails without the fix and passes with it.
Windows 11, x86_64 and ARM64: 51 passed, 10 skipped, 0 failed
macOS: all pass
Didn’t run Linux locally; CI should cover it

AI disclosure: I used Claude Code to track down the bug, write the fix and test, and run the Windows tests. I’ve reviewed the change and understand it.

@Pyre909
Pyre909 force-pushed the pr/async-iocp-copy branch from 8d35a28 to 6562b87 Compare October 19, 2026 21:46
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