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.
- package/.eslintrc.js +24 -0
- package/.idea/better-lock.iml +1 -7
- package/.idea/codeStyles/Project.xml +336 -5
- package/.idea/compiler.xml +7 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -2
- package/.idea/jsLibraryMappings.xml +0 -1
- package/.idea/markdown.xml +9 -0
- package/.idea/modules.xml +1 -1
- package/.idea/prettier.xml +8 -0
- package/.prettierrc.yaml +1 -3
- package/.travis.yml +2 -2
- package/README.md +113 -73
- package/dist/better_lock.d.ts +50 -0
- package/dist/better_lock.js +317 -0
- package/dist/better_lock.js.map +1 -0
- package/dist/errors.d.ts +44 -0
- package/dist/errors.js +131 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/internals.d.ts +75 -0
- package/dist/internals.js +75 -0
- package/dist/internals.js.map +1 -0
- package/dist/options.d.ts +54 -0
- package/dist/options.js +3 -0
- package/dist/options.js.map +1 -0
- package/dist/tools.d.ts +19 -0
- package/dist/tools.js +109 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/jest.config.js +4 -0
- package/package.json +32 -9
- package/spec/better_lock.spec.ts +614 -0
- package/src/better_lock.ts +430 -0
- package/src/errors.ts +124 -0
- package/src/index.ts +54 -0
- package/src/internals.ts +118 -0
- package/src/options.ts +63 -0
- package/src/tools.ts +111 -0
- package/src/types.ts +29 -0
- package/tsconfig.json +13 -0
- package/.idea/markdown-navigator/profiles_settings.xml +0 -3
- package/.idea/markdown-navigator.xml +0 -78
- package/.idea/misc.xml +0 -6
- package/.idea/watcherTasks.xml +0 -8
- package/index.js +0 -19
- package/spec/better_lock.spec.js +0 -602
- package/src/better_lock.js +0 -405
- package/src/errors.js +0 -81
- package/src/options.js +0 -104
- package/src/tools.js +0 -125
- package/src/types.js +0 -110
package/src/better_lock.js
DELETED
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
const tools = require('./tools');
|
|
2
|
-
|
|
3
|
-
const { LockJob, KeyQueue } = require('./types');
|
|
4
|
-
const {
|
|
5
|
-
BetterLockInternalError,
|
|
6
|
-
InvalidArgumentError,
|
|
7
|
-
WaitTimeoutError,
|
|
8
|
-
ExecutionTimeoutError,
|
|
9
|
-
QueueOverflowError,
|
|
10
|
-
JobAbortedError,
|
|
11
|
-
} = require('./errors');
|
|
12
|
-
const { BetterLockOptions, LockJobOptions, DEFAULT_OPTIONS } = require('./options');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Create a better lock object.
|
|
16
|
-
* @constructor
|
|
17
|
-
* @param {BetterLockOptions} options
|
|
18
|
-
*/
|
|
19
|
-
function BetterLock(options) {
|
|
20
|
-
options = new BetterLockOptions([BetterLock.DEFAULT_OPTIONS, options]);
|
|
21
|
-
|
|
22
|
-
const log = tools.makeLog(options.name, options.log);
|
|
23
|
-
|
|
24
|
-
/** @type {Object.<string, KeyQueue>} */
|
|
25
|
-
const _queues = {};
|
|
26
|
-
|
|
27
|
-
Object.assign(
|
|
28
|
-
this,
|
|
29
|
-
/** @lends {BetterLock.prototype} */ {
|
|
30
|
-
canAcquire,
|
|
31
|
-
acquire,
|
|
32
|
-
abort,
|
|
33
|
-
abortAll,
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// *******************************************************************************************************************
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Return or create a key queue for given key
|
|
41
|
-
* @return {KeyQueue}
|
|
42
|
-
*/
|
|
43
|
-
function getQueue(key) {
|
|
44
|
-
if (key === undefined || key === null) {
|
|
45
|
-
key = KeyQueue.DEFAULT_QUEUE_KEY;
|
|
46
|
-
}
|
|
47
|
-
let queue = _queues[key];
|
|
48
|
-
if (!queue) {
|
|
49
|
-
queue = _queues[key] = new KeyQueue(key);
|
|
50
|
-
}
|
|
51
|
-
return queue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Returns true if the caller can *immediately* acquire the lock. There is nothing holding the key and nothing in the queue.
|
|
56
|
-
* @param key
|
|
57
|
-
*/
|
|
58
|
-
function canAcquire(key) {
|
|
59
|
-
const queue = getQueue(key);
|
|
60
|
-
return !queue.active && !queue.jobs.length;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function normalizeAndValidateKey(key) {
|
|
64
|
-
if (key === undefined) {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
if (key === null) {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
if (tools.isString(key)) {
|
|
71
|
-
return key;
|
|
72
|
-
}
|
|
73
|
-
if (tools.isNumber(key)) {
|
|
74
|
-
return String(key);
|
|
75
|
-
}
|
|
76
|
-
// Invalid key value
|
|
77
|
-
throw new InvalidArgumentError(
|
|
78
|
-
options.name,
|
|
79
|
-
'key',
|
|
80
|
-
'a string, number, undefined, null, or an array of these types',
|
|
81
|
-
key
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Acquire the lock for given key or list of keys. If waiting on a list, keys will be acquired in order,
|
|
87
|
-
* as they become free. Any deadlock prevention must be handled by the caller.
|
|
88
|
-
*
|
|
89
|
-
* Once the lock is acquired, we will call the executor function, with a "done" method.
|
|
90
|
-
* You must call "done" to release the lock. Alternatively, return a Promise; lock will be released
|
|
91
|
-
* once promise resolves or rejects.
|
|
92
|
-
*
|
|
93
|
-
* Return values from your executor will be passed to callback (or the resulting promise).
|
|
94
|
-
* @template T
|
|
95
|
-
* @param {string|Number|Array|undefined} [key] Named key or array of keys for this particular call. Calls with different keys will be run in parallel. Not required.
|
|
96
|
-
* @param {function(done):Promise<T>} executor Function that will run inside the lock. Required.
|
|
97
|
-
* @param {function} [callback] Function to be called after the executor finishes or if we never enter the lock (timeout, queue depletion). Leave out to use promises.
|
|
98
|
-
* @param {LockJobOptions} [jobOptions] Options to be applied on this job only
|
|
99
|
-
* @return {Promise<T>}
|
|
100
|
-
*/
|
|
101
|
-
function acquire(key, executor, callback = undefined, jobOptions = undefined) {
|
|
102
|
-
if (tools.isFunction(key) || tools.isObject(key)) {
|
|
103
|
-
// Presume we weren't given a key
|
|
104
|
-
jobOptions = callback;
|
|
105
|
-
callback = executor;
|
|
106
|
-
executor = key;
|
|
107
|
-
key = undefined;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Repackage "key" into an array of keys
|
|
111
|
-
const keys = [];
|
|
112
|
-
if (tools.isArray(key)) {
|
|
113
|
-
// Make sure we don't add duplicates
|
|
114
|
-
const seenKeys = {};
|
|
115
|
-
for (let i = 0; i < key.length; i++) {
|
|
116
|
-
const normalizedKey = normalizeAndValidateKey(key[i]);
|
|
117
|
-
if (!seenKeys[normalizedKey]) {
|
|
118
|
-
keys.push(normalizedKey);
|
|
119
|
-
seenKeys[normalizedKey] = true;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
keys.push(normalizeAndValidateKey(key));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create callback wrapper for promise interface
|
|
127
|
-
if (!tools.isFunction(callback)) {
|
|
128
|
-
if (jobOptions === undefined) {
|
|
129
|
-
jobOptions = callback;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
callback = tools.callbackWithPromise();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Validate other options
|
|
136
|
-
if (!tools.isFunction(executor)) {
|
|
137
|
-
throw new InvalidArgumentError(options.name, 'executor', 'a function', executor);
|
|
138
|
-
}
|
|
139
|
-
if (!tools.isFunction(callback)) {
|
|
140
|
-
throw new InvalidArgumentError(options.name, 'callback', 'a function', callback);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Prepare job options
|
|
144
|
-
const effectiveJobOptions = jobOptions
|
|
145
|
-
? new LockJobOptions([options, jobOptions])
|
|
146
|
-
: // No need to create new object since these will be read-only and are subset of global options
|
|
147
|
-
options;
|
|
148
|
-
|
|
149
|
-
const job = new LockJob(keys, executor, callback, effectiveJobOptions);
|
|
150
|
-
|
|
151
|
-
// Set incoming stack
|
|
152
|
-
if (effectiveJobOptions.extend_stack_traces) {
|
|
153
|
-
const tempErr = new Error();
|
|
154
|
-
Error.captureStackTrace(tempErr, acquire);
|
|
155
|
-
job.incoming_stack = tempErr.stack;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Add job to its key queues
|
|
159
|
-
const queuesToUpdate = [];
|
|
160
|
-
for (let i = 0; i < keys.length; i++) {
|
|
161
|
-
const queue = getQueue(keys[i]);
|
|
162
|
-
queue.jobs.push(job);
|
|
163
|
-
queuesToUpdate.push(queue);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Start wait timer, if enabled
|
|
167
|
-
if (tools.isNumber(job.options.wait_timeout)) {
|
|
168
|
-
job.wait_timeout_id = setTimeout(onWaitTimeout.bind(null, job), job.options.wait_timeout);
|
|
169
|
-
|
|
170
|
-
// NOTE: timeout of 0 will NOT trigger error if lock can be acquired immediately.
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
log(`Enqueued ${job}`);
|
|
174
|
-
|
|
175
|
-
// Perform update of queues this job has modified. We don't do this in next tick or something,
|
|
176
|
-
// because we want canAcquire to immediately return false
|
|
177
|
-
if (queuesToUpdate.length) {
|
|
178
|
-
update(queuesToUpdate);
|
|
179
|
-
} else {
|
|
180
|
-
// Special case. We are given empty list of keys. We will immediately execute jobs like this.
|
|
181
|
-
executeJob(job);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return callback.promise;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Update state of given list of queues.
|
|
189
|
-
* @param {KeyQueue[]} queues
|
|
190
|
-
*/
|
|
191
|
-
function update(queues) {
|
|
192
|
-
for (let i = 0; i < queues.length; i++) {
|
|
193
|
-
const queue = queues[i];
|
|
194
|
-
|
|
195
|
-
// If key is available, have next job grab it
|
|
196
|
-
if (!queue.active && queue.jobs.length) {
|
|
197
|
-
/** @type {LockJob} */
|
|
198
|
-
const job = queue.jobs.splice(0, 1)[0];
|
|
199
|
-
queue.active = job;
|
|
200
|
-
job.waiting_count--;
|
|
201
|
-
|
|
202
|
-
if (job.waiting_count <= 0) {
|
|
203
|
-
// This job can be executed now.
|
|
204
|
-
executeJob(job);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Handle wait queue overflow
|
|
209
|
-
if (tools.isNumber(options.queue_size) && options.queue_size < queue.jobs.length) {
|
|
210
|
-
// Overflow. Reject the most recent job
|
|
211
|
-
const mostRecentJob = queue.jobs[queue.jobs.length - 1];
|
|
212
|
-
log(
|
|
213
|
-
`${queue.toString()} has overflown, so most recent job (${mostRecentJob}) was kicked out`
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
endJob(mostRecentJob, [
|
|
217
|
-
new QueueOverflowError(options.name, queue.key, queue.jobs.length, mostRecentJob),
|
|
218
|
-
]);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* @param {LockJob} job
|
|
225
|
-
*/
|
|
226
|
-
function executeJob(job) {
|
|
227
|
-
log(`Executing ${job}`);
|
|
228
|
-
|
|
229
|
-
// Clear wait timeout
|
|
230
|
-
clearTimeout(job.wait_timeout_id);
|
|
231
|
-
job.wait_timeout_id = null;
|
|
232
|
-
|
|
233
|
-
// We want to do the rest of this in a separate context, because we don't want user executor code
|
|
234
|
-
// to ever interfere with the code calling acquire().
|
|
235
|
-
|
|
236
|
-
setImmediate(() => {
|
|
237
|
-
// Make sure job hasn't been ended in the meantime
|
|
238
|
-
if (job.executed_at || job.ended_at) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Validate this job is holding all queues it needs
|
|
243
|
-
for (let i = 0; i < job.keys.length; i++) {
|
|
244
|
-
const queue = getQueue(job.keys[i]);
|
|
245
|
-
if (queue.active !== job) {
|
|
246
|
-
// This should never happen
|
|
247
|
-
return endJob(job, [
|
|
248
|
-
new BetterLockInternalError(
|
|
249
|
-
options.name,
|
|
250
|
-
`Corrupted wait queue state for ${queue.key}`
|
|
251
|
-
),
|
|
252
|
-
]);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Mark job as having started executing
|
|
257
|
-
job.executed_at = new Date();
|
|
258
|
-
|
|
259
|
-
if (tools.isNumber(job.options.execution_timeout)) {
|
|
260
|
-
job.execution_timeout_id = setTimeout(
|
|
261
|
-
onExecutionTimeout.bind(null, job),
|
|
262
|
-
job.options.execution_timeout
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (job.executor.length > 0) {
|
|
267
|
-
// Callback interface
|
|
268
|
-
job.executor(lockDone);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Promise interface
|
|
273
|
-
let executorResult;
|
|
274
|
-
try {
|
|
275
|
-
executorResult = job.executor();
|
|
276
|
-
} catch (err) {
|
|
277
|
-
// Error thrown directly from executor
|
|
278
|
-
lockDone(err);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Promise sniffing
|
|
282
|
-
// TODO if (options.promise_tester(promise)) {
|
|
283
|
-
if (executorResult && executorResult.then && executorResult.catch) {
|
|
284
|
-
executorResult.then(res => lockDone(null, res), err => lockDone(err));
|
|
285
|
-
} else {
|
|
286
|
-
// "promise" is just some random value. We don't have to wait
|
|
287
|
-
lockDone(null, executorResult);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function lockDone() {
|
|
291
|
-
log(`Done called for ${job}`);
|
|
292
|
-
|
|
293
|
-
endJob(job, arguments);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* @param {LockJob} job
|
|
300
|
-
*/
|
|
301
|
-
function onWaitTimeout(job) {
|
|
302
|
-
log(`${job} has timed out after waiting in queue for ${new Date() - job.enqueued_at}ms`);
|
|
303
|
-
|
|
304
|
-
endJob(job, [new WaitTimeoutError(options.name, job)]);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* @param {LockJob} job
|
|
309
|
-
*/
|
|
310
|
-
function onExecutionTimeout(job) {
|
|
311
|
-
log(`${job} has timed out after executing for ${new Date() - job.executed_at}ms`);
|
|
312
|
-
|
|
313
|
-
endJob(job, [new ExecutionTimeoutError(options.name, job)]);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* @param {LockJob} job
|
|
318
|
-
* @param {*[]|Array|IArguments} callbackArgs
|
|
319
|
-
*/
|
|
320
|
-
function endJob(job, callbackArgs) {
|
|
321
|
-
if (job.ended_at) {
|
|
322
|
-
log(
|
|
323
|
-
`WARNING: ${job} is trying to end, but it has already ended at ${job.ended_at.toISOString()}. Called with arguments: ${callbackArgs}`
|
|
324
|
-
);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
clearTimeout(job.wait_timeout_id);
|
|
329
|
-
job.wait_timeout_id = null;
|
|
330
|
-
|
|
331
|
-
clearTimeout(job.execution_timeout_id);
|
|
332
|
-
job.execution_timeout_id = null;
|
|
333
|
-
|
|
334
|
-
job.ended_at = new Date();
|
|
335
|
-
|
|
336
|
-
const queuesToUpdate = [];
|
|
337
|
-
for (let i = 0; i < job.keys.length; i++) {
|
|
338
|
-
const queue = getQueue(job.keys[i]);
|
|
339
|
-
queuesToUpdate.push(queue);
|
|
340
|
-
|
|
341
|
-
if (queue.active === job) {
|
|
342
|
-
// This job was holding on the queue's key
|
|
343
|
-
queue.active = null;
|
|
344
|
-
} else {
|
|
345
|
-
// This job was still in wait list for this queue
|
|
346
|
-
const index = queue.jobs.indexOf(job);
|
|
347
|
-
if (index >= 0) {
|
|
348
|
-
// Remove from queue
|
|
349
|
-
queue.jobs.splice(index, 1);
|
|
350
|
-
} else {
|
|
351
|
-
// Something is wrong. Where did this job come from?
|
|
352
|
-
log(
|
|
353
|
-
`WARNING: ${job} is ending, but it is not found anywhere in the queue for ${queue.key}`
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Call the callback
|
|
360
|
-
try {
|
|
361
|
-
job.callback.apply(null, callbackArgs);
|
|
362
|
-
} finally {
|
|
363
|
-
// Whatever happens, schedule a queue update
|
|
364
|
-
setImmediate(update, queuesToUpdate);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Abort all jobs for a given key (or from the default job queue, if no key is given).
|
|
370
|
-
* Job executors will not be called. Callbacks will be called with JobAbortedError.
|
|
371
|
-
* Currently executing job will not be interrupted.
|
|
372
|
-
* @param [key]
|
|
373
|
-
*/
|
|
374
|
-
function abort(key = undefined) {
|
|
375
|
-
const queue = getQueue(key);
|
|
376
|
-
queue.jobs.slice().forEach(job => {
|
|
377
|
-
endJob(job, [new JobAbortedError(options.name, job)]);
|
|
378
|
-
});
|
|
379
|
-
if (queue.active && !queue.active.executed_at) {
|
|
380
|
-
// If we have a job holding this queue that hasn't been executed yet, abort it as well
|
|
381
|
-
endJob(queue.active, [new JobAbortedError(options.name, queue.active)]);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Abort all pending jobs from all queues.
|
|
387
|
-
* Currently executing jobs will not be interrupted.
|
|
388
|
-
*/
|
|
389
|
-
function abortAll() {
|
|
390
|
-
abort(undefined);
|
|
391
|
-
for (const key in _queues) {
|
|
392
|
-
// noinspection JSUnfilteredForInLoop
|
|
393
|
-
abort(key);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Default options to be used when creating BetterLock instances.
|
|
400
|
-
*/
|
|
401
|
-
BetterLock.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
402
|
-
|
|
403
|
-
module.exports = {
|
|
404
|
-
BetterLock,
|
|
405
|
-
};
|
package/src/errors.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
class BetterLockError extends Error {
|
|
2
|
-
constructor(name, message, incomingStack = null) {
|
|
3
|
-
if (name) {
|
|
4
|
-
super(`[${name}] ${message}`);
|
|
5
|
-
} else {
|
|
6
|
-
super(message);
|
|
7
|
-
}
|
|
8
|
-
this.lock_name = name;
|
|
9
|
-
|
|
10
|
-
if (incomingStack) {
|
|
11
|
-
const withoutFirstLine = incomingStack
|
|
12
|
-
.split('\n')
|
|
13
|
-
.slice(1)
|
|
14
|
-
.join('\n');
|
|
15
|
-
this.stack =
|
|
16
|
-
this.stack +
|
|
17
|
-
'\n --------------------------------------------------------------------------------\n' +
|
|
18
|
-
withoutFirstLine;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class BetterLockInternalError extends BetterLockError {
|
|
24
|
-
constructor(name, message) {
|
|
25
|
-
const lastChar = message[message.length - 1];
|
|
26
|
-
if (lastChar !== '.' && lastChar !== '!' && lastChar !== '?') {
|
|
27
|
-
message += '.';
|
|
28
|
-
}
|
|
29
|
-
super(name, message + ' This is probably a bug inside "better-lock" library');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class InvalidArgumentError extends BetterLockError {
|
|
34
|
-
constructor(name, argument, expected, actual) {
|
|
35
|
-
super(name, `Argument "${argument}" must be ${expected} (got: "${actual}")`);
|
|
36
|
-
this.argument = argument;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class WaitTimeoutError extends BetterLockError {
|
|
41
|
-
constructor(name, job) {
|
|
42
|
-
const message = `${job} has timed out after ${new Date() - job.enqueued_at}ms in wait queue`;
|
|
43
|
-
super(name, message, job.incoming_stack);
|
|
44
|
-
this.job = job;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
class ExecutionTimeoutError extends BetterLockError {
|
|
49
|
-
constructor(name, job) {
|
|
50
|
-
const message = `${job} has timed out after ${new Date() - job.executed_at}ms of execution`;
|
|
51
|
-
super(name, message, job.incoming_stack);
|
|
52
|
-
this.job = job;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
class QueueOverflowError extends BetterLockError {
|
|
57
|
-
constructor(name, key, count, job) {
|
|
58
|
-
const message = `Too many jobs (${count}) waiting for ${key}. The most recent job (${job}) was kicked out`;
|
|
59
|
-
super(name, message, job.incoming_stack);
|
|
60
|
-
this.key = key;
|
|
61
|
-
this.count = count;
|
|
62
|
-
this.job = job;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
class JobAbortedError extends BetterLockError {
|
|
67
|
-
constructor(name, job) {
|
|
68
|
-
super(name, `${job} has been aborted by the user`, job.incoming_stack);
|
|
69
|
-
this.job = job;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
module.exports = {
|
|
74
|
-
BetterLockError,
|
|
75
|
-
BetterLockInternalError,
|
|
76
|
-
InvalidArgumentError,
|
|
77
|
-
WaitTimeoutError,
|
|
78
|
-
ExecutionTimeoutError,
|
|
79
|
-
QueueOverflowError,
|
|
80
|
-
JobAbortedError,
|
|
81
|
-
};
|
package/src/options.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
const { assign } = require('./tools');
|
|
2
|
-
|
|
3
|
-
const ALIASES = {
|
|
4
|
-
waitTimeout: 'wait_timeout',
|
|
5
|
-
executionTimeout: 'execution_timeout',
|
|
6
|
-
queueSize: 'queue_size',
|
|
7
|
-
overflowStrategy: 'overflow_strategy',
|
|
8
|
-
extendStackTraces: 'extend_stack_traces',
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Options that apply on BetterLock instance
|
|
13
|
-
*/
|
|
14
|
-
class BetterLockOptions {
|
|
15
|
-
constructor(/** BetterLockOptions|BetterLockOptions[] */ sources) {
|
|
16
|
-
/**
|
|
17
|
-
* How long can jobs wait in queue before timing out. Null to disable timeout.
|
|
18
|
-
* @type {Number|null}
|
|
19
|
-
*/
|
|
20
|
-
this.wait_timeout = undefined;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* How long can a job be executing before timing out. Null to disable timeout.
|
|
24
|
-
* If you do that, though, and you have a swallowed callback, the lock can remain locked permanently.
|
|
25
|
-
* @type {Number|null}
|
|
26
|
-
*/
|
|
27
|
-
this.execution_timeout = undefined;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Max queue size for waiting jobs.
|
|
31
|
-
* @type {Number|null}
|
|
32
|
-
*/
|
|
33
|
-
this.queue_size = undefined;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Lock name. This will be written in all logs and error messages, to help you distinguish between different locks
|
|
37
|
-
* @type {string}
|
|
38
|
-
*/
|
|
39
|
-
this.name = undefined;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Whether to log the internal actions. Set to true to use console.log. Alternatively, provide your own function
|
|
43
|
-
* @type {boolean|function}
|
|
44
|
-
*/
|
|
45
|
-
this.log = undefined;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* In any error produced by BetterLock, extend the stack trace to include incoming trace
|
|
49
|
-
* @type {boolean}
|
|
50
|
-
*/
|
|
51
|
-
this.extend_stack_traces = undefined;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Function that detects whether value returned by executor is a promise.
|
|
55
|
-
* This will ducktype-sniff the returned value. Depending on your Promise library of choice,
|
|
56
|
-
* you might want to tighten or loosen this bit.
|
|
57
|
-
* @type {function(p)}
|
|
58
|
-
*/
|
|
59
|
-
this.promise_tester = p => p && typeof p.then === 'function';
|
|
60
|
-
|
|
61
|
-
this.assign(sources);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
assign(sources) {
|
|
65
|
-
assign(this, sources, ALIASES);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Options that apply on LockJob
|
|
71
|
-
*/
|
|
72
|
-
class LockJobOptions {
|
|
73
|
-
constructor(/** LockJobOptions|LockJobOptions[] */ sources) {
|
|
74
|
-
/**
|
|
75
|
-
* How long can jobs wait in queue before timing out. Overrides BetterLockOptions value.
|
|
76
|
-
* @type {Number|null}
|
|
77
|
-
*/
|
|
78
|
-
this.wait_timeout = undefined;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* How long can a job be executing before timing out. Null to disable timeout. Overrides BetterLockOptions value.
|
|
82
|
-
* @type {Number|null}
|
|
83
|
-
*/
|
|
84
|
-
this.execution_timeout = undefined;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* In any error produced by BetterLock, extend the stack trace to include incoming trace. Overrides BetterLockOptions value.
|
|
88
|
-
* @type {boolean}
|
|
89
|
-
*/
|
|
90
|
-
this.extend_stack_traces = undefined;
|
|
91
|
-
|
|
92
|
-
this.assign(sources);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
assign(sources) {
|
|
96
|
-
assign(this, sources, ALIASES);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const DEFAULT_OPTIONS = new BetterLockOptions({
|
|
101
|
-
extend_stack_traces: true,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
module.exports = { BetterLockOptions, LockJobOptions, DEFAULT_OPTIONS };
|
package/src/tools.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
function isString(val) {
|
|
2
|
-
return typeof val === 'string';
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function isFunction(val) {
|
|
6
|
-
return typeof val === 'function';
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function isObject(val) {
|
|
10
|
-
return typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function isNumber(val, includeInfinity = false, includeNaN = false) {
|
|
14
|
-
if (typeof val !== 'number') {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
if (!includeInfinity && (val === Number.POSITIVE_INFINITY || val === Number.NEGATIVE_INFINITY)) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
if (!includeNaN && isNaN(val)) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function isArray(val) {
|
|
27
|
-
return Array.isArray(val);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function noop() {}
|
|
31
|
-
|
|
32
|
-
function makeLog(name, doLog) {
|
|
33
|
-
if (!doLog) {
|
|
34
|
-
return noop;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (doLog === true) {
|
|
38
|
-
doLog = console.log.bind(console);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (name) {
|
|
42
|
-
name = '[' + name + '] ';
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return function log(msg) {
|
|
46
|
-
if (name) {
|
|
47
|
-
doLog(name + msg);
|
|
48
|
-
} else {
|
|
49
|
-
doLog(msg);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function callbackWithPromise() {
|
|
55
|
-
let callback;
|
|
56
|
-
const promise = new Promise((resolve, reject) => {
|
|
57
|
-
callback = (err, result) => {
|
|
58
|
-
if (err) {
|
|
59
|
-
reject(err);
|
|
60
|
-
} else {
|
|
61
|
-
resolve(result);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
|
-
callback.promise = promise;
|
|
66
|
-
return callback;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Assign properties from sources to target, skipping over undefined-s
|
|
71
|
-
* @param target Target object which will be mutated
|
|
72
|
-
* @param {object|Array} sources One or more source objects which will provide properties
|
|
73
|
-
* @param {object} aliases Lookup mapping aliased properties from sources to properties on target
|
|
74
|
-
*/
|
|
75
|
-
function assign(target, sources, aliases) {
|
|
76
|
-
if (!isArray(sources)) {
|
|
77
|
-
sources = [sources];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const source of sources) {
|
|
81
|
-
if (!isObject(source)) {
|
|
82
|
-
// We must have an object to assign
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for (const key in source) {
|
|
87
|
-
if (!source.hasOwnProperty(key)) {
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const sourceValue = source[key];
|
|
92
|
-
if (sourceValue === undefined) {
|
|
93
|
-
// Do not overwrite undefineds
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const targetKey = (aliases && aliases[key]) || key;
|
|
98
|
-
const targetValue = target[targetKey];
|
|
99
|
-
|
|
100
|
-
if (isObject(sourceValue) && isObject(targetValue)) {
|
|
101
|
-
// Merge deep.
|
|
102
|
-
const cloneRoot = targetValue.constructor ? new targetValue.constructor() : {};
|
|
103
|
-
target[targetKey] = assign(cloneRoot, targetValue, sourceValue);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// In all other cases, copy by reference
|
|
108
|
-
target[targetKey] = sourceValue;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return target;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = {
|
|
116
|
-
isString,
|
|
117
|
-
isFunction,
|
|
118
|
-
isObject,
|
|
119
|
-
isNumber,
|
|
120
|
-
isArray,
|
|
121
|
-
noop,
|
|
122
|
-
makeLog,
|
|
123
|
-
callbackWithPromise,
|
|
124
|
-
assign,
|
|
125
|
-
};
|