Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 7b65bcf29a4d2c738d2dfa89f05c9d48bde0237f
      
https://github.com/WebKit/WebKit/commit/7b65bcf29a4d2c738d2dfa89f05c9d48bde0237f
  Author: Ben Nham <[email protected]>
  Date:   2025-11-03 (Mon, 03 Nov 2025)

  Changed paths:
    M Source/WebCore/workers/WorkerRunLoop.cpp

  Log Message:
  -----------
  Work around CF timers not being serviced promptly
https://bugs.webkit.org/show_bug.cgi?id=301665
rdar://154763428

Reviewed by Brady Eidson.

We have a long-standing bug where WorkerDedicatedRunLoop::runInMode sometimes 
consumes 100% of a
CPU. After adding logging for users running in to this, we've discovered this 
is because CFRunLoop
gets in to a state where CFRunLoopGetNextTimerFireDate returns a fire date in 
the distant past (say
~10 minutes ago), but the actual next alarm wakeup for the run loop is in the 
distant future (say ~5
minutes in the future). Since we're running CFRunLoop in polling mode 
(timeout=0), this causes us to
spin the CPU at 100% calling CFRunLoopRunInMode until the next alarm goes off 
(which would be 5
minutes of CPU spin in this example).

This is actually expected because while CFRunLoopTimerSetNextFireDate takes a 
wall clock time (which
does advance while the system sleeps), it actually creates a Mach timer based 
on mach_absolute_time
(which doesn't advance when the system sleeps). So if the system goes to sleep 
while a CF timer is
armed, then after the system resumes CFRunLoopGetNextTimerFireDate may return a 
wall timestamp in
the past, even though the timer will actually fire in the future.

There doesn't seem to be any correct way of fixing this other than rewriting 
our run loop to have
CFRunLoop drive everything. The current implementation that tries to ping pong 
between our run loop
and CFRunLoop while using CFRunLoopGetNextTimerFireDate to figure out if we 
have to spin the
CFRunLoop doesn't work given the issue I just described and can cause the CPU 
to spin until the
run loop timer actually fires.

This commit adds a mitigation that we can ship until we can refactor 
WorkerDedicatedRunLoop.
Basically, when we detect that there's a timer that should have fired in the 
distant past (more than
one second ago), we have CFRunLoop block for up to 1 second. In the buggy case 
that we're trying to
mitigate, this will cause us to wake up at most once per second, which is less 
than optimal but a
lot better than an infinite loop. In the non-buggy case, we don't end up with 
any extra perf penalty
because we now instruct CFRunLoop to exit immediately after it handles a source 
(which it will in
the normal case that the next timer fire date is in the past and the Mach timer 
did actually fire in
the past).

* Source/WebCore/workers/WorkerRunLoop.cpp:
(WebCore::WorkerDedicatedRunLoop::runInMode):

Canonical link: https://commits.webkit.org/302518@main



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to