@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
128 lines • 5.15 kB
JavaScript
"use strict";
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.Semaphore = void 0;
const promiseCompletionSource_1 = require("./promiseCompletionSource");
const cancellation_1 = require("./cancellation");
const errors_1 = require("../errors");
/**
* Semaphore-like object that allows multiple awaiters to coordinate exclusive access to a resource.
*/
class Semaphore {
/**
* Creates a new semaphore instance.
* @param initialCount Optional initial count. Defaults to 0.
*/
constructor(initialCount = 0) {
this.completions = [];
this.disposed = false;
this.count = initialCount;
}
/**
* Gets the current available count of the semaphore.
*/
get currentCount() {
return this.count;
}
/**
* Releases the semaphore, increasing the available count or unblicking one or more awaiters.
* @param releaseCount Optional specified count to release. Defaults to 1.
* @returns The previous count (before release).
*/
release(releaseCount = 1) {
if (this.disposed)
throw new errors_1.ObjectDisposedError(this);
const previousCount = this.count;
for (; releaseCount > 0; releaseCount--) {
if (this.completions.length > 0) {
// Something is waiting on the semaphore.
// Remove and complete the wait without incrementing the count.
const completion = this.completions.shift();
completion.resolve(true);
}
else {
// Nothing is currently waiting on the semaphore. Increment the available count.
this.count++;
}
}
return previousCount;
}
/**
* Releases the semaphore, but does not throw an `ObjectDisposedError` if it is already disposed.
*/
tryRelease() {
try {
this.release();
}
catch (e) {
if (!(e instanceof errors_1.ObjectDisposedError)) {
throw e;
}
}
}
async wait(timeoutOrCancellation, cancellation) {
const millisecondsTimeout = typeof timeoutOrCancellation === 'number' ? timeoutOrCancellation : undefined;
if (typeof cancellation === 'undefined' && typeof timeoutOrCancellation === 'object') {
cancellation = timeoutOrCancellation;
}
if (this.disposed)
throw new errors_1.ObjectDisposedError(this);
if (cancellation === null || cancellation === void 0 ? void 0 : cancellation.isCancellationRequested)
throw new cancellation_1.CancellationError();
if (this.count > 0) {
// The semaphore is available now.
this.count--;
return true;
}
else if (millisecondsTimeout === 0) {
// The semaphore is not available and the caller doesn't want to wait.
return false;
}
else {
const completion = new promiseCompletionSource_1.PromiseCompletionSource();
this.completions.push(completion);
// Start with a promise that completes with `true` when the wait succeeds.
const promises = [completion.promise];
if (millisecondsTimeout) {
// Race against a promise that completes with `false` when the timeout expires.
promises.push(new Promise((resolve) => setTimeout(() => resolve(false), millisecondsTimeout)));
}
if (cancellation) {
// Race against a promise that throws when the cancellation token is cancelled.
const cancellationCompletion = new promiseCompletionSource_1.PromiseCompletionSource();
cancellation.onCancellationRequested(() => {
cancellationCompletion.reject(new cancellation_1.CancellationError());
});
promises.push(cancellationCompletion.promise);
}
if (await Promise.race(promises)) {
// The wait succeeded.
return true;
}
else {
// The wait timed out. Remove the (not-completed) completion from the array.
const completionIndex = this.completions.indexOf(completion);
if (completionIndex >= 0)
this.completions.splice(completionIndex, 1);
return false;
}
}
}
/**
* Disposes the semaphore and throws a diposed error to any awaiters.
*/
dispose() {
if (this.disposed)
return;
this.disposed = true;
for (const completion of this.completions) {
completion.reject(new errors_1.ObjectDisposedError(this));
}
this.completions.splice(0, this.completions.length);
this.count = 0;
}
}
exports.Semaphore = Semaphore;
//# sourceMappingURL=semaphore.js.map