angular/packages/zone.js/test/node/timer.spec.ts
Alan Agius 982f1b1251 fix(zone.js): support Timeout.refresh in Node.js (#56852)
The `Timeout` object in Node.js has a `refresh` method, used to restart `setTimeout`/`setInterval` timers. Before this commit, `Timeout.refresh` was not handled, leading to memory leaks when using `fetch` in Node.js. This issue arose because `undici` (the Node.js fetch implementation) uses a refreshed `setTimeout` for cleanup operations.

For reference, see: 1dff4fd9b1/lib/util/timers.js (L45)

Fixes: #56586

PR Close #56852
2024-07-16 12:46:51 -07:00

94 lines
2.7 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {setTimeout as sleep} from 'node:timers/promises';
import {promisify} from 'node:util';
import {taskSymbol} from '../../lib/common/timers';
describe('node timer', () => {
it('util.promisify should work with setTimeout', (done: DoneFn) => {
const setTimeoutPromise = promisify(setTimeout);
setTimeoutPromise(50, 'value').then(
(value) => {
expect(value).toEqual('value');
done();
},
(error) => {
fail(`should not be here with error: ${error}.`);
},
);
});
it('util.promisify should work with setImmediate', (done: DoneFn) => {
const setImmediatePromise = promisify(setImmediate);
setImmediatePromise('value').then(
(value) => {
expect(value).toEqual('value');
done();
},
(error) => {
fail(`should not be here with error: ${error}.`);
},
);
});
it(`'Timeout.refresh' should restart the 'setTimeout' when it is not scheduled`, async () => {
const spy = jasmine.createSpy();
const timeout = setTimeout(spy, 100) as unknown as NodeJS.Timeout;
let iterations = 5;
for (let i = 1; i <= iterations; i++) {
timeout.refresh();
await sleep(150);
}
expect((timeout as any)[taskSymbol].runCount).toBe(iterations);
clearTimeout(timeout);
expect((timeout as any)[taskSymbol]).toBeNull();
expect(spy).toHaveBeenCalledTimes(iterations);
});
it(`'Timeout.refresh' restarts the 'setTimeout' when it is running`, async () => {
let timeout: NodeJS.Timeout;
const spy = jasmine.createSpy().and.callFake(() => timeout.refresh());
timeout = setTimeout(spy, 100) as unknown as NodeJS.Timeout;
await sleep(250);
expect((timeout as any)[taskSymbol].runCount).toBe(2);
clearTimeout(timeout);
expect((timeout as any)[taskSymbol]).toBeNull();
expect(spy).toHaveBeenCalledTimes(2);
});
it(`'Timeout.refresh' should restart the 'setInterval'`, async () => {
const spy = jasmine.createSpy();
const interval = setInterval(spy, 200) as unknown as NodeJS.Timeout;
// Restart the interval multiple times before the elapsed time.
for (let i = 1; i <= 4; i++) {
interval.refresh();
await sleep(100);
}
// Time did not elapse
expect((interval as any)[taskSymbol].runCount).toBe(0);
expect(spy).toHaveBeenCalledTimes(0);
await sleep(350);
expect((interval as any)[taskSymbol].runCount).toBe(2);
clearInterval(interval);
expect((interval as any)[taskSymbol]).toBeNull();
expect(spy).toHaveBeenCalledTimes(2);
});
});