bigjakk e5b411cee5 Initial release: patched Electron builds fixing WebSocket starvation
Fixes a Chromium regression where continuous mouse input (e.g., shooting
in FPS games) starves WebSocket/Worker message dispatch when
--disable-frame-rate-limit is active.

Includes:
- Patch diff for main_thread_scheduler_impl.cc
- Automated CDP stress test for verification
- Full build-from-source guide

Pre-built binaries available in Releases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:12:39 -08:00

Electron WebSocket Fix

Patched Electron builds that fix a Chromium regression where continuous mouse input starves WebSocket and Worker message dispatch when --disable-frame-rate-limit is active.

This is critical for competitive browser-based games (like Krunker) running in Electron, where shooting (holding left click + moving mouse) causes network freezes of 100-300ms+.

Downloads

Pre-built patched binaries (Windows x64):

File Electron Version Size
electron-v40.6.1-release-patched-win32-x64.zip v40.6.1 (latest stable) 133MB
electron-v42.0.0-nightly-release-patched-win32-x64.zip v42.0.0-nightly 137MB

Both are full release builds (is_official_build = true) with maximum optimizations.

The Problem

When an Electron app uses these flags (common for competitive gaming):

app.commandLine.appendSwitch('disable-frame-rate-limit');
app.commandLine.appendSwitch('disable-gpu-vsync');

...continuous mouse input with left-click held down causes WebSocket onmessage callbacks to be delayed by 100-300ms+. This manifests as network "freezing" -- player positions stop updating, hit registration breaks, and the game becomes unplayable during gunfights.

Root Cause

Three factors in Chromium's Blink main thread scheduler combine to create the issue:

  1. Input tasks at kHighestPriority (priority level 1) while WebSocket/Worker tasks sit at kNormalPriority (level 7)

  2. No cross-priority anti-starvation in task_queue_selector.cc -- it always picks from the highest active priority queue, with no mechanism to let lower-priority tasks run

  3. Compositor priority boost during input -- when mouse is held + moving (UseCase::kMainThreadCustomInputHandling), the compositor queue gets boosted to kHighestPriority. With --disable-frame-rate-limit, the BackToBackBeginFrameSource posts SEND_BEGIN_MAIN_FRAME tasks at zero delay, creating an infinite loop of highest-priority tasks that permanently starves everything else

This regression was introduced in Chromium 84 when PrioritizeCompositingAfterInput was made unconditional (CL 2132022).

The Fix

Two changes in one file (main_thread_scheduler_impl.cc):

  1. Lower input task priority from kHighestPriority to kNormalPriority
  2. Cap compositor priority to kNormalPriority via std::max()

See patches/ws-priority-patch.diff for the exact diff.

Test Results

12-second automated stress test with continuous CDP mouse input (left-click held, circular movement):

Build p99 Latency Max Latency Messages >50ms Mouse Events Frames
Unpatched ~97ms ~308ms 8.6% ~11,380 ~6,300
Patched ~34ms ~38ms 0% ~12,360 ~7,620

The patch not only eliminates starvation but actually improves both input throughput (+9% mouse events) and frame rate (+21% frames) because it prevents the input/compositor priority cascade from monopolizing the main thread.

Usage

Option A: Direct Binary

Extract the zip and run your app:

electron.exe path/to/your/app

Option B: Replace in node_modules

# Back up original
mv node_modules/electron/dist node_modules/electron/dist-original

# Extract patched version
mkdir node_modules/electron/dist
cd node_modules/electron/dist
unzip path/to/electron-v40.6.1-release-patched-win32-x64.zip

Option C: electron-builder

In package.json:

{
  "build": {
    "electronDist": "path/to/extracted/dist",
    "electronVersion": "40.6.1"
  }
}

Option D: electron-forge

In forge.config.js:

module.exports = {
  packagerConfig: {
    electronZipDir: 'path/to/extracted/dist'
  }
};

Verification

An automated stress test is included in the test/ directory:

cd test
npm install
path/to/patched/electron.exe cdp-test.js 8085 PATCHED

The test uses CDP Input.dispatchMouseEvent to simulate continuous mouse input (the only reliable automated method -- Electron's sendInputEvent API bypasses the compositor thread and doesn't trigger the bug).

Expected output for a patched build: 0% of WebSocket messages >50ms.

Building From Source

Full build instructions are in BUILD-GUIDE.md.

Quick summary:

# 1. Set up environment (depot_tools, VS Build Tools, Python, etc.)
# 2. Initialize and sync Electron source
e init --root=C:\electron my-build --import release
e sync

# 3. Check out desired version
cd src/electron && git checkout v40.6.1
cd .. && gclient sync --with_branch_heads --with_tags

# 4. Apply patch
git apply path/to/ws-priority-patch.diff

# 5. Configure and build
mkdir -p out/Release
# Set out/Release/args.gn:
#   import("//electron/build/args/release.gn")
#   is_official_build = true
#   use_remoteexec = false
#   use_reclient = false
buildtools/win/gn.exe gen out/Release
ninja -C out/Release electron
ninja -C out/Release electron:electron_dist_zip

Expect 6-10+ hours for a full build on a modern machine (24 cores, 64GB RAM).

Patch Details

The patch modifies Chromium source (not Electron source), so it applies to any Electron version. Line numbers may shift between versions but the function names remain the same:

  • ComputePriority() -- search for PrioritisationType::kInput
  • ComputeCompositorPriority() -- search for that function name

File: third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc

License

The patch itself is provided as-is. Electron is MIT licensed. Chromium is BSD licensed.

S
Description
No description provided
Readme 67 KiB
2026-03-02 15:12:59 +00:00
Languages
JavaScript 65.4%
HTML 34.6%