aboutsummaryrefslogtreecommitdiff
path: root/src/js/builtins/ProcessObjectInternals.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/builtins/ProcessObjectInternals.ts')
-rw-r--r--src/js/builtins/ProcessObjectInternals.ts202
1 files changed, 202 insertions, 0 deletions
diff --git a/src/js/builtins/ProcessObjectInternals.ts b/src/js/builtins/ProcessObjectInternals.ts
index e6c04c90f..2bb8648df 100644
--- a/src/js/builtins/ProcessObjectInternals.ts
+++ b/src/js/builtins/ProcessObjectInternals.ts
@@ -1,4 +1,5 @@
/*
+ * Copyright Joyent, Inc. and other Node contributors.
* Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -199,3 +200,204 @@ export function getStdinStream(fd) {
return stream;
}
+
+export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksFn, reportUncaughtExceptionFn) {
+ var queue;
+ var process;
+ var nextTickQueue = nextTickQueue;
+ var drainMicrotasks = drainMicrotasksFn;
+ var reportUncaughtException = reportUncaughtExceptionFn;
+
+ function validateFunction(cb) {
+ if (typeof cb !== "function") {
+ const err = new TypeError(`The "callback" argument must be of type "function". Received type ${typeof cb}`);
+ err.code = "ERR_INVALID_ARG_TYPE";
+ throw err;
+ }
+ }
+
+ var setup;
+ setup = () => {
+ queue = (function createQueue() {
+ // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
+ const kSize = 2048;
+ const kMask = kSize - 1;
+
+ // The FixedQueue is implemented as a singly-linked list of fixed-size
+ // circular buffers. It looks something like this:
+ //
+ // head tail
+ // | |
+ // v v
+ // +-----------+ <-----\ +-----------+ <------\ +-----------+
+ // | [null] | \----- | next | \------- | next |
+ // +-----------+ +-----------+ +-----------+
+ // | item | <-- bottom | item | <-- bottom | [empty] |
+ // | item | | item | | [empty] |
+ // | item | | item | | [empty] |
+ // | item | | item | | [empty] |
+ // | item | | item | bottom --> | item |
+ // | item | | item | | item |
+ // | ... | | ... | | ... |
+ // | item | | item | | item |
+ // | item | | item | | item |
+ // | [empty] | <-- top | item | | item |
+ // | [empty] | | item | | item |
+ // | [empty] | | [empty] | <-- top top --> | [empty] |
+ // +-----------+ +-----------+ +-----------+
+ //
+ // Or, if there is only one circular buffer, it looks something
+ // like either of these:
+ //
+ // head tail head tail
+ // | | | |
+ // v v v v
+ // +-----------+ +-----------+
+ // | [null] | | [null] |
+ // +-----------+ +-----------+
+ // | [empty] | | item |
+ // | [empty] | | item |
+ // | item | <-- bottom top --> | [empty] |
+ // | item | | [empty] |
+ // | [empty] | <-- top bottom --> | item |
+ // | [empty] | | item |
+ // +-----------+ +-----------+
+ //
+ // Adding a value means moving `top` forward by one, removing means
+ // moving `bottom` forward by one. After reaching the end, the queue
+ // wraps around.
+ //
+ // When `top === bottom` the current queue is empty and when
+ // `top + 1 === bottom` it's full. This wastes a single space of storage
+ // but allows much quicker checks.
+
+ class FixedCircularBuffer {
+ constructor() {
+ this.bottom = 0;
+ this.top = 0;
+ this.list = $newArrayWithSize(kSize);
+ this.next = null;
+ }
+
+ isEmpty() {
+ return this.top === this.bottom;
+ }
+
+ isFull() {
+ return ((this.top + 1) & kMask) === this.bottom;
+ }
+
+ push(data) {
+ this.list[this.top] = data;
+ this.top = (this.top + 1) & kMask;
+ }
+
+ shift() {
+ var { list, bottom } = this;
+ const nextItem = list[bottom];
+ if (nextItem === undefined) return null;
+ list[bottom] = undefined;
+ this.bottom = (bottom + 1) & kMask;
+ return nextItem;
+ }
+ }
+
+ class FixedQueue {
+ constructor() {
+ this.head = this.tail = new FixedCircularBuffer();
+ }
+
+ isEmpty() {
+ return this.head.isEmpty();
+ }
+
+ push(data) {
+ if (this.head.isFull()) {
+ // Head is full: Creates a new queue, sets the old queue's `.next` to it,
+ // and sets it as the new main queue.
+ this.head = this.head.next = new FixedCircularBuffer();
+ }
+ this.head.push(data);
+ }
+
+ shift() {
+ const tail = this.tail;
+ const next = tail.shift();
+ if (tail.isEmpty() && tail.next !== null) {
+ // If there is another queue, it forms the new tail.
+ this.tail = tail.next;
+ tail.next = null;
+ }
+ return next;
+ }
+ }
+
+ return new FixedQueue();
+ })();
+
+ function processTicksAndRejections() {
+ var tock;
+ do {
+ while ((tock = queue.shift()) !== null) {
+ var callback = tock.callback;
+ var args = tock.args;
+ var frame = tock.frame;
+ var restore = $getInternalField($asyncContext, 0);
+ $putInternalField($asyncContext, 0, frame);
+ try {
+ if (args === undefined) {
+ callback();
+ } else {
+ switch (args.length) {
+ case 1:
+ callback(args[0]);
+ break;
+ case 2:
+ callback(args[0], args[1]);
+ break;
+ case 3:
+ callback(args[0], args[1], args[2]);
+ break;
+ case 4:
+ callback(args[0], args[1], args[2], args[3]);
+ break;
+ default:
+ callback(...args);
+ break;
+ }
+ }
+ } catch (e) {
+ reportUncaughtException(e);
+ } finally {
+ $putInternalField($asyncContext, 0, restore);
+ }
+ }
+
+ drainMicrotasks();
+ } while (!queue.isEmpty());
+ }
+
+ $putInternalField(nextTickQueue, 0, 0);
+ $putInternalField(nextTickQueue, 1, queue);
+ $putInternalField(nextTickQueue, 2, processTicksAndRejections);
+ setup = undefined;
+ };
+
+ function nextTick(cb, args) {
+ validateFunction(cb);
+ if (setup) {
+ setup();
+ process = globalThis.process;
+ }
+ if (process._exiting) return;
+
+ queue.push({
+ callback: cb,
+ args: $argumentCount() > 1 ? Array.prototype.slice.$call(arguments, 1) : undefined,
+ frame: $getInternalField($asyncContext, 0),
+ });
+ $putInternalField(nextTickQueue, 0, 1);
+ }
+
+ return nextTick;
+}