Skip to content

Commit

Permalink
Forbid Semaphore::release for concurrency > 1, documentation, bump ve…
Browse files Browse the repository at this point in the history
…rsion.
  • Loading branch information
DirtyHairy committed Jul 9, 2020
1 parent 391433b commit 0ccb014
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -142,6 +146,7 @@ mutex
.then(function() {
// ...

// Please read and understand the WARNING above before using this API.
mutex.release();
});
```
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -248,6 +256,7 @@ semaphore
.then(function([value]) {
// ...

// Please read and understand the WARNING above before using this API.
semaphore.release();
});
```
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
13 changes: 11 additions & 2 deletions src/Semaphore.ts
Original file line number Diff line number Diff line change
@@ -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]> {
Expand All @@ -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;
Expand All @@ -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;
9 changes: 8 additions & 1 deletion test/semaphore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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', () => {
Expand Down

0 comments on commit 0ccb014

Please sign in to comment.