Skip to content

Fix timeout scheduling edge case and cancellation handling in SimpleJobExecutor#4886

Closed
mrhapile wants to merge 2 commits intoboa-dev:mainfrom
mrhapile:fix/simple-job-executor-timeout-bugs
Closed

Fix timeout scheduling edge case and cancellation handling in SimpleJobExecutor#4886
mrhapile wants to merge 2 commits intoboa-dev:mainfrom
mrhapile:fix/simple-job-executor-timeout-bugs

Conversation

@mrhapile
Copy link
Copy Markdown
Contributor

@mrhapile mrhapile commented Mar 5, 2026

This PR fixes two issues in SimpleJobExecutor.

  1. Timeout jobs scheduled exactly at now could be deferred due to the
    behavior of BTreeMap::split_off.
  2. Cancelled timeout jobs could still execute because the executor
    did not verify job.is_cancelled() before dispatch.
Screenshot 2026-03-06 at 1 27 46 AM

Fix:

  • Explicitly move jobs scheduled at now back into the execution set.
  • Add a guard preventing execution of cancelled jobs.

Two regression tests were added:
Screenshot 2026-03-06 at 1 58 06 AM
Screenshot 2026-03-06 at 1 59 03 AM

  • timeout_runs_at_exact_time
  • cancelled_timeout_should_not_execute

All tests pass locally (cargo test, cargo clippy).

…obExecutor

Signed-off-by: mrhapile <allinonegaming3456@gmail.com>
@mrhapile mrhapile requested a review from a team as a code owner March 5, 2026 20:32
Copilot AI review requested due to automatic review settings March 5, 2026 20:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SimpleJobExecutor edge cases where timeout jobs scheduled exactly at now were deferred and where cancelled timeout jobs could still execute.

Changes:

  • Adjust timeout scheduling logic so jobs at exactly now execute in the current tick.
  • Add a dispatch-time cancellation guard to prevent cancelled jobs from running.
  • Add regression tests for “exactly now” scheduling and cancellation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
core/engine/src/job.rs Fixes the split_off boundary behavior for now and adds a cancellation guard before calling timeout jobs.
core/engine/src/job/tests.rs Adds regression tests covering the now boundary case and cancelled timeout execution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +81 to +82
// Advance time realistically using system sleep so it becomes "< now"
std::thread::sleep(std::time::Duration::from_millis(10));
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using std::thread::sleep in unit tests is prone to flakiness (slow CI machines, timer granularity) and slows the test suite. Since this codebase already supports injecting a Clock, prefer a controllable test clock (e.g., a ManualClock backed by Cell<JsInstant> that you can advance in the test) so the job reliably becomes due without sleeping.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +92
if ran.get() {
panic!("Cancelled timeout job executed anyway! Bug confirmed.");
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be expressed as an assert!(!ran.get(), ...) (or assert_eq!(ran.get(), false, ...)) to keep the failure style consistent with other tests and produce cleaner assertion output.

Suggested change
if ran.get() {
panic!("Cancelled timeout job executed anyway! Bug confirmed.");
}
assert!(
!ran.get(),
"Cancelled timeout job executed anyway! Bug confirmed."
);

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +55
assert!(
ran.get(),
"Timeout job scheduled at exactly 'now' did NOT execute! Bug confirmed: off-by-one in split_off"
);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion message reads like a debugging note ("Bug confirmed") rather than stating the expected behavior. Consider rephrasing to a neutral expectation-focused message (e.g., "timeout scheduled at exactly now should execute in the same tick") to keep future failures clear and less tied to a specific underlying implementation detail.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 5, 2026

Test262 conformance changes

Test result main count PR count difference
Total 52,963 52,963 0
Passed 49,666 49,666 0
Ignored 2,284 2,284 0
Failed 1,013 1,013 0
Panics 0 0 0
Conformance 93.77% 93.77% 0.00%

Tested main commit: 5cbf23323e1be945022cd1e6332cbacc22e9a2c8
Tested PR commit: 598f5fda6048e57fe0c775bc11d1bfc34f747496
Compare commits: 5cbf233...598f5fd

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.29%. Comparing base (6ddc2b4) to head (598f5fd).
⚠️ Report is 762 commits behind head on main.

Files with missing lines Patch % Lines
core/engine/src/job.rs 66.66% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #4886       +/-   ##
===========================================
+ Coverage   47.24%   57.29%   +10.05%     
===========================================
  Files         476      555       +79     
  Lines       46892    60602    +13710     
===========================================
+ Hits        22154    34722    +12568     
- Misses      24738    25880     +1142     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

let mut jobs_to_keep = timeouts_borrow.split_off(&now);
// `split_off` keeps keys `>= now`. We want exactly `now` to execute in the
// current tick, so we move it back into `timeouts_borrow` (which becomes `jobs_to_run`).
if let Some(jobs_at_now) = jobs_to_keep.remove(&now) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is more than 1 job due now, this would only run one of them, right>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No — it will run all of them.

timeout_jobs is a BTreeMap<JsInstant, Vec<TimeoutJob>>, so remove(&now) returns the entire vector of jobs scheduled at that timestamp, and the executor then iterates over all of them.

@jedel1043
Copy link
Copy Markdown
Member

Might be superseeded by #4891

github-merge-queue bot pushed a commit that referenced this pull request Mar 6, 2026
After seeing the [avalanche](#4886)
[of](#4833)
[issues](#4785)
[that](#4749)
[our](#4782) executors have, I
don't think it's worth being too smart about when to exit the loop.
Folks who wanna have that behaviour can just implement their own
`JobExecutor`.

Thus, this reverts the implementation to the old behaviour of blocking
when having pending timeout and interval jobs. Fortunately,
`run_jobs_async` exists, so we can use it in our CLI to intersperse
executing jobs with executing parsing and execution.

And just for good measure, it also adds a `stop` cancellation token to
`SimpleJobExecutor`, in case folks still want to use it but that also
want to remotely stop the event loop's execution from another thread.
@jedel1043
Copy link
Copy Markdown
Member

Superseeded by #4891

@jedel1043 jedel1043 closed this Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants