Skip to content

fix(js): allow timer waits without Bun FFI - #1996

Merged
Lorenzobattistela merged 3 commits into
bendlang:mainfrom
EpsilonPhoenix:fix-js-timer-no-ffi
Nov 3, 2026
Merged

Lorenzobattistela merged 3 commits into
bendlang:mainfrom
EpsilonPhoenix:fix-js-timer-no-ffi

Conversation

@EpsilonPhoenix

@EpsilonPhoenix EpsilonPhoenix commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

A generated program containing IO.sleep(1) fails under Node with Cannot find module bun:ffi: the scheduler loads libc even when it is only waiting for a timer. Use Atomics.wait for waits with no file descriptors; socket waits continue to use select, and runnable work keeps its nonblocking poll. This also enables timer-backed channel deadlines and IO.within under Node.

The regression makes the FFI table inaccessible and checks forked sleep ordering and IO.within races. Nearby runtime comments are shortened to stay within the existing compiler token cap.

Validation:

  • New regression fails before the fix and passes under Bun and Node afterward.
  • sleep_timer, sleep_order, within, within_busy, and chan_try pass under Bun and Node.
  • spawn_sleep, select_eintr, tcp_try, udp_try, tcp_backlog_timer, and udp_backlog_timer pass under Bun.
  • Repository gate: 54/54. Hello-world proof project passes (does not prove runtime effects).
  • Independent Terra medium review: no findings.

chan_try_wake_timers produces its expected deliberate deadlock and exit code 1 with both the old and new wait implementations. Mini-cluster and macOS gates were not run.

@Lorenzobattistela

Lorenzobattistela commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks, the fix looks right. All gates pass merged onto main a346ded (repo 54/54, test 1803/1803, perf 124/124, ping 81/81, safe 0 false rejects), and the test passes under bun and node. comp.ts lands at exactly 66,000 / 66k.

One gap in the test: Within.main() returns before its 300 ms "late" sleep fires, so Timer.restore() puts BEND_SYS back while that sleep is still pending. The longest timer-only wait in the test therefore runs with the FFI table visible. I checked this with a mutation that loads FFI only on waits over 100 ms (fds.length > 0 || ms > 100). The current test still passes elow the test fails on it as it should.

Could you wait out the late sleep before restoring?

Within.main():

    # Within's 300 ms "late" sleep outlives its main: wait it out before
    # the FFI table comes back, so that last timer wait runs hidden too.
    u : Unit <- IO.sleep(400)
    Timer.restore()

(Alternatively, enable "Allow edits by maintainers" and we'll push it.)

@Lorenzobattistela
Lorenzobattistela merged commit 37802e4 into bendlang:main Nov 3, 2026
1 check passed
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.

2 participants