Skip to content

WebKit: use GetSystemTimePreciseAsFileTime for WallTime and QPC for MonotonicTime on Windows - #34428

Open
robobun wants to merge 8 commits into
mainfrom
farm/ba038d63/webkit-win-precise-time
Open

WebKit: use GetSystemTimePreciseAsFileTime for WallTime and QPC for MonotonicTime on Windows#34428
robobun wants to merge 8 commits into
mainfrom
farm/ba038d63/webkit-win-precise-time

Conversation

@robobun

@robobun robobun commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Bumps WEBKIT_VERSION to the preview build of oven-sh/WebKit#304 so Bun's Windows CI exercises the new Windows clock sources:

  • WTF::WallTime::now() (which backs Date.now(), new Date(), Intl) now reads GetSystemTimePreciseAsFileTime instead of the GetSystemTimeAsFileTime + QueryPerformanceCounter interpolation scheme that kept unsynchronized static state and could disagree with the kernel's precise clock by ~1ms.
  • MonotonicTime::now() / ContinuousTime::now() / ContinuousApproximateTime::now() now read QueryPerformanceCounter directly instead of wall-clock-clamping via an unsynchronized static double lastTime.

With that change, Date.now() and bun_core::time::milli_timestamp() read the same kernel clock on every platform (POSIX already did; both call clock_gettime(CLOCK_REALTIME)). That lets the two remaining JS-visible defaults that a user would compare against Date.now() read it directly:

  • Bun.randomUUIDv7()'s default timestamp now reads global.js_date_now(), the same clock source crypto.randomUUIDv7() already uses. Both are now js_date_now() + UUID7::init; only the option validation and disableEntropyCache differ.
  • new File([], name).lastModified's default now reads js_date_now(), which is what the File API spec says the default is ("the equivalent of Date.now()").

Both defaults now also respect setSystemTime(), matching Date.now().

Originally motivated by the Windows test-crypto-randomuuidv7.js flake in #32623 (assert(uuidTimestamp >= Date.now()) sampled just before the native call), which this removes at the source.

How did you verify your code works?

  • bun bd test test/js/bun/util/randomUUIDv7.test.ts (20/20 pass). Two new tests:
    • default timestamp is bracketed by Date.now(): loops 50k on Windows / 5k elsewhere asserting before <= {Bun.randomUUIDv7, crypto.randomUUIDv7, File.lastModified} <= after.
    • default timestamp respects setSystemTime(): pins the clock and checks all three read the pinned value.
  • The setSystemTime() test fails on main without the src/ changes (Bun.randomUUIDv7() and File.lastModified return the real clock), passes with them.
  • The Windows CI run on this PR is the real check for the WebKit-side clock swap; the preview build compiled green on all 43 targets (https://github.com/oven-sh/WebKit/releases/tag/autobuild-preview-pr-304-36986ac9).

Note: WEBKIT_VERSION points at a autobuild-preview-pr-* tag, which only exists while oven-sh/WebKit#304 is open. Before merging this, that PR needs to merge first and WEBKIT_VERSION should be set to the resulting main sha.


no test proof · iteration 7 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/util/randomUUIDv7.test.ts

…onotonicTime on Windows

Bumps WEBKIT_VERSION to the oven-sh/WebKit#304 preview build so bun's
Windows CI exercises the new clock sources.

With that change, JSC's Date.now() and bun_core::time::milli_timestamp()
read the same kernel clock on every platform, so the two remaining
JS-visible defaults that compared against Date.now() can read it directly:

- Bun.randomUUIDv7() default timestamp now reads global.js_date_now()
  instead of milli_timestamp(), which is the same clock source
  crypto.randomUUIDv7() already uses. Both paths are now the same
  js_date_now() + UUID7::init; only the option validation and
  disableEntropyCache differ.
- new File([], name).lastModified default now reads js_date_now(), which
  is what the File API spec says the default is ("the equivalent of
  Date.now()").

Both defaults also now respect setSystemTime(), matching Date.now().

Tests: a Date.now() bracketing check over Bun.randomUUIDv7 /
crypto.randomUUIDv7 / File.lastModified (50k iters on Windows where the
~1ms skew used to bite ~80% of samples), and a setSystemTime() pinning
check for all three.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

UUIDv7 time alignment

Layer / File(s) Summary
Shared UUIDv7 generation helpers
src/runtime/webcore/Crypto.rs
UUIDv7 timestamps use JavaScript time, entropy handling is centralized, and hexadecimal rendering uses a shared helper.
Node UUIDv7 integration
src/runtime/node/node_crypto_binding.rs
Node UUIDv7 generation delegates to the shared crypto helpers.
File timestamps and alignment tests
src/runtime/webcore/Blob.rs, test/js/bun/util/randomUUIDv7.test.ts, test/js/web/fetch/blob.test.ts
File defaults use JavaScript time, with tests covering current, Windows, and pinned timestamps across UUIDv7 and File APIs.

WebKit dependency pin

Layer / File(s) Summary
WebKit version configuration
scripts/build/deps/webkit.ts
The WebKit dependency tag is updated to a new autobuild preview version.

Possibly related PRs

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately reflects the main WebKit Windows clock-source update.
Description check ✅ Passed The description includes both required sections and enough implementation and verification detail.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 5:02 AM PT - Jul 17th, 2026

@robobun, your commit 48490a4 has 100 failures in Build #74528 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34428

That installs a local version of the PR into your bun-34428 executable, so you can run:

bun-34428 --bun

…calls the same core as Bun.randomUUIDv7

Both now route entropy+UUID7::init+hex-print through
webcore::crypto::{uuid_v7_at, uuid_v7_to_hex_js}. Only the argument
validation (Bun's encoding/timestamp vs Node's options object) stays
separate.
Comment thread test/js/bun/util/randomUUIDv7.test.ts
Comment thread test/js/bun/util/randomUUIDv7.test.ts
Comment thread src/runtime/webcore/Crypto.rs Outdated
…Crypto.rs comment

UUID7::init may bump the embedded timestamp by +1ms on 12-bit counter
rollover, so the test now only asserts the lower bound for the UUID paths
(which is what the WebKit clock change actually fixes) and keeps full
bracketing for File.lastModified, which has no counter. The Crypto.rs
comment is down to 3 lines and no longer claims the embedded ts is
bracketed or references the PR number.
Comment thread src/runtime/webcore/Blob.rs
Comment thread test/js/bun/util/randomUUIDv7.test.ts Outdated
…s lastModified-default to a bracket

The blob.test.ts comment described the pre-change native-clock skew that
this PR removes; drop it and assert the Date.now() bracket the default
now satisfies.

@claude claude Bot left a comment

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.

All prior feedback is addressed in 0708029 and 11736ac — the Rust/test changes look correct and I have nothing further on the code side. Not auto-approving because WEBKIT_VERSION still points at the autobuild-preview-pr-304-* tag (author-noted precondition) and the WebKit-side clock-source swap for WallTime/MonotonicTime affects every Date.now()/monotonic read on Windows, which warrants a human look at the Windows CI run before landing.

What was reviewed:

  • uuid_v7_at/uuid_v7_to_hex_js extraction — behavior-preserving vs. the two prior call sites; bun_boringssl_sys::rand_bytes matches the boringssl::rand_bytes alias used on the node side.
  • setSystemTime test: fresh subprocess so UUID_V7_LAST_TIMESTAMP starts at 0 and the pinned 1.7e12 is used exactly (second call same-ms, counter+1, no rollover).
  • blob.test.ts bracket tightening and the 3-line test comment now match the invariant the code guarantees.
Extended reasoning...

Overview

This PR bumps WEBKIT_VERSION to a preview build of oven-sh/WebKit#304 (Windows WallTimeGetSystemTimePreciseAsFileTime, MonotonicTime → QPC), and aligns three JS-visible defaults to read global.js_date_now() so they agree exactly with Date.now() and respect setSystemTime(): Bun.randomUUIDv7()'s default timestamp, crypto.randomUUIDv7() (now sharing a uuid_v7_at/uuid_v7_to_hex_js core with the Bun API), and new File(...).lastModified's default. Two subprocess tests cover the bracketing and setSystemTime behavior; blob.test.ts's stale-comment/wide-tolerance default test was tightened to a bracket.

Security risks

None. The only entropy-path change is that Bun.randomUUIDv7's cached-entropy read moved into a shared helper with an added disable_entropy_cache parameter that is hard-coded false at the Bun call site — identical bytes to before. crypto.randomUUIDv7's disableEntropyCache semantics are preserved (BoringSSL rand_bytes when true, VM cache otherwise).

Level of scrutiny

The Rust/test edits are small, mechanical, and well-covered by tests. The WebKit dependency bump is the high-scrutiny piece: it swaps the clock source underlying every Date.now(), new Date(), Intl formatting, and JSC monotonic-time read on Windows. That's a runtime-wide behavior change whose real validation is the Windows CI matrix, not local tests. It also currently pins a preview tag that the PR description says must be replaced with the merged sha before landing — a hard precondition a human needs to coordinate.

Other factors

Two prior review rounds from me were fully addressed (UUID upper-bound flake risk removed, comments condensed to ≤3 lines, stale blob.test.ts comment/tolerance updated). The stderr === "" pattern in the new subprocess tests matches the five existing tests in the file and was intentionally left as-is. No outstanding threads. Given the cross-repo dependency and the Windows-global timing impact, this should get a human sign-off after the Windows CI results are in and the WebKit PR has merged.

@robobun

robobun commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

CI on 11736ac (build 74265): 285/286 lanes passed, all 34 Windows lanes green (x64, x64-baseline, aarch64; builds + every test shard).

The one failure is test/js/web/timers/timer-heap-race.test.ts on debian-13 x64-asan: an ASAN leak in ConcurrentTask allocated via Bun__queueJSCDeferredWorkTaskConcurrently when JSC::WaiterListManager::unregister schedules deferred work during worker VM teardown. This PR does not touch worker/event-loop/WaiterListManager code, and the WebKit-side changes here are all under #if OS(WINDOWS) && USE(BUN_JSC_ADDITIONS) (no-op on Linux). The test is pre-existing on main and is being handled separately. The remaining entries in the annotation are flakes that passed on retry.

Ready for review; WEBKIT_VERSION still points at the autobuild-preview-pr-304-36986ac9 tag and should be swapped for the merged oven-sh/WebKit#304 main sha before this lands.

…ce.now() on Windows

Pure-JS observable of the WebKit-side clock change: performance.timeOrigin
is set from the precise kernel clock at startup and performance.now() is
QPC elapsed, so their sum tracks the precise clock. With Date.now() now
reading the same clock, sampling Date.now() then the sum gives
floor(t1) - t2 <= 0. On the previous WTF QPC-interpolated path, Date.now()
ran ~0.4ms ahead and ~72% of 200k samples had Date.now() > the sum.
Comment thread test/js/bun/util/randomUUIDv7.test.ts
Comment thread src/runtime/webcore/Crypto.rs Outdated
…cess; trim uuid_v7_at doc to 3 lines

In a subprocess, performance.timeOrigin is captured milliseconds before
the loop, so w32tm slew between VM init and the test cannot drift the
live wall clock ahead of timeOrigin + QPC elapsed.

@claude claude Bot left a comment

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.

52820f7 addresses both open threads from the last pass — the Date.now()/performance.now() clock test now runs in a fresh subprocess (so timeOrigin is captured immediately before the loop), and the uuid_v7_at doc comment is down to 3 lines. Nothing else new to flag beyond the inline WEBKIT_VERSION note.

Extended reasoning...

Commit 52820f7 landed after the previous review round and addresses both unresolved 05:47 threads: the Windows-only Date.now() <= timeOrigin + performance.now() test is now spawned in a subprocess with a comment explaining the w32tm-slew rationale, and the uuid_v7_at doc comment was trimmed from 4 to 3 lines. Verified both against the current diff. The only finding this run is the ephemeral autobuild-preview-pr-304-* tag in WEBKIT_VERSION, which the PR description already calls out as a pre-merge TODO; the inline comment tracks it as a review gate so it isn't merged by accident. Not approving because that swap plus the WebKit bump itself (Windows clock-source change affecting Date.now()/MonotonicTime) warrant a human sign-off.

Comment thread scripts/build/deps/webkit.ts Outdated
…ce clock test

origin + performance.now() at ~1.8e12 has ~0.0004ms double ULP, so the
sum can round below the true value and make d > p by a fraction of a
microsecond. CI hit d - p = 0.0002 on one sample. The old-path skew was
~0.4ms, so a 0.01ms threshold still separates the two cleanly.

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/js/bun/util/randomUUIDv7.test.ts (1)

255-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use module-scope imports in the child scripts.

These subprocess scripts use require() even though they are not testing dynamic/CommonJS loading. Move the imports to the top of each embedded script, or use a fixture file if -e cannot parse static imports.

As per coding guidelines, only use dynamic import or require when the test specifically covers it; otherwise use module-scope imports.

Proposed fix
-          const crypto = require("node:crypto");
+          import * as crypto from "node:crypto";

-          const { setSystemTime } = require("bun:test");
-          const crypto = require("node:crypto");
+          import { setSystemTime } from "bun:test";
+          import * as crypto from "node:crypto";

Also applies to: 288-290

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/js/bun/util/randomUUIDv7.test.ts` around lines 255 - 263, Update the
embedded child scripts around the crypto usage to use module-scope imports
instead of require(), including the additional occurrence near the later
referenced lines. Preserve the scripts’ existing behavior and use a fixture file
if the -e execution mode cannot parse static imports.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@test/js/bun/util/randomUUIDv7.test.ts`:
- Around line 255-263: Update the embedded child scripts around the crypto usage
to use module-scope imports instead of require(), including the additional
occurrence near the later referenced lines. Preserve the scripts’ existing
behavior and use a fixture file if the -e execution mode cannot parse static
imports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f9b0c42-92fd-47d1-8021-de46975f3820

📥 Commits

Reviewing files that changed from the base of the PR and between 0708029 and 5104e8f.

📒 Files selected for processing (3)
  • src/runtime/webcore/Crypto.rs
  • test/js/bun/util/randomUUIDv7.test.ts
  • test/js/web/fetch/blob.test.ts

autobuild-722f2a8a1a3da159a89b35730c5460a6ef58f0af is now published
(43 assets). Also pulls in the three WebKit main commits that landed
between the preview base and the merge: 365cb024 (Dockerfile.windows
build flags), ae5110d3 (defer termination at reifyStaticProperty call
sites), 8be99556 (AsyncContextSwapScope).

@claude claude Bot left a comment

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.

No new findings — all prior feedback (comment length, stale blob.test.ts assertion, in-process Date.now/perf.now test, preview WEBKIT_VERSION tag) is addressed, and WEBKIT_VERSION now points at the merged 40-hex main sha. Deferring to a human for the WebKit bump itself: it swaps the Windows WallTime/MonotonicTime clock sources engine-wide, which is beyond what I'll auto-approve.

What was reviewed:

  • uuid_v7_at/uuid_v7_to_hex_js extraction is behavior-preserving vs. both prior call sites (entropy source, disableEntropyCache, hex rendering).
  • js_date_now().max(0.0) as u64 and the File.lastModified default swap — negative/NaN handling and setSystemTime() interaction check out.
  • The three new subprocess tests: pipe draining, bracketing/rollover asymmetry, and the 0.01ms slack on the Windows-only clock test all look sound.
  • prebuiltUrl()/prebuiltDestDir() handle the new 40-hex sha via the existing non-autobuild- path.
Extended reasoning...

Overview

Six files: a WEBKIT_VERSION bump in scripts/build/deps/webkit.ts picking up oven-sh/WebKit#304 (Windows WTF::WallTime::now()GetSystemTimePreciseAsFileTime, MonotonicTime::now() → raw QPC); three small Rust changes in Crypto.rs / node_crypto_binding.rs / Blob.rs swapping bun_core::time::milli_timestamp()global.js_date_now() for Bun.randomUUIDv7()'s default timestamp and new File().lastModified's default, plus extracting the shared UUIDv7 core into uuid_v7_at() / uuid_v7_to_hex_js(); three new subprocess tests in randomUUIDv7.test.ts; and a tightened bracket assertion in blob.test.ts.

Security risks

None identified. The entropy paths are unchanged (still VM entropy cache or BoringSSL rand_bytes); the refactor moves existing code into a shared helper without changing which RNG is called or how many bytes are drawn. js_date_now() is an existing internal accessor already used by crypto.randomUUIDv7().

Level of scrutiny

The Bun-side Rust and test changes are small, well-tested, and I'd approve them on their own. The WebKit bump is the piece that warrants human sign-off: it changes the clock source for Date.now(), new Date(), Intl, and every WTF MonotonicTime/ContinuousTime consumer on Windows — engine-wide behavior whose actual implementation lives in a separate repo. That falls under REVIEW.md's Dependencies & vendoring situational section, and the last full-CI report in the thread (285/286 on build 74265) was for 11736aca; three test-touching commits and the sha swap have landed since, with build #74528 kicked off on the current HEAD but no result posted yet.

Other factors

Every inline thread from my earlier passes is resolved and reflected in the current diff: comments are ≤3 lines, the Date.now/perf.now test runs in a subprocess with 0.01ms double-precision slack, the stale blob.test.ts comment is gone with the assertion tightened to a bracket, and WEBKIT_VERSION is a permanent 40-hex main sha. The uuid_v7_at extraction removes the last duplication between the Bun and node:crypto UUIDv7 paths (the UUID7 import in node_crypto_binding.rs was correctly dropped). Nothing new to flag — deferring solely on the scope of the vendored-engine bump and pending CI on HEAD.

@robobun

robobun commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

CI on 48490a4 (build 74528): the debian-13 x64-asan lanes fail ~20 tests with unchecked exception at initializeTemplateObjects @ CodeBlock.cpp:1119 (thrown from Lookup.cpp:62).

This is not from oven-sh/WebKit#304 (those changes are all under #if OS(WINDOWS) in CurrentTime.cpp). It's from oven-sh/WebKit@ae5110d3 (WebKit PR #282, "Defer termination at the reifyStaticProperty call sites"), one of the three WebKit main commits that landed between the preview base and the merge of #304. That commit added a DECLARE_THROW_SCOPE in setUpStaticFunctionSlot, and JSC-internal callers like CodeBlock::initializeTemplateObjects don't exception-check after the call, tripping the scope verifier on the ASAN lane.

The preview build this PR was originally validated against (autobuild-preview-pr-304-36986ac9, 285/286 with all Windows green on build 74265) was based on e5f7fc2b1a, before ae5110d3 landed. The fix for ae5110d3 belongs in oven-sh/WebKit (either avoid the ThrowScope there, or add the missing checks at the JSC call sites) and is being handled separately; once that lands this PR's WEBKIT_VERSION can be bumped past it.

The Windows lanes (the actual subject of this PR) and this PR's own tests are green.

@robobun

robobun commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

The x64-asan validateExceptionChecks failures in build 74528 (setUpStaticFunctionSlot @ Lookup.cpp:62initializeTemplateObjects @ CodeBlock.cpp:1119) come from oven-sh/WebKit#282, which this PR's WebKit bump picks up. #282 added a DECLARE_THROW_SCOPE inside setUpStaticFunctionSlot; a ThrowScope's destructor simulates a throw on every destruction, so every first static-table property lookup now demands an exception check from its caller, and the many JSC-internal getOwnPropertySlot callers never do one.

Fix is oven-sh/WebKit#306: keep the DeferTerminationForAWhile and the early-return-on-exception, drop the ThrowScope and read vm.exceptionForInspection() instead. Once that merges, bumping WEBKIT_VERSION to the resulting commit should clear these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant