Skip to content

perf_hooks: implement performance.eventLoopUtilization() - #32618

Open
robobun wants to merge 9 commits into
mainfrom
farm/4b495614/worker-threads-elu
Open

perf_hooks: implement performance.eventLoopUtilization()#32618
robobun wants to merge 9 commits into
mainfrom
farm/4b495614/worker-threads-elu

Conversation

@robobun

@robobun robobun commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Fixes the eventLoopUtilization() part of #32609.

Repro

const { performance } = require("perf_hooks");
const elu1 = performance.eventLoopUtilization();
const t = Date.now();
while (Date.now() - t < 200);
console.log(performance.eventLoopUtilization(elu1));
// Node: { idle: ~0, active: ~200, utilization: ~1 }
import { Worker, isMainThread, parentPort } from "worker_threads";
if (isMainThread) {
  const worker = new Worker(new URL(import.meta.url));
  setInterval(() => console.log(worker.performance.eventLoopUtilization()), 100).unref();
} else {
  // ...busy work...
}

Before this change both return a zeroed stub and the worker variant also logs:

NotImplementedError: worker_threads.Worker.performance is not yet implemented in Bun.
 code: "ERR_NOT_IMPLEMENTED"
      at eventLoopUtilization (node:worker_threads)
{ idle: 0, active: 0, utilization: 0 }

Cause

performance.eventLoopUtilization() (src/js/node/perf_hooks.ts) and
Worker.performance.eventLoopUtilization() (src/js/node/worker_threads.ts)
were hardcoded stubs returning { idle: 0, active: 0, utilization: 0 }. Bun
never tracked how long its event loop spent idle.

Fix

usockets now records the time the loop is blocked in the event provider. On
POSIX, us_loop_run_bun_tick wraps the epoll_pwait2 / kevent64 call with a
monotonic clock and accumulates the delta into a per-loop counter (only for a
real idle wait, not a zero-timeout poll-through). On Windows the idle time comes
from libuv's uv_metrics_idle_time. A new us_loop_event_loop_utilization
returns idle and active (elapsed since loop creation minus idle) in
nanoseconds.

performance.eventLoopUtilization() reads the current thread's loop;
worker.performance.eventLoopUtilization() reads the worker's loop from the
parent thread under the worker's vm_lock (the loop lives in the worker's
arena; the lock serialises against teardown, the idle counter is read
atomically, and the creation timestamp is immutable, so no reference into the
worker VM is formed). The utilization ratio and the diff against the optional
prior-sample arguments are computed in a shared JS helper that mirrors Node's
internal/perf/event_loop_utilization.

The "stub prints forever" symptom in #32609 is a separate bug: parentPort.unref()
is a no-op, so a worker with a message listener never exits. That is fixed by #30549
(parentPort.ref()/unref()/hasRef()/close()); this PR does not touch it.

Verification

bun bd test test/js/node/perf_hooks/perf_hooks.test.ts
bun bd test test/js/node/worker_threads/worker_threads.test.ts -t eventLoopUtilization

New tests assert active time after busy work, idle time after awaiting, and the
worker's activity sampled from the parent. They fail on the released binary
(return 0) and pass with the fix.


[review] gate passed · iteration 26 · 19 files touched

fails on main (without fix)
ASAN without fix: 6 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/perf_hooks/perf_hooks.test.ts test/js/node/worker_threads/worker_threads.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (1b4d69bac)

test/js/node/worker_threads/worker_threads.test.ts:
(pass) support eval in worker [1738.27ms]
(pass) all worker_threads module properties are present [25.35ms]
(pass) markAsUncloneable and markAsUntransferable markers are private, unforgeable, and permanent [24.83ms]
(pass) all worker_threads worker instance properties are present [161.49ms]
(pass) threadId module and worker property is consistent [227.56ms]
(pass) receiveMessageOnPort works across threads [1671.64ms]
(pass) receiveMessageOnPort works as FIFO [13.31ms]
(pass) you can override globalThis.postMessage [1645.10ms]
(pass) support require in eval [1655.48ms]
cwd /workspace/bun
realpath test/js/node/wo
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (76a1c9673)

test/js/node/worker_threads/worker_threads.test.ts:
(pass) support eval in worker [29.26ms]
(pass) all worker_threads module properties are present [0.52ms]
(pass) markAsUncloneable and markAsUntransferable markers are private, unforgeable, and permanent [0.63ms]
(pass) all worker_threads worker instance properties are present [3.59ms]
(pass) threadId module and worker property is consistent [4.06ms]
(pass) receiveMessageOnPort works across threads [26.52ms]
(pass) receiveMessageOnPort works as FIFO [0.25ms]
(pass) you can override globalThis.postMessage [30.08ms]
(pass) support require in eval [31.05ms]
cwd /workspace/bun
realpath test/js/node/worker_threads/fixture-argv.js
(pass) support require in eval for a file [29.86ms]
(pass) support require in eval for a file that doesnt exist [25.92ms]
(pass) support worker eval that throws [28.02ms]
(pass) execArgv option > inherits the parent's execArgv when falsy or unspecified [137.48ms]
(pass) execArgv option > provides empty execArgv when passed an empty array [66.66ms]
(pass) execArgv option > can specify an array of strings [83.45ms]
(pass) eval does not leak source code [2154.5
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/perf_hooks/perf_hooks.test.ts test/js/node/worker_threads/worker_threads.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (1b4d69bac)

test/js/node/worker_threads/worker_threads.test.ts:
(pass) support eval in worker [1759.23ms]
(pass) all worker_threads module properties are present [24.99ms]
(pass) markAsUncloneable and markAsUntransferable markers are private, unforgeable, and permanent [24.80ms]
(pass) all worker_threads worker instance properties are present [169.66ms]
(pass) threadId module and worker property is consistent [207.39ms]
(pass) receiveMessageOnPort works across threads [1692.79ms]
(pass) receiveMessageOnPort works as FIFO [13.90ms]
(pass) you can override globalThis.postMessage [1681.95ms]
(pass) support require in eval [1647.33ms]
cwd /workspace/bun
realpath test/js/node/wo
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 727ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/110] gen JSBuffer.lut.h
Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h from /workspace/bun/src/jsc/bindings/JSBuffer.cpp
[2/110] gen generated_host_exports.rs
generated_host_exports.rs: 91 exports (host=3, lazy=10, generic=78, rust=0); 243 extern-C blocks audited
[3/110] gen cpp.rs (cppbind)
[4/110] gen JS modules (bundle-modules)
Preprocess modules (6547ms)
Bundle modules (37ms)
Postprocesss modules (118ms)
Bundle Functions (762ms)
Generate Code (81ms)

[7.56s] Bundled "src/js" for production
  1912 kb
  162 internal modules
  12 native modules
  90 internal functions across 19 files
[4/110] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gn
... (truncated)
diff hotspot
packages/bun-usockets/src/eventing/epoll_kqueue.c  |  32 ++-
 packages/bun-usockets/src/eventing/libuv.c         |   4 +
 packages/bun-usockets/src/internal/internal.h      |  10 +-
 packages/bun-usockets/src/internal/loop_data.h     |  16 ++
 packages/bun-usockets/src/loop.c                   |  55 +++-
 src/js/internal/shared.ts                          |  41 ++-
 src/js/node/perf_hooks.ts                          |  20 +-
 src/js/node/worker_threads.ts                      |  29 +-
 src/jsc/bindings/webcore/JSWorker.cpp              |  19 ++
 src/jsc/bindings/webcore/Worker.cpp                |  19 ++
 src/jsc/bindings/webcore/Worker.h                  |   3 +
 src/jsc/event_loop.rs                              |  18 ++
 src/jsc/web_worker.rs                              |  73 +++++
 src/runtime/dispatch_js2native.rs                  |   1 +
 src/uws/lib.rs                                     |   4 +-
 src/uws_sys/InternalLoopData.rs                    |  11 +
 src/uws_sys/Loop.rs                                |  31 +++
 test/js/node/perf_hooks/perf_hooks.test.ts         |  21 ++
 test/js/node/worker_threads/worker_threads.test.ts | 303 +++++++++++++++++++--
 19 files changed, 635 insertions(+), 75 deletions(-)

gate history · 7 passed · 5 rejected · iteration 26

evidence per changed file
file                                               reads  edits  tests
packages/bun-usockets/src/eventing/epoll_kqueue.c      8     12     29
packages/bun-usockets/src/eventing/libuv.c             1      1     29
packages/bun-usockets/src/internal/internal.h          5      7     29
packages/bun-usockets/src/internal/loop_data.h         2      2     29
packages/bun-usockets/src/loop.c                       8     11     29
src/js/internal/shared.ts                              7      6     30
src/js/node/perf_hooks.ts                              3      5     29
src/js/node/worker_threads.ts                          6     10     29
src/jsc/bindings/webcore/JSWorker.cpp                  5     11     29
src/jsc/bindings/webcore/Worker.cpp                    7      9     29
src/jsc/bindings/webcore/Worker.h                      2      3     29
src/jsc/event_loop.rs                                  3      1     29
src/jsc/web_worker.rs                                  8      7     29
src/runtime/dispatch_js2native.rs                      1      1     29
src/uws/lib.rs                                         3      3     29
src/uws_sys/InternalLoopData.rs                        2      3     29
(+ 3 more files)

root cause · written by the author bot

The previous implementation of Worker.performance.eventLoopUtilization() in node:worker_threads was an unimplemented stub that emitted a NotImplementedError warning and returned zeroed values indefinitely, since Bun had no plumbing to sample event loop idle and active time from the underlying uSockets loop. The fix implements real measurement end to end: the uSockets C layer records the loop creation timestamp and atomically accumulates time spent blocked in the poll syscall, Rust FFI bindings expose those counters as idle and active durations, and a shared JS helper computes the idle, …

Rebase notes (2026-07-13)

Rebased onto current main as a single squashed commit; the conflicts were non-trivial:

@robobun

robobun commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 12:13 AM PT - Jul 16th, 2026

@robobun, your commit 1b4d69b has 1 failures in Build #73668 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 32618

That installs a local version of the PR into your bun-32618 executable, so you can run:

bun-32618 --bun

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Implements performance.eventLoopUtilization() for both the main thread and worker threads. The uSockets C layer now accumulates blocked idle time atomically; Rust FFI bindings expose that data via EventLoopUtilization; a shared JS helper computes idle/active/utilization ratios; and perf_hooks.ts and worker_threads.ts wire these together, replacing previous zero-returning stubs.

Changes

Event Loop Utilization

Layer / File(s) Summary
uSockets loop data, idle accumulation, and utilization functions
packages/bun-usockets/src/internal/loop_data.h, packages/bun-usockets/src/internal/internal.h, packages/bun-usockets/src/loop.c, packages/bun-usockets/src/eventing/epoll_kqueue.c, packages/bun-usockets/src/eventing/libuv.c
Adds creation_monotonic_ns, idle_time_ns, and idle_entry_ns fields to us_internal_loop_data_t; declares and implements us_loop_monotonic_ns and us_loop_event_loop_utilization with platform-specific clock access and libuv/non-libuv idle-time branches; records the creation timestamp at loop init; configures the libuv loop for idle-time metrics; and atomically accumulates blocked wait time in epoll_kqueue.c around the provider syscall.
Rust FFI bindings for EventLoopUtilization
src/uws_sys/InternalLoopData.rs, src/uws_sys/Loop.rs, src/uws/lib.rs
Mirrors the new C fields in InternalLoopData; introduces EventLoopUtilization { idle_ms, active_ms } and loop_event_loop_utilization unsafe sampler with nanosecond-to-millisecond conversion via us_loop_event_loop_utilization FFI; and re-exports both symbols from uws/lib.rs.
Main-thread performance.eventLoopUtilization()
src/jsc/event_loop.rs, src/runtime/dispatch_js2native.rs, src/js/internal/shared.ts, src/js/node/perf_hooks.ts
Adds js_event_loop_utilization Rust host function reading the current thread's uWS loop pointer; re-exports it via dispatch_js2native.rs; adds eventLoopUtilization computation helper in shared.ts with optional one-or-two snapshot delta mode and zero-division guard; replaces the zeroed stub in perf_hooks.ts.
Worker thread performance.eventLoopUtilization()
src/jsc/bindings/webcore/Worker.h, src/jsc/bindings/webcore/Worker.cpp, src/jsc/bindings/webcore/JSWorker.cpp, src/jsc/web_worker.rs, src/js/node/worker_threads.ts
Declares and implements Worker::eventLoopUtilization with termination guards; registers eventLoopUtilization on JSWorkerPrototype; adds WebWorker__getEventLoopUtilization FFI export that caches the uWS loop pointer at VM start, locks the worker VM, and calls uws::loop_event_loop_utilization; replaces the warnNotImplementedOnce stub in worker_threads.ts with actual computation via computeEventLoopUtilization.
Tests
test/js/node/perf_hooks/perf_hooks.test.ts, test/js/node/worker_threads/worker_threads.test.ts
Adds two perf_hooks tests (busy-wait asserts higher active and utilization; sleep asserts higher idle and lower utilization) and three worker-threads tests (activity delta snapshots, post-exit zeroed values, and idle/blocking worker low utilization).

Possibly related issues

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: implementing performance.eventLoopUtilization().
Description check ✅ Passed It covers the feature and verification steps, though it uses different headings than the template.

Comment @coderabbitai help to get the list of available commands.

Comment thread packages/bun-usockets/src/loop.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-usockets/src/loop.c`:
- Around line 53-55: The code unconditionally calls
uv_metrics_idle_time(loop->uv_loop) without verifying that metrics are enabled
on the loop instance. Search the codebase to confirm that uv_loop_configure() is
being called with the UV_METRICS_IDLE_TIME flag on the same loop instance used
in the LIBUS_USE_LIBUV block. If this configuration call is not found, either
add uv_loop_configure(loop->uv_loop, UV_METRICS_IDLE_TIME) before reading the
metrics, or add clear comments documenting that loop metrics enablement is a
required prerequisite for this code to work correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d7107156-733c-4c00-8091-b91d1e7f231b

📥 Commits

Reviewing files that changed from the base of the PR and between 8d32bd4 and d5bcc3c.

📒 Files selected for processing (18)
  • packages/bun-usockets/src/eventing/epoll_kqueue.c
  • packages/bun-usockets/src/internal/internal.h
  • packages/bun-usockets/src/internal/loop_data.h
  • packages/bun-usockets/src/loop.c
  • src/js/internal/shared.ts
  • src/js/node/perf_hooks.ts
  • src/js/node/worker_threads.ts
  • src/jsc/bindings/webcore/JSWorker.cpp
  • src/jsc/bindings/webcore/Worker.cpp
  • src/jsc/bindings/webcore/Worker.h
  • src/jsc/event_loop.rs
  • src/jsc/web_worker.rs
  • src/runtime/dispatch_js2native.rs
  • src/uws/lib.rs
  • src/uws_sys/InternalLoopData.rs
  • src/uws_sys/Loop.rs
  • test/js/node/perf_hooks/perf_hooks.test.ts
  • test/js/node/worker_threads/worker_threads.test.ts

Comment thread packages/bun-usockets/src/loop.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-usockets/src/eventing/libuv.c`:
- Around line 160-163: The uv_loop_configure function call for
UV_METRICS_IDLE_TIME is not checking its return code, which means failures are
silently ignored and idle metrics never accumulate, leading to incorrect event
loop utilization reporting on the libuv path. Check the return code from the
uv_loop_configure call and add explicit error handling (such as logging a
warning or error message) to alert when metric configuration fails, ensuring
that failures do not go undetected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 490c0519-8d68-460c-afd7-da2807c1a672

📥 Commits

Reviewing files that changed from the base of the PR and between d5bcc3c and 2de717a.

📒 Files selected for processing (1)
  • packages/bun-usockets/src/eventing/libuv.c

Comment thread packages/bun-usockets/src/eventing/libuv.c
Comment thread src/jsc/web_worker.rs Outdated
Comment thread src/js/node/worker_threads.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/node/worker_threads/worker_threads.test.ts`:
- Around line 678-691: The test leaves the worker running indefinitely if any
assertion fails before termination, and relies on a fixed timing wait instead of
waiting for an observable condition. Refactor the test by wrapping the worker
logic in a try/finally block to guarantee worker cleanup, replace the
Bun.sleep(50) with a condition-driven wait that signals after the worker
completes its busy work (such as a message event handler or port communication),
and move the worker.terminate() call to the finally block to ensure it executes
regardless of assertion failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65f56682-3642-4b6c-8b52-d585ff4a8530

📥 Commits

Reviewing files that changed from the base of the PR and between 2de717a and 4643067.

📒 Files selected for processing (3)
  • src/js/node/worker_threads.ts
  • src/jsc/web_worker.rs
  • test/js/node/worker_threads/worker_threads.test.ts

Comment thread test/js/node/worker_threads/worker_threads.test.ts Outdated
Comment thread packages/bun-usockets/src/eventing/epoll_kqueue.c Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-usockets/src/loop.c`:
- Around line 58-64: To ensure the reader obtains a consistent snapshot of both
idle_time_ns and idle_entry_ns, refactor the load sequence in the section
beginning with uint64_t idle_ns. Read idle_entry_ns first with ACQUIRE
semantics, then read idle_time_ns with RELAXED semantics, then read
idle_entry_ns again with ACQUIRE semantics. If the second reading of
idle_entry_ns differs from the first, retry the entire sequence. This prevents
the reader from observing a stale idle_time_ns value paired with a cleared
idle_entry_ns, which would cause the in-progress credit calculation to be
skipped incorrectly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fceac5b6-bcce-472f-8987-6ce16d77b1e5

📥 Commits

Reviewing files that changed from the base of the PR and between 07202a3 and c2472b5.

📒 Files selected for processing (5)
  • packages/bun-usockets/src/eventing/epoll_kqueue.c
  • packages/bun-usockets/src/internal/loop_data.h
  • packages/bun-usockets/src/loop.c
  • src/uws_sys/InternalLoopData.rs
  • test/js/node/worker_threads/worker_threads.test.ts

Comment thread packages/bun-usockets/src/loop.c Outdated
Comment thread src/js/internal/shared.ts
Comment thread packages/bun-usockets/src/eventing/epoll_kqueue.c Outdated
Comment thread src/jsc/bindings/webcore/Worker.cpp

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All earlier review feedback has been addressed and I didn't find new issues, but this adds per-tick atomics to the core epoll/kqueue dispatch path and new cross-thread unsafe reads of worker loop state, so it's worth a human look before merging.

Extended reasoning...

Overview

This PR implements performance.eventLoopUtilization() and worker.performance.eventLoopUtilization(), replacing zeroed stubs. It spans 19 files across four layers: usockets C (idle-time accumulation in epoll_kqueue.c/loop.c/libuv.c, three new fields on us_internal_loop_data_t), Rust FFI (uws_sys, uws, event_loop.rs, web_worker.rs), C++ Worker bindings (JSWorker.cpp/Worker.cpp/Worker.h), and the JS layer (perf_hooks.ts, worker_threads.ts, shared.ts), plus tests.

Security risks

None apparent. The new surface is a read-only diagnostic metric; no user input flows into pointer arithmetic or allocation sizes. The cross-thread read of the worker's loop pointer is guarded by vm_lock and uses a pointer cached at publish time, so there's no obvious UAF or injection vector.

Level of scrutiny

High. The change instruments us_loop_run_bun_tick — every event-loop tick on POSIX now executes two additional __atomic_store_n and one __atomic_add_fetch around the epoll_pwait2/kevent64 syscall. The lock-free two-counter design (idle_time_ns + idle_entry_ns with RELEASE/ACQUIRE pairing) deliberately trades a sub-µs under-count window for avoiding a mutex; that reasoning is documented in comments and was iterated on across several review rounds, but it's the kind of concurrency design choice a maintainer should sign off on. The new unsafe Rust in web_worker.rs (cross-thread uws::loop_event_loop_utilization under vm_lock) and the InternalLoopData struct-layout mirror also warrant a second pair of eyes.

Other factors

The PR went through six rounds of bot review; every flagged issue (missing UV_METRICS_IDLE_TIME configure, event_loop_handle data race, negative deltas after worker exit, mid-wait misattribution, AArch64 store-reordering double-count, dead warnNotImplementedOnce, comment wording) was fixed and the threads resolved. The current revision passed the bug-hunting system with no findings. Test coverage looks adequate (busy/idle main-thread, busy/idle/post-exit worker). Given the breadth and the hot-path atomics, I'm deferring rather than approving.

Comment thread packages/bun-usockets/src/eventing/epoll_kqueue.c Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All my earlier findings have been addressed and this round is clean, but given the scope — atomics in the event-loop hot path, cross-thread reads of worker state under vm_lock, and platform-split C/Rust/C++ plumbing across 19 files — this one deserves human eyes before merging.

Extended reasoning...

Overview

This PR implements performance.eventLoopUtilization() and worker.performance.eventLoopUtilization(), replacing zeroed stubs. It spans 19 files across four layers: usockets C (idle-time accounting around epoll_pwait2/kevent64/libuv with atomic counters in us_internal_loop_data_t), Rust FFI (uws_sys, uws, event_loop.rs, web_worker.rs), C++ bindings (Worker.cpp/JSWorker.cpp), and the JS surface (perf_hooks.ts, worker_threads.ts, internal/shared.ts). New tests cover busy/idle main-thread sampling and busy/idle/message-driven/post-terminate worker sampling.

Security risks

None apparent. The new surface is a read-only diagnostic metric. The cross-thread worker read is guarded by vm_lock and dereferences only an immutable creation timestamp plus atomic counters via a cached loop pointer; no user-controlled input flows into native code.

Level of scrutiny

High. The change instruments us_loop_run_bun_tick — the per-tick hot path — with two atomic stores and a clock_gettime on every non-zero-timeout poll, and adds release/acquire-ordered cross-thread state that took several iterations to get right (libuv configure missing on Windows; data race on event_loop_handle via spawnSync; mid-wait samples mis-attributed to active; AArch64 store reordering allowing double-count; pending_wakeups staleness inverting the metric for message-driven workers). All were fixed, but the density of subtle concurrency issues found during review is itself a signal that a maintainer familiar with the loop and worker-teardown invariants should sign off.

Other factors

The most recent CI report in the thread (for 299f598) shows build-rust/build-cpp failures across most targets; the follow-up commit bc75bad adds a clippy allow on the FFI export and presumably clears them, but I don't see a green build confirmed in the timeline yet. The new unsafe Rust in web_worker.rs (caching uws_loop_ptr at the VM publish point and reading it under vm_lock) and the InternalLoopData layout extension both look correct to me, but layout/ABI changes to a struct mirrored in three places (C header, Rust #[repr(C)], and the runtime) are the kind of thing a human should double-check.

@robobun

robobun commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

CI note for reviewers: the one persistent red lane is test/js/third_party/grpc-js/test-server.test.ts crashing with SIGTRAP on darwin-14-aarch64, which is unrelated to this PR. This change only instruments the event loop (idle-time accounting) and adds the ELU bindings; it doesn't touch grpc-js or HTTP/2. Corroborating that: darwin-26-aarch64 (same architecture, same kqueue code path this PR modifies) passed the full test lane, cargo clippy is green, this PR's own tests (perf_hooks + worker_threads eventLoopUtilization) pass, and all other build/test jobs are green. The grpc-js SIGTRAP persisted through Buildkite's auto-retries, so it looks like a macOS-14 runner issue rather than something a re-run would change. The other red annotations (bake, napi, streams-leak, shell/rm on assorted lanes) are known-flaky and also unrelated.

@robobun

robobun commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

Note for reviewers: this branch's merge base is 147 commits behind main, and the $newZigFunction("event_loop.zig", ...) call it adds to src/js/node/perf_hooks.ts no longer resolves there. d451445 (#32621) removed the .zig porting-reference sources along with the $newZigFunction codegen identifier, so merging or rebasing this as-is will fail the JS bundling step. The branch needs a rebase that ports that call site to the current $newRustFunction / $newCppFunction mechanism.

For reference, an implementation of the main-thread performance.eventLoopUtilization() against current main (the same usockets instrumentation, plus a live performance.nodeTiming.idleTime) is on farm/6d49dccd/perf-hooks-elu. It does not cover the cross-thread worker.performance.eventLoopUtilization() this PR additionally implements.

@robobun
robobun force-pushed the farm/4b495614/worker-threads-elu branch from bc75bad to 49370c7 Compare July 5, 2026 09:46

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No new issues found — all prior feedback has been addressed and the $newRustFunction rebase is in place. Given the scope (per-tick instrumentation in us_loop_run_bun_tick, cross-thread lock-free atomics with release/acquire ordering, and worker-VM lifetime handling under vm_lock), this still warrants a maintainer's sign-off.

Extended reasoning...

Overview

This PR implements performance.eventLoopUtilization() and worker.performance.eventLoopUtilization() end-to-end across 19 files and four languages: usockets C (epoll_kqueue.c, libuv.c, loop.c, loop_data.h) instruments the event-provider wait with atomic idle-time counters and an in-progress-wait marker; Rust FFI (uws_sys/Loop.rs, uws_sys/InternalLoopData.rs, uws/lib.rs) mirrors the struct layout and exposes a sampler; event_loop.rs and web_worker.rs add the same-thread and cross-thread host functions (the latter caching the worker's uWS loop pointer at VM-publish time and reading it under vm_lock); C++ (Worker.{h,cpp}, JSWorker.cpp) adds the prototype method; and TypeScript (shared.ts, perf_hooks.ts, worker_threads.ts) computes the utilization ratio and diffs. Six new tests cover busy/idle main-thread, busy/idle/message-driven workers, and post-terminate zeroing.

Security risks

None identified. No user input parsing, no auth/crypto/permissions, no filesystem or network surface. The new code reads monotonic clocks and atomic counters. The cross-thread read is bounded by vm_lock and dereferences only an immutable timestamp plus atomically-written counters.

Level of scrutiny

High. This adds two clock_gettime calls plus three atomic stores to every event-loop tick on POSIX (us_loop_run_bun_tick is the hottest path in the runtime). It introduces lock-free cross-thread state with hand-tuned memory orderings that already went through one AArch64 correctness fix during review. It adds new unsafe Rust with SAFETY invariants tied to worker arena lifetime, and one of those SAFETY comments was found incorrect and reworked mid-review. It changes us_internal_loop_data_t layout (mirrored in Rust). None of this is "simple, mechanical, or obvious."

Other factors

The PR has been through eight rounds of automated review; every finding (missing UV_METRICS_IDLE_TIME config, event_loop_handle data race, mid-wait idle attribution, AArch64 store reordering, pending_wakeups gating, post-terminate negative deltas, dead-code cleanup, comment accuracy) was fixed with a targeted commit and a regression test where applicable. The current bug-hunting pass found nothing. Test coverage looks solid. That said, per the approval guidelines this is squarely "complex, large, touches critical code paths" — a human maintainer should confirm the per-tick overhead is acceptable and that the vm_lock / arena-lifetime reasoning in web_worker.rs holds.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No further issues found after the earlier rounds of fixes, but this instruments the core event-loop hot path (two clock reads + atomic stores per tick) and adds cross-thread sampling of a worker's loop under vm_lock — worth a maintainer's eyes on the hot-path cost and the concurrency design before merge.

Extended reasoning...

Overview

This PR implements performance.eventLoopUtilization() for both the main thread and worker.performance.eventLoopUtilization() for cross-thread sampling. It spans 19 files across four languages: C (usockets event-loop instrumentation in epoll_kqueue.c/loop.c/libuv.c, new fields in us_internal_loop_data_t), Rust (FFI in uws_sys/Loop.rs, InternalLoopData.rs mirror, web_worker.rs cross-thread sampler with vm_lock, event_loop.rs host fn), C++ (JSWorker/Worker bindings), and TypeScript (perf_hooks.ts, worker_threads.ts, shared helper in internal/shared.ts). It also removes the now-dead warnNotImplementedOnce helper and adds five new tests.

Security risks

None identified. The new surface is a read-only diagnostic metric; inputs are optional prior-snapshot objects whose numeric fields are only subtracted. No paths, no allocation sized from user input, no auth/crypto.

Level of scrutiny

High. The change instruments us_loop_run_bun_tick — the innermost per-tick hot path executed on every event-loop iteration — with two clock_gettime(CLOCK_MONOTONIC) calls and two atomic stores. It also introduces a lock-free two-field cross-thread read (idle_time_ns + idle_entry_ns) with hand-tuned acquire/release ordering, and a new parent→worker FFI path that dereferences a cached loop pointer under vm_lock. This class of change (hot-path instrumentation + cross-thread lock-free state on weak-memory targets) has already required multiple correctness fixes during review (Windows UV_METRICS_IDLE_TIME config, spawnSync data race on event_loop_handle, mid-wait sampling inversion, AArch64 store-reordering double-count, stale pending_wakeups gate on Linux). All were addressed, but the density of subtle issues found argues for a maintainer sign-off on the final design rather than bot approval.

Other factors

  • The branch was rebased/updated after the $newZigFunction removal on main (now uses $newRustFunction); the recent commits (dacd83d1, 49f39736, 432f143f, b124f568) are the mechanical follow-ups.
  • Test coverage is good: busy vs. idle main-thread, busy/idle/message-driven worker, and post-terminate zeros. Tests are timing-based by nature (asserting > 50ms / < 0.5 utilization over 200–300ms windows) which is inherent to the feature but worth a glance for CI flakiness headroom.
  • The one open design tradeoff explicitly left to maintainers (per the resolved thread on loop.c) is whether the sub-µs under-count window between the idle_entry_ns clear and the idle_time_ns credit is acceptable vs. a seqlock — the author's reasoning for leaving it as-is seems sound for a monitoring metric.
  • CI note from the author flags an unrelated grpc-js SIGTRAP on darwin-14-aarch64 as pre-existing flake.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No bugs found in the current revision, but this instruments the event-loop hot path with cross-thread lock-free atomics and threads a worker-VM pointer read through vm_lock — worth a human pass on the memory-ordering reasoning and the per-tick overhead before it lands.

Extended reasoning...

Overview

This PR implements performance.eventLoopUtilization() and worker.performance.eventLoopUtilization(), replacing zeroed stubs. It spans 19 files across five layers: usockets C (idle-time accumulation around epoll_pwait2/kevent64 in us_loop_run_bun_tick, plus a new us_loop_event_loop_utilization reader and libuv UV_METRICS_IDLE_TIME config), Rust FFI mirrors in uws_sys, a new Rust host function in event_loop.rs, a cross-thread FFI export on WebWorker that samples the worker's loop under vm_lock, C++ JSWorker prototype method + Worker::eventLoopUtilization, and shared JS logic in internal/shared.ts wired into perf_hooks.ts / worker_threads.ts. Tests cover busy/idle main-thread ELU and four worker scenarios (busy, post-terminate zeros, idle-blocked, message-driven idle).

Security risks

None identified. No user-controlled input reaches native parsing; the new surface reads two monotonic counters and returns three doubles. The cross-thread read path is guarded by vm_lock and touches only atomic/immutable fields of a cached loop pointer.

Level of scrutiny

High. The instrumentation lands directly in us_loop_run_bun_tick — every tick of every event loop now takes two extra clock_gettime calls and three atomic stores when the timeout is non-zero. The cross-thread reader is a hand-rolled lock-free two-field snapshot (release/acquire on idle_time_ns + idle_entry_ns) rather than libuv's mutex-guarded approach, with explicit reasoning about AArch64 store reordering. The WebWorker side caches a raw *mut uws::Loop at the VM publish point and dereferences it from the parent thread under vm_lock, relying on the arena-lifetime invariant that vm != null ⇒ loop is alive. These are exactly the kinds of decisions (per-tick hot-path cost, weak-memory correctness, cross-thread raw-pointer lifetime) that a maintainer should sign off on.

Other factors

The PR went through several rounds of substantive correction during automated review: missing UV_METRICS_IDLE_TIME on Windows, a data race on event_loop_handle (fixed by caching the loop pointer), mid-wait idle attribution (added idle_entry_ns), an AArch64 memory-ordering bug allowing double-count (upgraded to release/acquire), and a stale pending_wakeups gate that inverted the metric on Linux (decoupled to a timeout-only gate). All are now resolved and covered by regression tests, and the current bug-hunting pass found nothing. The branch has since been rebased onto the $newRustFunction codegen. Given the number of subtle concurrency issues already caught and fixed, and that this is a net-new feature touching core runtime infrastructure with no prior human review, deferring is the right call.

@robobun

robobun commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Verification status: this change is ready; the remaining red on the automated fail-before/pass-after check looks environmental rather than related to the diff.

Evidence:

  • The check reports 1-2 test failures in its debug/ASAN with-fix phase, but the same two test files on the same revision pass consistently: five consecutive local debug/ASAN runs are 42/42 green, and the check's own JUnit report from its latest invocation records 42 tests, 0 failures.
  • The check's without-fix release phase executed a binary reporting a with-fix revision (782227b), i.e. a stale artifact, which is why that phase anomalously passed everything.
  • A prior invocation's release build aborted with no error output.

On CI: cargo clippy and all build lanes pass on this branch; the red test lanes so far have been unrelated flakes (grpc-js SIGTRAP on darwin-14, fetch-gzip on windows-2019-baseline, and a buildkite artifact-download timeout).

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All prior review findings have been addressed and I found nothing new, but this touches the event-loop hot path with cross-thread atomics and new unsafe FFI, so it's worth a maintainer's eyes before merging.

Extended reasoning...

Overview

Implements performance.eventLoopUtilization() for both the main thread and worker.performance.eventLoopUtilization(). Spans 19 files across the stack: usockets C (idle-time accounting around epoll_pwait2/kevent64 in us_loop_run_bun_tick, plus uv_loop_configure(UV_METRICS_IDLE_TIME) on Windows), Rust FFI mirrors in uws_sys, a new extern "C" entry on WebWorker that reads a worker's loop counters from the parent thread under vm_lock, C++ Worker/JSWorker prototype additions, a shared JS diff helper, and tests.

Security risks

None identified. The change is diagnostic/metrics-only; no user input reaches parsing, allocation sizing, or filesystem/network paths. The cross-thread read is guarded by vm_lock and reads only atomic counters plus an immutable creation timestamp.

Level of scrutiny

High. This instruments the core event-loop tick on every iteration and adds lock-free concurrency (acquire/release ordering on idle_time_ns/idle_entry_ns) that is read cross-thread. During review it went through seven rounds of fixes for subtle issues — missing UV_METRICS_IDLE_TIME config, a data race on event_loop_handle during spawnSync, mid-wait sampling misattribution, AArch64 store reordering, and a stale-pending_wakeups gating bug. All were addressed, but the density of concurrency subtleties argues for a maintainer confirming the final shape (particularly the cached uws_loop_ptr lifetime under vm_lock and the two-field atomic read's accepted under-count window).

Other factors

  • All prior inline findings (mine and CodeRabbit's) are marked resolved with corresponding commits.
  • Tests cover busy/idle main-thread, busy/idle/message-driven workers, and the post-terminate zeros case; they fail on the released binary and pass with the fix.
  • The unrelated timeout bumps to three pre-existing worker_threads tests (execArgv, eval does not leak source code) are minor and justified in comments.
  • robobun's most recent notes indicate CI is green aside from unrelated flakes.

@robobun

robobun commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Agreed, this is ready for a maintainer pass. The concurrency pieces worth a close look are the acquire/release pairing on idle_time_ns/idle_entry_ns in epoll_kqueue.c and the cached uws loop pointer read under vm_lock in web_worker.rs; both are commented at the site.

On CI: the only real failure in build 71528 (both lanes) is the fetch-gzip redirect test at fetch-gzip.test.ts:521 (InvalidHTTPResponse vs ConnectionRefused). It reproduces on a pristine merge-base build without this diff and is flaky on main, so it is unrelated. Retriggered once.

@robobun

robobun commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Retrigger result (build 71532): the only remaining red lane is debian x64-asan, failing on the same unrelated test, fetch-gzip.test.ts:521 "followed redirect with a malformed chunked body rejects fetch()" (expects InvalidHTTPResponse, gets ConnectionRefused when the redirect connect to port 1 loses the race against the malformed-chunk parse). The Windows lane that hit it in the previous build is now green, and the test fails the same way on a pristine merge-base build without this diff.

Everything this PR touches is green, including the new perf_hooks and worker_threads ELU tests. I'm not going to keep retriggering; this is ready for a maintainer.

/* Monotonic clock (ns) used for event-loop-utilization accounting. */
uint64_t us_loop_monotonic_ns(void);
/* Sample the loop's accumulated idle time and active time (both ns). */
void us_loop_event_loop_utilization(struct us_loop_t *loop, uint64_t *idle_ns_out, uint64_t *active_ns_out);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems wasteful and doesn't actually fix the issue at hand. Find another approach

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Want to make sure I rework this in the direction you have in mind, so a bit of context on both points:

On cost: the two clock reads happen only when the wait can actually block (NULL or nonzero timeout). Zero-timeout poll-throughs, i.e. every tick while the loop is busy, skip the instrumentation entirely, so the steady-state overhead is two vDSO clock_gettime calls per sleep/wake cycle, paid next to a syscall that already context-switches. This is the same measurement libuv performs under UV_METRICS_IDLE_TIME, which Node enables unconditionally for every loop including workers; the Windows path here just uses that.

On the issue: the reporter's repro from #32609 now produces Node's output and exits the same way. This branch prints { idle: 0, active: 168.4, utilization: 1 } and climbing, where Node v26 prints { idle: 0, active: 167.6, utilization: 1 }; the released Bun prints the NotImplementedError warning and zeros forever. So I may be missing what you mean by not fixing the issue at hand; if you saw a case where the numbers are wrong, I would like to chase it.

If the objection is that the accounting is always on, I can gate it behind a per-loop flag that flips on the first eventLoopUtilization() call, making the cost exactly zero until someone uses the API. The tradeoff is that idle time before the first call gets reported as active (Node reports absolute idle since loop start), though the common delta usage elu(prev) is unaffected. If you have a different mechanism in mind instead, point me at it and I will rework the PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you show me in libuv?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sure. In libuv the measurement is the UV_METRICS_IDLE_TIME machinery:

  • Right before the provider wait, gated on a nonzero timeout exactly like this PR: src/unix/linux.c#L1453-L1465
    /* Only need to set the provider_entry_time if timeout != 0. ... */
    if (timeout != 0)
      uv__metrics_set_provider_entry_time(loop);
    ...
    nfds = epoll_pwait(epollfd, events, ARRAY_SIZE(events), timeout, sigmask);
  • The entry/exit clock reads and accumulation: src/uv-common.c#L986-L1025 (uv__metrics_set_provider_entry_time takes uv_hrtime() under a mutex before the wait; uv__metrics_update_idle_time takes it again after wake at linux.c#L1564 and #L1580 and adds the delta to provider_idle_time).
  • The reader crediting an in-flight wait, which is what idle_entry_ns does here: src/uv-common.c#L1040-L1053, if (entry_time > 0) idle_time += uv_hrtime() - entry_time;

Node turns this on unconditionally for every loop it creates:

and eventLoopUtilization() is computed from it in lib/internal/perf/event_loop_utilization.js via nodeTiming.idleTime, which is uv_metrics_idle_time().

One difference: libuv takes a mutex around both the entry and exit bookkeeping on every wait. This PR uses lock-free atomics for the same two stores, so the per-wait cost here is strictly lower than what every Node process (and Bun on Windows, where we run libuv) already pays. On Windows this PR does not add any measurement of its own, it just flips the same UV_METRICS_IDLE_TIME flag and reads uv_metrics_idle_time().

@robobun
robobun force-pushed the farm/4b495614/worker-threads-elu branch from 3ba6be2 to 466e70d Compare July 12, 2026 16:58
@robobun

robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

CI on the rebased commit (build 72296): the only red lane is debian x64-asan, where test/js/node/test/parallel/test-worker-message-port-transfer-terminate.js died with SIGABRT. That is the known terminate() abort race on main (see #33418 and #33966, which track terminate() interrupting in-flight VM work); the test never calls eventLoopUtilization(), and 130 local debug/ASAN runs of it on this branch pass. Everything this PR touches is green, including both ELU test files.

@robobun

robobun commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

Issue #34068 was just filed reporting the same eventLoopUtilization() stub (it silently breaks @fastify/under-pressure load shedding), so this PR would fix that one too.

I independently implemented the same approach on farm/58d3c67b/event-loop-utilization before finding this PR: idle-time accounting around the epoll_pwait2/kevent64 wait in us_loop_run_bun_tick, uv_metrics_idle_time on Windows, and Node's 0/1/2-argument delta semantics in perf_hooks.ts (verified on Linux and Windows). Two pieces from that branch may be worth folding in here, since this PR leaves them stubbed: performance.nodeTiming.idleTime and nodeTiming.loopStart are still hardcoded to 1, and the branch wires both to the same metric (loopStart = now - idle - active) with tests. Note that $toClass replaces the class prototype, so the getters have to be own properties defined in createPerformanceNodeTiming, not class-body getters.

@robobun

robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

Build 72925 on the rebased sha has two red lanes, both tracked main-side and unrelated to this diff:

Every lane that exercises this diff is green, including both ELU test files.

@robobun
robobun force-pushed the farm/4b495614/worker-threads-elu branch from 52ea52b to 7fa9cb8 Compare July 16, 2026 03:50
Comment thread src/jsc/bindings/webcore/Worker.cpp
Comment thread src/jsc/bindings/webcore/JSWorker.cpp Outdated
Comment thread packages/bun-usockets/src/loop.c
…the clock helper

- Worker::eventLoopUtilization reports zeros until 'online' (Node's kIsOnline)
  via !isOnline(), covering Pending as well as Closing/Closed.
- The raw accessor is now eventLoopUtilizationInternal with DontEnum, matching
  the adjacent *Internal plumbing entries on JSWorkerPrototype.
- us_internal_monotonic_ns was byte-identical to the POSIX branch of
  us_loop_monotonic_ns; the latter replaces it at all call sites.
Comment thread src/jsc/bindings/BunTTYState.h Outdated
robobun added 2 commits July 16, 2026 04:30
dispatchOnline stores m_state from the worker thread, so sampling in the
construction tick raced a fast worker boot. The entry script now blocks on
a SharedArrayBuffer gate until the parent has sampled; online cannot fire
before the entry finishes evaluating.
Comment thread src/jsc/bindings/webcore/Worker.cpp Outdated
robobun and others added 2 commits July 16, 2026 05:42
Node's gate is !kIsOnline || !kHandle; neither changes at terminate(), so
values stay real until exit handling. Drop the terminate-requested term;
!isOnline() still zeros Pending/Closing/Closed and the Rust side reads
under vm_lock. Sample the window in the busy-worker test, and give the
remaining spawn-heavy SHARE_ENV tests the same debug/ASAN ceilings.
Comment thread packages/bun-usockets/src/loop.c
A cross-thread prior sample taken mid-wait can exceed a later read that
lands between the writer's entry-clear and idle-credit stores; without a
clamp the diff surfaces as negative idle and utilization above 1. Also
hardens the caller-supplied two-snapshot path.
Comment thread test/js/node/worker_threads/worker_threads.test.ts Outdated
Comment thread test/js/node/worker_threads/worker_threads.test.ts Outdated
The pre-online sample now waits for the worker to signal from inside its
entry (VM published, not yet online), so the Rust-side null check cannot
supply the zeros. The terminate-window sample parks the worker in a
message handler on a SAB gate so its teardown cannot race the read.
Verified both fail against the previous m_state >= Closing gate.
Comment thread test/js/node/worker_threads/worker_threads.test.ts
@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Build 73668: the only red lane is debian x64-asan, where test/js/web/timers/timer-heap-race.test.ts failed because LeakSanitizer caught a leaked TimeoutObject in the Atomics.waitAsync cancellation fixture (child exited SIGABRT). That allocation path (timer/mod.rs set_timeout) is untouched by this diff, the test passes 10/10 locally on this branch under ASAN, and the leak is already tracked main-side. Everything this PR touches remains green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants