UNPKG

prex

Version:

Async coordination primitives and extensions on top of ES6 Promises

287 lines (285 loc) 9.44 kB
"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. See LICENSE file in the project root for details. ***************************************************************************** */ Object.defineProperty(exports, "__esModule", { value: true }); const list_1 = require("./list"); const cancellation_1 = require("./cancellation"); const adapter_1 = require("./adapter"); const disposable_1 = require("@esfx/disposable"); /** * Coordinates readers and writers for a resource. */ class ReaderWriterLock { constructor() { this._readers = new list_1.LinkedList(); this._upgradeables = new list_1.LinkedList(); this._upgrades = new list_1.LinkedList(); this._writers = new list_1.LinkedList(); this._count = 0; } /** * Asynchronously waits for and takes a read lock on a resource. * * @param token A CancellationToken used to cancel the request. */ read(token) { return new Promise((resolve, reject) => { const _token = adapter_1.getToken(token); _token.throwIfCancellationRequested(); if (this._canTakeReadLock()) { resolve(this._takeReadLock()); return; } const node = this._readers.push(() => { registration.unregister(); if (_token.cancellationRequested) { reject(new cancellation_1.CancelError()); } else { resolve(this._takeReadLock()); } }); const registration = _token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new cancellation_1.CancelError()); } }); }); } /** * Asynchronously waits for and takes a read lock on a resource * that can later be upgraded to a write lock. * * @param token A CancellationToken used to cancel the request. */ upgradeableRead(token) { return new Promise((resolve, reject) => { const _token = adapter_1.getToken(token); _token.throwIfCancellationRequested(); if (this._canTakeUpgradeableReadLock()) { resolve(this._takeUpgradeableReadLock()); return; } const node = this._upgradeables.push(() => { registration.unregister(); if (_token.cancellationRequested) { reject(new cancellation_1.CancelError()); } else { resolve(this._takeUpgradeableReadLock()); } }); const registration = _token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new cancellation_1.CancelError()); } }); }); } /** * Asynchronously waits for and takes a write lock on a resource. * * @param token A CancellationToken used to cancel the request. */ write(token) { return new Promise((resolve, reject) => { const _token = adapter_1.getToken(token); _token.throwIfCancellationRequested(); if (this._canTakeWriteLock()) { resolve(this._takeWriteLock()); return; } const node = this._writers.push(() => { registration.unregister(); if (_token.cancellationRequested) { reject(new cancellation_1.CancelError()); } else { resolve(this._takeWriteLock()); } }); const registration = _token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new cancellation_1.CancelError()); } }); }); } _upgrade(token) { return new Promise((resolve, reject) => { const _token = adapter_1.getToken(token); _token.throwIfCancellationRequested(); if (this._canTakeUpgradeLock()) { resolve(this._takeUpgradeLock()); return; } const node = this._upgrades.push(() => { registration.unregister(); if (_token.cancellationRequested) { reject(new cancellation_1.CancelError()); } else { resolve(this._takeUpgradeLock()); } }); const registration = _token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new cancellation_1.CancelError()); } }); }); } _processLockRequests() { if (this._processWriteLockRequest()) return; if (this._processUpgradeRequest()) return; this._processUpgradeableReadLockRequest(); this._processReadLockRequests(); } _canTakeReadLock() { return this._count >= 0 && this._writers.size === 0 && this._upgrades.size === 0 && this._writers.size === 0; } _processReadLockRequests() { if (this._canTakeReadLock()) { this._readers.forEach(resolve => resolve()); this._readers.clear(); } } _takeReadLock() { let released = false; this._count++; const release = () => { if (released) throw new Error("Lock already released."); released = true; this._releaseReadLock(); }; return { release, [disposable_1.Disposable.dispose]: release, }; } _releaseReadLock() { this._count--; this._processLockRequests(); } _canTakeUpgradeableReadLock() { return this._count >= 0 && !this._upgradeable; } _processUpgradeableReadLockRequest() { if (this._canTakeUpgradeableReadLock()) { const resolve = this._upgradeables.shift(); if (resolve) { resolve(); } } } _takeUpgradeableReadLock() { const upgrade = (token) => { if (this._upgradeable !== hold) throw new Error("Lock already released."); return this._upgrade(token); }; const release = () => { if (this._upgradeable !== hold) throw new Error("Lock already released."); this._releaseUpgradeableReadLock(); }; const hold = { upgrade, release, [disposable_1.Disposable.dispose]: release, }; this._count++; this._upgradeable = hold; return hold; } _releaseUpgradeableReadLock() { if (this._count === -1) { this._count = 0; } else { this._count--; } this._upgraded = undefined; this._upgradeable = undefined; this._processLockRequests(); } _canTakeUpgradeLock() { return this._count === 1 && this._upgradeable && !this._upgraded; } _processUpgradeRequest() { if (this._canTakeUpgradeLock()) { const resolve = this._upgrades.shift(); if (resolve) { resolve(); return true; } } return false; } _takeUpgradeLock() { const release = () => { if (this._upgraded !== hold) throw new Error("Lock already released."); this._releaseUpgradeLock(); }; const hold = { release, [disposable_1.Disposable.dispose]: release }; this._upgraded = hold; this._count = -1; return hold; } _releaseUpgradeLock() { this._upgraded = undefined; this._count = 1; this._processLockRequests(); } _canTakeWriteLock() { return this._count === 0; } _processWriteLockRequest() { if (this._canTakeWriteLock()) { const resolve = this._writers.shift(); if (resolve) { resolve(); return true; } } return false; } _takeWriteLock() { let released = false; this._count = -1; const release = () => { if (released) throw new Error("Lock already released."); released = true; this._releaseWriteLock(); }; return { release, [disposable_1.Disposable.dispose]: release, }; } _releaseWriteLock() { this._count = 0; this._processLockRequests(); } } exports.ReaderWriterLock = ReaderWriterLock;