@async-mutex/mutex 0.0.1-security → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @async-mutex/mutex might be problematic. Click here for more details.

package/README.md CHANGED
@@ -1,5 +1,540 @@
1
- # Security holding package
2
-
3
- This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
4
-
5
- Please refer to www.npmjs.com/advisories?search=%40async-mutex%2Fmutex for more information.
1
+ [![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)
2
+ [![NPM version](https://badge.fury.io/js/mutex.svg)](https://badge.fury.io/js/mutex)
3
+ [![Coverage Status](https://coveralls.io/repos/github/async-mutex/mutex/badge.svg?branch=master)](https://coveralls.io/github/async-mutex/mutex?branch=master)
4
+
5
+ # What is it?
6
+
7
+ This package implements primitives for synchronizing asynchronous operations in
8
+ Javascript.
9
+
10
+ ## Mutex
11
+
12
+ The term "mutex" usually refers to a data structure used to synchronize
13
+ concurrent processes running on different threads. For example, before accessing
14
+ a non-threadsafe resource, a thread will lock the mutex. This is guaranteed
15
+ to block the thread until no other thread holds a lock on the mutex and thus
16
+ enforces exclusive access to the resource. Once the operation is complete, the
17
+ thread releases the lock, allowing other threads to acquire a lock and access the
18
+ resource.
19
+
20
+ While Javascript is strictly single-threaded, the asynchronous nature of its
21
+ execution model allows for race conditions that require similar synchronization
22
+ primitives. Consider for example a library communicating with a web worker that
23
+ needs to exchange several subsequent messages with the worker in order to achieve
24
+ a task. As these messages are exchanged in an asynchronous manner, it is perfectly
25
+ possible that the library is called again during this process. Depending on the
26
+ way state is handled during the async process, this will lead to race conditions
27
+ that are hard to fix and even harder to track down.
28
+
29
+ This library solves the problem by applying the concept of mutexes to Javascript.
30
+ Locking the mutex will return a promise that resolves once the mutex becomes
31
+ available. Once the async process is complete (usually taking multiple
32
+ spins of the event loop), a callback supplied to the caller should be called in order
33
+ to release the mutex, allowing the next scheduled worker to execute.
34
+
35
+ # Semaphore
36
+
37
+ Imagine a situation where you need to control access to several instances of
38
+ a shared resource. For example, you might want to distribute images between several
39
+ worker processes that perform transformations, or you might want to create a web
40
+ crawler that performs a defined number of requests in parallel.
41
+
42
+ A semaphore is a data structure that is initialized with an arbitrary integer value and that
43
+ can be locked multiple times.
44
+ As long as the semaphore value is positive, locking it will return the current value
45
+ and the locking process will continue execution immediately; the semaphore will
46
+ be decremented upon locking. Releasing the lock will increment the semaphore again.
47
+
48
+ Once the semaphore has reached zero, the next process that attempts to acquire a lock
49
+ will be suspended until another process releases its lock and this increments the semaphore
50
+ again.
51
+
52
+ This library provides a semaphore implementation for Javascript that is similar to the
53
+ mutex implementation described above.
54
+
55
+ # How to use it?
56
+
57
+ ## Installation
58
+
59
+ You can install the library into your project via npm
60
+
61
+ npm install mutex
62
+
63
+ The library is written in TypeScript and will work in any environment that
64
+ supports ES5, ES6 promises and `Array.isArray`. On ancient browsers,
65
+ a shim can be used (e.g. [core-js](https://github.com/zloirock/core-js)).
66
+ No external typings are required for using this library with
67
+ TypeScript (version >= 2).
68
+
69
+ Starting with Node 12.16 and 13.7, native ES6 style imports are supported.
70
+
71
+ **WARNING:** Node 13 versions < 13.2.0 fail to import this package correctly.
72
+ Node 12 and earlier are fine, as are newer versions of Node 13.
73
+
74
+ ## Importing
75
+
76
+ **CommonJS:**
77
+ ```javascript
78
+ var Mutex = require('mutex').Mutex;
79
+ var Semaphore = require('mutex').Semaphore;
80
+ var withTimeout = require('mutex').withTimeout;
81
+ ```
82
+
83
+ **ES6:**
84
+ ```javascript
85
+ import {Mutex, Semaphore, withTimeout} from 'mutex';
86
+ ```
87
+
88
+ **TypeScript:**
89
+ ```typescript
90
+ import {Mutex, MutexInterface, Semaphore, SemaphoreInterface, withTimeout} from 'mutex';
91
+ ```
92
+
93
+ With the latest version of Node, native ES6 style imports are supported.
94
+
95
+ ## Mutex API
96
+
97
+ ### Creating
98
+
99
+ ```typescript
100
+ const mutex = new Mutex();
101
+ ```
102
+
103
+ Create a new mutex.
104
+
105
+ ### Synchronized code execution
106
+
107
+ Promise style:
108
+ ```typescript
109
+ mutex
110
+ .runExclusive(() => {
111
+ // ...
112
+ })
113
+ .then((result) => {
114
+ // ...
115
+ });
116
+ ```
117
+
118
+ async/await:
119
+ ```typescript
120
+ await mutex.runExclusive(async () => {
121
+ // ...
122
+ });
123
+ ```
124
+
125
+ `runExclusive` schedules the supplied callback to be run once the mutex is unlocked.
126
+ The function may return a promise. Once the promise is resolved or rejected (or immediately after
127
+ execution if an immediate value was returned),
128
+ the mutex is released. `runExclusive` returns a promise that adopts the state of the function result.
129
+
130
+ The mutex is released and the result rejected if an exception occurs during execution
131
+ of the callback.
132
+
133
+ ### Manual locking / releasing
134
+
135
+ Promise style:
136
+ ```typescript
137
+ mutex
138
+ .acquire()
139
+ .then(function(release) {
140
+ // ...
141
+
142
+ release();
143
+ });
144
+ ```
145
+
146
+ async/await:
147
+ ```typescript
148
+ const release = await mutex.acquire();
149
+ try {
150
+ // ...
151
+ } finally {
152
+ release();
153
+ }
154
+ ```
155
+
156
+ `acquire` returns an (ES6) promise that will resolve as soon as the mutex is
157
+ available. The promise resolves with a function `release` that
158
+ must be called once the mutex should be released again. The `release` callback
159
+ is idempotent.
160
+
161
+ **IMPORTANT:** Failure to call `release` will hold the mutex locked and will
162
+ likely deadlock the application. Make sure to call `release` under all circumstances
163
+ and handle exceptions accordingly.
164
+
165
+ ### Unscoped release
166
+
167
+ As an alternative to calling the `release` callback returned by `acquire`, the mutex
168
+ can be released by calling `release` directly on it:
169
+
170
+ ```typescript
171
+ mutex.release();
172
+ ```
173
+
174
+ ### Checking whether the mutex is locked
175
+
176
+ ```typescript
177
+ mutex.isLocked();
178
+ ```
179
+
180
+ ### Cancelling pending locks
181
+
182
+ Pending locks can be cancelled by calling `cancel()` on the mutex. This will reject
183
+ all pending locks with `E_CANCELED`:
184
+
185
+ Promise style:
186
+ ```typescript
187
+ import {E_CANCELED} from 'mutex';
188
+
189
+ mutex
190
+ .runExclusive(() => {
191
+ // ...
192
+ })
193
+ .then(() => {
194
+ // ...
195
+ })
196
+ .catch(e => {
197
+ if (e === E_CANCELED) {
198
+ // ...
199
+ }
200
+ });
201
+ ```
202
+
203
+ async/await:
204
+ ```typescript
205
+ import {E_CANCELED} from 'mutex';
206
+
207
+ try {
208
+ await mutex.runExclusive(() => {
209
+ // ...
210
+ });
211
+ } catch (e) {
212
+ if (e === E_CANCELED) {
213
+ // ...
214
+ }
215
+ }
216
+ ```
217
+
218
+ This works with `acquire`, too:
219
+ if `acquire` is used for locking, the resulting promise will reject with `E_CANCELED`.
220
+
221
+ The error that is thrown can be customized by passing a different error to the `Mutex`
222
+ constructor:
223
+
224
+ ```typescript
225
+ const mutex = new Mutex(new Error('fancy custom error'));
226
+ ```
227
+
228
+ Note that while all pending locks are cancelled, a currently held lock will not be
229
+ revoked. In consequence, the mutex may not be available even after `cancel()` has been called.
230
+
231
+ ### Waiting until the mutex is available
232
+
233
+ You can wait until the mutex is available without locking it by calling `waitForUnlock()`.
234
+ This will return a promise that resolve once the mutex can be acquired again. This operation
235
+ will not lock the mutex, and there is no guarantee that the mutex will still be available
236
+ once an async barrier has been encountered.
237
+
238
+ Promise style:
239
+ ```typescript
240
+ mutex
241
+ .waitForUnlock()
242
+ .then(() => {
243
+ // ...
244
+ });
245
+ ```
246
+
247
+ Async/await:
248
+ ```typescript
249
+ await mutex.waitForUnlock();
250
+ // ...
251
+ ```
252
+
253
+
254
+ ## Semaphore API
255
+
256
+ ### Creating
257
+
258
+ ```typescript
259
+ const semaphore = new Semaphore(initialValue);
260
+ ```
261
+
262
+ Creates a new semaphore. `initialValue` is an arbitrary integer that defines the
263
+ initial value of the semaphore.
264
+
265
+ ### Synchronized code execution
266
+
267
+ Promise style:
268
+ ```typescript
269
+ semaphore
270
+ .runExclusive(function(value) {
271
+ // ...
272
+ })
273
+ .then(function(result) {
274
+ // ...
275
+ });
276
+ ```
277
+
278
+ async/await:
279
+ ```typescript
280
+ await semaphore.runExclusive(async (value) => {
281
+ // ...
282
+ });
283
+ ```
284
+
285
+ `runExclusive` schedules the supplied callback to be run once the semaphore is available.
286
+ The callback will receive the current value of the semaphore as its argument.
287
+ The function may return a promise. Once the promise is resolved or rejected (or immediately after
288
+ execution if an immediate value was returned),
289
+ the semaphore is released. `runExclusive` returns a promise that adopts the state of the function result.
290
+
291
+ The semaphore is released and the result rejected if an exception occurs during execution
292
+ of the callback.
293
+
294
+ `runExclusive` accepts a first optional argument `weight`. Specifying a `weight` will decrement the
295
+ semaphore by the specified value, and the callback will only be invoked once the semaphore's
296
+ value greater or equal to `weight`.
297
+
298
+ `runExclusive` accepts a second optional argument `priority`. Specifying a greater value for `priority`
299
+ tells the scheduler to run this task before other tasks. `priority` can be any real number. The default
300
+ is zero.
301
+
302
+ ### Manual locking / releasing
303
+
304
+ Promise style:
305
+ ```typescript
306
+ semaphore
307
+ .acquire()
308
+ .then(function([value, release]) {
309
+ // ...
310
+
311
+ release();
312
+ });
313
+ ```
314
+
315
+ async/await:
316
+ ```typescript
317
+ const [value, release] = await semaphore.acquire();
318
+ try {
319
+ // ...
320
+ } finally {
321
+ release();
322
+ }
323
+ ```
324
+
325
+ `acquire` returns an (ES6) promise that will resolve as soon as the semaphore is
326
+ available. The promise resolves to an array with the
327
+ first entry being the current value of the semaphore, and the second value a
328
+ function that must be called to release the semaphore once the critical operation
329
+ has completed. The `release` callback is idempotent.
330
+
331
+ **IMPORTANT:** Failure to call `release` will hold the semaphore locked and will
332
+ likely deadlock the application. Make sure to call `release` under all circumstances
333
+ and handle exceptions accordingly.
334
+
335
+ `acquire` accepts a first optional argument `weight`. Specifying a `weight` will decrement the
336
+ semaphore by the specified value, and the semaphore will only be acquired once its
337
+ value is greater or equal to `weight`.
338
+
339
+ `acquire` accepts a second optional argument `priority`. Specifying a greater value for `priority`
340
+ tells the scheduler to release the semaphore to the caller before other callers. `priority` can be
341
+ any real number. The default is zero.
342
+
343
+ ### Unscoped release
344
+
345
+ As an alternative to calling the `release` callback returned by `acquire`, the semaphore
346
+ can be released by calling `release` directly on it:
347
+
348
+ ```typescript
349
+ semaphore.release();
350
+ ```
351
+
352
+ `release` accepts an optional argument `weight` and increments the semaphore accordingly.
353
+
354
+ **IMPORTANT:** Releasing a previously acquired semaphore with the releaser that was
355
+ returned by acquire will automatically increment the semaphore by the correct weight. If
356
+ you release by calling the unscoped `release` you have to supply the correct weight
357
+ yourself!
358
+
359
+ ### Getting the semaphore value
360
+
361
+ ```typescript
362
+ semaphore.getValue()
363
+ ```
364
+
365
+ ### Checking whether the semaphore is locked
366
+
367
+ ```typescript
368
+ semaphore.isLocked();
369
+ ```
370
+
371
+ The semaphore is considered to be locked if its value is either zero or negative.
372
+
373
+ ### Setting the semaphore value
374
+
375
+ The value of a semaphore can be set directly to a desired value. A positive value will
376
+ cause the semaphore to schedule any pending waiters accordingly.
377
+
378
+ ```typescript
379
+ semaphore.setValue();
380
+ ```
381
+
382
+ ### Cancelling pending locks
383
+
384
+ Pending locks can be cancelled by calling `cancel()` on the semaphore. This will reject
385
+ all pending locks with `E_CANCELED`:
386
+
387
+ Promise style:
388
+ ```typescript
389
+ import {E_CANCELED} from 'mutex';
390
+
391
+ semaphore
392
+ .runExclusive(() => {
393
+ // ...
394
+ })
395
+ .then(() => {
396
+ // ...
397
+ })
398
+ .catch(e => {
399
+ if (e === E_CANCELED) {
400
+ // ...
401
+ }
402
+ });
403
+ ```
404
+
405
+ async/await:
406
+ ```typescript
407
+ import {E_CANCELED} from 'mutex';
408
+
409
+ try {
410
+ await semaphore.runExclusive(() => {
411
+ // ...
412
+ });
413
+ } catch (e) {
414
+ if (e === E_CANCELED) {
415
+ // ...
416
+ }
417
+ }
418
+ ```
419
+
420
+ This works with `acquire`, too:
421
+ if `acquire` is used for locking, the resulting promise will reject with `E_CANCELED`.
422
+
423
+ The error that is thrown can be customized by passing a different error to the `Semaphore`
424
+ constructor:
425
+
426
+ ```typescript
427
+ const semaphore = new Semaphore(2, new Error('fancy custom error'));
428
+ ```
429
+
430
+ Note that while all pending locks are cancelled, any currently held locks will not be
431
+ revoked. In consequence, the semaphore may not be available even after `cancel()` has been called.
432
+
433
+ ### Waiting until the semaphore is available
434
+
435
+ You can wait until the semaphore is available without locking it by calling `waitForUnlock()`.
436
+ This will return a promise that resolve once the semaphore can be acquired again. This operation
437
+ will not lock the semaphore, and there is no guarantee that the semaphore will still be available
438
+ once an async barrier has been encountered.
439
+
440
+ Promise style:
441
+ ```typescript
442
+ semaphore
443
+ .waitForUnlock()
444
+ .then(() => {
445
+ // ...
446
+ });
447
+ ```
448
+
449
+ Async/await:
450
+ ```typescript
451
+ await semaphore.waitForUnlock();
452
+ // ...
453
+ ```
454
+
455
+ `waitForUnlock` accepts optional arguments `weight` and `priority`. The promise will resolve as soon
456
+ as it is possible to `acquire` the semaphore with the given weight and priority. Scheduled tasks with
457
+ the greatest `priority` values execute first.
458
+
459
+
460
+ ## Limiting the time waiting for a mutex or semaphore to become available
461
+
462
+ Sometimes it is desirable to limit the time a program waits for a mutex or
463
+ semaphore to become available. The `withTimeout` decorator can be applied
464
+ to both semaphores and mutexes and changes the behavior of `acquire` and
465
+ `runExclusive` accordingly.
466
+
467
+ ```typescript
468
+ import {withTimeout, E_TIMEOUT} from 'mutex';
469
+
470
+ const mutexWithTimeout = withTimeout(new Mutex(), 100);
471
+ const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100);
472
+ ```
473
+
474
+ The API of the decorated mutex or semaphore is unchanged.
475
+
476
+ The second argument of `withTimeout` is the timeout in milliseconds. After the
477
+ timeout is exceeded, the promise returned by `acquire` and `runExclusive` will
478
+ reject with `E_TIMEOUT`. The latter will not run the provided callback in case
479
+ of an timeout.
480
+
481
+ The third argument of `withTimeout` is optional and can be used to
482
+ customize the error with which the promise is rejected.
483
+
484
+ ```typescript
485
+ const mutexWithTimeout = withTimeout(new Mutex(), 100, new Error('new fancy error'));
486
+ const semaphoreWithTimeout = withTimeout(new Semaphore(5), 100, new Error('new fancy error'));
487
+ ```
488
+
489
+ ### Failing early if the mutex or semaphore is not available
490
+
491
+ A shortcut exists for the case where you do not want to wait for a lock to
492
+ be available at all. The `tryAcquire` decorator can be applied to both mutexes
493
+ and semaphores and changes the behavior of `acquire` and `runExclusive` to
494
+ immediately throw `E_ALREADY_LOCKED` if the mutex is not available.
495
+
496
+ Promise style:
497
+ ```typescript
498
+ import {tryAcquire, E_ALREADY_LOCKED} from '@async-mutex/mutex';
499
+
500
+ tryAcquire(semaphoreOrMutex)
501
+ .runExclusive(() => {
502
+ // ...
503
+ })
504
+ .then(() => {
505
+ // ...
506
+ })
507
+ .catch(e => {
508
+ if (e === E_ALREADY_LOCKED) {
509
+ // ...
510
+ }
511
+ });
512
+ ```
513
+
514
+ async/await:
515
+ ```typescript
516
+ import {tryAcquire, E_ALREADY_LOCKED} from '@async-mutex/mutex';
517
+
518
+ try {
519
+ await tryAcquire(semaphoreOrMutex).runExclusive(() => {
520
+ // ...
521
+ });
522
+ } catch (e) {
523
+ if (e === E_ALREADY_LOCKED) {
524
+ // ...
525
+ }
526
+ }
527
+ ```
528
+
529
+ Again, the error can be customized by providing a custom error as second argument to
530
+ `tryAcquire`.
531
+
532
+ ```typescript
533
+ tryAcquire(semaphoreOrMutex, new Error('new fancy error'))
534
+ .runExclusive(() => {
535
+ // ...
536
+ });
537
+ ```
538
+ # License
539
+
540
+ Feel free to use this library under the conditions of the MIT license.