UNPKG

@davidcal/fec-raptorq

Version:

Node.js wrapper for RaptorQ forward error correction

106 lines (83 loc) 2.69 kB
import { create_promise } from "./create_promise.js"; import { call_as_async } from "./call_as_async.js"; import { create_sync_factory } from "./create_sync_factory.js"; import { iter_take } from "./iter_take.js"; const LOCK_ERROR_MSG = `Developer Alert [Critical] A lock is permanently locked due to an exception. There is no exposed process in place to detect this incident. A safety invariant would be broken if this lock were automatically released. If this is a routine error, the developer should have instead processed the error in a non-disruptive manner.`; const max_concurrency = 64; class MultiLock { _init() { this._locks = new Map(); this._queue = new Set(); } _process_queue() { const cumulative_scopes = new Set(); for (const request of iter_take(this._queue, max_concurrency)) { const { scopes, res } = request; let should_res = false; if (this._can_acquire(scopes, cumulative_scopes)) { this._lock_scopes(scopes); this._queue.delete(request); should_res = true; } for (const scope of scopes) { cumulative_scopes.add(scope); } should_res && res(); } } _can_acquire(scopes, cumulative_scopes) { return scopes.every((scope) => !this._locks.has(scope) && !cumulative_scopes.has(scope) ); } _lock_scopes(scopes) { for (const scope of scopes) { this._locks.set(scope, true); } } _unlock_scopes(scopes) { for (const scope of scopes) { this._locks.delete(scope); } this._process_queue(); } create_scope() { return Symbol("[multi_lock].scope"); } async acquire(scopes, callback) { const [promise, res] = create_promise(); const request = { scopes, res, }; this._queue.add(request); this._process_queue(); await promise; try { var result = await call_as_async(callback); } catch(e) { console.error(LOCK_ERROR_MSG); throw e; } this._unlock_scopes(scopes); return result; } } /** * @stability 1 - experimental * * Creates a monolithic multi-lock where granular scopes can be acquired to increase throughput. * * A single multi-lock is not prone to deadlocks as all scopes are acquired and released in a single atomic operation. * * Overlapping acquisitions are performed sequentially to prevent starvation of older requests. * * Invoke `create_scope` to create a new scope. * * Pass an array of scopes alongside a callback to `acquire` to make an acquisition request. */ export const create_multi_lock = create_sync_factory(MultiLock);