You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
With one actor, a send runs the handler inside the sender, so a blocking handler deadlocks the program #3112
A send (!) is asynchronous: the sender goes on, and the actor handles the message on a scheduler thread. With only one actor, it isn't. The runtime's main-thread mode (docs/runtime-optimizations.md, "Main Thread Actor Mode") runs the first actor's handler synchronously inside the sender's !, until a second actor is spawned. So what a program does depends on how many actors it has:
message Drain { conn: ptr, want: int }
actor Reader { receive { Drain(conn, want) -> { /* read `want` bytes from conn */ } } }
main() {
// ... a connected pair: client, conn ...
r = spawn(Reader())
r ! Drain { conn: conn, want: 1048576 } // runs Reader's handler right here
tcp.write_all(client, data) // never reached: the reader waits for these bytes
}
That program hangs; spawn a second actor that does nothing and it runs.
A blocking handler blocks the sender. A server actor started with a ! Start {} never returns control to main, and a reader that waits for main deadlocks. The tests and examples avoid this by spawning a dummy actor first (spawn(SchedHelper()), spawn(Sched())), which 43 files in tests/ and contrib/ do for no other reason.
Order changes with the count. With one actor, the handler has finished before ! returns. With two, it runs later on another thread. Code that is correct with two actors can observe a different interleaving with one, and the reverse.
An embedding host loses its actors. When C holds the main thread, an actor in main-thread mode cannot run, so docs/c-embedding.md has the host call aether_scheduler_poll between frames to make progress.
The message payload points at the sender's stack (g_skip_free), which holds only because the handler finishes before the sender's frame does.
Fix:! is asynchronous whatever the actor count. The threaded runtime delivers through the scheduler from the first actor on, and main-thread mode, its main_thread_only path and the stack-pointer payload are removed. The cooperative (no-threads) runtime queues a send and runs it at the next wait_for_idle or poll, as it does once there is more than one actor. The single-actor message path is measured before and after (the ping-pong and counter benchmarks), and the dummy actors in the 43 files are removed. The docs that describe main-thread mode, and the embedding guide's poll requirement for it, are updated.
Fixed by bed800c, "A send is asynchronous whatever the actor count; send batches only wrap loops that cannot wait (#3112, #3117)", which merged with #3101 (the commit named the issue without a closing keyword, so it stayed open). Checked on main at 00df4c7 (0.848.0), MinGW gcc, Windows: tests/regression/test_send_async_one_actor.ae passes.
Activity
A send is asynchronous whatever the actor count; send batches only wr…
fixup! A send is asynchronous whatever the actor count; send batches …
nicolas-maman commentedon Oct 26, 2026
Fixed by bed800c, "A send is asynchronous whatever the actor count; send batches only wrap loops that cannot wait (#3112, #3117)", which merged with #3101 (the commit named the issue without a closing keyword, so it stayed open). Checked on main at 00df4c7 (0.848.0), MinGW gcc, Windows: tests/regression/test_send_async_one_actor.ae passes.