codebytere

#52862: perf: don't interrupt the uv loop for I/O changes made inside uv_run()

Merged
Created: Aug 16, 2026, 9:34:54 AM
Merged: Aug 17, 2026, 7:22:00 AM
5 comments
Target: main

Description of Change

Electron drives Node's libuv loop from two threads: the UI thread runs uv_run(UV_RUN_NOWAIT), and an embed thread polls the loop's backend fd and wakes the UI thread when something is ready. Our libuv patch (feat_add_uv_loop_interrupt_on_io_change_option_to_uv_loop_configure.patch, from libuv/libuv#3308) interrupts that poll whenever a handle or watcher is started, so a timer started from a Chromium task gets noticed even when the embed thread is already asleep on an older timeout.

The interrupt also fired for handles started while uv_run() itself was executing, which Node does on almost every tick for its immediate and timer handles. No thread is polling at that point, and the embed thread re-reads uv_backend_timeout() after uv_run() returns anyway, so each of those interrupts was a wasted syscall on the UI thread plus a spurious wakeup that scheduled an extra, empty loop run. This regenerates the patch with a uv_loop_interrupt_suspend() / uv_loop_interrupt_resume() pair (a depth in the loop's private fields, loop thread only) and has UvRunOnce wrap its uv_run() in them, since that is the one place we know the embed thread is parked on embed_sem_. uv_async_send() is never suppressed, and a uv_run() issued from anywhere else (a native module pumping the loop from a Chromium task, say) keeps interrupting as before. The patch's test-embed.c had also stopped compiling against current libuv; that is fixed, with a new case where a timer started while interrupts are suspended must still fire on time without an interrupt being written.

Measured on Linux with a testing build and an idle app running setInterval(() => {}, 10) in the main process, over a 5 s strace window:

  • main-thread eventfd writes: 468 before, 0 after
  • embed-thread wakeups of the UI thread: 913 before, 459 after (one loop run per tick instead of two)
  • timer/immediate ordering and setTimeout / setImmediate / fs.readFile / TCP round-trip latency unchanged; node-spec, api-app-spec, api-utility-process-spec and the message loop parts of chromium-spec pass locally
  • macOS and Windows go through the same gate; relying on CI there

Checklist

Release Notes

Notes: Reduced idle main-process CPU wakeups caused by Node.js timers and immediates.

Backports

42-x-y
Merged
PR Number
#52907
Merged At
Aug 17, 2026, 9:38:14 AM
Released In
Not yet
Release Date
Not yet
43-x-y
Merged
PR Number
#52905
Merged At
Aug 17, 2026, 12:32:11 PM
Released In
Not yet
Release Date
Not yet
44-x-y
Merged
PR Number
#52906
Merged At
Aug 17, 2026, 8:54:32 AM
Released In
v44.0.0-beta.5
Release Date
Aug 17, 2026, 3:01:12 PM

Semver Impact

Major
Breaking changes
Minor
New features
Patch
Bug fixes
None
Docs, tests, etc.

Semantic Versioning helps users understand the impact of updates:

  • Major (X.y.z): Breaking changes that may require code modifications
  • Minor (x.Y.z): New features that maintain backward compatibility
  • Patch (x.y.Z): Bug fixes that don't change the API
  • None: Changes that don't affect using facing parts of Electron