#52862: perf: don't interrupt the uv loop for I/O changes made inside uv_run()
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-specand the message loop parts ofchromium-specpass locally - macOS and Windows go through the same gate; relying on CI there
Checklist
- PR description included and stakeholders cc'd
-
npm testpasses - tests are changed or added
- relevant API documentation, tutorials, and examples are updated and follow the documentation style guide
Release Notes
Notes: Reduced idle main-process CPU wakeups caused by Node.js timers and immediates.
Backports
Semver Impact
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