async-mutex
Advanced tools
Comparing version 0.2.6 to 0.3.0
# Changelog | ||
## 0.3.0 | ||
* Deprecate `Mutex::release` / `Semaphore::release` and remove them from the | ||
documentation. The methods are still available in 0.3.x, but will be removed in | ||
0.4.0. | ||
I don't like breaking existing APIs, but using those methods is inherently | ||
dangerous as they can accidentially release locks acquired in a completely | ||
different place. Furthermore, they are mostly useless for semaphores. I consider | ||
adding them an unfortunate mistake on my end. | ||
A safe alternative is the usage of `runExclusive` which allows to execute | ||
blocks exclusively and automatically manages acquiring and releasing the | ||
mutex or semaphore. | ||
* Add `Mutex::cancel` / `Semaphore::cancel` for rejecting all currently pending | ||
locks. | ||
* Add `tryAcquire` decorator for lock-or-fail semantics. | ||
## 0.2.6 | ||
* Fix a nasty [bug](https://github.com/DirtyHairy/async-mutex/issues/27) related to | ||
consecutive calls to `mutex::release`. | ||
consecutive calls to `Mutex::release`. | ||
@@ -8,0 +26,0 @@ ## 0.2.5 |
export { default as Mutex } from './Mutex'; | ||
export { default as Semaphore } from './Semaphore'; | ||
export { withTimeout } from './withTimeout'; | ||
export { tryAcquire } from './tryAcquire'; | ||
export * from './errors'; |
import { __awaiter, __generator } from "tslib"; | ||
import Semaphore from './Semaphore'; | ||
var Mutex = /** @class */ (function () { | ||
function Mutex() { | ||
this._semaphore = new Semaphore(1); | ||
function Mutex(cancelError) { | ||
this._semaphore = new Semaphore(1, cancelError); | ||
} | ||
@@ -26,7 +26,11 @@ Mutex.prototype.acquire = function () { | ||
}; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
Mutex.prototype.release = function () { | ||
this._semaphore.release(); | ||
}; | ||
Mutex.prototype.cancel = function () { | ||
return this._semaphore.cancel(); | ||
}; | ||
return Mutex; | ||
}()); | ||
export default Mutex; |
import { __awaiter, __generator } from "tslib"; | ||
import { E_CANCELED } from './errors'; | ||
var Semaphore = /** @class */ (function () { | ||
function Semaphore(_maxConcurrency) { | ||
function Semaphore(_maxConcurrency, _cancelError) { | ||
if (_cancelError === void 0) { _cancelError = E_CANCELED; } | ||
this._maxConcurrency = _maxConcurrency; | ||
this._cancelError = _cancelError; | ||
this._queue = []; | ||
@@ -14,6 +17,8 @@ if (_maxConcurrency <= 0) { | ||
var locked = this.isLocked(); | ||
var ticket = new Promise(function (r) { return _this._queue.push(r); }); | ||
var ticketPromise = new Promise(function (resolve, reject) { | ||
return _this._queue.push({ resolve: resolve, reject: reject }); | ||
}); | ||
if (!locked) | ||
this._dispatch(); | ||
return ticket; | ||
return ticketPromise; | ||
}; | ||
@@ -44,5 +49,6 @@ Semaphore.prototype.runExclusive = function (callback) { | ||
}; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
Semaphore.prototype.release = function () { | ||
if (this._maxConcurrency > 1) { | ||
throw new Error('this method is unavailabel on semaphores with concurrency > 1; use the scoped release returned by acquire instead'); | ||
throw new Error('this method is unavailable on semaphores with concurrency > 1; use the scoped release returned by acquire instead'); | ||
} | ||
@@ -55,6 +61,11 @@ if (this._currentReleaser) { | ||
}; | ||
Semaphore.prototype.cancel = function () { | ||
var _this = this; | ||
this._queue.forEach(function (ticket) { return ticket.reject(_this._cancelError); }); | ||
this._queue = []; | ||
}; | ||
Semaphore.prototype._dispatch = function () { | ||
var _this = this; | ||
var nextConsumer = this._queue.shift(); | ||
if (!nextConsumer) | ||
var nextTicket = this._queue.shift(); | ||
if (!nextTicket) | ||
return; | ||
@@ -69,3 +80,3 @@ var released = false; | ||
}; | ||
nextConsumer([this._value--, this._currentReleaser]); | ||
nextTicket.resolve([this._value--, this._currentReleaser]); | ||
}; | ||
@@ -72,0 +83,0 @@ return Semaphore; |
import { __awaiter, __generator } from "tslib"; | ||
import { E_TIMEOUT } from './errors'; | ||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
export function withTimeout(sync, timeout, timeoutError) { | ||
var _this = this; | ||
if (timeoutError === void 0) { timeoutError = new Error('timeout'); } | ||
if (timeoutError === void 0) { timeoutError = E_TIMEOUT; } | ||
return { | ||
acquire: function () { | ||
return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { | ||
var isTimeout, ticket, release; | ||
var isTimeout, handle, ticket, release, e_1; | ||
return __generator(this, function (_a) { | ||
@@ -14,8 +15,11 @@ switch (_a.label) { | ||
isTimeout = false; | ||
setTimeout(function () { | ||
handle = setTimeout(function () { | ||
isTimeout = true; | ||
reject(timeoutError); | ||
}, timeout); | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, sync.acquire()]; | ||
case 1: | ||
case 2: | ||
ticket = _a.sent(); | ||
@@ -29,3 +33,11 @@ if (isTimeout) { | ||
} | ||
return [2 /*return*/]; | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
e_1 = _a.sent(); | ||
if (!isTimeout) { | ||
clearTimeout(handle); | ||
reject(e_1); | ||
} | ||
return [3 /*break*/, 4]; | ||
case 4: return [2 /*return*/]; | ||
} | ||
@@ -65,7 +77,11 @@ }); | ||
}, | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
release: function () { | ||
sync.release(); | ||
}, | ||
cancel: function () { | ||
return sync.cancel(); | ||
}, | ||
isLocked: function () { return sync.isLocked(); }, | ||
}; | ||
} |
@@ -6,1 +6,3 @@ export { default as Mutex } from './Mutex'; | ||
export { withTimeout } from './withTimeout'; | ||
export { tryAcquire } from './tryAcquire'; | ||
export * from './errors'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.withTimeout = exports.Semaphore = exports.Mutex = void 0; | ||
exports.tryAcquire = exports.withTimeout = exports.Semaphore = exports.Mutex = void 0; | ||
var tslib_1 = require("tslib"); | ||
var Mutex_1 = require("./Mutex"); | ||
@@ -10,1 +11,4 @@ Object.defineProperty(exports, "Mutex", { enumerable: true, get: function () { return Mutex_1.default; } }); | ||
Object.defineProperty(exports, "withTimeout", { enumerable: true, get: function () { return withTimeout_1.withTimeout; } }); | ||
var tryAcquire_1 = require("./tryAcquire"); | ||
Object.defineProperty(exports, "tryAcquire", { enumerable: true, get: function () { return tryAcquire_1.tryAcquire; } }); | ||
tslib_1.__exportStar(require("./errors"), exports); |
import MutexInterface from './MutexInterface'; | ||
declare class Mutex implements MutexInterface { | ||
constructor(cancelError?: Error); | ||
acquire(): Promise<MutexInterface.Releaser>; | ||
runExclusive<T>(callback: MutexInterface.Worker<T>): Promise<T>; | ||
isLocked(): boolean; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
release(): void; | ||
cancel(): void; | ||
private _semaphore; | ||
} | ||
export default Mutex; |
@@ -6,4 +6,4 @@ "use strict"; | ||
var Mutex = /** @class */ (function () { | ||
function Mutex() { | ||
this._semaphore = new Semaphore_1.default(1); | ||
function Mutex(cancelError) { | ||
this._semaphore = new Semaphore_1.default(1, cancelError); | ||
} | ||
@@ -29,7 +29,11 @@ Mutex.prototype.acquire = function () { | ||
}; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
Mutex.prototype.release = function () { | ||
this._semaphore.release(); | ||
}; | ||
Mutex.prototype.cancel = function () { | ||
return this._semaphore.cancel(); | ||
}; | ||
return Mutex; | ||
}()); | ||
exports.default = Mutex; |
@@ -5,3 +5,5 @@ interface MutexInterface { | ||
isLocked(): boolean; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
release(): void; | ||
cancel(): void; | ||
} | ||
@@ -8,0 +10,0 @@ declare namespace MutexInterface { |
import SemaphoreInterface from './SemaphoreInterface'; | ||
declare class Semaphore implements SemaphoreInterface { | ||
private _maxConcurrency; | ||
constructor(_maxConcurrency: number); | ||
private _cancelError; | ||
constructor(_maxConcurrency: number, _cancelError?: Error); | ||
acquire(): Promise<[number, SemaphoreInterface.Releaser]>; | ||
runExclusive<T>(callback: SemaphoreInterface.Worker<T>): Promise<T>; | ||
isLocked(): boolean; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
release(): void; | ||
cancel(): void; | ||
private _dispatch; | ||
@@ -10,0 +13,0 @@ private _queue; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var errors_1 = require("./errors"); | ||
var Semaphore = /** @class */ (function () { | ||
function Semaphore(_maxConcurrency) { | ||
function Semaphore(_maxConcurrency, _cancelError) { | ||
if (_cancelError === void 0) { _cancelError = errors_1.E_CANCELED; } | ||
this._maxConcurrency = _maxConcurrency; | ||
this._cancelError = _cancelError; | ||
this._queue = []; | ||
@@ -16,6 +19,8 @@ if (_maxConcurrency <= 0) { | ||
var locked = this.isLocked(); | ||
var ticket = new Promise(function (r) { return _this._queue.push(r); }); | ||
var ticketPromise = new Promise(function (resolve, reject) { | ||
return _this._queue.push({ resolve: resolve, reject: reject }); | ||
}); | ||
if (!locked) | ||
this._dispatch(); | ||
return ticket; | ||
return ticketPromise; | ||
}; | ||
@@ -46,5 +51,6 @@ Semaphore.prototype.runExclusive = function (callback) { | ||
}; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
Semaphore.prototype.release = function () { | ||
if (this._maxConcurrency > 1) { | ||
throw new Error('this method is unavailabel on semaphores with concurrency > 1; use the scoped release returned by acquire instead'); | ||
throw new Error('this method is unavailable on semaphores with concurrency > 1; use the scoped release returned by acquire instead'); | ||
} | ||
@@ -57,6 +63,11 @@ if (this._currentReleaser) { | ||
}; | ||
Semaphore.prototype.cancel = function () { | ||
var _this = this; | ||
this._queue.forEach(function (ticket) { return ticket.reject(_this._cancelError); }); | ||
this._queue = []; | ||
}; | ||
Semaphore.prototype._dispatch = function () { | ||
var _this = this; | ||
var nextConsumer = this._queue.shift(); | ||
if (!nextConsumer) | ||
var nextTicket = this._queue.shift(); | ||
if (!nextTicket) | ||
return; | ||
@@ -71,3 +82,3 @@ var released = false; | ||
}; | ||
nextConsumer([this._value--, this._currentReleaser]); | ||
nextTicket.resolve([this._value--, this._currentReleaser]); | ||
}; | ||
@@ -74,0 +85,0 @@ return Semaphore; |
@@ -5,3 +5,5 @@ interface SemaphoreInterface { | ||
isLocked(): boolean; | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
release(): void; | ||
cancel(): void; | ||
} | ||
@@ -8,0 +10,0 @@ declare namespace SemaphoreInterface { |
@@ -5,10 +5,11 @@ "use strict"; | ||
var tslib_1 = require("tslib"); | ||
var errors_1 = require("./errors"); | ||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
function withTimeout(sync, timeout, timeoutError) { | ||
var _this = this; | ||
if (timeoutError === void 0) { timeoutError = new Error('timeout'); } | ||
if (timeoutError === void 0) { timeoutError = errors_1.E_TIMEOUT; } | ||
return { | ||
acquire: function () { | ||
return new Promise(function (resolve, reject) { return tslib_1.__awaiter(_this, void 0, void 0, function () { | ||
var isTimeout, ticket, release; | ||
var isTimeout, handle, ticket, release, e_1; | ||
return tslib_1.__generator(this, function (_a) { | ||
@@ -18,8 +19,11 @@ switch (_a.label) { | ||
isTimeout = false; | ||
setTimeout(function () { | ||
handle = setTimeout(function () { | ||
isTimeout = true; | ||
reject(timeoutError); | ||
}, timeout); | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, sync.acquire()]; | ||
case 1: | ||
case 2: | ||
ticket = _a.sent(); | ||
@@ -33,3 +37,11 @@ if (isTimeout) { | ||
} | ||
return [2 /*return*/]; | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
e_1 = _a.sent(); | ||
if (!isTimeout) { | ||
clearTimeout(handle); | ||
reject(e_1); | ||
} | ||
return [3 /*break*/, 4]; | ||
case 4: return [2 /*return*/]; | ||
} | ||
@@ -69,5 +81,9 @@ }); | ||
}, | ||
/** @deprecated Deprecated in 0.3.0, will be removed in 0.4.0. Use runExclusive instead. */ | ||
release: function () { | ||
sync.release(); | ||
}, | ||
cancel: function () { | ||
return sync.cancel(); | ||
}, | ||
isLocked: function () { return sync.isLocked(); }, | ||
@@ -74,0 +90,0 @@ }; |
{ | ||
"name": "async-mutex", | ||
"version": "0.2.6", | ||
"version": "0.3.0", | ||
"description": "A mutex for guarding async workflows", | ||
@@ -58,17 +58,17 @@ "scripts": { | ||
"@sinonjs/fake-timers": "^6.0.1", | ||
"@types/mocha": "^8.0.4", | ||
"@types/node": "^14.14.10", | ||
"@types/mocha": "^8.2.0", | ||
"@types/node": "^14.14.25", | ||
"@types/sinonjs__fake-timers": "^6.0.2", | ||
"@typescript-eslint/eslint-plugin": "^4.8.2", | ||
"@typescript-eslint/parser": "^4.8.2", | ||
"@typescript-eslint/eslint-plugin": "^4.14.2", | ||
"@typescript-eslint/parser": "^4.14.2", | ||
"coveralls": "^3.1.0", | ||
"eslint": "^7.14.0", | ||
"eslint": "^7.19.0", | ||
"import-sort-style-eslint": "^6.0.0", | ||
"mocha": "^8.2.1", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.2.0", | ||
"prettier": "^2.2.1", | ||
"prettier-plugin-import-sort": "^0.0.6", | ||
"rollup": "^2.33.3", | ||
"ts-node": "^9.0.0", | ||
"typescript": "^4.1.2" | ||
"rollup": "^2.38.5", | ||
"ts-node": "^9.1.1", | ||
"typescript": "^4.1.3" | ||
}, | ||
@@ -87,4 +87,4 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"tslib": "^2.0.0" | ||
"tslib": "^2.1.0" | ||
} | ||
} |
293
README.md
@@ -1,3 +0,3 @@ | ||
[![Build Status](https://travis-ci.org/DirtyHairy/async-mutex.svg?branch=master)](https://travis-ci.org/DirtyHairy/async-mutex) | ||
[![npm version](https://badge.fury.io/js/async-mutex.svg)](https://badge.fury.io/js/async-mutex) | ||
[![Build status](https://github.com/DirtyHairy/async-mutex/workflows/Build%20and%20Tests/badge.svg)](https://github.com/DirtyHairy/async-mutex/actions?query=workflow%3A%22Build+and+Tests%22) | ||
[![NPM version](https://badge.fury.io/js/async-mutex.svg)](https://badge.fury.io/js/async-mutex) | ||
[![Coverage Status](https://coveralls.io/repos/github/DirtyHairy/async-mutex/badge.svg?branch=master)](https://coveralls.io/github/DirtyHairy/async-mutex?branch=master) | ||
@@ -88,4 +88,2 @@ | ||
With the latest version of Node, native ES6 style imports are supported. | ||
**TypeScript:** | ||
@@ -96,2 +94,4 @@ ```typescript | ||
With the latest version of Node, native ES6 style imports are supported. | ||
## Mutex API | ||
@@ -107,3 +107,3 @@ | ||
### Locking | ||
### Synchronized code execution | ||
@@ -113,5 +113,35 @@ Promise style: | ||
mutex | ||
.runExclusive(function() { | ||
// ... | ||
}) | ||
.then(function(result) { | ||
// ... | ||
}); | ||
``` | ||
async/await: | ||
```typescript | ||
await mutex.runExclusive(async () => { | ||
// ... | ||
}); | ||
``` | ||
`runExclusive` schedules the supplied callback to be run once the mutex is unlocked. | ||
The function may return a promise. Once the promise is resolved or rejected (or immediately after | ||
execution if an immediate value was returned), | ||
the mutex is released. `runExclusive` returns a promise that adopts the state of the function result. | ||
The mutex is released and the result rejected if an exception occurs during execution | ||
if the callback. | ||
### Manual locking / releasing | ||
Promise style: | ||
```typescript | ||
mutex | ||
.acquire() | ||
.then(function(release) { | ||
// ... | ||
release(); | ||
}); | ||
@@ -131,3 +161,3 @@ ``` | ||
`acquire` returns an (ES6) promise that will resolve as soon as the mutex is | ||
available and ready to be accessed. The promise resolves with a function `release` that | ||
available. The promise resolves with a function `release` that | ||
must be called once the mutex should be released again. | ||
@@ -139,19 +169,32 @@ | ||
#### Alternate release API | ||
`acquire` / `release` should be considered a low level API. In most situations, | ||
`runExclusive` will be a better choice that automatically takes care of releasing | ||
the mutex once a block of code has executed exclusively. | ||
A locked mutex can also be released by calling the `release` method on the mutex. This will | ||
release the current lock on the mutex. | ||
### Checking whether the mutex is locked | ||
**WARNING:** Using this API comes with the inherent danger of releasing a mutex locked | ||
in an entirely unrelated place. Use with care. | ||
```typescript | ||
mutex.isLocked(); | ||
``` | ||
### Cancelling pending locks. | ||
Pending locks can be cancelled by calling `cancel()` on the mutex. This will reject | ||
all pending locks with `E_CANCELED`: | ||
Promise style: | ||
```typescript | ||
import {E_CANCELED} from 'async-mutex'; | ||
mutex | ||
.acquire() | ||
.then(function() { | ||
.runExclusive(() => { | ||
// ... | ||
// Please read and understand the WARNING above before using this API. | ||
mutex.release(); | ||
}) | ||
.then(() => { | ||
// ... | ||
}) | ||
.catch(e => { | ||
if (e === E_CANCELED) { | ||
// ... | ||
} | ||
}); | ||
@@ -162,11 +205,39 @@ ``` | ||
```typescript | ||
await mutex.acquire(); | ||
import {E_CANCELED} from 'async-mutex'; | ||
try { | ||
// ... | ||
} finally { | ||
// Please read and understand the WARNING above before using this API. | ||
mutex.release(); | ||
await mutex.runExclusive(() => { | ||
// ... | ||
}); | ||
} catch (e) { | ||
if (e === E_CANCELED) { | ||
// ... | ||
} | ||
} | ||
``` | ||
This works with `aquire`, too: | ||
if `acquire` is used for locking, the resulting promise will reject with `E_CANCELED`. | ||
The error that is thrown can be customized by passing a different error to the `Mutex` | ||
constructor: | ||
```typescript | ||
const mutex = new Mutex(new Error('fancy custom error')); | ||
``` | ||
Note that while all pending locks are cancelled, a currently held lock will not be | ||
revoked. In consequence, the mutex may not be available even after `cancel()` has been called. | ||
## Semaphore API | ||
### Creating | ||
```typescript | ||
const semaphore = new Semaphore(initialValue); | ||
``` | ||
Creates a new semaphore. `initialValue` is a positive integer that defines the | ||
initial value of the semaphore (aka the maximum number of concurrent consumers). | ||
### Synchronized code execution | ||
@@ -176,4 +247,4 @@ | ||
```typescript | ||
mutex | ||
.runExclusive(function() { | ||
semaphore | ||
.runExclusive(function(value) { | ||
// ... | ||
@@ -188,3 +259,3 @@ }) | ||
```typescript | ||
await mutex.runExclusive(async () => { | ||
await semaphore.runExclusive(async (value) => { | ||
// ... | ||
@@ -194,29 +265,13 @@ }); | ||
`runExclusive` schedules the supplied callback to be run once the mutex is unlocked. | ||
`runExclusive` schedules the supplied callback to be run once the semaphore is available. | ||
The callback will receive the current value of the semaphore as its argument. | ||
The function may return a promise. Once the promise is resolved or rejected (or immediately after | ||
execution if an immediate value was returned), | ||
the mutex is released. `runExclusive` returns a promise that adopts the state of the function result. | ||
the semaphore is released. `runExclusive` returns a promise that adopts the state of the function result. | ||
The mutex is released and the result rejected if an exception occurs during execution | ||
if the callback. | ||
The semaphore is released and the result rejected if an exception occurs during execution | ||
of the callback. | ||
### Checking whether the mutex is locked | ||
### Manual locking / releasing | ||
```typescript | ||
mutex.isLocked(); | ||
``` | ||
## Semaphore API | ||
### Creating | ||
```typescript | ||
const semaphore = new Semaphore(initialValue); | ||
``` | ||
Creates a new semaphore. `initialValue` is a positive integer that defines the | ||
initial value of the semaphore (aka the maximum number of concurrent consumers) | ||
### Locking | ||
Promise style: | ||
@@ -244,3 +299,3 @@ ```typescript | ||
`acquire` returns an (ES6) promise that will resolve as soon as the semaphore is | ||
available and ready to be accessed. The promise resolves to an array with the | ||
available. The promise resolves to an array with the | ||
first entry being the current value of the semaphore, and the second value a | ||
@@ -254,44 +309,34 @@ function that must be called to release the semaphore once the critical operation | ||
#### Alternate release API | ||
`acquire` / `release` should be considered a low level API. In most situations, | ||
`runExclusive` will be a better choice that automatically takes care of releasing | ||
the mutex once a block of code has executed exclusively. | ||
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. | ||
### Checking whether the semaphore is locked | ||
**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 | ||
semaphore | ||
.acquire() | ||
.then(function([value]) { | ||
// ... | ||
// Please read and understand the WARNING above before using this API. | ||
semaphore.release(); | ||
}); | ||
semaphore.isLocked(); | ||
``` | ||
async/await: | ||
```typescript | ||
const [value] = await semaphore.acquire(); | ||
try { | ||
// ... | ||
} finally { | ||
// Please read and understand the WARNING above before using this API. | ||
semaphore.release(); | ||
} | ||
``` | ||
The semaphore is considered to be locked if it has a value of zero. | ||
### Synchronized code execution | ||
### Cancelling pending locks. | ||
Pending locks can be cancelled by calling `cancel()` on the sempahore. This will reject | ||
all pending locks with `E_CANCELED`: | ||
Promise style: | ||
```typescript | ||
import {E_CANCELED} from 'async-mutex'; | ||
semaphore | ||
.runExclusive(function(value) { | ||
.runExclusive(() => { | ||
// ... | ||
}) | ||
.then(function(result) { | ||
.then(() => { | ||
// ... | ||
}) | ||
.catch(e => { | ||
if (e === E_CANCELED) { | ||
// ... | ||
} | ||
}); | ||
@@ -302,23 +347,27 @@ ``` | ||
```typescript | ||
await semaphore.runExclusive(async (value) => { | ||
// ... | ||
}); | ||
import {E_CANCELED} from 'async-mutex'; | ||
try { | ||
await semaphore.runExclusive(() => { | ||
// ... | ||
}); | ||
} catch (e) { | ||
if (e === E_CANCELED) { | ||
// ... | ||
} | ||
} | ||
``` | ||
`runExclusive` schedules the supplied callback to be run once the semaphore is available. | ||
The callback will receive the current value of the semaphore as its argument. | ||
The function may return a promise. Once the promise is resolved or rejected (or immediately after | ||
execution if an immediate value was returned), | ||
the semaphore is released. `runExclusive` returns a promise that adopts the state of the function result. | ||
This works with `aquire`, too: | ||
if `acquire` is used for locking, the resulting promise will reject with `E_CANCELED`. | ||
The semaphore is released and the result rejected if an exception occurs during execution | ||
if the callback. | ||
The error that is thrown can be customized by passing a different error to the `Semaphore` | ||
constructor: | ||
### Checking whether the semaphore is locked | ||
```typescript | ||
semaphore.isLocked(); | ||
const semaphore = new Semaphore(2, new Error('fancy custom error')); | ||
``` | ||
The semaphore is considered to be locked if it has a value of zero. | ||
Note that while all pending locks are cancelled, any currently held locks will not be | ||
revoked. In consequence, the semaphore may not be available even after `cancel()` has been called. | ||
@@ -333,4 +382,6 @@ ## Limiting the time waiting for a mutex or semaphore to become available | ||
```typescript | ||
const mutexWithTimeout = withTimeout(new Mutex(), 100, new Error('timeout')); | ||
const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100, new Error('timeout')); | ||
import {withTimeout, E_TIMEOUT} from 'asymc-mutex`; | ||
const mutexWithTimeout = withTimeout(new Mutex(), 100); | ||
const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100); | ||
``` | ||
@@ -340,6 +391,6 @@ | ||
The second argument of `withTimeout` is | ||
the timeout in milliseconds. After the timeout is exceeded, the promise returned by | ||
`acquire` and `runExclusive` will reject. The latter will not run the provided callback in | ||
case of an timeout. | ||
The second argument of `withTimeout` is the timeout in milliseconds. After the | ||
timeout is exceeded, the promise returned by `acquire` and `runExclusive` will | ||
reject with `E_TIMEOUT`. The latter will not run the provided callback in case | ||
of an timeout. | ||
@@ -349,4 +400,54 @@ The third argument of `withTimeout` is optional and can be used to | ||
```typescript | ||
const mutexWithTimeout = withTimeout(new Mutex(), 100, new Error('new fancy error')); | ||
const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100, new Error('new fancy error')); | ||
``` | ||
### Failing early if the mutex or semaphore is not available | ||
A shortcut exists for the case where you do not want to wait for a lock to | ||
be available at all. The `tryAcquire` decorator can be applied to both mutexes | ||
and semaphores and changes the behavior of `acquire` and `runExclusive` to | ||
immediately throw `E_ALREADY_LOCKED` if the mutex is not available. | ||
Promise style: | ||
```typescript | ||
import {tryAcquire, E_ALREADY_LOCKED} from 'asymc-mutex`; | ||
tryAcquire(semaphoreOrMutex) | ||
.runExclusive(() => { | ||
// ... | ||
}) | ||
.then(() => { | ||
// ... | ||
}) | ||
.catch(e => { | ||
if (e === E_ALREADY_LOCKED) { | ||
// ... | ||
} | ||
}); | ||
``` | ||
async/await: | ||
```typescript | ||
import {tryAcquire, E_ALREADY_LOCKED} from 'asymc-mutex`; | ||
try { | ||
await tryAcquire(semaphoreOrMutex).runExclusive(() => { | ||
// ... | ||
}); | ||
} catch (e) { | ||
if (e === E_NOT_AVAILABLE { | ||
// ... | ||
} | ||
} | ||
``` | ||
Again, the error can be customized by providing a custom error as second argument to | ||
`tryAcquire`. | ||
# License | ||
Feel free to use this library under the conditions of the MIT license. |
Sorry, the diff of this file is not supported yet
48167
29
718
438
Updatedtslib@^2.1.0