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
6 changed files with 58 additions and 85 deletions

View File

@@ -153,7 +153,15 @@ pub fn processNamesArray(
) catch |err| {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
switch (err) {
error.EISNOTDIR, error.EISDIR, error.EACCESS, error.EPERM, error.ENOENT, error.FileNotFound => {},
error.EISNOTDIR, error.EISDIR, error.EACCESS, error.EPERM, error.ENOENT, error.FileNotFound => {
log.addErrorFmt(
source,
item.loc,
allocator,
"Workspace not found \"{s}\"",
.{input_path},
) catch {};
},
error.MissingPackageName => {
log.addErrorFmt(
source,

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

@@ -34,9 +34,9 @@ test("bad workspace path", () => {
});
const text = stderr!.toString();
// Missing workspace paths are silently ignored, matching npm behavior
expect(text).not.toContain('Workspace not found "i-dont-exist"');
expect(exitCode).toBe(0);
expect(text).toContain('Workspace not found "i-dont-exist"');
expect(exitCode).toBe(1);
});
test("workspace with ./ should not crash", () => {

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);
});

View File

@@ -1,81 +0,0 @@
import { spawnSync } from "bun";
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
test("missing literal workspace path should not error", () => {
using dir = tempDir("issue-26970", {
"package.json": JSON.stringify({
name: "test",
workspaces: ["terraform"],
}),
});
const { stderr, exitCode } = spawnSync({
cmd: [bunExe(), "install"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const text = stderr!.toString();
expect(text).not.toContain("Workspace not found");
expect(exitCode).toBe(0);
});
test("missing glob workspace pattern should not error", () => {
using dir = tempDir("issue-26970", {
"package.json": JSON.stringify({
name: "test",
workspaces: ["terraform*"],
}),
});
const { stderr, exitCode } = spawnSync({
cmd: [bunExe(), "install"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const text = stderr!.toString();
expect(text).not.toContain("Workspace not found");
expect(exitCode).toBe(0);
});
test("literal and glob missing workspaces behave the same", () => {
using literalDir = tempDir("issue-26970", {
"package.json": JSON.stringify({
name: "test",
workspaces: ["nonexistent"],
}),
});
const literalResult = spawnSync({
cmd: [bunExe(), "install"],
cwd: String(literalDir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
using globDir = tempDir("issue-26970", {
"package.json": JSON.stringify({
name: "test",
workspaces: ["nonexistent*"],
}),
});
const globResult = spawnSync({
cmd: [bunExe(), "install"],
cwd: String(globDir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
// Both should succeed with exit code 0
expect(literalResult.exitCode).toBe(0);
expect(globResult.exitCode).toBe(0);
});