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