summaryrefslogtreecommitdiff
path: root/cli/js/timers.ts
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-03-11 10:53:06 +0100
committerGitHub <noreply@github.com>2020-03-11 10:53:06 +0100
commit99a0c6df79b903e4fe72ce066787039bdede3868 (patch)
tree1c0abdb964a2e052b593dc8fa3e515f76dbc0642 /cli/js/timers.ts
parent94f4c6807a34a564f567b984e71e36e9cb9c5005 (diff)
reorg: cli/js/compiler/, move more API to cli/js/web/ (#4310)
- moves compiler implementation to "cli/js/compiler/" directory - moves more APIs to "cli/js/web": * "console.ts" * "console_table.ts" * "performance.ts" * "timers.ts" * "workers.ts" - removes some dead code from "cli/js/"
Diffstat (limited to 'cli/js/timers.ts')
-rw-r--r--cli/js/timers.ts315
1 files changed, 0 insertions, 315 deletions
diff --git a/cli/js/timers.ts b/cli/js/timers.ts
deleted file mode 100644
index 844a0b204..000000000
--- a/cli/js/timers.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
-import { assert } from "./util.ts";
-import { startGlobalTimer, stopGlobalTimer } from "./ops/timers.ts";
-import { RBTree } from "./rbtree.ts";
-
-const { console } = globalThis;
-
-interface Timer {
- id: number;
- callback: () => void;
- delay: number;
- due: number;
- repeat: boolean;
- scheduled: boolean;
-}
-
-// Timeout values > TIMEOUT_MAX are set to 1.
-const TIMEOUT_MAX = 2 ** 31 - 1;
-
-let globalTimeoutDue: number | null = null;
-
-let nextTimerId = 1;
-const idMap = new Map<number, Timer>();
-type DueNode = { due: number; timers: Timer[] };
-const dueTree = new RBTree<DueNode>((a, b) => a.due - b.due);
-
-function clearGlobalTimeout(): void {
- globalTimeoutDue = null;
- stopGlobalTimer();
-}
-
-let pendingEvents = 0;
-const pendingFireTimers: Timer[] = [];
-let hasPendingFireTimers = false;
-let pendingScheduleTimers: Timer[] = [];
-
-async function setGlobalTimeout(due: number, now: number): Promise<void> {
- // Since JS and Rust don't use the same clock, pass the time to rust as a
- // relative time value. On the Rust side we'll turn that into an absolute
- // value again.
- const timeout = due - now;
- assert(timeout >= 0);
- // Send message to the backend.
- globalTimeoutDue = due;
- pendingEvents++;
- // FIXME(bartlomieju): this is problematic, because `clearGlobalTimeout`
- // is synchronous. That means that timer is cancelled, but this promise is still pending
- // until next turn of event loop. This leads to "leaking of async ops" in tests;
- // because `clearTimeout/clearInterval` might be the last statement in test function
- // `opSanitizer` will immediately complain that there is pending op going on, unless
- // some timeout/defer is put in place to allow promise resolution.
- // Ideally `clearGlobalTimeout` doesn't return until this op is resolved, but
- // I'm not if that's possible.
- await startGlobalTimer(timeout);
- pendingEvents--;
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- fireTimers();
-}
-
-function setOrClearGlobalTimeout(due: number | null, now: number): void {
- if (due == null) {
- clearGlobalTimeout();
- } else {
- setGlobalTimeout(due, now);
- }
-}
-
-function schedule(timer: Timer, now: number): void {
- assert(!timer.scheduled);
- assert(now <= timer.due);
- // There are more timers pending firing.
- // We must ensure new timer scheduled after them.
- // Push them to a queue that would be depleted after last pending fire
- // timer is fired.
- // (This also implies behavior of setInterval)
- if (hasPendingFireTimers) {
- pendingScheduleTimers.push(timer);
- return;
- }
- // Find or create the list of timers that will fire at point-in-time `due`.
- const maybeNewDueNode = { due: timer.due, timers: [] };
- let dueNode = dueTree.find(maybeNewDueNode);
- if (dueNode === null) {
- dueTree.insert(maybeNewDueNode);
- dueNode = maybeNewDueNode;
- }
- // Append the newly scheduled timer to the list and mark it as scheduled.
- dueNode!.timers.push(timer);
- timer.scheduled = true;
- // If the new timer is scheduled to fire before any timer that existed before,
- // update the global timeout to reflect this.
- if (globalTimeoutDue === null || globalTimeoutDue > timer.due) {
- setOrClearGlobalTimeout(timer.due, now);
- }
-}
-
-function unschedule(timer: Timer): void {
- // Check if our timer is pending scheduling or pending firing.
- // If either is true, they are not in tree, and their idMap entry
- // will be deleted soon. Remove it from queue.
- let index = -1;
- if ((index = pendingScheduleTimers.indexOf(timer)) >= 0) {
- pendingScheduleTimers.splice(index);
- return;
- }
- if ((index = pendingFireTimers.indexOf(timer)) >= 0) {
- pendingFireTimers.splice(index);
- return;
- }
- // If timer is not in the 2 pending queues and is unscheduled,
- // it is not in the tree.
- if (!timer.scheduled) {
- return;
- }
- const searchKey = { due: timer.due, timers: [] };
- // Find the list of timers that will fire at point-in-time `due`.
- const list = dueTree.find(searchKey)!.timers;
- if (list.length === 1) {
- // Time timer is the only one in the list. Remove the entire list.
- assert(list[0] === timer);
- dueTree.remove(searchKey);
- // If the unscheduled timer was 'next up', find when the next timer that
- // still exists is due, and update the global alarm accordingly.
- if (timer.due === globalTimeoutDue) {
- const nextDueNode: DueNode | null = dueTree.min();
- setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, Date.now());
- }
- } else {
- // Multiple timers that are due at the same point in time.
- // Remove this timer from the list.
- const index = list.indexOf(timer);
- assert(index > -1);
- list.splice(index, 1);
- }
-}
-
-function fire(timer: Timer): void {
- // If the timer isn't found in the ID map, that means it has been cancelled
- // between the timer firing and the promise callback (this function).
- if (!idMap.has(timer.id)) {
- return;
- }
- // Reschedule the timer if it is a repeating one, otherwise drop it.
- if (!timer.repeat) {
- // One-shot timer: remove the timer from this id-to-timer map.
- idMap.delete(timer.id);
- } else {
- // Interval timer: compute when timer was supposed to fire next.
- // However make sure to never schedule the next interval in the past.
- const now = Date.now();
- timer.due = Math.max(now, timer.due + timer.delay);
- schedule(timer, now);
- }
- // Call the user callback. Intermediate assignment is to avoid leaking `this`
- // to it, while also keeping the stack trace neat when it shows up in there.
- const callback = timer.callback;
- callback();
-}
-
-function fireTimers(): void {
- const now = Date.now();
- // Bail out if we're not expecting the global timer to fire.
- if (globalTimeoutDue === null || pendingEvents > 0) {
- return;
- }
- // After firing the timers that are due now, this will hold the first timer
- // list that hasn't fired yet.
- let nextDueNode: DueNode | null;
- while ((nextDueNode = dueTree.min()) !== null && nextDueNode.due <= now) {
- dueTree.remove(nextDueNode);
- // Fire all the timers in the list.
- for (const timer of nextDueNode.timers) {
- // With the list dropped, the timer is no longer scheduled.
- timer.scheduled = false;
- // Place the callback to pending timers to fire.
- pendingFireTimers.push(timer);
- }
- }
- if (pendingFireTimers.length > 0) {
- hasPendingFireTimers = true;
- // Fire the list of pending timers as a chain of microtasks.
- globalThis.queueMicrotask(firePendingTimers);
- } else {
- setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, now);
- }
-}
-
-function firePendingTimers(): void {
- if (pendingFireTimers.length === 0) {
- // All timer tasks are done.
- hasPendingFireTimers = false;
- // Schedule all new timers pushed during previous timer executions
- const now = Date.now();
- for (const newTimer of pendingScheduleTimers) {
- newTimer.due = Math.max(newTimer.due, now);
- schedule(newTimer, now);
- }
- pendingScheduleTimers = [];
- // Reschedule for next round of timeout.
- const nextDueNode = dueTree.min();
- const due = nextDueNode && Math.max(nextDueNode.due, now);
- setOrClearGlobalTimeout(due, now);
- } else {
- // Fire a single timer and allow its children microtasks scheduled first.
- fire(pendingFireTimers.shift()!);
- // ...and we schedule next timer after this.
- globalThis.queueMicrotask(firePendingTimers);
- }
-}
-
-export type Args = unknown[];
-
-function checkThis(thisArg: unknown): void {
- if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
- throw new TypeError("Illegal invocation");
- }
-}
-
-function checkBigInt(n: unknown): void {
- if (typeof n === "bigint") {
- throw new TypeError("Cannot convert a BigInt value to a number");
- }
-}
-
-function setTimer(
- cb: (...args: Args) => void,
- delay: number,
- args: Args,
- repeat: boolean
-): number {
- // Bind `args` to the callback and bind `this` to globalThis(global).
- const callback: () => void = cb.bind(globalThis, ...args);
- // In the browser, the delay value must be coercible to an integer between 0
- // and INT32_MAX. Any other value will cause the timer to fire immediately.
- // We emulate this behavior.
- const now = Date.now();
- if (delay > TIMEOUT_MAX) {
- console.warn(
- `${delay} does not fit into` +
- " a 32-bit signed integer." +
- "\nTimeout duration was set to 1."
- );
- delay = 1;
- }
- delay = Math.max(0, delay | 0);
-
- // Create a new, unscheduled timer object.
- const timer = {
- id: nextTimerId++,
- callback,
- args,
- delay,
- due: now + delay,
- repeat,
- scheduled: false
- };
- // Register the timer's existence in the id-to-timer map.
- idMap.set(timer.id, timer);
- // Schedule the timer in the due table.
- schedule(timer, now);
- return timer.id;
-}
-
-/** Sets a timer which executes a function once after the timer expires. */
-export function setTimeout(
- cb: (...args: Args) => void,
- delay = 0,
- ...args: Args
-): number {
- checkBigInt(delay);
- // @ts-ignore
- checkThis(this);
- return setTimer(cb, delay, args, false);
-}
-
-/** Repeatedly calls a function, with a fixed time delay between each call. */
-export function setInterval(
- cb: (...args: Args) => void,
- delay = 0,
- ...args: Args
-): number {
- checkBigInt(delay);
- // @ts-ignore
- checkThis(this);
- return setTimer(cb, delay, args, true);
-}
-
-/** Clears a previously set timer by id. AKA clearTimeout and clearInterval. */
-function clearTimer(id: number): void {
- id = Number(id);
- const timer = idMap.get(id);
- if (timer === undefined) {
- // Timer doesn't exist any more or never existed. This is not an error.
- return;
- }
- // Unschedule the timer if it is currently scheduled, and forget about it.
- unschedule(timer);
- idMap.delete(timer.id);
-}
-
-export function clearTimeout(id = 0): void {
- checkBigInt(id);
- if (id === 0) {
- return;
- }
- clearTimer(id);
-}
-
-export function clearInterval(id = 0): void {
- checkBigInt(id);
- if (id === 0) {
- return;
- }
- clearTimer(id);
-}