async-mutex
Advanced tools
Comparing version 0.1.4 to 0.2.0
# Changelog | ||
## 0.1.0 | ||
## 0.2.0 | ||
* Initial release | ||
* Add a `Semaphore`, reimplement `Mutex` on top of it | ||
* Add a `withTimeout` decorator that limits the time the program waits | ||
for the mutex or semaphore to become available | ||
* Support native ES6 imports on Node >= 12 | ||
* Provide an ES6 module entrypoint for ES6 aware bundlers | ||
* Dependency bump | ||
* Switch from TSlint to ESlint | ||
* Enable code coverage in tests | ||
## 0.1.1 | ||
## 0.1.4 | ||
* Fix documentation for `acquire` | ||
* Documentation updates (thanks to hmil and 0xflotus) | ||
* Update build dependencies | ||
## 0.1.3 | ||
* Move deps to devDependencies (thanks to Meirion Hughes for the PR) | ||
* Upgrade deps | ||
## 0.1.2 | ||
@@ -18,10 +31,8 @@ | ||
## 0.1.3 | ||
## 0.1.1 | ||
* Move deps to devDependencies (thanks to Meirion Hughes for the PR) | ||
* Upgrade deps | ||
* Fix documentation for `acquire` | ||
## 0.1.4 | ||
## 0.1.0 | ||
* Documentation updates (thanks to hmil and 0xflotus) | ||
* Update build dependencies | ||
* Initial release |
export { default as Mutex } from './Mutex'; | ||
export { default as MutexInterface } from './MutexInterface'; | ||
export { default as Semaphore } from './Semaphore'; | ||
export { default as SemaphoreInterface } from './SemaphoreInterface'; | ||
export { withTimeout } from './withTimeout'; |
@@ -5,1 +5,6 @@ "use strict"; | ||
exports.Mutex = Mutex_1.default; | ||
var Semaphore_1 = require("./Semaphore"); | ||
exports.Semaphore = Semaphore_1.default; | ||
var withTimeout_1 = require("./withTimeout"); | ||
exports.withTimeout = withTimeout_1.withTimeout; | ||
//# sourceMappingURL=index.js.map |
import MutexInterface from './MutexInterface'; | ||
declare class Mutex implements MutexInterface { | ||
isLocked(): boolean; | ||
acquire(): Promise<MutexInterface.Releaser>; | ||
runExclusive<T>(callback: MutexInterface.Worker<T>): Promise<T>; | ||
private _dispatchNext; | ||
private _queue; | ||
private _pending; | ||
isLocked(): boolean; | ||
private _semaphore; | ||
} | ||
export default Mutex; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var tslib_1 = require("tslib"); | ||
var Semaphore_1 = require("./Semaphore"); | ||
var Mutex = /** @class */ (function () { | ||
function Mutex() { | ||
this._queue = []; | ||
this._pending = false; | ||
this._semaphore = new Semaphore_1.default(1); | ||
} | ||
Mutex.prototype.isLocked = function () { | ||
return this._pending; | ||
}; | ||
Mutex.prototype.acquire = function () { | ||
var _this = this; | ||
var ticket = new Promise(function (resolve) { return _this._queue.push(resolve); }); | ||
if (!this._pending) { | ||
this._dispatchNext(); | ||
} | ||
return ticket; | ||
}; | ||
Mutex.prototype.runExclusive = function (callback) { | ||
return this | ||
.acquire() | ||
.then(function (release) { | ||
var result; | ||
try { | ||
result = callback(); | ||
} | ||
catch (e) { | ||
release(); | ||
throw (e); | ||
} | ||
return Promise | ||
.resolve(result) | ||
.then(function (x) { return (release(), x); }, function (e) { | ||
release(); | ||
throw e; | ||
return tslib_1.__awaiter(this, void 0, void 0, function () { | ||
var _a, releaser; | ||
return tslib_1.__generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: return [4 /*yield*/, this._semaphore.acquire()]; | ||
case 1: | ||
_a = _b.sent(), releaser = _a[1]; | ||
return [2 /*return*/, releaser]; | ||
} | ||
}); | ||
}); | ||
}; | ||
Mutex.prototype._dispatchNext = function () { | ||
if (this._queue.length > 0) { | ||
this._pending = true; | ||
this._queue.shift()(this._dispatchNext.bind(this)); | ||
} | ||
else { | ||
this._pending = false; | ||
} | ||
Mutex.prototype.runExclusive = function (callback) { | ||
return this._semaphore.runExclusive(function () { return callback(); }); | ||
}; | ||
Mutex.prototype.isLocked = function () { | ||
return this._semaphore.isLocked(); | ||
}; | ||
return Mutex; | ||
}()); | ||
exports.default = Mutex; | ||
//# sourceMappingURL=Mutex.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
//# sourceMappingURL=MutexInterface.js.map |
{ | ||
"name": "async-mutex", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"description": "A mutex for guarding async workflows", | ||
"scripts": { | ||
"prepublish": "tslint src/**/*.ts test/**/*.ts && tsc", | ||
"pretest": "tslint src/**/*.ts test/**/*.ts && tsc -p tsconfig.test.json", | ||
"test": "mocha -R spec -u tdd build/test" | ||
"lint": "eslint src/**/*.ts test/**/*.ts", | ||
"build": "tsc && tsc -p tsconfig.es6.json && tsc -p tsconfig.mjs.json && rollup -o index.mjs mjs/index.js", | ||
"prepublish": "yarn test && yarn build", | ||
"test": "yarn lint && nyc --reporter=text --reporter=html --reporter=lcov mocha test/*.ts", | ||
"coveralls": "cat ./coverage/lcov.info | coveralls" | ||
}, | ||
@@ -16,2 +18,31 @@ "author": "Christian Speckner <cnspeckn@googlemail.com> (https://github.com/DirtyHairy/)", | ||
}, | ||
"prettier": { | ||
"printWidth": 120, | ||
"tabWidth": 4, | ||
"singleQuote": true, | ||
"parser": "typescript" | ||
}, | ||
"importSort": { | ||
".js, .jsx, .ts, .tsx": { | ||
"style": "eslint", | ||
"parser": "typescript" | ||
} | ||
}, | ||
"eslintConfig": { | ||
"root": true, | ||
"parser": "@typescript-eslint/parser", | ||
"plugins": [ | ||
"@typescript-eslint" | ||
], | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/eslint-recommended", | ||
"plugin:@typescript-eslint/recommended" | ||
], | ||
"rules": { | ||
"eqeqeq": "error", | ||
"@typescript-eslint/no-namespace": "off", | ||
"no-async-promise-executor": "off" | ||
} | ||
}, | ||
"keywords": [ | ||
@@ -22,14 +53,34 @@ "mutex", | ||
"files": [ | ||
"lib" | ||
"lib", | ||
"es6", | ||
"index.mjs" | ||
], | ||
"devDependencies": { | ||
"@types/mocha": "^5.2.7", | ||
"@types/node": "^12.7.8", | ||
"mocha": "^6.2.0", | ||
"tslint": "^5.20.0", | ||
"@sinonjs/fake-timers": "^6.0.1", | ||
"@types/mocha": "^7.0.2", | ||
"@types/node": "^13.11.0", | ||
"@types/sinonjs__fake-timers": "^6.0.1", | ||
"@typescript-eslint/eslint-plugin": "^2.26.0", | ||
"@typescript-eslint/parser": "^2.26.0", | ||
"coveralls": "^3.0.11", | ||
"eslint": "^6.8.0", | ||
"import-sort-style-eslint": "^6.0.0", | ||
"mocha": "^7.1.1", | ||
"nyc": "^15.0.1", | ||
"prettier": "^2.0.2", | ||
"prettier-plugin-import-sort": "^0.0.4", | ||
"rollup": "^2.3.2", | ||
"ts-node": "^8.8.1", | ||
"typescript": "^3.6.3" | ||
}, | ||
"main": "lib/index.js", | ||
"module": "es6/index.js", | ||
"types": "lib/index.d.ts", | ||
"dependencies": {} | ||
"exports": { | ||
"import": "./index.mjs", | ||
"require": "./lib/index.js" | ||
}, | ||
"dependencies": { | ||
"tslib": "^1.11.1" | ||
} | ||
} |
194
README.md
[![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) | ||
[![Coverage Status](https://coveralls.io/repos/github/DirtyHairy/async-mutex/badge.svg?branch=master)](https://coveralls.io/github/DirtyHairy/async-mutex?branch=master) | ||
# What is it? | ||
This package implements a mutex for synchronizing asynchronous operations in | ||
This package implements primitives for synchronizing asynchronous operations in | ||
Javascript. | ||
## Mutex | ||
The term "mutex" usually refers to a data structure used to synchronize | ||
@@ -27,7 +30,27 @@ concurrent processes running on different threads. For example, before accessing | ||
This library solves the problem by applying the concept of mutexes to Javascript. | ||
A mutex is locked by providing a worker callback that will be called once no other locks | ||
are held on the mutex. Once the async process is complete (usually taking multiple | ||
spins of the event loop), a callback supplied to the worker is called in order | ||
Locking the mutex will return a promise that resolves once the mutex becomes | ||
available. Once the async process is complete (usually taking multiple | ||
spins of the event loop), a callback supplied to the caller is called in order | ||
to release the mutex, allowing the next scheduled worker to execute. | ||
# Semaphore | ||
Imagine a situation where you need to control access to several instances of | ||
a shared resource. For example, you might want to distribute images between several | ||
worker processes that perform transformations, or you might want to create a web | ||
crawler that performs a defined number of requests in parallel. | ||
A semaphore is a data structure that is initialized to a positive integer value and that | ||
can be locked multiple times. | ||
As long as the semaphore value is positive, locking it will return the current value | ||
and the locking process will continue execution immediatelly; the semaphore will | ||
be decremented upon locking. Releasing the lock will increment the semaphore again. | ||
Once the semaphore has reached zero, the next process that attempts to acquire a lock | ||
will be suspended until another process releases its lock and this increments the semaphore | ||
again. | ||
This library provides a semaphore implementation for Javascript that is similar to the | ||
mutex implementation described above. | ||
# How to use it? | ||
@@ -42,3 +65,3 @@ | ||
The library is written in TypeScript and will work in any environment that | ||
supports ES5 and ES6 promises. If ES6 promises are not supported natively, | ||
supports ES5, ES6 promises and `Array.isArray`. On ancient browsers, | ||
a shim can be used (e.g. [core-js](https://github.com/zloirock/core-js)). | ||
@@ -48,24 +71,29 @@ No external typings are required for using this library with | ||
Starting with Node 12, native ES6 style imports are supported. | ||
## Importing | ||
ES5 / CommonJS | ||
**CommonJS:** | ||
```javascript | ||
var asyncMutex = require('async-mutex').Mutex; | ||
var Mutex = require('async-mutex').Mutex; | ||
var Semaphore = require('async-mutex').Semaphore; | ||
var withTimeout = require('async-mutex').withTimeout; | ||
``` | ||
ES6 | ||
**ES6:** | ||
```javascript | ||
import {Mutex} from 'async-mutex'; | ||
import {Mutex, Semaphore, withTimeout} from 'async-mutex'; | ||
``` | ||
TypeScript | ||
Starting with Node 12, native ES6 style imports are supported. | ||
**TypeScript:** | ||
```typescript | ||
import {Mutex, MutexInterface} from 'async-mutex'; | ||
import {Mutex, MutexInterface, Semaphore, SemaphoreInterface, withTimeout} from 'async-mutex'; | ||
``` | ||
## API | ||
## Mutex API | ||
### Creating | ||
ES5/ES6/TypeScript | ||
```typescript | ||
@@ -79,3 +107,3 @@ const mutex = new Mutex(); | ||
ES5/ES6/TypeScript | ||
Promise style: | ||
```typescript | ||
@@ -89,12 +117,3 @@ mutex | ||
`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 | ||
must be called once the mutex should be released again. | ||
**IMPORTANT:** Failure to call `release` will hold the mutex locked and will | ||
lilely deadlock the application. Make sure to call `release` under all circumstances | ||
and handle exceptions accordingly. | ||
##### Async function example (ESnext/TypeScript) | ||
async/await: | ||
```typescript | ||
@@ -110,5 +129,13 @@ const release = await mutex.acquire(); | ||
`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 | ||
must be called once the mutex should be released again. | ||
**IMPORTANT:** Failure to call `release` will hold the mutex locked and will | ||
lilely deadlock the application. Make sure to call `release` under all circumstances | ||
and handle exceptions accordingly. | ||
### Synchronized code execution | ||
ES5/ES6/TypeScript | ||
Promise style: | ||
```typescript | ||
@@ -124,11 +151,6 @@ mutex | ||
##### Async function example (ESnext/TypeScript) | ||
This example is equivalent to the `async`/`await` example that | ||
locks the mutex directly: | ||
async/await: | ||
```typescript | ||
await mutex.runExclusive(async () => { | ||
const i = await store.get(); | ||
await store.put(i + 1); | ||
// ... | ||
}); | ||
@@ -138,5 +160,5 @@ ``` | ||
`runExclusive` schedules the supplied callback to be run once the mutex is unlocked. | ||
The function is expected to return a [Promises/A+](https://promisesaplus.com/) | ||
compliant promise. Once the promise is resolved (or rejected), the mutex is released. | ||
`runExclusive` returns a promise that adopts the state of the function result. | ||
The function may return a promise. Once the promise is resolved or rejected (or immediatelly after | ||
execution if an immediate value was returned), | ||
the mutex is released. `runExclusive` returns a promise that adopts the state of the function result. | ||
@@ -148,3 +170,2 @@ The mutex is released and the result rejected if an exception occurs during execution | ||
ES5/ES6/TypeScript | ||
```typescript | ||
@@ -154,4 +175,105 @@ 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: | ||
```typescript | ||
semaphore | ||
.acquire() | ||
.then(function([value, release]) { | ||
// ... | ||
}); | ||
``` | ||
async/await: | ||
```typescript | ||
const [value, release] = await semaphore.acquire(); | ||
try { | ||
// ... | ||
} finally { | ||
release(); | ||
} | ||
``` | ||
`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 | ||
first entry being the current value of the semaphore, and the second value a | ||
function that must be called to release the semaphore once the critical operation | ||
has completed. | ||
**IMPORTANT:** Failure to call `release` will hold the semaphore locked and will | ||
lilely deadlock the application. Make sure to call `release` under all circumstances | ||
and handle exceptions accordingly. | ||
### Synchronized code execution | ||
Promise style: | ||
```typescript | ||
semaphore | ||
.runExclusive(function(value) { | ||
// ... | ||
}) | ||
.then(function(result) { | ||
// ... | ||
}); | ||
``` | ||
async/await: | ||
```typescript | ||
await semaphore.runExclusive(async (value) => { | ||
// ... | ||
}); | ||
``` | ||
`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 immediatelly after | ||
execution if an immediate value was returned), | ||
the semaphore is released. `runExclusive` returns a promise that adopts the state of the function result. | ||
The semaphore is released and the result rejected if an exception occurs during execution | ||
if the callback. | ||
### Checking whether the semaphore is locked | ||
```typescript | ||
semaphore.isLocked(); | ||
``` | ||
The semaphore is considered to be locked if it has a value of zero. | ||
## Limiting the time waiting for a mutex or semaphore to become available | ||
Sometimes it is desirable to limit the time a program waits for a mutex or | ||
semaphore to become available. The `withTimeout` decorator can be applied | ||
to both semaphores and mutexes and changes the behavior of `acquire` and | ||
`runExclusive` accordingly. | ||
```typescript | ||
const mutexWithTimeout = withTimeout(new Mutex(), 100, new Error('timeout')); | ||
const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100, new Error('timeout')); | ||
``` | ||
The API of the decorated mutex or semaphore is unchanged. | ||
The second argument of `withTimeout` is | ||
the timout in milliseconds. After the timeout is exceeded, the promsie returned by | ||
`acquire` and `runExclusive` will reject. The latter will not run the provided callback in | ||
case of an timeout. | ||
The third argument of `withTimeout` is optional and can be used to | ||
customize the error with which the promise is rejected. | ||
# License | ||
Feel free to use this library under the conditions of the MIT license. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
40193
35
511
270
1
16
+ Addedtslib@^1.11.1
+ Addedtslib@1.14.1(transitive)