better-lock 1.0.2 → 2.0.2-beta

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.
Files changed (55) hide show
  1. package/.eslintrc.js +24 -0
  2. package/.idea/better-lock.iml +1 -7
  3. package/.idea/codeStyles/Project.xml +336 -5
  4. package/.idea/compiler.xml +7 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -2
  6. package/.idea/jsLibraryMappings.xml +0 -1
  7. package/.idea/markdown.xml +9 -0
  8. package/.idea/modules.xml +1 -1
  9. package/.idea/prettier.xml +8 -0
  10. package/.prettierrc.yaml +1 -3
  11. package/.travis.yml +2 -2
  12. package/README.md +113 -73
  13. package/dist/better_lock.d.ts +50 -0
  14. package/dist/better_lock.js +317 -0
  15. package/dist/better_lock.js.map +1 -0
  16. package/dist/errors.d.ts +44 -0
  17. package/dist/errors.js +131 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/index.d.ts +16 -0
  20. package/dist/index.js +28 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/internals.d.ts +75 -0
  23. package/dist/internals.js +75 -0
  24. package/dist/internals.js.map +1 -0
  25. package/dist/options.d.ts +54 -0
  26. package/dist/options.js +3 -0
  27. package/dist/options.js.map +1 -0
  28. package/dist/tools.d.ts +19 -0
  29. package/dist/tools.js +109 -0
  30. package/dist/tools.js.map +1 -0
  31. package/dist/types.d.ts +23 -0
  32. package/dist/types.js +3 -0
  33. package/dist/types.js.map +1 -0
  34. package/jest.config.js +4 -0
  35. package/package.json +32 -9
  36. package/spec/better_lock.spec.ts +614 -0
  37. package/src/better_lock.ts +430 -0
  38. package/src/errors.ts +124 -0
  39. package/src/index.ts +54 -0
  40. package/src/internals.ts +118 -0
  41. package/src/options.ts +63 -0
  42. package/src/tools.ts +111 -0
  43. package/src/types.ts +29 -0
  44. package/tsconfig.json +13 -0
  45. package/.idea/markdown-navigator/profiles_settings.xml +0 -3
  46. package/.idea/markdown-navigator.xml +0 -78
  47. package/.idea/misc.xml +0 -6
  48. package/.idea/watcherTasks.xml +0 -8
  49. package/index.js +0 -19
  50. package/spec/better_lock.spec.js +0 -602
  51. package/src/better_lock.js +0 -405
  52. package/src/errors.js +0 -81
  53. package/src/options.js +0 -104
  54. package/src/tools.js +0 -125
  55. package/src/types.js +0 -110
package/README.md CHANGED
@@ -6,6 +6,7 @@ A (better) node.js lock library.
6
6
 
7
7
  ### Features
8
8
 
9
+ - Typescript-ready
9
10
  - Named and keyed locks
10
11
  - Queue and execution timeouts
11
12
  - Queue size limit
@@ -21,6 +22,8 @@ A (better) node.js lock library.
21
22
 
22
23
  ```bash
23
24
  npm install --save better-lock
25
+ # or
26
+ yarn add better-lock
24
27
  ```
25
28
 
26
29
  ### Usage examples
@@ -29,28 +32,25 @@ npm install --save better-lock
29
32
 
30
33
  ```javascript
31
34
  const lock = new BetterLock();
32
- //...
33
- lock
34
- .acquire(() => {
35
- // Inside the lock. Just make sure you return a promise
36
- return doSomethingThatReturnsPromise()
37
- .then(() => {
38
- return 'my result';
39
- });
40
- })
41
- .then(
42
- res => {
43
- // Outside the lock. You will get whatever the promise chain has returned.
44
- console.log(res); // my result
45
- },
46
- err => {
47
- // Either your or BetterLock's error
35
+
36
+ try {
37
+ const res = await lock.acquire(async () => {
38
+ // Inside the lock. It will stay closed until the promise you return resolves or rejects.
39
+ await doSomeAsyncTask();
40
+ return 'my result';
48
41
  });
42
+
43
+ // Outside the lock. You will get whatever the promise chain has returned.
44
+ console.log(res); // "my result"
45
+ }
46
+ catch (err) {
47
+ // Either your or BetterLock's error
48
+ }
49
49
  ```
50
50
 
51
51
  ##### Advanced example
52
52
 
53
- ```javascript
53
+ ```typescript
54
54
  const lock = new BetterLock({
55
55
  name: 'FileLock', // To be used in error reporting and logging
56
56
  log: winstonLogger.debug, // Give it your logger with appropeiate level
@@ -59,28 +59,30 @@ const lock = new BetterLock({
59
59
  queue_size: 1, // At most one pending job
60
60
  });
61
61
 
62
- function processFile(filename, callback) {
63
- lock.acquire(filename, () => {
64
- return appendToFile(filename)
65
- .then(result => {
66
- return updateDb(result);
67
- });
68
- }).then(result => {
69
- callback(null, {
70
- status: true,
71
- result,
62
+ async function processFile(filename) {
63
+ try {
64
+ const result = await lock.acquire(filename, async () => {
65
+ const appended = await appendToFile(filename);
66
+ return updateDb(appended);
72
67
  });
73
- }).catch(err => {
68
+ return {
69
+ status: true,
70
+ result
71
+ };
72
+ }
73
+ catch (err) {
74
74
  if (err instanceof BetterLock.QueueOverflowError) {
75
- return callback(null, {
76
- status: false, // The job was discarded
77
- });
75
+ // The job was discarded
76
+ return {
77
+ status: false
78
+ };
78
79
  }
80
+
79
81
  if (err instanceof BetterLock.ExecutionTimeoutError) {
80
82
  winstonLogger.warn('Potential swallowed callback! Stack trace to the entry site:', err.stack);
81
83
  }
82
- return callback(err);
83
- });
84
+ throw err;
85
+ }
84
86
  }
85
87
 
86
88
  ```
@@ -129,7 +131,7 @@ lock.acquire(done => {
129
131
  // Outside the lock
130
132
  if (err) {
131
133
  // Either your or BetterLock's error
132
- console.error(err);
134
+ console.error(err);
133
135
  }
134
136
  });
135
137
  ```
@@ -139,16 +141,17 @@ You can see a bunch more usage examples in the spec file, [here](spec/better_loc
139
141
  ### API
140
142
 
141
143
  - `new BetterLock(options)`
142
- Create a new instance of `BetterLock`. Options should match interface of `BetterLockOptions`. See below for details.
144
+ Create a new instance of `BetterLock`. Options should match interface `BetterLockOptions`. See below for details.
143
145
 
144
146
  - `BetterLock.acquire([key], executor, [callback], [jobOptions])`
145
- The main method you'll want to call. For each `key`, given `executor` will be called only one at a time. Returns a promise that will be resolved with whatever `executor` returns.
147
+ The main method you'll want to call. For each `key`, given `executor` will be called only one at a time. If you don't provide `callback`, it will return a promise that will be resolved with whatever `executor` returns.
148
+
146
149
  - `key`: Arbitrary string under which to lock. It allows you to use the same lock instance for multiple parallel concerns. Eg. this might be a database record id or filename.
147
150
  - `executor`: Function that will be called within the lock. This function should have one of two forms.
148
- 1. *Without arguments*, in which case it should return a promise. Lock will remain locked until the promise resolves.
149
- 2. *With single `done`* argument. In this case, the executor should call `done(err, res)` once it is done. Arguments passed to done will be passed to the callback of the lock.
151
+ 1. _Without arguments_, in which case it should return a promise. Lock will remain locked until the promise resolves.
152
+ 2. _With single `done`_ argument. In this case, the executor should call `done(err, res)` once it is done. Arguments passed to done will be passed to the callback of the lock.
150
153
  - `callback`: Optional callback that will be called once executor exits. Results from executor (resolved/rejected value or arguments given to `done`) will be passed along. This can be used in addition to the returned promise.
151
- - `jobOptions`: An object that should match interface of `LockJobOptions`. A subset of main options that will serve as overrides for this particular job (for example, timeout settings).
154
+ - `jobOptions`: An object that should match interface `BetterLockJobOptions`. A subset of main options that will serve as overrides for this particular job (for example, timeout settings).
152
155
 
153
156
  - `BetterLock.canAcquire([key])`
154
157
  Returns true if given key can be acquired.
@@ -159,10 +162,9 @@ You can see a bunch more usage examples in the spec file, [here](spec/better_loc
159
162
  - `BetterLock.abortAll()`
160
163
  Abort all jobs for all keys. This is suitable to be called during shutdown of your app.
161
164
 
162
-
163
165
  ### Options
164
166
 
165
- All available options with defaults can be seen [here](src/options.js).
167
+ All available options can be seen [here](src/options.ts).
166
168
 
167
169
  `BetterLockOptions` are provided when you construct a lock instance. A subset of options given in `LockJobOptions` can be provided when you call `lock.acquire`, as the last argument.
168
170
 
@@ -186,58 +188,96 @@ Most commonly used options are:
186
188
  - `queue_size`
187
189
  Max queue size for waiting jobs.
188
190
 
189
- Options are presented using `snake_case`, but you can also provide them using `camelCase` keys, if that better suits your code style (eg. `extend_stack_traces` becomes `extendStackTraces`).
190
-
191
- During runtime, you can change the defaults like this:
191
+ Default options are a static member `DEFAULT_OPTIONS` on the `BetterLock` class. That can be seen [here](src/better_lock.ts). During runtime, you can change the defaults like this:
192
192
 
193
193
  ```javascript
194
- const BetterLock = require('better-lock');
194
+ import BetterLock from 'better-lock';
195
195
 
196
196
  BetterLock.DEFAULT_OPTIONS.wait_timeout = 1000;
197
- ```
197
+ ```
198
198
 
199
- ### Motivation
199
+ ### When to use
200
200
 
201
- I needed something like [async-lock](https://github.com/rogierschouten/async-lock), but a bit easier to debug.
201
+ This library is a good fit if:
202
202
 
203
- This library improves upon `async-lock` in following ways:
203
+ - You need a local lock in your node.js application.
204
+ - You have some advanced needs, like multiple keys per lock, timeouts, aborting...
205
+ - You like good error messages, with full stack traces.
206
+ - You like good types, either through typescript or JSDoc comments
204
207
 
205
- - Good error messages and log facility
206
- - Added features (execution timeout, abort jobs...)
207
- - Extended stack traces (so when you get an error, you have a full stack trace of the original calling code)
208
- - JSDoc comments (helps if you're using an IDE)
208
+ The library is not a good fit if:
209
209
 
210
- Following features are present in both libraries:
210
+ - **You need lock reentrancy.**
211
+ There is no good solution for this in node.js that I know of. And this library doesn't offer any.
211
212
 
212
- - Multiple keys per lock instance
213
- - Acquire multiple keys in a single call
214
- - Interface, with executor and the callback function
215
- - Promises
216
- - Timeout and queue limit
213
+ - **You need a shared lock between different nodes**
214
+ This library is a single process only. If you need to coordinate multiple apps or services, you need a different library.
217
215
 
218
- Following features are present in `async-lock`, but not here:
216
+ ### Change log
219
217
 
220
- - Domain reentrancy (domains are going away)
218
+ #### **2.0.0** (_2021/05/30_)
221
219
 
222
- **NOTE:** If you want to sync multiple node instances doing the same operation, this library will not help you. You need something that works over network and can use a shared arbiter of who gets the lock (eg. redis).
220
+ Major update. The entire library was rewritten in typescript, so you should now get typings in most editors. We also had to switch tests from mocha + chai to jest (easier ts integration).
223
221
 
224
- ### Change log
222
+ The API and features have remained largely the same, just a bit of a refresh.
223
+
224
+ Non-breaking and internal changes:
225
+
226
+ - We now export all error names as a type script type. A few other types as well.
227
+ - Errors now have `name` parameter, which matches these names.
228
+
229
+ Breaking changes:
225
230
 
226
- Date|Version|Change
227
- ----|-------|------
228
- 2018/06/04|0.1.1|You can now use a Number as job name
229
- 2018/09/27|0.2.0|Code reformat, better pattern for loading options. No feature upgrades.
230
- 2018/09/27|0.2.1|Better and customizable Promise detection. Restored DEFAULT_OPTIONS.
231
- 2018/10/01|0.3.0|Can abort jobs waiting in queue.
232
- 2018/10/01|0.3.1|Updated CI to use the current node versions (0.8 & 0.10). Older node versions should continue to work, but are no longer tested. Also, README updates.
233
- 2019/01/28|1.0.0|Major update. Added multi-key locks and refactored a bunch of internals. Removed `OVERFLOW_STRATEGIES` and related options, which is mostly the reason for the major version bump. The library should otherwise work the same.
234
- 2019/01/28|1.0.1|Handle empty key list
231
+ - You can no longer use camel case versions of external-facing objects. Eg. you can no longer pass `waitTimeout` instead of `wait_timeout`. In retrospect, this was a pretty flaky API to maintain.
232
+
233
+ - Internal `LockJob` class is no longer exported.
234
+
235
+ - Also, errors no longer expose internal `LockJob` instances (`err.job`). We now instead provide the most important fields from the job (`id` and `keys`).
236
+
237
+ - Error names have been renamed to have `BetterLock` prefix. Eg. `WaitTimeoutError` -> `BetterLockWaitTimeoutError`. This will influence `err.name` and `err.message` parameters. The idea here is, if you see `BetterLock` error in the wild, you will know what generated it.
238
+
239
+ - We have renamed `BetterLock.BetterLockError` to `BetterLock.BaseError` and `BetterLock.BetterLockInternalError` to `BetterLock.InternalError` to better match the naming scheme.
240
+
241
+ - Since Options are no longer a class but interface, we are no longer exporting them under `BetterLock.Options`. You can do `import {BetterLockOptions} from 'better-lock';` to get the typescript type.
242
+
243
+ #### **1.0.1** (_2019/01/28_)
244
+
245
+ Handle empty key list
246
+
247
+ #### **1.0.0** (_2019/01/28_)
248
+
249
+ Major version bump.
250
+
251
+ - Added multi-key locks and refactored a bunch of internals.
252
+ - Removed `OVERFLOW_STRATEGIES` and related options, which is mostly the reason for the major version bump. The library should otherwise work the same.
253
+
254
+ #### **0.3.1** (_2018/10/01_)
255
+
256
+ Updated CI to use the current node versions (0.8 & 0.10). Older node versions should continue to work, but are no longer tested. Also, README updates.
257
+
258
+ #### **0.3.0** (_2018/10/01_)
259
+
260
+ Can abort jobs waiting in queue.
261
+
262
+ #### **0.2.1** (_2018/09/27_)
263
+
264
+ - Better and customizable Promise detection.
265
+ - Restored DEFAULT_OPTIONS.
266
+
267
+ #### **0.2.0** (_2018/09/27_)
268
+
269
+ Code reformat, better pattern for loading options. No feature upgrades.
270
+
271
+ #### **0.1.1** (_2018/06/04_)
272
+
273
+ You can now use a Number as job name
235
274
 
236
275
  ### Development
237
276
 
238
277
  Fork, then git clone. The project is already set up with a WebStorm project, if that's your cup of tee.
239
278
 
240
279
  To run tests, with coverage:
280
+
241
281
  ```
242
282
  npm run test
243
283
  ```
@@ -246,4 +286,4 @@ If you want to contribute, create a branch off master, do your work and then mak
246
286
 
247
287
  ## License
248
288
 
249
- [MIT](./LICENSE)
289
+ [MIT](./LICENSE)
@@ -0,0 +1,50 @@
1
+ import { ILockJobOptions, IOptions } from './options';
2
+ import { ICallback, ICallbackExecutor, ILockKey, IPromiseExecutor } from './types';
3
+ /**
4
+ * Create a better lock object.
5
+ */
6
+ export declare class BetterLock {
7
+ /**
8
+ * Default options to be used when creating BetterLock instances.
9
+ */
10
+ static DEFAULT_OPTIONS: IOptions;
11
+ private options;
12
+ private log;
13
+ private queues;
14
+ constructor(options?: IOptions);
15
+ /**
16
+ * Return or create a key queue for given key
17
+ */
18
+ private getQueue;
19
+ /**
20
+ * Returns true if the caller can *immediately* acquire the lock. There is nothing holding the key and nothing in the queue.
21
+ */
22
+ canAcquire(key?: string): boolean;
23
+ private normalizeAndValidateKey;
24
+ acquire<TResult>(executor: IPromiseExecutor<TResult>, jobOptions?: ILockJobOptions): Promise<TResult>;
25
+ acquire<TResult>(executor: ICallbackExecutor<TResult>, jobOptions?: ILockJobOptions): Promise<TResult>;
26
+ acquire<TResult>(executor: ICallbackExecutor<TResult>, callback: ICallback<TResult>, jobOptions?: ILockJobOptions): void;
27
+ acquire<TResult>(key: ILockKey | Array<ILockKey>, executor: IPromiseExecutor<TResult>, jobOptions?: ILockJobOptions): Promise<TResult>;
28
+ acquire<TResult>(key: ILockKey | Array<ILockKey>, executor: ICallbackExecutor<TResult>, jobOptions?: ILockJobOptions): Promise<TResult>;
29
+ acquire<TResult>(key: ILockKey | Array<ILockKey>, executor: ICallbackExecutor<TResult>, callback: ICallback<TResult>, jobOptions?: ILockJobOptions): void;
30
+ /**
31
+ * Update state of given list of queues.
32
+ */
33
+ private update;
34
+ private executeJob;
35
+ private onWaitTimeout;
36
+ private onExecutionTimeout;
37
+ private endJob;
38
+ /**
39
+ * Abort all jobs for a given key (or from the default job queue, if no key is given).
40
+ * Job executors will not be called. Callbacks will be called with JobAbortedError.
41
+ * Currently executing job will not be interrupted.
42
+ * @param [key]
43
+ */
44
+ abort(key?: ILockKey): void;
45
+ /**
46
+ * Abort all pending jobs from all queues.
47
+ * Currently executing jobs will not be interrupted.
48
+ */
49
+ abortAll(): void;
50
+ }
@@ -0,0 +1,317 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BetterLock = void 0;
4
+ var errors_1 = require("./errors");
5
+ var internals_1 = require("./internals");
6
+ var tools = require("./tools");
7
+ /**
8
+ * Create a better lock object.
9
+ */
10
+ var BetterLock = /** @class */ (function () {
11
+ function BetterLock(options) {
12
+ this.options = tools.assign({}, [BetterLock.DEFAULT_OPTIONS, options]);
13
+ this.log = tools.makeLog(this.options.name, this.options.log);
14
+ this.queues = {};
15
+ }
16
+ /**
17
+ * Return or create a key queue for given key
18
+ */
19
+ BetterLock.prototype.getQueue = function (key) {
20
+ if (key === undefined || key === null) {
21
+ key = internals_1.KeyQueue.DEFAULT_QUEUE_KEY;
22
+ }
23
+ var queue = this.queues[key];
24
+ if (!queue) {
25
+ queue = this.queues[key] = new internals_1.KeyQueue(String(key));
26
+ }
27
+ return queue;
28
+ };
29
+ /**
30
+ * Returns true if the caller can *immediately* acquire the lock. There is nothing holding the key and nothing in the queue.
31
+ */
32
+ BetterLock.prototype.canAcquire = function (key) {
33
+ var queue = this.getQueue(key);
34
+ return !queue.active && !queue.jobs.length;
35
+ };
36
+ BetterLock.prototype.normalizeAndValidateKey = function (key) {
37
+ if (key === undefined) {
38
+ return null;
39
+ }
40
+ if (key === null) {
41
+ return null;
42
+ }
43
+ if (tools.isString(key)) {
44
+ return key;
45
+ }
46
+ if (tools.isNumber(key)) {
47
+ return String(key);
48
+ }
49
+ // Invalid key value
50
+ throw new errors_1.BetterLockInvalidArgumentError(this.options.name, 'key', 'an instance of ILockKey: string, number, undefined or null', key);
51
+ };
52
+ /**
53
+ * Acquire the lock for given key or list of keys. If waiting on a list, keys will be acquired in order,
54
+ * as they become free. Any deadlock prevention must be handled by the caller.
55
+ *
56
+ * Once the lock is acquired, we will call the executor function, with a "done" method.
57
+ * You must call "done" to release the lock. Alternatively, return a Promise; lock will be released
58
+ * once promise resolves or rejects.
59
+ *
60
+ * Return values from your executor will be passed to callback (or the resulting promise).
61
+ *
62
+ * @param key Named key or array of keys for this particular call. Calls with different keys will be run in parallel. Not required.
63
+ * @param executor Function that will run inside the lock. Required.
64
+ * @param callback Function to be called after the executor finishes or if we never enter the lock (timeout, queue depletion). Leave out to use promises.
65
+ * @param jobOptions Options to be applied on this job only
66
+ */
67
+ BetterLock.prototype.acquire = function (key, executor, callback, jobOptions) {
68
+ var _this = this;
69
+ if (tools.isFunction(key) || tools.isObject(key)) {
70
+ // Presume we weren't given a key (first form)
71
+ jobOptions = callback;
72
+ callback = executor;
73
+ executor = key;
74
+ key = null;
75
+ }
76
+ // Repackage "key" into an array of keys
77
+ var keys = [];
78
+ if (Array.isArray(key)) {
79
+ var arrayOfKeys = key;
80
+ // Make sure we don't add duplicates
81
+ var seenKeys = new Set();
82
+ for (var i = 0; i < arrayOfKeys.length; i++) {
83
+ var normalizedKey = this.normalizeAndValidateKey(arrayOfKeys[i]);
84
+ if (!seenKeys.has(normalizedKey)) {
85
+ keys.push(normalizedKey);
86
+ seenKeys.add(normalizedKey);
87
+ }
88
+ }
89
+ }
90
+ else {
91
+ keys.push(this.normalizeAndValidateKey(key));
92
+ }
93
+ // Create callback wrapper for promise interface
94
+ if (!tools.isFunction(callback)) {
95
+ if (jobOptions === undefined) {
96
+ jobOptions = callback;
97
+ }
98
+ callback = tools.callbackWithPromise();
99
+ }
100
+ // Validate other options
101
+ if (!tools.isFunction(executor)) {
102
+ throw new errors_1.BetterLockInvalidArgumentError(this.options.name, 'executor', 'a function', executor);
103
+ }
104
+ if (!tools.isFunction(callback)) {
105
+ throw new errors_1.BetterLockInvalidArgumentError(this.options.name, 'callback', 'a function', callback);
106
+ }
107
+ // Prepare job options
108
+ var effectiveJobOptions = jobOptions
109
+ ? tools.assign({}, [this.options, jobOptions])
110
+ : // No need to create new object since these will be read-only and are subset of global options
111
+ this.options;
112
+ var job = new internals_1.LockJob(keys, executor, callback, effectiveJobOptions);
113
+ // Set incoming stack
114
+ if (effectiveJobOptions.extend_stack_traces) {
115
+ var tempErr = new Error();
116
+ Error.captureStackTrace(tempErr, this.acquire);
117
+ job.incoming_stack = tempErr.stack;
118
+ }
119
+ // Add job to its key queues
120
+ var queuesToUpdate = [];
121
+ for (var i = 0; i < keys.length; i++) {
122
+ var queue = this.getQueue(keys[i]);
123
+ queue.jobs.push(job);
124
+ queuesToUpdate.push(queue);
125
+ }
126
+ // Start wait timer, if enabled
127
+ if (tools.isNumber(job.options.wait_timeout)) {
128
+ job.wait_timeout_id = setTimeout(function () { return _this.onWaitTimeout(job); }, job.options.wait_timeout);
129
+ // NOTE: timeout of 0 will NOT trigger error if lock can be acquired immediately.
130
+ }
131
+ this.log("Enqueued " + job);
132
+ // Perform update of queues this job has modified. We don't do this in next tick or something,
133
+ // because we want canAcquire to immediately return false
134
+ if (queuesToUpdate.length) {
135
+ this.update(queuesToUpdate);
136
+ }
137
+ else {
138
+ // Special case. We are given empty list of keys. We will immediately execute jobs like this.
139
+ this.executeJob(job);
140
+ }
141
+ return callback.promise;
142
+ };
143
+ /**
144
+ * Update state of given list of queues.
145
+ */
146
+ BetterLock.prototype.update = function (queues) {
147
+ for (var i = 0; i < queues.length; i++) {
148
+ var queue = queues[i];
149
+ // If key is available, have next job grab it
150
+ if (!queue.active && queue.jobs.length) {
151
+ var job = queue.jobs.splice(0, 1)[0];
152
+ queue.active = job;
153
+ job.waiting_count--;
154
+ if (job.waiting_count <= 0) {
155
+ // This job can be executed now.
156
+ this.executeJob(job);
157
+ }
158
+ }
159
+ // Handle wait queue overflow
160
+ if (tools.isNumber(this.options.queue_size) && this.options.queue_size < queue.jobs.length) {
161
+ // Overflow. Reject the most recent job
162
+ var mostRecentJob = queue.jobs[queue.jobs.length - 1];
163
+ this.log(queue.toString() + " has overflown, so most recent job (" + mostRecentJob + ") was kicked out");
164
+ this.endJob(mostRecentJob, [
165
+ new errors_1.BetterLockQueueOverflowError(this.options.name, queue.key, queue.jobs.length, mostRecentJob),
166
+ ]);
167
+ }
168
+ }
169
+ };
170
+ BetterLock.prototype.executeJob = function (job) {
171
+ var _this = this;
172
+ this.log("Executing " + job);
173
+ // Clear wait timeout
174
+ clearTimeout(job.wait_timeout_id);
175
+ job.wait_timeout_id = null;
176
+ // We want to do the rest of this in a separate context, because we don't want user executor code
177
+ // to ever interfere with the code calling acquire().
178
+ setImmediate(function () {
179
+ // Make sure job hasn't been ended in the meantime
180
+ if (job.executed_at || job.ended_at) {
181
+ return;
182
+ }
183
+ // Validate this job is holding all queues it needs
184
+ for (var i = 0; i < job.keys.length; i++) {
185
+ var queue = _this.getQueue(job.keys[i]);
186
+ if (queue.active !== job) {
187
+ // This should never happen
188
+ return _this.endJob(job, [
189
+ new errors_1.BetterLockInternalError(_this.options.name, "Corrupted wait queue state for " + queue.key),
190
+ ]);
191
+ }
192
+ }
193
+ // Mark job as having started executing
194
+ job.executed_at = new Date();
195
+ if (tools.isNumber(job.options.execution_timeout)) {
196
+ job.execution_timeout_id = setTimeout(function () { return _this.onExecutionTimeout(job); }, job.options.execution_timeout);
197
+ }
198
+ var lockDone = function () {
199
+ var args = [];
200
+ for (var _i = 0; _i < arguments.length; _i++) {
201
+ args[_i] = arguments[_i];
202
+ }
203
+ _this.log("Done called for " + job);
204
+ _this.endJob(job, args);
205
+ };
206
+ if (job.executor.length > 0) {
207
+ // Callback interface
208
+ job.executor(lockDone);
209
+ return;
210
+ }
211
+ // Promise interface
212
+ var executorResult;
213
+ try {
214
+ executorResult = job.executor();
215
+ }
216
+ catch (err) {
217
+ // Error thrown directly from executor
218
+ lockDone(err);
219
+ }
220
+ // Promise sniffing
221
+ if (_this.options.promise_tester(executorResult)) {
222
+ executorResult.then(function (res) { return lockDone(null, res); }, function (err) { return lockDone(err); });
223
+ }
224
+ else {
225
+ // "promise" is just some random value. We don't have to wait
226
+ lockDone(null, executorResult);
227
+ }
228
+ });
229
+ };
230
+ BetterLock.prototype.onWaitTimeout = function (job) {
231
+ this.log(job + " has timed out after waiting in queue for " + (new Date().valueOf() - job.enqueued_at.valueOf()) + "ms");
232
+ this.endJob(job, [new errors_1.BetterLockWaitTimeoutError(this.options.name, job)]);
233
+ };
234
+ BetterLock.prototype.onExecutionTimeout = function (job) {
235
+ this.log(job + " has timed out after executing for " + (new Date().valueOf() - job.executed_at.valueOf()) + "ms");
236
+ this.endJob(job, [new errors_1.BetterLockExecutionTimeoutError(this.options.name, job)]);
237
+ };
238
+ BetterLock.prototype.endJob = function (job, callbackArgs) {
239
+ var _this = this;
240
+ if (job.ended_at) {
241
+ this.log("WARNING: " + job + " is trying to end, but it has already ended at " + job.ended_at.toISOString() + ". Called with arguments: " + callbackArgs);
242
+ return;
243
+ }
244
+ clearTimeout(job.wait_timeout_id);
245
+ job.wait_timeout_id = null;
246
+ clearTimeout(job.execution_timeout_id);
247
+ job.execution_timeout_id = null;
248
+ job.ended_at = new Date();
249
+ var queuesToUpdate = [];
250
+ for (var i = 0; i < job.keys.length; i++) {
251
+ var queue = this.getQueue(job.keys[i]);
252
+ queuesToUpdate.push(queue);
253
+ if (queue.active === job) {
254
+ // This job was holding on the queue's key
255
+ queue.active = null;
256
+ }
257
+ else {
258
+ // This job was still in wait list for this queue
259
+ var index = queue.jobs.indexOf(job);
260
+ if (index >= 0) {
261
+ // Remove from queue
262
+ queue.jobs.splice(index, 1);
263
+ }
264
+ else {
265
+ // Something is wrong. Where did this job come from?
266
+ this.log("WARNING: " + job + " is ending, but it is not found anywhere in the queue for " + queue.key);
267
+ }
268
+ }
269
+ }
270
+ // Call the callback
271
+ try {
272
+ job.callback.apply(null, callbackArgs);
273
+ }
274
+ finally {
275
+ // Whatever happens, schedule a queue update
276
+ setImmediate(function () { return _this.update(queuesToUpdate); });
277
+ }
278
+ };
279
+ /**
280
+ * Abort all jobs for a given key (or from the default job queue, if no key is given).
281
+ * Job executors will not be called. Callbacks will be called with JobAbortedError.
282
+ * Currently executing job will not be interrupted.
283
+ * @param [key]
284
+ */
285
+ BetterLock.prototype.abort = function (key) {
286
+ var _this = this;
287
+ var queue = this.getQueue(key);
288
+ queue.jobs.slice().forEach(function (job) {
289
+ _this.endJob(job, [new errors_1.BetterLockJobAbortedError(_this.options.name, job)]);
290
+ });
291
+ if (queue.active && !queue.active.executed_at) {
292
+ // If we have a job holding this queue that hasn't been executed yet, abort it as well
293
+ this.endJob(queue.active, [new errors_1.BetterLockJobAbortedError(this.options.name, queue.active)]);
294
+ }
295
+ };
296
+ /**
297
+ * Abort all pending jobs from all queues.
298
+ * Currently executing jobs will not be interrupted.
299
+ */
300
+ BetterLock.prototype.abortAll = function () {
301
+ this.abort(null);
302
+ for (var key in this.queues) {
303
+ // noinspection JSUnfilteredForInLoop
304
+ this.abort(key);
305
+ }
306
+ };
307
+ /**
308
+ * Default options to be used when creating BetterLock instances.
309
+ */
310
+ BetterLock.DEFAULT_OPTIONS = {
311
+ extend_stack_traces: true,
312
+ promise_tester: function (p) { return p && typeof p.then === 'function'; },
313
+ };
314
+ return BetterLock;
315
+ }());
316
+ exports.BetterLock = BetterLock;
317
+ //# sourceMappingURL=better_lock.js.map