UNPKG

prex-es5

Version:

Async coordination primitives and extensions on top of ES6 Promises

288 lines (285 loc) 9.27 kB
/*! ***************************************************************************** Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. See LICENSE file in the project root for details. ***************************************************************************** */ import { LinkedList } from "./list"; import { CancellationToken, CancelError } from "./cancellation"; import { isMissing, isInstance } from "./utils"; /** * Coordinates readers and writers for a resource. */ export class ReaderWriterLock { constructor() { this._readers = new LinkedList(); this._upgradeables = new LinkedList(); this._upgrades = new LinkedList(); this._writers = new 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) => { if (isMissing(token)) token = CancellationToken.none; if (!isInstance(token, CancellationToken)) throw new TypeError("CancellationToken expected: token."); token.throwIfCancellationRequested(); if (this._canTakeReadLock()) { resolve(this._takeReadLock()); return; } const node = this._readers.push(() => { registration.unregister(); if (token.cancellationRequested) { reject(new CancelError()); } else { resolve(this._takeReadLock()); } }); const registration = token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new 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) => { if (isMissing(token)) token = CancellationToken.none; if (!isInstance(token, CancellationToken)) throw new TypeError("CancellationToken expected: token."); token.throwIfCancellationRequested(); if (this._canTakeUpgradeableReadLock()) { resolve(this._takeUpgradeableReadLock()); return; } const node = this._upgradeables.push(() => { registration.unregister(); if (token.cancellationRequested) { reject(new CancelError()); } else { resolve(this._takeUpgradeableReadLock()); } }); const registration = token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new 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) => { if (isMissing(token)) token = CancellationToken.none; if (!isInstance(token, CancellationToken)) throw new TypeError("CancellationToken expected: token."); token.throwIfCancellationRequested(); if (this._canTakeWriteLock()) { resolve(this._takeWriteLock()); return; } const node = this._writers.push(() => { registration.unregister(); if (token.cancellationRequested) { reject(new CancelError()); } else { resolve(this._takeWriteLock()); } }); const registration = token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new CancelError()); } }); }); } _upgrade(token) { return new Promise((resolve, reject) => { if (isMissing(token)) token = CancellationToken.none; if (!isInstance(token, CancellationToken)) throw new TypeError("CancellationToken expected: token."); token.throwIfCancellationRequested(); if (this._canTakeUpgradeLock()) { resolve(this._takeUpgradeLock()); return; } const node = this._upgrades.push(() => { registration.unregister(); if (token.cancellationRequested) { reject(new CancelError()); } else { resolve(this._takeUpgradeLock()); } }); const registration = token.register(() => { if (node.list) { node.list.deleteNode(node); reject(new 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++; return { release: () => { if (released) throw new Error("Lock already released."); released = true; this._releaseReadLock(); } }; } _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 hold = { upgrade: (token) => { if (this._upgradeable !== hold) throw new Error("Lock already released."); return this._upgrade(token); }, release: () => { if (this._upgradeable !== hold) throw new Error("Lock already released."); this._releaseUpgradeableReadLock(); } }; 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 hold = { release: () => { if (this._upgraded !== hold) throw new Error("Lock already released."); this._releaseUpgradeLock(); } }; 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; return { release: () => { if (released) throw new Error("Lock already released."); released = true; this._releaseWriteLock(); } }; } _releaseWriteLock() { this._count = 0; this._processLockRequests(); } } //# sourceMappingURL=readerwriterlock.js.map