diff --git a/CHANGELOG.md b/CHANGELOG.md index 9030b88..f5f41d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.4 + + * Calling Semaphore::release on a semaphore with concurrency > 1 will not work + as expected; throw an exception in this case + * Make the warning on using Semaphore::release and Mutex::release more prominent + ## 0.2.3 * Add alternate Semaphore::release and Mutex::release API diff --git a/README.md b/README.md index 42f22e1..a4e7ea0 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,11 @@ and handle exceptions accordingly. #### Alternate release API -A locked mutex can also be released by calling the `release` method on the mutex: +A locked mutex can also be released by calling the `release` method on the mutex. This will +release the current lock on the mutex. + +**WARNING:** Using this API comes with the inherent danger of releasing a mutex locked +in an entirely unrelated place. Use with care. Promise style: ```typescript @@ -142,6 +146,7 @@ mutex .then(function() { // ... + // Please read and understand the WARNING above before using this API. mutex.release(); }); ``` @@ -152,13 +157,11 @@ await mutex.acquire(); try { // ... } finally { + // Please read and understand the WARNING above before using this API. mutex.release(); } ``` -**WARNING:** Using this API comes with the inherent danger of releasing a mutex locked -in an entirely unrelated place. Use with care. - ### Synchronized code execution Promise style: @@ -239,7 +242,12 @@ and handle exceptions accordingly. #### Alternate release API -A locked semaphore can also be released by calling the `release` method on the semaphore: +A locked semaphore can also be released by calling the `release` method on the semaphore. +This will release the most recent lock on the semaphore. As such, this will only work with +semaphores with `maxValue == 1`. Calling this on other semaphores will throw an exception. + +**WARNING:** Using this API comes with the inherent danger of releasing a semaphore locked +in an entirely unrelated place. Use with care. Promise style: ```typescript @@ -248,6 +256,7 @@ semaphore .then(function([value]) { // ... + // Please read and understand the WARNING above before using this API. semaphore.release(); }); ``` @@ -258,13 +267,11 @@ const [value] = await semaphore.acquire(); try { // ... } finally { + // Please read and understand the WARNING above before using this API. semaphore.release(); } ``` -**WARNING:** Using this API comes with the inherent danger of releasing a semaphore locked -in an entirely unrelated place. Use with care. - ### Synchronized code execution Promise style: diff --git a/package.json b/package.json index 60e172a..67145ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "async-mutex", - "version": "0.2.3", + "version": "0.2.4", "description": "A mutex for guarding async workflows", "scripts": { "lint": "eslint src/**/*.ts test/**/*.ts", diff --git a/src/Semaphore.ts b/src/Semaphore.ts index c692990..5eb072a 100644 --- a/src/Semaphore.ts +++ b/src/Semaphore.ts @@ -1,10 +1,12 @@ import SemaphoreInterface from './SemaphoreInterface'; class Semaphore implements SemaphoreInterface { - constructor(private _value: number) { - if (_value <= 0) { + constructor(private _maxConcurrency: number) { + if (_maxConcurrency <= 0) { throw new Error('semaphore must be initialized to a positive value'); } + + this._value = _maxConcurrency; } acquire(): Promise<[number, SemaphoreInterface.Releaser]> { @@ -31,6 +33,12 @@ class Semaphore implements SemaphoreInterface { } release(): void { + if (this._maxConcurrency > 1) { + throw new Error( + 'this method is unavailabel on semaphores with concurrency > 1; use the scoped release returned by acquire instead' + ); + } + if (this._currentReleaser) { this._currentReleaser(); this._currentReleaser = undefined; @@ -57,6 +65,7 @@ class Semaphore implements SemaphoreInterface { private _queue: Array<(lease: [number, SemaphoreInterface.Releaser]) => void> = []; private _currentReleaser: SemaphoreInterface.Releaser | undefined; + private _value: number; } export default Semaphore; diff --git a/test/semaphore.ts b/test/semaphore.ts index 993cfc0..64462ba 100644 --- a/test/semaphore.ts +++ b/test/semaphore.ts @@ -198,7 +198,8 @@ export const semaphoreSuite = (factory: () => SemaphoreInterface): void => { }); test('the release method releases a locked semaphore', async () => { - await semaphore.acquire(); + semaphore = new Semaphore(1); + await semaphore.acquire(); assert(semaphore.isLocked()); @@ -208,8 +209,14 @@ export const semaphoreSuite = (factory: () => SemaphoreInterface): void => { }); test('calling release on a unlocked semaphore does not throw', () => { + semaphore = new Semaphore(1); + semaphore.release(); }); + + test('calling release on a semaphore with concurrency > 1 throws', () => { + assert.throws(() => semaphore.release()); + }); }; suite('Semaphore', () => {