mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
feat(ext/timers): add refTimer, unrefTimer API (#12953)
This commit is contained in:
parent
1507b8c984
commit
69ad5f0e78
4 changed files with 177 additions and 13 deletions
12
cli/dts/lib.deno.unstable.d.ts
vendored
12
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -1022,6 +1022,18 @@ declare namespace Deno {
|
|||
* Release an advisory file-system lock for the provided file.
|
||||
*/
|
||||
export function funlockSync(rid: number): void;
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* Make the timer of the given id blocking the event loop from finishing
|
||||
*/
|
||||
export function refTimer(id: number): void;
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* Make the timer of the given id not blocking the event loop from finishing
|
||||
*/
|
||||
export function unrefTimer(id: number): void;
|
||||
}
|
||||
|
||||
declare function fetch(
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
unreachable,
|
||||
} from "./test_util.ts";
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
Deno.test(async function functionParameterBindingSuccess() {
|
||||
const promise = deferred();
|
||||
let count = 0;
|
||||
|
@ -573,3 +575,121 @@ Deno.test(
|
|||
await p;
|
||||
},
|
||||
);
|
||||
|
||||
async function execCode(code: string) {
|
||||
const p = Deno.run({
|
||||
cmd: [
|
||||
Deno.execPath(),
|
||||
"eval",
|
||||
"--unstable",
|
||||
code,
|
||||
],
|
||||
stdout: "piped",
|
||||
});
|
||||
const [status, output] = await Promise.all([p.status(), p.output()]);
|
||||
p.close();
|
||||
return [status.code, decoder.decode(output)];
|
||||
}
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const timer = setTimeout(() => console.log("1"));
|
||||
Deno.unrefTimer(timer);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer - mix ref and unref 1",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const timer1 = setTimeout(() => console.log("1"), 200);
|
||||
const timer2 = setTimeout(() => console.log("2"), 400);
|
||||
const timer3 = setTimeout(() => console.log("3"), 600);
|
||||
Deno.unrefTimer(timer3);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "1\n2\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer - mix ref and unref 2",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const timer1 = setTimeout(() => console.log("1"), 200);
|
||||
const timer2 = setTimeout(() => console.log("2"), 400);
|
||||
const timer3 = setTimeout(() => console.log("3"), 600);
|
||||
Deno.unrefTimer(timer1);
|
||||
Deno.unrefTimer(timer2);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "1\n2\n3\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer - unref interval",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
let i = 0;
|
||||
const timer1 = setInterval(() => {
|
||||
console.log("1");
|
||||
i++;
|
||||
if (i === 5) {
|
||||
Deno.unrefTimer(timer1);
|
||||
}
|
||||
}, 10);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "1\n1\n1\n1\n1\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer - unref then ref 1",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const timer1 = setTimeout(() => console.log("1"), 10);
|
||||
Deno.unrefTimer(timer1);
|
||||
Deno.refTimer(timer1);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "1\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer - unref then ref",
|
||||
permissions: { run: true },
|
||||
fn: async () => {
|
||||
const [statusCode, output] = await execCode(`
|
||||
const timer1 = setTimeout(() => {
|
||||
console.log("1");
|
||||
Deno.refTimer(timer2);
|
||||
}, 10);
|
||||
const timer2 = setTimeout(() => console.log("2"), 20);
|
||||
Deno.unrefTimer(timer2);
|
||||
`);
|
||||
assertEquals(statusCode, 0);
|
||||
assertEquals(output, "1\n2\n");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "unrefTimer - invalid calls do nothing",
|
||||
permissions: { run: true },
|
||||
fn: () => {
|
||||
Deno.unrefTimer(NaN);
|
||||
Deno.refTimer(NaN);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
// deno-lint-ignore camelcase
|
||||
NumberPOSITIVE_INFINITY,
|
||||
PromisePrototypeThen,
|
||||
SymbolFor,
|
||||
TypeError,
|
||||
} = window.__bootstrap.primordials;
|
||||
const { webidl } = window.__bootstrap;
|
||||
|
@ -87,7 +88,7 @@
|
|||
* The keys in this map correspond to the key ID's in the spec's map of active
|
||||
* timers. The values are the timeout's cancel rid.
|
||||
*
|
||||
* @type {Map<number, number>}
|
||||
* @type {Map<number, { cancelRid: number, isRef: boolean, promiseId: number }>}
|
||||
*/
|
||||
const activeTimers = new Map();
|
||||
|
||||
|
@ -112,20 +113,21 @@
|
|||
// previousId be an implementation-defined integer than is greater than zero
|
||||
// and does not already exist in global's map of active timers.
|
||||
let id;
|
||||
let cancelRid;
|
||||
let timerInfo;
|
||||
if (prevId !== undefined) {
|
||||
// `prevId` is only passed for follow-up calls on intervals
|
||||
assert(repeat);
|
||||
id = prevId;
|
||||
cancelRid = MapPrototypeGet(activeTimers, id);
|
||||
timerInfo = MapPrototypeGet(activeTimers, id);
|
||||
} else {
|
||||
// TODO(@andreubotella): Deal with overflow.
|
||||
// https://github.com/whatwg/html/issues/7358
|
||||
id = nextId++;
|
||||
cancelRid = core.opSync("op_timer_handle");
|
||||
const cancelRid = core.opSync("op_timer_handle");
|
||||
timerInfo = { cancelRid, isRef: true, promiseId: -1 };
|
||||
|
||||
// Step 4 in "run steps after a timeout".
|
||||
MapPrototypeSet(activeTimers, id, cancelRid);
|
||||
MapPrototypeSet(activeTimers, id, timerInfo);
|
||||
}
|
||||
|
||||
// 3. If the surrounding agent's event loop's currently running task is a
|
||||
|
@ -175,7 +177,7 @@
|
|||
}
|
||||
} else {
|
||||
// 6. Otherwise, remove global's map of active timers[id].
|
||||
core.tryClose(cancelRid);
|
||||
core.tryClose(timerInfo.cancelRid);
|
||||
MapPrototypeDelete(activeTimers, id);
|
||||
}
|
||||
},
|
||||
|
@ -192,7 +194,7 @@
|
|||
runAfterTimeout(
|
||||
() => ArrayPrototypePush(timerTasks, task),
|
||||
timeout,
|
||||
cancelRid,
|
||||
timerInfo,
|
||||
);
|
||||
|
||||
return id;
|
||||
|
@ -219,9 +221,17 @@
|
|||
* @param {() => void} cb Will be run after the timeout, if it hasn't been
|
||||
* cancelled.
|
||||
* @param {number} millis
|
||||
* @param {number} cancelRid
|
||||
* @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo
|
||||
*/
|
||||
function runAfterTimeout(cb, millis, cancelRid) {
|
||||
function runAfterTimeout(cb, millis, timerInfo) {
|
||||
const cancelRid = timerInfo.cancelRid;
|
||||
const sleepPromise = core.opAsync("op_sleep", millis, cancelRid);
|
||||
timerInfo.promiseId =
|
||||
sleepPromise[SymbolFor("Deno.core.internalPromiseId")];
|
||||
if (!timerInfo.isRef) {
|
||||
core.unrefOp(timerInfo.promiseId);
|
||||
}
|
||||
|
||||
/** @type {ScheduledTimer} */
|
||||
const timerObject = {
|
||||
millis,
|
||||
|
@ -242,7 +252,7 @@
|
|||
|
||||
// 1.
|
||||
PromisePrototypeThen(
|
||||
core.opAsync("op_sleep", millis, cancelRid),
|
||||
sleepPromise,
|
||||
() => {
|
||||
// 2. Wait until any invocations of this algorithm that had the same
|
||||
// global and orderingIdentifier, that started before this one, and
|
||||
|
@ -334,9 +344,9 @@
|
|||
function clearTimeout(id = 0) {
|
||||
checkThis(this);
|
||||
id = webidl.converters.long(id);
|
||||
const cancelHandle = MapPrototypeGet(activeTimers, id);
|
||||
if (cancelHandle !== undefined) {
|
||||
core.tryClose(cancelHandle);
|
||||
const timerInfo = MapPrototypeGet(activeTimers, id);
|
||||
if (timerInfo !== undefined) {
|
||||
core.tryClose(timerInfo.cancelRid);
|
||||
MapPrototypeDelete(activeTimers, id);
|
||||
}
|
||||
}
|
||||
|
@ -346,6 +356,24 @@
|
|||
clearTimeout(id);
|
||||
}
|
||||
|
||||
function refTimer(id) {
|
||||
const timerInfo = MapPrototypeGet(activeTimers, id);
|
||||
if (timerInfo === undefined || timerInfo.isRef) {
|
||||
return;
|
||||
}
|
||||
timerInfo.isRef = true;
|
||||
core.refOp(timerInfo.promiseId);
|
||||
}
|
||||
|
||||
function unrefTimer(id) {
|
||||
const timerInfo = MapPrototypeGet(activeTimers, id);
|
||||
if (timerInfo === undefined || !timerInfo.isRef) {
|
||||
return;
|
||||
}
|
||||
timerInfo.isRef = false;
|
||||
core.unrefOp(timerInfo.promiseId);
|
||||
}
|
||||
|
||||
window.__bootstrap.timers = {
|
||||
setTimeout,
|
||||
setInterval,
|
||||
|
@ -354,5 +382,7 @@
|
|||
handleTimerMacrotask,
|
||||
opNow,
|
||||
sleepSync,
|
||||
refTimer,
|
||||
unrefTimer,
|
||||
};
|
||||
})(this);
|
||||
|
|
|
@ -139,5 +139,7 @@
|
|||
flockSync: __bootstrap.fs.flockSync,
|
||||
funlock: __bootstrap.fs.funlock,
|
||||
funlockSync: __bootstrap.fs.funlockSync,
|
||||
refTimer: __bootstrap.timers.refTimer,
|
||||
unrefTimer: __bootstrap.timers.unrefTimer,
|
||||
};
|
||||
})(this);
|
||||
|
|
Loading…
Reference in a new issue