UNPKG

@microsoft/dev-tunnels-ssh

Version:
128 lines 5.15 kB
"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