Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/integration.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,27 @@ pub fn run_async_main(main : async () -> Unit) -> Unit {
let _ = @coroutine.spawn(main)
@event_loop.reschedule()
}

///|
/// Scheduler-only entry for wasm / wasm-gc. The async library does not
/// provide IO on these targets (`sleep`, `Timer`, sockets, fs, etc. all
/// `abort` via the existing `unimplemented.mbt` stubs), but pure
/// coroutine code (`pause`, `with_task_group`, `aqueue`, `semaphore`,
/// `cond_var`) does not depend on the event loop and works fine — this
/// shim just drains the ready queue.
#coverage.skip
#cfg(target="wasm")
#doc(hidden)
pub fn run_async_main(main : async () -> Unit) -> Unit {
let _ = @coroutine.spawn(main)
@event_loop.reschedule()
}

///|
#coverage.skip
#cfg(target="wasm-gc")
#doc(hidden)
pub fn run_async_main(main : async () -> Unit) -> Unit {
let _ = @coroutine.spawn(main)
@event_loop.reschedule()
}
9 changes: 9 additions & 0 deletions src/internal/event_loop/unimplemented.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ pub fn Timer::cancel(timer : Timer) -> Unit {
ignore(timer)
abort("`Timer::cancel()` is unimplemented on WASM backend")
}

///|
/// WASM stub for the scheduler-only `reschedule` entry point. Mirrors
/// the JS shim — no IO, just drains ready coroutines until none remain.
pub fn reschedule() -> Unit {
while !@coroutine.no_more_work() {
@coroutine.reschedule()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid spinning when no runnable coroutine exists

reschedule() loops until @coroutine.no_more_work() is true, but no_more_work() also depends on the internal blocking counter. If a coroutine calls @coroutine.suspend() and no coroutine is ready (for example, waiting for an external callback or a deadlocked wait), this condition stays false while the ready queue is empty, so this loop becomes a tight CPU spin that never yields back to the host. On wasm/wasm-gc this can deadlock embedding scenarios the shim is meant to support, because the host event loop cannot run while this function is busy-looping.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 4c1b564. Switched the guard from !@coroutine.no_more_work() to @coroutine.has_immediately_ready_task() (= !scheduler.run_later.is_empty()). Now the loop drains only immediately-ready work; if all ready coroutines are processed and only blocked ones remain (e.g., suspended on an external host callback), reschedule() returns and yields back to the host. The host can re-enter via run_async_main when new work becomes available, mirroring the spirit of the JS shim (which delegates continuation to set_timeout(0, reschedule)).

}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline this loop into call sites

Loading