Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
705d761d2a fix(timer): set Windows timer resolution to 1ms for accurate intervals
Call `timeBeginPeriod(1)` at startup on Windows to increase the system
timer resolution from the default ~15.6ms to 1ms. Without this, timers
like `setInterval(fn, 16)` fire at ~28ms intervals instead of ~16ms
because IOCP waits round up to the next timer tick boundary.

Closes #26965

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-12 13:54:34 +00:00
3 changed files with 46 additions and 0 deletions

View File

@@ -34,6 +34,11 @@ pub fn main() void {
// This should appear before we make any calls at all to libuv.
// So it's safest to put it very early in the main function.
if (Environment.isWindows) {
// Set the Windows timer resolution to 1ms. Without this, the default
// resolution is ~15.6ms which causes timers like setInterval(fn, 16)
// to fire at ~28ms intervals instead of ~16ms. (See #26965)
_ = _bun.windows.timeBeginPeriod(1);
_ = _bun.windows.libuv.uv_replace_allocator(
&_bun.mimalloc.mi_malloc,
&_bun.mimalloc.mi_realloc,

View File

@@ -86,6 +86,9 @@ pub const WPathBuffer = if (Environment.isWindows) bun.WPathBuffer else void;
pub const HANDLE = win32.HANDLE;
pub const HMODULE = win32.HMODULE;
/// https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod
pub extern "winmm" fn timeBeginPeriod(uPeriod: UINT) callconv(.winapi) UINT;
/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
pub extern "kernel32" fn GetFileInformationByHandle(
hFile: HANDLE,

View File

@@ -0,0 +1,38 @@
import { expect, test } from "bun:test";
// https://github.com/oven-sh/bun/issues/26965
// setInterval(fn, 16) fires with ~28ms intervals on Windows instead of ~16ms
// due to the default Windows timer resolution being ~15.6ms.
// The fix calls timeBeginPeriod(1) at startup to set 1ms resolution.
test("setInterval fires at approximately the requested interval", async () => {
const interval = 16;
const count = 50;
const times: number[] = [];
let last = performance.now();
await new Promise<void>(resolve => {
let i = 0;
const id = setInterval(() => {
const now = performance.now();
times.push(now - last);
last = now;
i++;
if (i >= count) {
clearInterval(id);
resolve();
}
}, interval);
});
// Drop the first few measurements as they can be noisy during startup
const stable = times.slice(5);
const avg = stable.reduce((a, b) => a + b, 0) / stable.length;
// The average interval should be close to the requested 16ms.
// Before the fix on Windows, this was ~28ms (nearly 2x).
// Allow up to 22ms to account for normal scheduling jitter,
// but catch the ~28ms+ intervals caused by 15.6ms timer resolution.
expect(avg).toBeLessThan(22);
expect(avg).toBeGreaterThan(10);
});