fix(job): resolve 100% CPU usage when idling on future timeouts#4926
fix(job): resolve 100% CPU usage when idling on future timeouts#4926ashnaaseth2325-oss wants to merge 4 commits intoboa-dev:mainfrom
Conversation
When only future-scheduled timeout jobs remain in the queue and no async futures are in-flight, the executor looped tightly through yield_now().await, consuming 100% CPU for the full wait duration. Fix by sleeping on a oneshot channel until the earliest timeout is due before re-entering the loop. Also correct the split_off boundary from &now to &(now + 1ns) so that jobs whose deadline equals the current instant are dispatched immediately rather than deferred. Signed-off-by: ashnaaseth2325-oss <ashnaaseth2325@gmail.com>
Test262 conformance changes
Tested main commit: |
Replace the bare `rx.await` sleep with a `poll_fn`-based future that re-checks `context.clock().now() >= deadline` on every poll. This lets test-controlled clocks (FixedClock) advance the deadline without waiting for real wall-clock time, while still parking the thread in production via the oneshot channel and background thread::sleep. Also collapse the nested `if` to satisfy clippy::collapsible_if. Signed-off-by: ashnaaseth2325-oss <ashnaaseth2325@gmail.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4926 +/- ##
===========================================
+ Coverage 47.24% 57.66% +10.41%
===========================================
Files 476 556 +80
Lines 46892 60952 +14060
===========================================
+ Hits 22154 35147 +12993
- Misses 24738 25805 +1067 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Hello @jedel1043 @hansl |
|
Hello @jedel1043 @hansl @nekevss |
| std::thread::spawn(move || { | ||
| std::thread::sleep(dur); | ||
| let _ = tx.send(()); | ||
| }); |
There was a problem hiding this comment.
This is very inefficient. We are essentially creating a whole thread to just wait for dur milliseconds.
I don't think this is something we can fix on the SimpleJobExecutor. This executor should be kept as simple as possible, but to properly support this we need an async executor to sleep using an async timer.
|
Related: #4994. Uses no additional threads by integrating with |
|
Hello @jedel1043 Thanks for the feedback! That’s a fair point! Spawning a thread just to wait for dur isn’t ideal for a simple executor. I agree this is probably better handled by a proper async executor. |
Summary
This PR fixes a performance bug where the
SimpleJobExecutorpins a CPU core at 100% while waiting for asetTimeoutorsetIntervaldelay. The issue occurs because the executor doesn't have a way to "sleep" when there are no active async jobs to poll, causing it to spin in a tight loop until the timer expires.Steps to Reproduce
Run any script with a
setTimeoutdelay:During the 5 second wait, the engine burns 100% of a CPU core despite having no work to do.
Problem
The current
run_jobs_asyncloop has a logic gap. We prevent the loop from terminating (because a timeout is pending), but we only yield execution if there are active async futures. If the async group is empty, the engine falls into a "tight spin" ; it stays awake but has nothing to execute, leading to unnecessary high CPU usage.Fix
I updated the loop guards to recognize the "idle wait" state. If the sync queues are empty and no async work is active, the executor now calculates the time remaining until the next scheduled
TimeoutJoband parks the thread until that deadline.Result
This fix significantly improves the efficiency of the
SimpleJobExecutorfor any host environment using standard JS timers.