Skip to content

fix(runtime): make timer IDs 1-based to avoid sentinel collision#5379

Open
iammdzaidalam wants to merge 2 commits into
boa-dev:mainfrom
iammdzaidalam:fix/timer-id-zero-collision
Open

fix(runtime): make timer IDs 1-based to avoid sentinel collision#5379
iammdzaidalam wants to merge 2 commits into
boa-dev:mainfrom
iammdzaidalam:fix/timer-id-zero-collision

Conversation

@iammdzaidalam
Copy link
Copy Markdown
Contributor

Closes #5378

Problem

IntervalInnerState::next_id() returns the internal counter before incrementing it. Since the counter starts at 0 via Default, the first valid timer gets handle 0:

fn next_id(&mut self) -> JsResult<u32> {
    self.active_map.retain(|_, v| !v.revoked());
    let id = self.id;
    self.id = id.checked_add(1)...;
    Ok(id)
}

At the same time, both set_timeout and set_interval return Ok(0) when no callback is provided. That makes the first real timer and the "nothing was scheduled" sentinel indistinguishable. It also violates WHATWG HTML timer semantics, which require positive timer handles, and breaks common JavaScript code that treats a returned timer ID as truthy.

Fix

Update next_id() to increment the counter before returning it, so valid timer IDs become 1-based again. This preserves 0 for the no-callback sentinel and restores the pre-#5289 behavior.

Tests

  • timer_ids_are_positive_and_unique asserts that timer IDs returned by setTimeout and setInterval are greater than 0 and unique.
  • no_callback_returns_zero_sentinel asserts that setTimeout() with no callback still returns 0 and does not collide with a valid timer ID.

Verification

  • cargo test -p boa_runtime
  • cargo run -p boa_cli -- -e "console.log(setTimeout(() => {}, 10)); console.log(setTimeout(() => {}, 10)); console.log(setTimeout())"

Before the fix, the first valid timer returned 0. After the fix, the output is 1, 2, 0.

@iammdzaidalam iammdzaidalam requested a review from a team as a code owner May 18, 2026 20:03
@github-actions github-actions Bot added C-Tests Issues and PRs related to the tests. C-Runtime Issues and PRs related to Boa's runtime features Waiting On Review Waiting on reviews from the maintainers labels May 18, 2026
@github-actions github-actions Bot added this to the v1.0.0 milestone May 18, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

Test262 conformance changes

Test result main count PR count difference
Total 53,125 53,125 0
Passed 51,051 51,051 0
Ignored 1,482 1,482 0
Failed 592 592 0
Panics 0 0 0
Conformance 96.10% 96.10% 0.00%

Tested main commit: d2caa75c7cda9f163612820e2297bf5a4b1f1299
Tested PR commit: 28813195f5165d2e9a62a6cb53c3576cd5107a8e
Compare commits: d2caa75...2881319

@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 73.91304% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.80%. Comparing base (6ddc2b4) to head (2881319).
⚠️ Report is 966 commits behind head on main.

Files with missing lines Patch % Lines
core/engine/src/job.rs 0.00% 4 Missing ⚠️
core/engine/src/context/mod.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #5379       +/-   ##
===========================================
+ Coverage   47.24%   59.80%   +12.56%     
===========================================
  Files         476      566       +90     
  Lines       46892    62935    +16043     
===========================================
+ Hits        22154    37641    +15487     
- Misses      24738    25294      +556     

☔ 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm pretty sure these kinds of tests are already in our WPT suite, they're just disabled. It might be better to enable those instead.

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.

Hi @jedel1043, Checked the WPT runner, the timers category isn't enabled yet so those tests aren't actually running. Only console, encoding, url and fetch are set up. Want me to enable those instead of keeping the custom tests here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes... that's literally what I said...

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.

Quick question, at line 87 in core/runtime/src/interval.rs, why are we clamping the delay here instead of following the WebIDL long conversion behavior?

let delay = u64::from(delay.clamp_finite(0, u32::MAX));

I was trying the WPT timers suite locally, and tests like type-long-settimeout / type-long-setinterval fail because values like Math.pow(2, 32) seem to get clamped to u32::MAX instead of wrapping back to 0, so Boa ends up scheduling a ~49 day timeout.

Feels like that’s a separate timer/WebIDL issue beyond the timer ID collision fix, but I wanted to check if the current behavior here was intentional.

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.

i mean if you run : cargo run -p boa_cli -- -e "setTimeout(() => console.log('Should fire instantly!'), Math.pow(2, 32));" ... hangs and does absolutely nothing

while something like node : node -e "setTimeout(() => console.log('Should fire instantly!'), Math.pow(2, 32));" prints:

TimeoutOverflowWarning: 4294967296 does not fit into a 32-bit signed integer.
Timeout duration was set to 1.
Should fire instantly!

Copy link
Copy Markdown
Member

@jedel1043 jedel1043 May 19, 2026

Choose a reason for hiding this comment

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

No direction, since even the runtime itself has no direction :)
It started (and still is) as a testing environment to see if you can use Boa to build a runtime, but it shouldn't be used in production

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.

Timeout is conformant because the spec just says that value is the number of milliseconds to wait to trigger the function. There's no mention of 32 bits.

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.

Copy link
Copy Markdown
Contributor Author

@iammdzaidalam iammdzaidalam May 19, 2026

Choose a reason for hiding this comment

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

sorry for the late reply, i tried switching the delay conversion to ToInt32 since the spec uses long there. And with that, Math.pow(2, 32) wraps to 0 and fires instantly locally. Timer tests pass too.

is it a right approach? should I keep this separate from the id fix or put it in this PR?

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.

Yes that would be the right approach.

Comment thread core/runtime/src/interval.rs Outdated
Signed-off-by: iammdzaidalam <161572905+iammdzaidalam@users.noreply.github.com>
@iammdzaidalam iammdzaidalam force-pushed the fix/timer-id-zero-collision branch from c5c9a90 to ea51866 Compare May 18, 2026 22:14
Comment thread core/runtime/src/interval.rs Outdated
…sues

Signed-off-by: iammdzaidalam <161572905+iammdzaidalam@users.noreply.github.com>
@github-actions github-actions Bot added C-Dependencies Pull requests that update a dependency file C-Builtins PRs and Issues related to builtins/intrinsics labels May 20, 2026
@iammdzaidalam
Copy link
Copy Markdown
Contributor Author

hello @hansl @jedel1043, updated the pr.

  • moved timer ids to NonZeroU32
  • fixed clearTimeout / clearInterval behavior
  • switched delay conversion to to_i32() (for webidl long behavior)
  • enabled the WPT timer tests

also had to fix a windows WPT path issue, add clear_jobs for interval tests, and add the missing experimental cfg flags in iterable/tests.rs.

If needed i can split the last part.

@iammdzaidalam iammdzaidalam requested review from hansl and jedel1043 May 20, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-Builtins PRs and Issues related to builtins/intrinsics C-Dependencies Pull requests that update a dependency file C-Runtime Issues and PRs related to Boa's runtime features C-Tests Issues and PRs related to the tests. Waiting On Review Waiting on reviews from the maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: setTimeout/setInterval return timer ID 0 for first timer, colliding with no-op sentinel

3 participants