prex-es5
Version:
Async coordination primitives and extensions on top of ES6 Promises
288 lines (285 loc) • 9.27 kB
JavaScript
/*! *****************************************************************************
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