mortice
Version:
Isomorphic read/write lock that works in single processes, node clusters and web workers
945 lines (924 loc) • 31.8 kB
JavaScript
"use strict";
(() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// node_modules/eventemitter3/index.js
var require_eventemitter3 = __commonJS({
"node_modules/eventemitter3/index.js"(exports, module) {
"use strict";
var has = Object.prototype.hasOwnProperty;
var prefix = "~";
function Events() {
}
if (Object.create) {
Events.prototype = /* @__PURE__ */ Object.create(null);
if (!new Events().__proto__) prefix = false;
}
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
function addListener(emitter, event, fn, context, once) {
if (typeof fn !== "function") {
throw new TypeError("The listener must be a function");
}
var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
else emitter._events[evt] = [emitter._events[evt], listener];
return emitter;
}
function clearEvent(emitter, evt) {
if (--emitter._eventsCount === 0) emitter._events = new Events();
else delete emitter._events[evt];
}
function EventEmitter2() {
this._events = new Events();
this._eventsCount = 0;
}
EventEmitter2.prototype.eventNames = function eventNames() {
var names = [], events2, name;
if (this._eventsCount === 0) return names;
for (name in events2 = this._events) {
if (has.call(events2, name)) names.push(prefix ? name.slice(1) : name);
}
if (Object.getOwnPropertySymbols) {
return names.concat(Object.getOwnPropertySymbols(events2));
}
return names;
};
EventEmitter2.prototype.listeners = function listeners(event) {
var evt = prefix ? prefix + event : event, handlers = this._events[evt];
if (!handlers) return [];
if (handlers.fn) return [handlers.fn];
for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
ee[i] = handlers[i].fn;
}
return ee;
};
EventEmitter2.prototype.listenerCount = function listenerCount(event) {
var evt = prefix ? prefix + event : event, listeners = this._events[evt];
if (!listeners) return 0;
if (listeners.fn) return 1;
return listeners.length;
};
EventEmitter2.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return false;
var listeners = this._events[evt], len = arguments.length, args, i;
if (listeners.fn) {
if (listeners.once) this.removeListener(event, listeners.fn, void 0, true);
switch (len) {
case 1:
return listeners.fn.call(listeners.context), true;
case 2:
return listeners.fn.call(listeners.context, a1), true;
case 3:
return listeners.fn.call(listeners.context, a1, a2), true;
case 4:
return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5:
return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
case 6:
return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
}
for (i = 1, args = new Array(len - 1); i < len; i++) {
args[i - 1] = arguments[i];
}
listeners.fn.apply(listeners.context, args);
} else {
var length = listeners.length, j;
for (i = 0; i < length; i++) {
if (listeners[i].once) this.removeListener(event, listeners[i].fn, void 0, true);
switch (len) {
case 1:
listeners[i].fn.call(listeners[i].context);
break;
case 2:
listeners[i].fn.call(listeners[i].context, a1);
break;
case 3:
listeners[i].fn.call(listeners[i].context, a1, a2);
break;
case 4:
listeners[i].fn.call(listeners[i].context, a1, a2, a3);
break;
default:
if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) {
args[j - 1] = arguments[j];
}
listeners[i].fn.apply(listeners[i].context, args);
}
}
}
return true;
};
EventEmitter2.prototype.on = function on(event, fn, context) {
return addListener(this, event, fn, context, false);
};
EventEmitter2.prototype.once = function once(event, fn, context) {
return addListener(this, event, fn, context, true);
};
EventEmitter2.prototype.removeListener = function removeListener(event, fn, context, once) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return this;
if (!fn) {
clearEvent(this, evt);
return this;
}
var listeners = this._events[evt];
if (listeners.fn) {
if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
clearEvent(this, evt);
}
} else {
for (var i = 0, events2 = [], length = listeners.length; i < length; i++) {
if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
events2.push(listeners[i]);
}
}
if (events2.length) this._events[evt] = events2.length === 1 ? events2[0] : events2;
else clearEvent(this, evt);
}
return this;
};
EventEmitter2.prototype.removeAllListeners = function removeAllListeners(event) {
var evt;
if (event) {
evt = prefix ? prefix + event : event;
if (this._events[evt]) clearEvent(this, evt);
} else {
this._events = new Events();
this._eventsCount = 0;
}
return this;
};
EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
EventEmitter2.prefixed = prefix;
EventEmitter2.EventEmitter = EventEmitter2;
if ("undefined" !== typeof module) {
module.exports = EventEmitter2;
}
}
});
// node_modules/eventemitter3/index.mjs
var import_index = __toESM(require_eventemitter3(), 1);
// node_modules/p-timeout/index.js
var TimeoutError = class extends Error {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
};
var AbortError = class extends Error {
constructor(message) {
super();
this.name = "AbortError";
this.message = message;
}
};
var getDOMException = (errorMessage) => globalThis.DOMException === void 0 ? new AbortError(errorMessage) : new DOMException(errorMessage);
var getAbortedReason = (signal) => {
const reason = signal.reason === void 0 ? getDOMException("This operation was aborted.") : signal.reason;
return reason instanceof Error ? reason : getDOMException(reason);
};
function pTimeout(promise, options) {
const {
milliseconds,
fallback,
message,
customTimers = { setTimeout, clearTimeout }
} = options;
let timer;
const wrappedPromise = new Promise((resolve, reject) => {
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
}
if (options.signal) {
const { signal } = options;
if (signal.aborted) {
reject(getAbortedReason(signal));
}
const abortHandler = () => {
reject(getAbortedReason(signal));
};
signal.addEventListener("abort", abortHandler, { once: true });
promise.finally(() => {
signal.removeEventListener("abort", abortHandler);
});
}
if (milliseconds === Number.POSITIVE_INFINITY) {
promise.then(resolve, reject);
return;
}
const timeoutError = new TimeoutError();
timer = customTimers.setTimeout.call(void 0, () => {
if (fallback) {
try {
resolve(fallback());
} catch (error) {
reject(error);
}
return;
}
if (typeof promise.cancel === "function") {
promise.cancel();
}
if (message === false) {
resolve();
} else if (message instanceof Error) {
reject(message);
} else {
timeoutError.message = message ?? `Promise timed out after ${milliseconds} milliseconds`;
reject(timeoutError);
}
}, milliseconds);
(async () => {
try {
resolve(await promise);
} catch (error) {
reject(error);
}
})();
});
const cancelablePromise = wrappedPromise.finally(() => {
cancelablePromise.clear();
});
cancelablePromise.clear = () => {
customTimers.clearTimeout.call(void 0, timer);
timer = void 0;
};
return cancelablePromise;
}
// node_modules/p-queue/dist/lower-bound.js
function lowerBound(array, value, comparator) {
let first = 0;
let count = array.length;
while (count > 0) {
const step = Math.trunc(count / 2);
let it = first + step;
if (comparator(array[it], value) <= 0) {
first = ++it;
count -= step + 1;
} else {
count = step;
}
}
return first;
}
// node_modules/p-queue/dist/priority-queue.js
var PriorityQueue = class {
#queue = [];
enqueue(run2, options) {
options = {
priority: 0,
...options
};
const element = {
priority: options.priority,
run: run2
};
if (this.size && this.#queue[this.size - 1].priority >= options.priority) {
this.#queue.push(element);
return;
}
const index = lowerBound(this.#queue, element, (a, b) => b.priority - a.priority);
this.#queue.splice(index, 0, element);
}
dequeue() {
const item = this.#queue.shift();
return item?.run;
}
filter(options) {
return this.#queue.filter((element) => element.priority === options.priority).map((element) => element.run);
}
get size() {
return this.#queue.length;
}
};
// node_modules/p-queue/dist/index.js
var PQueue = class extends import_index.default {
#carryoverConcurrencyCount;
#isIntervalIgnored;
#intervalCount = 0;
#intervalCap;
#interval;
#intervalEnd = 0;
#intervalId;
#timeoutId;
#queue;
#queueClass;
#pending = 0;
// The `!` is needed because of https://github.com/microsoft/TypeScript/issues/32194
#concurrency;
#isPaused;
#throwOnTimeout;
/**
Per-operation timeout in milliseconds. Operations fulfill once `timeout` elapses if they haven't already.
Applies to each future operation.
*/
timeout;
// TODO: The `throwOnTimeout` option should affect the return types of `add()` and `addAll()`
constructor(options) {
super();
options = {
carryoverConcurrencyCount: false,
intervalCap: Number.POSITIVE_INFINITY,
interval: 0,
concurrency: Number.POSITIVE_INFINITY,
autoStart: true,
queueClass: PriorityQueue,
...options
};
if (!(typeof options.intervalCap === "number" && options.intervalCap >= 1)) {
throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${options.intervalCap?.toString() ?? ""}\` (${typeof options.intervalCap})`);
}
if (options.interval === void 0 || !(Number.isFinite(options.interval) && options.interval >= 0)) {
throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${options.interval?.toString() ?? ""}\` (${typeof options.interval})`);
}
this.#carryoverConcurrencyCount = options.carryoverConcurrencyCount;
this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;
this.#intervalCap = options.intervalCap;
this.#interval = options.interval;
this.#queue = new options.queueClass();
this.#queueClass = options.queueClass;
this.concurrency = options.concurrency;
this.timeout = options.timeout;
this.#throwOnTimeout = options.throwOnTimeout === true;
this.#isPaused = options.autoStart === false;
}
get #doesIntervalAllowAnother() {
return this.#isIntervalIgnored || this.#intervalCount < this.#intervalCap;
}
get #doesConcurrentAllowAnother() {
return this.#pending < this.#concurrency;
}
#next() {
this.#pending--;
this.#tryToStartAnother();
this.emit("next");
}
#onResumeInterval() {
this.#onInterval();
this.#initializeIntervalIfNeeded();
this.#timeoutId = void 0;
}
get #isIntervalPaused() {
const now = Date.now();
if (this.#intervalId === void 0) {
const delay2 = this.#intervalEnd - now;
if (delay2 < 0) {
this.#intervalCount = this.#carryoverConcurrencyCount ? this.#pending : 0;
} else {
if (this.#timeoutId === void 0) {
this.#timeoutId = setTimeout(() => {
this.#onResumeInterval();
}, delay2);
}
return true;
}
}
return false;
}
#tryToStartAnother() {
if (this.#queue.size === 0) {
if (this.#intervalId) {
clearInterval(this.#intervalId);
}
this.#intervalId = void 0;
this.emit("empty");
if (this.#pending === 0) {
this.emit("idle");
}
return false;
}
if (!this.#isPaused) {
const canInitializeInterval = !this.#isIntervalPaused;
if (this.#doesIntervalAllowAnother && this.#doesConcurrentAllowAnother) {
const job = this.#queue.dequeue();
if (!job) {
return false;
}
this.emit("active");
job();
if (canInitializeInterval) {
this.#initializeIntervalIfNeeded();
}
return true;
}
}
return false;
}
#initializeIntervalIfNeeded() {
if (this.#isIntervalIgnored || this.#intervalId !== void 0) {
return;
}
this.#intervalId = setInterval(() => {
this.#onInterval();
}, this.#interval);
this.#intervalEnd = Date.now() + this.#interval;
}
#onInterval() {
if (this.#intervalCount === 0 && this.#pending === 0 && this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = void 0;
}
this.#intervalCount = this.#carryoverConcurrencyCount ? this.#pending : 0;
this.#processQueue();
}
/**
Executes all queued functions until it reaches the limit.
*/
#processQueue() {
while (this.#tryToStartAnother()) {
}
}
get concurrency() {
return this.#concurrency;
}
set concurrency(newConcurrency) {
if (!(typeof newConcurrency === "number" && newConcurrency >= 1)) {
throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${newConcurrency}\` (${typeof newConcurrency})`);
}
this.#concurrency = newConcurrency;
this.#processQueue();
}
async #throwOnAbort(signal) {
return new Promise((_resolve, reject) => {
signal.addEventListener("abort", () => {
reject(signal.reason);
}, { once: true });
});
}
async add(function_, options = {}) {
options = {
timeout: this.timeout,
throwOnTimeout: this.#throwOnTimeout,
...options
};
return new Promise((resolve, reject) => {
this.#queue.enqueue(async () => {
this.#pending++;
this.#intervalCount++;
try {
options.signal?.throwIfAborted();
let operation = function_({ signal: options.signal });
if (options.timeout) {
operation = pTimeout(Promise.resolve(operation), { milliseconds: options.timeout });
}
if (options.signal) {
operation = Promise.race([operation, this.#throwOnAbort(options.signal)]);
}
const result = await operation;
resolve(result);
this.emit("completed", result);
} catch (error) {
if (error instanceof TimeoutError && !options.throwOnTimeout) {
resolve();
return;
}
reject(error);
this.emit("error", error);
} finally {
this.#next();
}
}, options);
this.emit("add");
this.#tryToStartAnother();
});
}
async addAll(functions, options) {
return Promise.all(functions.map(async (function_) => this.add(function_, options)));
}
/**
Start (or resume) executing enqueued tasks within concurrency limit. No need to call this if queue is not paused (via `options.autoStart = false` or by `.pause()` method.)
*/
start() {
if (!this.#isPaused) {
return this;
}
this.#isPaused = false;
this.#processQueue();
return this;
}
/**
Put queue execution on hold.
*/
pause() {
this.#isPaused = true;
}
/**
Clear the queue.
*/
clear() {
this.#queue = new this.#queueClass();
}
/**
Can be called multiple times. Useful if you for example add additional items at a later time.
@returns A promise that settles when the queue becomes empty.
*/
async onEmpty() {
if (this.#queue.size === 0) {
return;
}
await this.#onEvent("empty");
}
/**
@returns A promise that settles when the queue size is less than the given limit: `queue.size < limit`.
If you want to avoid having the queue grow beyond a certain size you can `await queue.onSizeLessThan()` before adding a new item.
Note that this only limits the number of items waiting to start. There could still be up to `concurrency` jobs already running that this call does not include in its calculation.
*/
async onSizeLessThan(limit) {
if (this.#queue.size < limit) {
return;
}
await this.#onEvent("next", () => this.#queue.size < limit);
}
/**
The difference with `.onEmpty` is that `.onIdle` guarantees that all work from the queue has finished. `.onEmpty` merely signals that the queue is empty, but it could mean that some promises haven't completed yet.
@returns A promise that settles when the queue becomes empty, and all promises have completed; `queue.size === 0 && queue.pending === 0`.
*/
async onIdle() {
if (this.#pending === 0 && this.#queue.size === 0) {
return;
}
await this.#onEvent("idle");
}
async #onEvent(event, filter) {
return new Promise((resolve) => {
const listener = () => {
if (filter && !filter()) {
return;
}
this.off(event, listener);
resolve();
};
this.on(event, listener);
});
}
/**
Size of the queue, the number of queued items waiting to run.
*/
get size() {
return this.#queue.size;
}
/**
Size of the queue, filtered by the given options.
For example, this can be used to find the number of items remaining in the queue with a specific priority level.
*/
sizeBy(options) {
return this.#queue.filter(options).length;
}
/**
Number of running items (no longer in the queue).
*/
get pending() {
return this.#pending;
}
/**
Whether the queue is currently paused.
*/
get isPaused() {
return this.#isPaused;
}
};
// node_modules/observable-webworkers/dist/src/index.js
var events = {};
var observable = (worker) => {
worker.addEventListener("message", (event) => {
observable.dispatchEvent("message", worker, event);
});
if (worker.port != null) {
worker.port.addEventListener("message", (event) => {
observable.dispatchEvent("message", worker, event);
});
}
};
observable.addEventListener = (type, fn) => {
if (events[type] == null) {
events[type] = [];
}
events[type].push(fn);
};
observable.removeEventListener = (type, fn) => {
if (events[type] == null) {
return;
}
events[type] = events[type].filter((listener) => listener === fn);
};
observable.dispatchEvent = function(type, worker, event) {
if (events[type] == null) {
return;
}
events[type].forEach((fn) => fn(worker, event));
};
var src_default = observable;
// src/constants.ts
var WORKER_REQUEST_READ_LOCK = "lock:worker:request-read";
var WORKER_RELEASE_READ_LOCK = "lock:worker:release-read";
var MASTER_GRANT_READ_LOCK = "lock:master:grant-read";
var WORKER_REQUEST_WRITE_LOCK = "lock:worker:request-write";
var WORKER_RELEASE_WRITE_LOCK = "lock:worker:release-write";
var MASTER_GRANT_WRITE_LOCK = "lock:master:grant-write";
// src/utils.ts
var nanoid = (size = 21) => {
return Math.random().toString().substring(2);
};
// src/browser.ts
var handleWorkerLockRequest = (emitter, masterEvent, requestType, releaseType, grantType) => {
return (worker, event) => {
if (event.data.type !== requestType) {
return;
}
const requestEvent = {
type: event.data.type,
name: event.data.name,
identifier: event.data.identifier
};
emitter.dispatchEvent(new MessageEvent(masterEvent, {
data: {
name: requestEvent.name,
handler: async () => {
worker.postMessage({
type: grantType,
name: requestEvent.name,
identifier: requestEvent.identifier
});
await new Promise((resolve) => {
const releaseEventListener = (event2) => {
if (event2?.data == null) {
return;
}
const releaseEvent = {
type: event2.data.type,
name: event2.data.name,
identifier: event2.data.identifier
};
if (releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) {
worker.removeEventListener("message", releaseEventListener);
resolve();
}
};
worker.addEventListener("message", releaseEventListener);
});
}
}
}));
};
};
var makeWorkerLockRequest = (name, requestType, grantType, releaseType) => {
return async () => {
const id = nanoid();
globalThis.postMessage({
type: requestType,
identifier: id,
name
});
return new Promise((resolve) => {
const listener = (event) => {
if (event?.data == null) {
return;
}
const responseEvent = {
type: event.data.type,
identifier: event.data.identifier
};
if (responseEvent.type === grantType && responseEvent.identifier === id) {
globalThis.removeEventListener("message", listener);
resolve(() => {
globalThis.postMessage({
type: releaseType,
identifier: id,
name
});
});
}
};
globalThis.addEventListener("message", listener);
});
};
};
var defaultOptions = {
singleProcess: false
};
var browser_default = (options) => {
options = Object.assign({}, defaultOptions, options);
const isPrimary = Boolean(globalThis.document) || options.singleProcess;
if (isPrimary) {
const emitter = new EventTarget();
src_default.addEventListener("message", handleWorkerLockRequest(emitter, "requestReadLock", WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK));
src_default.addEventListener("message", handleWorkerLockRequest(emitter, "requestWriteLock", WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK));
return emitter;
}
return {
isWorker: true,
readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK),
writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK)
};
};
// src/index.ts
var mutexes = {};
var implementation;
async function createReleaseable(queue, options) {
let res;
const p = new Promise((resolve) => {
res = resolve;
});
void queue.add(async () => pTimeout((async () => {
await new Promise((resolve) => {
res(() => {
resolve();
});
});
})(), {
milliseconds: options.timeout
}));
return p;
}
var createMutex = (name, options) => {
if (implementation.isWorker === true) {
return {
readLock: implementation.readLock(name, options),
writeLock: implementation.writeLock(name, options)
};
}
const masterQueue = new PQueue({ concurrency: 1 });
let readQueue;
return {
async readLock() {
if (readQueue != null) {
return createReleaseable(readQueue, options);
}
readQueue = new PQueue({
concurrency: options.concurrency,
autoStart: false
});
const localReadQueue = readQueue;
const readPromise = createReleaseable(readQueue, options);
void masterQueue.add(async () => {
localReadQueue.start();
await localReadQueue.onIdle().then(() => {
if (readQueue === localReadQueue) {
readQueue = null;
}
});
});
return readPromise;
},
async writeLock() {
readQueue = null;
return createReleaseable(masterQueue, options);
}
};
};
var defaultOptions2 = {
name: "lock",
concurrency: Infinity,
timeout: 846e5,
singleProcess: false
};
function createMortice(options) {
const opts = Object.assign({}, defaultOptions2, options);
if (implementation == null) {
implementation = browser_default(opts);
if (implementation.isWorker !== true) {
implementation.addEventListener("requestReadLock", (event) => {
if (mutexes[event.data.name] == null) {
return;
}
void mutexes[event.data.name].readLock().then(async (release) => event.data.handler().finally(() => {
release();
}));
});
implementation.addEventListener("requestWriteLock", async (event) => {
if (mutexes[event.data.name] == null) {
return;
}
void mutexes[event.data.name].writeLock().then(async (release) => event.data.handler().finally(() => {
release();
}));
});
}
}
if (mutexes[opts.name] == null) {
mutexes[opts.name] = createMutex(opts.name, opts);
}
return mutexes[opts.name];
}
// node_modules/delay/index.js
var createAbortError = () => {
const error = new Error("Delay aborted");
error.name = "AbortError";
return error;
};
var clearMethods = /* @__PURE__ */ new WeakMap();
function createDelay({ clearTimeout: defaultClear, setTimeout: defaultSet } = {}) {
return (milliseconds, { value, signal } = {}) => {
if (signal?.aborted) {
return Promise.reject(createAbortError());
}
let timeoutId;
let settle;
let rejectFunction;
const clear = defaultClear ?? clearTimeout;
const signalListener = () => {
clear(timeoutId);
rejectFunction(createAbortError());
};
const cleanup = () => {
if (signal) {
signal.removeEventListener("abort", signalListener);
}
};
const delayPromise = new Promise((resolve, reject) => {
settle = () => {
cleanup();
resolve(value);
};
rejectFunction = reject;
timeoutId = (defaultSet ?? setTimeout)(settle, milliseconds);
});
if (signal) {
signal.addEventListener("abort", signalListener, { once: true });
}
clearMethods.set(delayPromise, () => {
clear(timeoutId);
timeoutId = null;
settle();
});
return delayPromise;
};
}
var delay = createDelay();
var delay_default = delay;
// test/fixtures/lock.ts
async function lock(type, muxex, counts, result, timeout = 0) {
counts[type]++;
const index = counts[type];
result.push(`${type} ${index} waiting`);
const release = await muxex[`${type}Lock`]();
result.push(`${type} ${index} start`);
if (timeout > 0) {
await delay_default(timeout);
}
result.push(`${type} ${index} complete`);
release();
}
// test/fixtures/worker-single-thread.ts
async function run() {
const mutex = createMortice({
singleProcess: true
});
const counts = {
read: 0,
write: 0
};
const result = [];
void lock("write", mutex, counts, result);
void lock("read", mutex, counts, result);
void lock("read", mutex, counts, result);
void lock("read", mutex, counts, result, 500);
void lock("write", mutex, counts, result);
await lock("read", mutex, counts, result);
return result;
}
run().then((result = []) => {
globalThis.postMessage({
type: "done",
result
});
}, (err) => {
globalThis.postMessage({
type: "error",
error: {
message: err.message,
stack: err.stack
}
});
});
})();