Skip to content

Commit 7a5c452

Browse files
authored
Merge pull request #5751 from cloudflare/dlapid/nodejs_global_timers
Enable nodejs style global timers with a compat flag.
2 parents 78a4eec + d51725b commit 7a5c452

File tree

7 files changed

+485
-8
lines changed

7 files changed

+485
-8
lines changed

src/node/internal/internal_timers.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@
2626
import { validateFunction } from 'node-internal:validators';
2727
import { default as timersUtil } from 'node-internal:timers';
2828

29+
// Capture original global timer functions at module load time, before they might be
30+
// overridden by the enable_nodejs_global_timers compat flag. This avoids circular
31+
// dependency issues when our setTimeout/setInterval return Timeout objects.
32+
// Cast to number-returning functions since we know we're capturing the original
33+
// implementations before any override.
34+
const originalSetTimeout = globalThis.setTimeout.bind(globalThis) as (
35+
callback: (...args: unknown[]) => unknown,
36+
ms?: number,
37+
...args: unknown[]
38+
) => number;
39+
const originalSetInterval = globalThis.setInterval.bind(globalThis) as (
40+
callback: (...args: unknown[]) => unknown,
41+
ms?: number,
42+
...args: unknown[]
43+
) => number;
44+
const originalClearTimeout = globalThis.clearTimeout.bind(globalThis) as (
45+
id?: number
46+
) => void;
47+
const originalClearInterval = globalThis.clearInterval.bind(globalThis) as (
48+
id?: number
49+
) => void;
50+
2951
let clearTimeoutImpl: (obj: Timeout) => void;
3052

3153
export class Timeout {
@@ -54,18 +76,17 @@ export class Timeout {
5476
}
5577

5678
#constructTimer(): number {
57-
// @ts-expect-error TS2322 Due to difference between Node.js and globals
5879
this.#timer = this.#isRepeat
59-
? globalThis.setInterval(this.#callback, this.#after, ...this.#args)
60-
: globalThis.setTimeout(this.#callback, this.#after, ...this.#args);
80+
? originalSetInterval(this.#callback, this.#after, ...this.#args)
81+
: originalSetTimeout(this.#callback, this.#after, ...this.#args);
6182
return this.#timer;
6283
}
6384

6485
#clearTimeout(): void {
6586
if (this.#isRepeat) {
66-
globalThis.clearInterval(this.#timer);
87+
originalClearInterval(this.#timer);
6788
} else {
68-
globalThis.clearTimeout(this.#timer);
89+
originalClearTimeout(this.#timer);
6990
}
7091
}
7192

@@ -133,7 +154,7 @@ export function clearTimeout(
133154
if (timer instanceof Timeout) {
134155
clearTimeoutImpl(timer);
135156
} else if (typeof timer === 'number') {
136-
globalThis.clearTimeout(timer);
157+
originalClearTimeout(timer);
137158
}
138159
}
139160

@@ -162,7 +183,7 @@ export function clearInterval(
162183
if (timer instanceof Timeout) {
163184
clearTimeoutImpl(timer);
164185
} else if (typeof timer === 'number') {
165-
globalThis.clearInterval(timer);
186+
originalClearInterval(timer);
166187
}
167188
}
168189

@@ -182,7 +203,7 @@ export function unenroll(timer: unknown): void {
182203
if (timer instanceof Timeout) {
183204
clearTimeoutImpl(timer);
184205
} else if (typeof timer === 'number') {
185-
globalThis.clearTimeout(timer);
206+
originalClearTimeout(timer);
186207
}
187208
}
188209

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2017-2025 Cloudflare, Inc.
2+
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3+
// https://opensource.org/licenses/Apache-2.0
4+
5+
// This module overrides globalThis timer functions with Node.js-compatible versions
6+
// when loaded. It is loaded by worker.c++ when enable_nodejs_global_timers compat
7+
// flag is enabled.
8+
//
9+
// After loading:
10+
// - globalThis.setTimeout and globalThis.setInterval return Timeout objects
11+
// with methods like refresh(), ref(), unref(), and hasRef() instead of numeric IDs
12+
// - globalThis.setImmediate and globalThis.clearImmediate are available
13+
14+
import {
15+
setTimeout,
16+
setInterval,
17+
clearTimeout,
18+
clearInterval,
19+
setImmediate,
20+
clearImmediate,
21+
} from 'node-internal:internal_timers';
22+
23+
globalThis.setTimeout = setTimeout as unknown as typeof globalThis.setTimeout;
24+
globalThis.setInterval =
25+
setInterval as unknown as typeof globalThis.setInterval;
26+
globalThis.clearTimeout =
27+
clearTimeout as unknown as typeof globalThis.clearTimeout;
28+
globalThis.clearInterval =
29+
clearInterval as unknown as typeof globalThis.clearInterval;
30+
globalThis.setImmediate = setImmediate;
31+
globalThis.clearImmediate = clearImmediate;

src/workerd/api/node/tests/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,12 @@ wd_test(
365365
data = ["timers-nodejs-test.js"],
366366
)
367367

368+
wd_test(
369+
src = "timers-global-override-test.wd-test",
370+
args = ["--experimental"],
371+
data = ["timers-global-override-test.js"],
372+
)
373+
368374
wd_test(
369375
src = "async_hooks-nodejs-test.wd-test",
370376
args = ["--experimental"],

0 commit comments

Comments
 (0)