17e5c415c6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
190 lines
6.4 KiB
Markdown
190 lines
6.4 KiB
Markdown
# 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](https://krunker.io)) running in Electron, where shooting (holding left click + moving mouse) causes network freezes of 100-300ms+.
|
|
|
|
## Downloads
|
|
|
|
Pre-built patched binaries are available on the [Releases page](https://gitea.crjlab.net/bigjakk/Electron-Websocket-Fix/releases):
|
|
|
|
### 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 |
|
|
|
|
### Linux x64
|
|
|
|
| File | Electron Version | Size |
|
|
|------|-----------------|------|
|
|
| `electron-v43.0.0-nightly-release-patched-linux-x64.zip` | v43.0.0-nightly | 113MB |
|
|
|
|
All builds are full release builds (`is_official_build = true`) with maximum optimizations. Additional versions can be built from source -- see [`BUILD-GUIDE.md`](BUILD-GUIDE.md).
|
|
|
|
## The Problem
|
|
|
|
When an Electron app uses these flags (common for competitive gaming):
|
|
|
|
```javascript
|
|
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](https://chromium-review.googlesource.com/c/chromium/src/+/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`](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:
|
|
|
|
**Windows:**
|
|
|
|
```bash
|
|
electron.exe path/to/your/app
|
|
```
|
|
|
|
**Linux:**
|
|
|
|
```bash
|
|
./electron path/to/your/app
|
|
```
|
|
|
|
### Option B: Replace in node_modules
|
|
|
|
```bash
|
|
# 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`:
|
|
|
|
```json
|
|
{
|
|
"build": {
|
|
"electronDist": "path/to/extracted/dist",
|
|
"electronVersion": "40.6.1"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Option D: electron-forge
|
|
|
|
In `forge.config.js`:
|
|
|
|
```js
|
|
module.exports = {
|
|
packagerConfig: {
|
|
electronZipDir: 'path/to/extracted/dist'
|
|
}
|
|
};
|
|
```
|
|
|
|
## Verification
|
|
|
|
An automated stress test is included in the [`test/`](test/) directory:
|
|
|
|
```bash
|
|
cd test
|
|
npm install
|
|
|
|
# Windows
|
|
path/to/patched/electron.exe cdp-test.js 8085 PATCHED
|
|
|
|
# Linux
|
|
path/to/patched/electron 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`](BUILD-GUIDE.md).
|
|
|
|
Quick summary:
|
|
|
|
```bash
|
|
# 1. Set up environment (depot_tools, build toolchain, Python, etc.)
|
|
# 2. Initialize and sync Electron source
|
|
e init --root=$HOME/electron my-build --import release # Linux
|
|
e init --root=C:\electron my-build --import release # Windows
|
|
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/linux64/gn gen out/Release # Linux
|
|
buildtools/win/gn.exe gen out/Release # Windows
|
|
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 since Electron 10 (Chromium 84+) on any platform (Windows, Linux, macOS). 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.
|