Files
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

74 lines
4.1 KiB
Diff

diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index cf7455daf6b2e..9757d1d336ddc 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -2755,7 +2755,15 @@ TaskPriority MainThreadSchedulerImpl::ComputePriority(
case MainThreadTaskQueue::QueueTraits::PrioritisationType::kCompositor:
return main_thread_only().compositor_priority;
case MainThreadTaskQueue::QueueTraits::PrioritisationType::kInput:
- return TaskPriority::kHighestPriority;
+ // Lowered from kHighestPriority to kNormalPriority to prevent input
+ // tasks from starving WebSocket/Worker message dispatch when
+ // --disable-frame-rate-limit is active. Without cross-priority
+ // anti-starvation in the task queue selector, ANY priority above
+ // kNormalPriority causes starvation during continuous mouse input.
+ // Testing shows kNormalPriority actually improves both frame rate
+ // and input throughput vs kHighestPriority, because it prevents the
+ // input→compositor priority cascade from monopolizing the thread.
+ return TaskPriority::kNormalPriority;
case MainThreadTaskQueue::QueueTraits::PrioritisationType::kBestEffort:
return TaskPriority::kBestEffortPriority;
case MainThreadTaskQueue::QueueTraits::PrioritisationType::kRegular:
@@ -2823,31 +2831,29 @@ TaskPriority MainThreadSchedulerImpl::ComputeCompositorPriority() const {
ComputeCompositorPriorityForMainFrame();
std::optional<TaskPriority> use_case_priority =
ComputeCompositorPriorityFromUseCase();
+
+ TaskPriority result;
if (!targeted_main_frame_priority && !use_case_priority) {
- return TaskPriority::kNormalPriority;
+ result = TaskPriority::kNormalPriority;
} else if (!use_case_priority) {
- return *targeted_main_frame_priority;
+ result = *targeted_main_frame_priority;
} else if (!targeted_main_frame_priority) {
- return *use_case_priority;
- }
-
- // Both are set, so some reconciliation is needed.
- CHECK(targeted_main_frame_priority && use_case_priority);
- // If either votes for the highest priority, use that to simplify the
- // remaining case.
- if (*targeted_main_frame_priority == TaskPriority::kHighestPriority ||
- *use_case_priority == TaskPriority::kHighestPriority) {
- return TaskPriority::kHighestPriority;
- }
- // Otherwise, this must be a combination of UseCase::kCompositorGesture and
- // rendering starvation since all other use cases set the priority to highest.
- CHECK(current_use_case() == UseCase::kCompositorGesture &&
- (main_thread_only().main_frame_prioritization_state ==
- RenderingPrioritizationState::kRenderingStarved ||
- main_thread_only().main_frame_prioritization_state ==
- RenderingPrioritizationState::kRenderingStarvedByRenderBlocking));
- CHECK_LE(*targeted_main_frame_priority, *use_case_priority);
- return *targeted_main_frame_priority;
+ result = *use_case_priority;
+ } else {
+ // Both are set — take the higher priority (lower numeric value).
+ result = std::min(*targeted_main_frame_priority, *use_case_priority);
+ }
+
+ // Cap compositor priority to kNormalPriority. Without this cap,
+ // back-to-back BeginFrame tasks at kHighestPriority (triggered by
+ // continuous mouse input + --disable-frame-rate-limit) create a tight
+ // compositor loop that permanently starves kNormalPriority tasks
+ // (WebSocket onmessage, Worker postMessage). The task queue selector
+ // has no cross-priority anti-starvation, so any priority above kNormal
+ // causes indefinite deferral of lower-priority work. Rendering starvation
+ // detection in ComputeCompositorPriorityForMainFrame() is sufficient to
+ // protect against actual frame drops when compositor priority is capped.
+ return std::max(result, TaskPriority::kNormalPriority);
}
void MainThreadSchedulerImpl::UpdateCompositorTaskQueuePriority() {