1
0
Fork 0
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:
Yoshiya Hinosawa 2021-12-09 17:00:55 +09:00 committed by GitHub
parent 1507b8c984
commit 69ad5f0e78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 177 additions and 13 deletions

View file

@ -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(

View file

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

View file

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

View file

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