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>
5.8 KiB
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:
-
Input tasks at
kHighestPriority(priority level 1) while WebSocket/Worker tasks sit atkNormalPriority(level 7) -
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 -
Compositor priority boost during input -- when mouse is held + moving (
UseCase::kMainThreadCustomInputHandling), the compositor queue gets boosted tokHighestPriority. With--disable-frame-rate-limit, theBackToBackBeginFrameSourcepostsSEND_BEGIN_MAIN_FRAMEtasks 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):
- Lower input task priority from
kHighestPrioritytokNormalPriority - Cap compositor priority to
kNormalPriorityviastd::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 forPrioritisationType::kInputComputeCompositorPriority()-- 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.