Expand description
GIL-free completion wake for Handle.as_asyncio (HDL-17 to HDL-21 in
crate::handle).
Each asyncio loop that can watch a file descriptor (loop.add_reader,
which every selector-based loop, including all of Monarch’s, provides) gets
one [LoopWake]: a queue of u64 tokens and a non-blocking pipe whose read
end the loop watches. A loop without add_reader keeps the
call_soon_threadsafe path (HDL-21).
waker and read_fd are the two ends of the pipe; tx and rx are the
two ends of the token queue. When a Handle completes, its Tokio observer
pushes its token and writes one byte to the pipe ([Notifier::notify]),
without the GIL. The byte only wakes the loop; the queue says which awaits
completed. The loop’s reader drains the pipe, then the queue, and settles
the future of each completed await.
The loop’s selector is the channel’s only strong owner, through its
on_readable callback. The registry holds the loop as a weak key and the
channel through a weak reference. Each entry holds its pending await’s
future strongly, because that reference is the waiting task’s only owner
outside the task’s own cycle; asyncio holds tasks weakly. Closing the loop
therefore releases the channel, its read end, its entries and their
futures; the write end lasts until every notifier is done (HDL-20).
__traverse__ lets the garbage collector free an unclosed loop (HDL-19,
which also records the case that is not freed):
registry: weak(loop) ──▶ weak(LoopWake)
loop's selector: read_fd ──▶ LoopWake.on_readable
LoopWake
├─ tx / rx queue of u64 tokens
├─ waker pipe write end, shared with every Notifier
├─ read_fd pipe read end, registered with `add_reader`
└─ entries token → (asyncio future, Handle)