agenda 2.1.0 → 3.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.
- package/History.md +52 -2
- package/README.md +14 -24
- package/lib/agenda/cancel.js +2 -2
- package/lib/agenda/database.js +8 -1
- package/lib/agenda/define.js +1 -0
- package/lib/agenda/every.js +2 -3
- package/lib/agenda/find-and-lock-next-job.js +28 -36
- package/lib/agenda/index.js +7 -2
- package/lib/agenda/job-processing-queue.js +81 -0
- package/lib/agenda/jobs.js +3 -1
- package/lib/agenda/now.js +12 -7
- package/lib/agenda/save-job.js +3 -2
- package/lib/agenda/schedule.js +2 -2
- package/lib/agenda/start.js +1 -0
- package/lib/job/compute-next-run-at.js +12 -6
- package/lib/job/fail.js +1 -0
- package/lib/job/index.js +1 -0
- package/lib/job/is-running.js +3 -0
- package/lib/job/priority.js +1 -0
- package/lib/job/repeat-every.js +5 -1
- package/lib/job/run.js +9 -4
- package/lib/utils/process-jobs.js +35 -82
- package/package.json +25 -14
package/History.md
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
Next
|
|
2
2
|
====
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
* https://github.com/agenda/agenda/pulls
|
|
5
|
+
|
|
6
|
+
3.1.0 / 2020-04-07
|
|
7
|
+
==================
|
|
8
|
+
|
|
9
|
+
_Stay safe!_
|
|
10
|
+
|
|
11
|
+
* Fix for skipImmediate resetting nextRunAt to current date ([#860](https://github.com/agenda/agenda/pull/860)) (Thanks @AshlinDuncan!)
|
|
12
|
+
* Fix deprecated reconnect options ([#948](https://github.com/agenda/agenda/pull/948)) (Thanks @ekegodigital!)
|
|
13
|
+
* Add ability to set a skip when querying jobs. ([#898](https://github.com/agenda/agenda/pull/898)) (Thanks @cjolif!)
|
|
14
|
+
|
|
15
|
+
Internal:
|
|
16
|
+
* Fixed deprecated MongoDB functions in tests ([#928](https://github.com/agenda/agenda/pull/928)) (Thanks @MichielDeMey!)
|
|
17
|
+
* Updated devDependencies
|
|
18
|
+
|
|
19
|
+
Thank you @koresar, @sampathBlam, and @MichielDeMey helping to review PRs for this release! 👏
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
3.0.0 / 2020-02-13
|
|
23
|
+
==================
|
|
24
|
+
|
|
25
|
+
* Support MongoDB's Unified Topology Design ([#921](https://github.com/agenda/agenda/pull/921)) (Thanks @viktorzavadil!)
|
|
26
|
+
* Fix: check that the new nextRunAt is different that the previous nextRunAt ([#863](https://github.com/agenda/agenda/pull/863)) (Thanks @RaphaelRheault!)
|
|
27
|
+
* Update dependencies. Most notably MongoDB driver 3.4 → 3.5 ([#899](https://github.com/agenda/agenda/pull/899), [#900](https://github.com/agenda/agenda/pull/900), [#903](https://github.com/agenda/agenda/pull/903), [#906](https://github.com/agenda/agenda/pull/906), [#908](https://github.com/agenda/agenda/pull/908), [#910](https://github.com/agenda/agenda/pull/910), [#912](https://github.com/agenda/agenda/pull/912), [#913](https://github.com/agenda/agenda/pull/913), [#920](https://github.com/agenda/agenda/pull/920), [#922](https://github.com/agenda/agenda/pull/922))
|
|
28
|
+
* Documentation updates, thanks @MichielDeMey and @Sunghee2. ([#923](https://github.com/agenda/agenda/pull/923) & [#907](https://github.com/agenda/agenda/pull/907))
|
|
29
|
+
|
|
30
|
+
BREAKING
|
|
31
|
+
--------
|
|
32
|
+
|
|
33
|
+
* Stop testing for Node.js 8. This might still work but we're no longer actively testing for it. ([#925](https://github.com/agenda/agenda/pull/925))
|
|
34
|
+
|
|
35
|
+
2.3.0 / 2019-12-16
|
|
36
|
+
==================
|
|
37
|
+
|
|
38
|
+
* Improved performance in situations when there are many "expired" jobs in the database ([#869](https://github.com/agenda/agenda/pull/869)) (Thanks @mfred488!)
|
|
39
|
+
* Fix periodic node.js process unhandledRejection ([#887](https://github.com/agenda/agenda/pull/887)) (Thanks @koresar and @Scorpil)
|
|
40
|
+
* Update dependencies
|
|
41
|
+
|
|
42
|
+
2.2.0 / 2019-11-24
|
|
43
|
+
==================
|
|
44
|
+
|
|
45
|
+
* Fix `skipImmediate` option in `.every` ([#861](https://github.com/agenda/agenda/pull/861)) (Thanks @erics2783!)
|
|
46
|
+
* Add try/catch block to agenda#now method ([#876](https://github.com/agenda/agenda/pull/876)) (Thanks @sampathBlam!)
|
|
47
|
+
* Refactor job queuing mechanism. Agenda n ow guarantees priority when executing jobs scheduled the same datetime. Fixes also some tests. ([#852](https://github.com/agenda/agenda/pull/852)) (Thank you @dmbarreiro!)
|
|
48
|
+
* Update dependencies (Kudos @simison!)
|
|
49
|
+
Most notably `mongodb` ~3.2.7 -> ~3.3.0 ([changelog](https://github.com/mongodb/node-mongodb-native/tree/v3.3.0)) — highlights:
|
|
50
|
+
- Mongo DB Server Version 4.2 feature support
|
|
51
|
+
- Merged `mongodb-core` into `node-mongodb-native`
|
|
52
|
+
- Beta support for MongoDB Client-Side Encryption
|
|
53
|
+
- SRV Polling for Sharded Clusters
|
|
54
|
+
* Updates to documentation (Thank you @lautarobock, @sampathBlam, @indatawetrust)
|
|
5
55
|
|
|
6
56
|
2.1.0 / 2019-09-09
|
|
7
57
|
==================
|
|
8
58
|
* Support async functions in job processing ([#653](https://github.com/agenda/agenda/pull/653)) (thanks @princjef!)
|
|
9
|
-
* Allow sorting and limiting jobs when searching (
|
|
59
|
+
* Allow sorting and limiting jobs when searching ([#665](https://github.com/agenda/agenda/pull/665)) (thank you @edwin-jones)
|
|
10
60
|
* Update MongoClient connection settings with `useNewUrlParser: true` to remove the deprecation warning. ([#806](https://github.com/agenda/agenda/pull/806)) (thanks @dpawson905!)
|
|
11
61
|
* Allow valid date strings when scheduling ([#808](https://github.com/agenda/agenda/pull/808)) (Thanks @wingsbob!)
|
|
12
62
|
* Update dependencies ([#820](https://github.com/agenda/agenda/pull/820))
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://cdn.
|
|
2
|
+
<img src="https://cdn.jsdelivr.net/gh/agenda/agenda@master/agenda.svg" alt="Agenda" width="100" height="100">
|
|
3
3
|
</p>
|
|
4
4
|
<p align="center">
|
|
5
5
|
A light-weight job scheduling library for Node.js
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
- Event backed job queue that you can hook into.
|
|
26
26
|
- [Agendash](https://github.com/agenda/agendash): optional standalone web-interface.
|
|
27
27
|
- [Agenda-rest](https://github.com/agenda/agenda-rest): optional standalone REST API.
|
|
28
|
+
- [inversify-agenda](https://github.com/lautarobock/inversify-agenda) - Some utilities for the development of agenda workers with Inversify
|
|
28
29
|
|
|
29
30
|
### Feature Comparison
|
|
30
31
|
|
|
@@ -48,7 +49,7 @@ Agenda is great if you need something that is simple and backed by MongoDB.
|
|
|
48
49
|
| Persistence | ✓ | ✓ | ✓ |
|
|
49
50
|
| UI | ✓ | | ✓ |
|
|
50
51
|
| REST API | | | ✓ |
|
|
51
|
-
| Optimized for | Jobs / Messages |
|
|
52
|
+
| Optimized for | Jobs / Messages | Messages | Jobs |
|
|
52
53
|
|
|
53
54
|
_Kudos for making the comparison chart goes to [Bull](https://www.npmjs.com/package/bull#feature-comparison) maintainers._
|
|
54
55
|
|
|
@@ -416,10 +417,6 @@ under `job.attrs.data`.
|
|
|
416
417
|
`options` is an optional argument that will be passed to [`job.repeatEvery`](#repeateveryinterval-options).
|
|
417
418
|
In order to use this argument, `data` must also be specified.
|
|
418
419
|
|
|
419
|
-
`cb` is an optional callback function which will be called when the job has been
|
|
420
|
-
persisted in the database.
|
|
421
|
-
|
|
422
|
-
|
|
423
420
|
Returns the `job`.
|
|
424
421
|
|
|
425
422
|
```js
|
|
@@ -449,9 +446,6 @@ Schedules a job to run `name` once at a given time. `when` can be a `Date` or a
|
|
|
449
446
|
`data` is an optional argument that will be passed to the processing function
|
|
450
447
|
under `job.attrs.data`.
|
|
451
448
|
|
|
452
|
-
`cb` is an optional callback function which will be called when the job has been
|
|
453
|
-
persisted in the database.
|
|
454
|
-
|
|
455
449
|
Returns the `job`.
|
|
456
450
|
|
|
457
451
|
```js
|
|
@@ -493,37 +487,33 @@ console.log('Job successfully saved');
|
|
|
493
487
|
## Managing Jobs
|
|
494
488
|
|
|
495
489
|
|
|
496
|
-
### jobs(mongodb-native query, mongodb-native sort, mongodb-native limit)
|
|
490
|
+
### jobs(mongodb-native query, mongodb-native sort, mongodb-native limit, mongodb-native skip)
|
|
497
491
|
|
|
498
|
-
Lets you query (then sort and
|
|
492
|
+
Lets you query (then sort, limit and skip the result) all of the jobs in the agenda job's database. These are full [mongodb-native](https://github.com/mongodb/node-mongodb-native) `find`, `sort`, `limit` and `skip` commands. See mongodb-native's documentation for details.
|
|
499
493
|
|
|
500
494
|
```js
|
|
501
|
-
const jobs = await agenda.jobs({name: 'printAnalyticsReport'}, {data:-1}, 3);
|
|
495
|
+
const jobs = await agenda.jobs({name: 'printAnalyticsReport'}, {data:-1}, 3, 1);
|
|
502
496
|
// Work with jobs (see below)
|
|
503
497
|
```
|
|
504
498
|
|
|
505
|
-
### cancel(mongodb-native query
|
|
499
|
+
### cancel(mongodb-native query)
|
|
506
500
|
|
|
507
|
-
Cancels any jobs matching the passed mongodb-native query, and removes them from the database.
|
|
501
|
+
Cancels any jobs matching the passed mongodb-native query, and removes them from the database. Returns a Promise resolving to the number of cancelled jobs, or rejecting on error.
|
|
508
502
|
|
|
509
503
|
```js
|
|
510
|
-
agenda.cancel({name: 'printAnalyticsReport'}
|
|
511
|
-
// ...
|
|
512
|
-
});
|
|
504
|
+
const numRemoved = await agenda.cancel({name: 'printAnalyticsReport'});
|
|
513
505
|
```
|
|
514
506
|
|
|
515
507
|
This functionality can also be achieved by first retrieving all the jobs from the database using `agenda.jobs()`, looping through the resulting array and calling `job.remove()` on each. It is however preferable to use `agenda.cancel()` for this use case, as this ensures the operation is atomic.
|
|
516
508
|
|
|
517
|
-
### purge(
|
|
509
|
+
### purge()
|
|
518
510
|
|
|
519
|
-
Removes all jobs in the database without defined behaviors. Useful if you change a definition name and want to remove old jobs.
|
|
511
|
+
Removes all jobs in the database without defined behaviors. Useful if you change a definition name and want to remove old jobs. Returns a Promise resolving to the number of removed jobs, or rejecting on error.
|
|
520
512
|
|
|
521
513
|
*IMPORTANT:* Do not run this before you finish defining all of your jobs. If you do, you will nuke your database of jobs.
|
|
522
514
|
|
|
523
515
|
```js
|
|
524
|
-
agenda.purge(
|
|
525
|
-
// ...
|
|
526
|
-
});
|
|
516
|
+
const numRemoved = await agenda.purge();
|
|
527
517
|
```
|
|
528
518
|
|
|
529
519
|
## Starting the job processor
|
|
@@ -710,7 +700,7 @@ try {
|
|
|
710
700
|
console.log('Successfully removed job from collection');
|
|
711
701
|
} catch (e) {
|
|
712
702
|
console.error('Error removing job from collection');
|
|
713
|
-
}
|
|
703
|
+
}
|
|
714
704
|
```
|
|
715
705
|
|
|
716
706
|
### disable()
|
|
@@ -960,7 +950,7 @@ const agenda = new Agenda(connectionOpts);
|
|
|
960
950
|
const jobTypes = process.env.JOB_TYPES ? process.env.JOB_TYPES.split(',') : [];
|
|
961
951
|
|
|
962
952
|
jobTypes.forEach(type => {
|
|
963
|
-
require('./
|
|
953
|
+
require('./jobs/' + type)(agenda);
|
|
964
954
|
});
|
|
965
955
|
|
|
966
956
|
if (jobTypes.length) {
|
package/lib/agenda/cancel.js
CHANGED
|
@@ -15,8 +15,8 @@ module.exports = async function(query) {
|
|
|
15
15
|
const {result} = await this._collection.deleteMany(query);
|
|
16
16
|
debug('%s jobs cancelled', result.n);
|
|
17
17
|
return result.n;
|
|
18
|
-
} catch (
|
|
18
|
+
} catch (error) {
|
|
19
19
|
debug('error trying to delete jobs from MongoDB');
|
|
20
|
-
throw
|
|
20
|
+
throw error;
|
|
21
21
|
}
|
|
22
22
|
};
|
package/lib/agenda/database.js
CHANGED
|
@@ -25,8 +25,13 @@ module.exports = function(url, collection, options, cb) {
|
|
|
25
25
|
url = 'mongodb://' + url;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
let reconnectOptions = {autoReconnect: true, reconnectTries: Number.MAX_SAFE_INTEGER, reconnectInterval: this._processEvery};
|
|
29
|
+
if (options && options.useUnifiedTopology && options.useUnifiedTopology === true) {
|
|
30
|
+
reconnectOptions = {};
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
collection = collection || 'agendaJobs';
|
|
29
|
-
options =
|
|
34
|
+
options = {...reconnectOptions, ...options};
|
|
30
35
|
MongoClient.connect(url, options, (error, client) => {
|
|
31
36
|
if (error) {
|
|
32
37
|
debug('error connecting to MongoDB using collection: [%s]', collection);
|
|
@@ -35,8 +40,10 @@ module.exports = function(url, collection, options, cb) {
|
|
|
35
40
|
} else {
|
|
36
41
|
throw error;
|
|
37
42
|
}
|
|
43
|
+
|
|
38
44
|
return;
|
|
39
45
|
}
|
|
46
|
+
|
|
40
47
|
debug('successful connection to MongoDB using collection: [%s]', collection);
|
|
41
48
|
self._db = client;
|
|
42
49
|
self._mdb = client.db();
|
package/lib/agenda/define.js
CHANGED
package/lib/agenda/every.js
CHANGED
|
@@ -25,7 +25,6 @@ module.exports = async function(interval, names, data, options) {
|
|
|
25
25
|
|
|
26
26
|
job.attrs.type = 'single';
|
|
27
27
|
job.repeatEvery(interval, options);
|
|
28
|
-
job.computeNextRunAt();
|
|
29
28
|
await job.save();
|
|
30
29
|
|
|
31
30
|
return job;
|
|
@@ -46,8 +45,8 @@ module.exports = async function(interval, names, data, options) {
|
|
|
46
45
|
debug('every() -> all jobs created successfully');
|
|
47
46
|
|
|
48
47
|
return Promise.all(jobs);
|
|
49
|
-
} catch (
|
|
50
|
-
debug('every() -> error creating one or more of the jobs');
|
|
48
|
+
} catch (error) { // @TODO: catch - ignore :O
|
|
49
|
+
debug('every() -> error creating one or more of the jobs', error);
|
|
51
50
|
}
|
|
52
51
|
};
|
|
53
52
|
|
|
@@ -8,77 +8,69 @@ const {createJob} = require('../utils');
|
|
|
8
8
|
* @function
|
|
9
9
|
* @param {String} jobName name of job to try to lock
|
|
10
10
|
* @param {Object} definition definition used to tell how job is run
|
|
11
|
-
* @param {Function} cb called when job lock fails or passes
|
|
12
11
|
* @access protected
|
|
13
12
|
* @caller jobQueueFilling() only
|
|
14
|
-
* @returns
|
|
13
|
+
* @returns Promise
|
|
15
14
|
*/
|
|
16
|
-
module.exports = function(jobName, definition
|
|
15
|
+
module.exports = async function(jobName, definition) {
|
|
17
16
|
const self = this;
|
|
18
17
|
const now = new Date();
|
|
19
18
|
const lockDeadline = new Date(Date.now().valueOf() - definition.lockLifetime);
|
|
20
|
-
debug('_findAndLockNextJob(%s, [Function]
|
|
19
|
+
debug('_findAndLockNextJob(%s, [Function])', jobName);
|
|
21
20
|
|
|
22
21
|
// Don't try and access MongoDB if we've lost connection to it.
|
|
23
22
|
// Trying to resolve crash on Dev PC when it resumes from sleep. NOTE: Does this still happen?
|
|
24
23
|
const s = this._mdb.s || this._mdb.db.s;
|
|
25
|
-
if (s.topology.connections().length === 0) {
|
|
24
|
+
if (s.topology.connections && s.topology.connections().length === 0 && !this._mongoUseUnifiedTopology) {
|
|
26
25
|
if (s.topology.autoReconnect && !s.topology.isDestroyed()) {
|
|
27
26
|
// Continue processing but notify that Agenda has lost the connection
|
|
28
27
|
debug('Missing MongoDB connection, not attempting to find and lock a job');
|
|
29
28
|
self.emit('error', new Error('Lost MongoDB connection'));
|
|
30
|
-
cb();
|
|
31
29
|
} else {
|
|
32
30
|
// No longer recoverable
|
|
33
31
|
debug('topology.autoReconnect: %s, topology.isDestroyed(): %s', s.topology.autoReconnect, s.topology.isDestroyed());
|
|
34
|
-
|
|
32
|
+
throw new Error('MongoDB connection is not recoverable, application restart required');
|
|
35
33
|
}
|
|
36
34
|
} else {
|
|
37
35
|
// /**
|
|
38
36
|
// * Query used to find job to run
|
|
39
|
-
// * @type {{$
|
|
37
|
+
// * @type {{$and: [*]}}
|
|
40
38
|
// */
|
|
41
39
|
const JOB_PROCESS_WHERE_QUERY = {
|
|
42
|
-
$
|
|
40
|
+
$and: [{
|
|
43
41
|
name: jobName,
|
|
44
|
-
lockedAt: null,
|
|
45
|
-
nextRunAt: {$lte: this._nextScanAt},
|
|
46
42
|
disabled: {$ne: true}
|
|
47
43
|
}, {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
lockedAt: {$lte: lockDeadline},
|
|
55
|
-
disabled: {$ne: true}
|
|
44
|
+
$or: [{
|
|
45
|
+
lockedAt: {$eq: null},
|
|
46
|
+
nextRunAt: {$lte: this._nextScanAt}
|
|
47
|
+
}, {
|
|
48
|
+
lockedAt: {$lte: lockDeadline}
|
|
49
|
+
}]
|
|
56
50
|
}]
|
|
57
51
|
};
|
|
58
52
|
|
|
59
53
|
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
* Query used to set a job as locked
|
|
55
|
+
* @type {{$set: {lockedAt: Date}}}
|
|
56
|
+
*/
|
|
63
57
|
const JOB_PROCESS_SET_QUERY = {$set: {lockedAt: now}};
|
|
64
58
|
|
|
65
59
|
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
* Query used to affect what gets returned
|
|
61
|
+
* @type {{returnOriginal: boolean, sort: object}}
|
|
62
|
+
*/
|
|
69
63
|
const JOB_RETURN_QUERY = {returnOriginal: false, sort: this._sort};
|
|
70
64
|
|
|
71
65
|
// Find ONE and ONLY ONE job and set the 'lockedAt' time so that job begins to be processed
|
|
72
|
-
this._collection.findOneAndUpdate(JOB_PROCESS_WHERE_QUERY, JOB_PROCESS_SET_QUERY, JOB_RETURN_QUERY
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
cb(err, job);
|
|
82
|
-
});
|
|
66
|
+
const result = await this._collection.findOneAndUpdate(JOB_PROCESS_WHERE_QUERY, JOB_PROCESS_SET_QUERY, JOB_RETURN_QUERY);
|
|
67
|
+
|
|
68
|
+
let job;
|
|
69
|
+
if (result.value) {
|
|
70
|
+
debug('found a job available to lock, creating a new job on Agenda with id [%s]', result.value._id);
|
|
71
|
+
job = createJob(self, result.value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return job;
|
|
83
75
|
}
|
|
84
76
|
};
|
package/lib/agenda/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const {EventEmitter} = require('events');
|
|
2
2
|
const humanInterval = require('human-interval');
|
|
3
|
+
const JobProcessingQueue = require('./job-processing-queue');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @class Agenda
|
|
@@ -22,6 +23,7 @@ const humanInterval = require('human-interval');
|
|
|
22
23
|
* @property {Array} _jobsToLock
|
|
23
24
|
*/
|
|
24
25
|
class Agenda extends EventEmitter {
|
|
26
|
+
// eslint-disable-next-line default-param-last
|
|
25
27
|
constructor(config = {}, cb) {
|
|
26
28
|
super();
|
|
27
29
|
|
|
@@ -38,10 +40,10 @@ class Agenda extends EventEmitter {
|
|
|
38
40
|
this._definitions = {};
|
|
39
41
|
this._runningJobs = [];
|
|
40
42
|
this._lockedJobs = [];
|
|
41
|
-
this._jobQueue =
|
|
43
|
+
this._jobQueue = new JobProcessingQueue();
|
|
42
44
|
this._defaultLockLifetime = config.defaultLockLifetime || 10 * 60 * 1000; // 10 minute default lockLifetime
|
|
43
45
|
this._sort = config.sort || {nextRunAt: 1, priority: -1};
|
|
44
|
-
this._indices =
|
|
46
|
+
this._indices = {name: 1, ...this._sort, priority: -1, lockedAt: 1, nextRunAt: 1, disabled: 1};
|
|
45
47
|
|
|
46
48
|
this._isLockingOnTheFly = false;
|
|
47
49
|
this._jobsToLock = [];
|
|
@@ -49,6 +51,9 @@ class Agenda extends EventEmitter {
|
|
|
49
51
|
|
|
50
52
|
if (config.mongo) {
|
|
51
53
|
this.mongo(config.mongo, config.db ? config.db.collection : undefined, cb);
|
|
54
|
+
if (config.mongo.s && config.mongo.topology && config.mongo.topology.s) {
|
|
55
|
+
this._mongoUseUnifiedTopology = Boolean(config.mongo.topology.s.options.useUnifiedTopology);
|
|
56
|
+
}
|
|
52
57
|
} else if (config.db) {
|
|
53
58
|
this.database(config.db.address, config.db.collection, config.db.options, cb);
|
|
54
59
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class
|
|
3
|
+
* @param {Object} args - Job Options
|
|
4
|
+
* @property {Object} agenda - The Agenda instance
|
|
5
|
+
* @property {Object} attrs
|
|
6
|
+
*/
|
|
7
|
+
class JobProcessingQueue {
|
|
8
|
+
constructor() {
|
|
9
|
+
this._queue = [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get length() {
|
|
13
|
+
return this._queue.length;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pops and returns last queue element (next job to be processed) without checking concurrency.
|
|
19
|
+
* @returns {Job} Next Job to be processed
|
|
20
|
+
*/
|
|
21
|
+
JobProcessingQueue.prototype.pop = function() {
|
|
22
|
+
return this._queue.pop();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Inserts job in first queue position
|
|
27
|
+
* @param {Job} job job to add to queue
|
|
28
|
+
* @returns {undefined}
|
|
29
|
+
*/
|
|
30
|
+
JobProcessingQueue.prototype.push = function(job) {
|
|
31
|
+
this._queue.push(job);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Inserts job in queue where it will be order from left to right in decreasing
|
|
36
|
+
* order of nextRunAt and priority (in case of same nextRunAt), if all values
|
|
37
|
+
* are even the first jobs to be introduced will have priority
|
|
38
|
+
* @param {Job} job job to add to queue
|
|
39
|
+
* @returns {undefined}
|
|
40
|
+
*/
|
|
41
|
+
JobProcessingQueue.prototype.insert = function(job) {
|
|
42
|
+
const matchIndex = this._queue.findIndex(element => {
|
|
43
|
+
if (element.attrs.nextRunAt.getTime() <= job.attrs.nextRunAt.getTime()) {
|
|
44
|
+
if (element.attrs.nextRunAt.getTime() === job.attrs.nextRunAt.getTime()) {
|
|
45
|
+
if (element.attrs.priority >= job.attrs.priority) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (matchIndex === -1) {
|
|
57
|
+
this._queue.push(job);
|
|
58
|
+
} else {
|
|
59
|
+
this._queue.splice(matchIndex, 0, job);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns (does not pop, element remains in queue) first element (always from the right)
|
|
65
|
+
* that can be processed (not blocked by concurrency execution)
|
|
66
|
+
* @param {Object} agendaDefinitions job to add to queue
|
|
67
|
+
* @returns {Job} Next Job to be processed
|
|
68
|
+
*/
|
|
69
|
+
JobProcessingQueue.prototype.returnNextConcurrencyFreeJob = function(agendaDefinitions) {
|
|
70
|
+
let next;
|
|
71
|
+
for (next = this._queue.length - 1; next > 0; next -= 1) {
|
|
72
|
+
const def = agendaDefinitions[this._queue[next].attrs.name];
|
|
73
|
+
if (def.concurrency > def.running) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this._queue[next];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
module.exports = JobProcessingQueue;
|
package/lib/agenda/jobs.js
CHANGED
|
@@ -8,13 +8,15 @@ const {createJob} = require('../utils');
|
|
|
8
8
|
* @param {Object} query object for MongoDB
|
|
9
9
|
* @param {Object} sort object for MongoDB
|
|
10
10
|
* @param {Number} limit number of documents to return from MongoDB
|
|
11
|
+
* @param {Number} number of documents to skip in MongoDB
|
|
11
12
|
* @returns {Promise} resolves when fails or passes
|
|
12
13
|
*/
|
|
13
|
-
module.exports = async function(query = {}, sort = {}, limit = 0) {
|
|
14
|
+
module.exports = async function(query = {}, sort = {}, limit = 0, skip = 0) {
|
|
14
15
|
const result = await this._collection
|
|
15
16
|
.find(query)
|
|
16
17
|
.sort(sort)
|
|
17
18
|
.limit(limit)
|
|
19
|
+
.skip(skip)
|
|
18
20
|
.toArray();
|
|
19
21
|
|
|
20
22
|
return result.map(job => createJob(this, job));
|
package/lib/agenda/now.js
CHANGED
|
@@ -8,16 +8,21 @@ const noCallback = require('../no-callback');
|
|
|
8
8
|
* @function
|
|
9
9
|
* @param {String} name name of job to schedule
|
|
10
10
|
* @param {Object} data data to pass to job
|
|
11
|
-
* @returns {
|
|
11
|
+
* @returns {Promise} resolves with the new job instance created
|
|
12
12
|
*/
|
|
13
13
|
module.exports = async function(name, data) {
|
|
14
|
-
// eslint-disable-next-line prefer-rest-params
|
|
15
|
-
noCallback(arguments, 2);
|
|
16
14
|
debug('Agenda.now(%s, [Object])', name);
|
|
17
|
-
|
|
15
|
+
try {
|
|
16
|
+
// eslint-disable-next-line prefer-rest-params
|
|
17
|
+
noCallback(arguments, 2);
|
|
18
|
+
const job = this.create(name, data);
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
job.schedule(new Date());
|
|
21
|
+
await job.save();
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
return job;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
debug('error trying to create a job for this exact moment');
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
23
28
|
};
|
package/lib/agenda/save-job.js
CHANGED
|
@@ -126,12 +126,13 @@ module.exports = async function(job) {
|
|
|
126
126
|
const result = await this._collection.findOneAndUpdate(query, update, {upsert: true, returnOriginal: false});
|
|
127
127
|
return processDbResult(job, result);
|
|
128
128
|
}
|
|
129
|
+
|
|
129
130
|
// If all else fails, the job does not exist yet so we just insert it into MongoDB
|
|
130
131
|
debug('using default behavior, inserting new job via insertOne() with props that were set: \n%O', props);
|
|
131
132
|
const result = await this._collection.insertOne(props);
|
|
132
133
|
return processDbResult(job, result);
|
|
133
|
-
} catch (
|
|
134
|
+
} catch (error) {
|
|
134
135
|
debug('processDbResult() received an error, job was not updated/created');
|
|
135
|
-
throw
|
|
136
|
+
throw error;
|
|
136
137
|
}
|
|
137
138
|
};
|
package/lib/agenda/schedule.js
CHANGED
|
@@ -40,9 +40,9 @@ module.exports = function(when, names, data) {
|
|
|
40
40
|
const jobs = await Promise.all(names.map(name => createJob(when, name, data)));
|
|
41
41
|
debug('Agenda.schedule()::createJobs() -> all jobs created successfully');
|
|
42
42
|
return jobs;
|
|
43
|
-
} catch (
|
|
43
|
+
} catch (error) {
|
|
44
44
|
debug('Agenda.schedule()::createJobs() -> error creating one or more of the jobs');
|
|
45
|
-
throw
|
|
45
|
+
throw error;
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
|
package/lib/agenda/start.js
CHANGED
|
@@ -14,6 +14,7 @@ module.exports = async function() {
|
|
|
14
14
|
debug('Agenda.start was already called, ignoring');
|
|
15
15
|
return this._ready;
|
|
16
16
|
}
|
|
17
|
+
|
|
17
18
|
await this._ready;
|
|
18
19
|
debug('Agenda.start called, creating interval to call processJobs every [%dms]', this._processEvery);
|
|
19
20
|
this._processInterval = setInterval(processJobs.bind(this), this._processEvery);
|
|
@@ -15,6 +15,7 @@ module.exports = function() {
|
|
|
15
15
|
const interval = this.attrs.repeatInterval;
|
|
16
16
|
const timezone = this.attrs.repeatTimezone;
|
|
17
17
|
const {repeatAt} = this.attrs;
|
|
18
|
+
const previousNextRunAt = this.attrs.nextRunAt || new Date();
|
|
18
19
|
this.attrs.nextRunAt = undefined;
|
|
19
20
|
|
|
20
21
|
const dateForTimezone = date => {
|
|
@@ -22,6 +23,7 @@ module.exports = function() {
|
|
|
22
23
|
if (timezone !== null) {
|
|
23
24
|
date.tz(timezone);
|
|
24
25
|
}
|
|
26
|
+
|
|
25
27
|
return date;
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -36,23 +38,26 @@ module.exports = function() {
|
|
|
36
38
|
try {
|
|
37
39
|
const cronTime = new CronTime(interval);
|
|
38
40
|
let nextDate = cronTime._getNextDateFrom(lastRun);
|
|
39
|
-
if (nextDate.valueOf() === lastRun.valueOf()) {
|
|
41
|
+
if (nextDate.valueOf() === lastRun.valueOf() || nextDate.valueOf() <= previousNextRunAt.valueOf()) {
|
|
40
42
|
// Handle cronTime giving back the same date for the next run time
|
|
41
43
|
nextDate = cronTime._getNextDateFrom(dateForTimezone(new Date(lastRun.valueOf() + 1000)));
|
|
42
44
|
}
|
|
45
|
+
|
|
43
46
|
this.attrs.nextRunAt = nextDate;
|
|
44
|
-
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString());
|
|
45
|
-
|
|
47
|
+
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, new Date(this.attrs.nextRunAt).toISOString());
|
|
48
|
+
// Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored
|
|
49
|
+
} catch (error) { // eslint-disable-line no-unused-vars
|
|
46
50
|
// Nope, humanInterval then!
|
|
47
51
|
try {
|
|
48
52
|
if (!this.attrs.lastRunAt && humanInterval(interval)) {
|
|
49
53
|
this.attrs.nextRunAt = lastRun.valueOf();
|
|
50
|
-
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString());
|
|
54
|
+
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, new Date(this.attrs.nextRunAt).toISOString());
|
|
51
55
|
} else {
|
|
52
56
|
this.attrs.nextRunAt = lastRun.valueOf() + humanInterval(interval);
|
|
53
|
-
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString());
|
|
57
|
+
debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, new Date(this.attrs.nextRunAt).toISOString());
|
|
54
58
|
}
|
|
55
|
-
|
|
59
|
+
// Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored
|
|
60
|
+
} catch (error) {} // eslint-disable-line no-unused-vars
|
|
56
61
|
} finally {
|
|
57
62
|
if (isNaN(this.attrs.nextRunAt)) {
|
|
58
63
|
this.attrs.nextRunAt = undefined;
|
|
@@ -90,5 +95,6 @@ module.exports = function() {
|
|
|
90
95
|
} else if (repeatAt) {
|
|
91
96
|
computeFromRepeatAt.call(this);
|
|
92
97
|
}
|
|
98
|
+
|
|
93
99
|
return this;
|
|
94
100
|
};
|
package/lib/job/fail.js
CHANGED
package/lib/job/index.js
CHANGED
package/lib/job/is-running.js
CHANGED
|
@@ -13,11 +13,14 @@ module.exports = function() {
|
|
|
13
13
|
if (!this.attrs.lastRunAt) {
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
|
+
|
|
16
17
|
if (!this.attrs.lastFinishedAt) {
|
|
17
18
|
return true;
|
|
18
19
|
}
|
|
20
|
+
|
|
19
21
|
if (this.attrs.lockedAt && this.attrs.lastRunAt.getTime() > this.attrs.lastFinishedAt.getTime()) {
|
|
20
22
|
return true;
|
|
21
23
|
}
|
|
24
|
+
|
|
22
25
|
return false;
|
|
23
26
|
};
|
package/lib/job/priority.js
CHANGED
package/lib/job/repeat-every.js
CHANGED
|
@@ -13,9 +13,13 @@ module.exports = function(interval, options) {
|
|
|
13
13
|
this.attrs.repeatInterval = interval;
|
|
14
14
|
this.attrs.repeatTimezone = options.timezone ? options.timezone : null;
|
|
15
15
|
if (options.skipImmediate) {
|
|
16
|
-
|
|
16
|
+
// Set the lastRunAt time to the nextRunAt so that the new nextRunAt will be computed in reference to the current value.
|
|
17
|
+
this.attrs.lastRunAt = this.attrs.nextRunAt || new Date();
|
|
17
18
|
this.computeNextRunAt();
|
|
18
19
|
this.attrs.lastRunAt = undefined;
|
|
20
|
+
} else {
|
|
21
|
+
this.computeNextRunAt();
|
|
19
22
|
}
|
|
23
|
+
|
|
20
24
|
return this;
|
|
21
25
|
};
|
package/lib/job/run.js
CHANGED
|
@@ -12,6 +12,8 @@ module.exports = function() {
|
|
|
12
12
|
const {agenda} = self;
|
|
13
13
|
const definition = agenda._definitions[self.attrs.name];
|
|
14
14
|
|
|
15
|
+
// @TODO: this lint issue should be looked into: https://eslint.org/docs/rules/no-async-promise-executor
|
|
16
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
15
17
|
return new Promise(async(resolve, reject) => {
|
|
16
18
|
self.attrs.lastRunAt = new Date();
|
|
17
19
|
debug('[%s:%s] setting lastRunAt to: %s', self.attrs.name, self.attrs._id, self.attrs.lastRunAt.toISOString());
|
|
@@ -24,6 +26,7 @@ module.exports = function() {
|
|
|
24
26
|
if (finished) {
|
|
25
27
|
return;
|
|
26
28
|
}
|
|
29
|
+
|
|
27
30
|
finished = true;
|
|
28
31
|
|
|
29
32
|
if (err) {
|
|
@@ -34,9 +37,9 @@ module.exports = function() {
|
|
|
34
37
|
|
|
35
38
|
self.attrs.lockedAt = null;
|
|
36
39
|
|
|
37
|
-
await self.save().catch(
|
|
40
|
+
await self.save().catch(error => {
|
|
38
41
|
debug('[%s:%s] failed to be saved to MongoDB', self.attrs.name, self.attrs._id);
|
|
39
|
-
reject(
|
|
42
|
+
reject(error);
|
|
40
43
|
});
|
|
41
44
|
debug('[%s:%s] was saved successfully to MongoDB', self.attrs.name, self.attrs._id);
|
|
42
45
|
|
|
@@ -49,6 +52,7 @@ module.exports = function() {
|
|
|
49
52
|
agenda.emit('success:' + self.attrs.name, self);
|
|
50
53
|
debug('[%s:%s] has succeeded', self.attrs.name, self.attrs._id);
|
|
51
54
|
}
|
|
55
|
+
|
|
52
56
|
agenda.emit('complete', self);
|
|
53
57
|
agenda.emit('complete:' + self.attrs.name, self);
|
|
54
58
|
debug('[%s:%s] job finished at [%s] and was unlocked', self.attrs.name, self.attrs._id, self.attrs.lastFinishedAt);
|
|
@@ -65,6 +69,7 @@ module.exports = function() {
|
|
|
65
69
|
debug('[%s:%s] has no definition, can not run', self.attrs.name, self.attrs._id);
|
|
66
70
|
throw new Error('Undefined job');
|
|
67
71
|
}
|
|
72
|
+
|
|
68
73
|
if (definition.fn.length === 2) {
|
|
69
74
|
debug('[%s:%s] process function being called', self.attrs.name, self.attrs._id);
|
|
70
75
|
await definition.fn(self, jobCallback);
|
|
@@ -73,9 +78,9 @@ module.exports = function() {
|
|
|
73
78
|
await definition.fn(self);
|
|
74
79
|
await jobCallback();
|
|
75
80
|
}
|
|
76
|
-
} catch (
|
|
81
|
+
} catch (error) {
|
|
77
82
|
debug('[%s:%s] unknown error occurred', self.attrs.name, self.attrs._id);
|
|
78
|
-
await jobCallback(
|
|
83
|
+
await jobCallback(error);
|
|
79
84
|
}
|
|
80
85
|
});
|
|
81
86
|
};
|
|
@@ -52,9 +52,11 @@ module.exports = function(extraJob) {
|
|
|
52
52
|
if (self._lockLimit && self._lockLimit <= self._lockedJobs.length) {
|
|
53
53
|
shouldLock = false;
|
|
54
54
|
}
|
|
55
|
+
|
|
55
56
|
if (jobDefinition.lockLimit && jobDefinition.lockLimit <= jobDefinition.locked) {
|
|
56
57
|
shouldLock = false;
|
|
57
58
|
}
|
|
59
|
+
|
|
58
60
|
debug('job [%s] lock status: shouldLock = %s', name, shouldLock);
|
|
59
61
|
return shouldLock;
|
|
60
62
|
}
|
|
@@ -65,46 +67,13 @@ module.exports = function(extraJob) {
|
|
|
65
67
|
* @param {boolean} inFront puts the job in front of queue if true
|
|
66
68
|
* @returns {undefined}
|
|
67
69
|
*/
|
|
68
|
-
function enqueueJobs(jobs
|
|
70
|
+
function enqueueJobs(jobs) {
|
|
69
71
|
if (!Array.isArray(jobs)) {
|
|
70
72
|
jobs = [jobs];
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
jobs.forEach(job => {
|
|
74
|
-
|
|
75
|
-
let start;
|
|
76
|
-
let loopCondition;
|
|
77
|
-
let endCondition;
|
|
78
|
-
let inc;
|
|
79
|
-
|
|
80
|
-
if (inFront) {
|
|
81
|
-
start = jobQueue.length ? jobQueue.length - 1 : 0;
|
|
82
|
-
inc = -1;
|
|
83
|
-
loopCondition = function() {
|
|
84
|
-
return jobIndex >= 0;
|
|
85
|
-
};
|
|
86
|
-
endCondition = function(queuedJob) {
|
|
87
|
-
return !queuedJob || queuedJob.attrs.priority < job.attrs.priority;
|
|
88
|
-
};
|
|
89
|
-
} else {
|
|
90
|
-
start = 0;
|
|
91
|
-
inc = 1;
|
|
92
|
-
loopCondition = function() {
|
|
93
|
-
return jobIndex < jobQueue.length;
|
|
94
|
-
};
|
|
95
|
-
endCondition = function(queuedJob) {
|
|
96
|
-
return queuedJob.attrs.priority >= job.attrs.priority;
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
for (jobIndex = start; loopCondition(); jobIndex += inc) {
|
|
101
|
-
if (endCondition(jobQueue[jobIndex])) {
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Insert the job to the queue at its prioritized position for processing
|
|
107
|
-
jobQueue.splice(jobIndex, 0, job);
|
|
76
|
+
jobQueue.insert(job);
|
|
108
77
|
});
|
|
109
78
|
}
|
|
110
79
|
|
|
@@ -114,7 +83,7 @@ module.exports = function(extraJob) {
|
|
|
114
83
|
* We do this because sometimes jobs are scheduled but will be run before the next process time
|
|
115
84
|
* @returns {undefined}
|
|
116
85
|
*/
|
|
117
|
-
function lockOnTheFly() {
|
|
86
|
+
async function lockOnTheFly() {
|
|
118
87
|
// Already running this? Return
|
|
119
88
|
if (self._isLockingOnTheFly) {
|
|
120
89
|
debug('lockOnTheFly() already running, returning');
|
|
@@ -158,26 +127,22 @@ module.exports = function(extraJob) {
|
|
|
158
127
|
const options = {returnOriginal: false};
|
|
159
128
|
|
|
160
129
|
// Lock the job in MongoDB!
|
|
161
|
-
self._collection.findOneAndUpdate(criteria, update, options
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
enqueueJobs(job);
|
|
172
|
-
jobProcessing();
|
|
173
|
-
}
|
|
130
|
+
const resp = await self._collection.findOneAndUpdate(criteria, update, options);
|
|
131
|
+
|
|
132
|
+
if (resp.value) {
|
|
133
|
+
const job = createJob(self, resp.value);
|
|
134
|
+
debug('found job [%s] that can be locked on the fly', job.attrs.name);
|
|
135
|
+
self._lockedJobs.push(job);
|
|
136
|
+
definitions[job.attrs.name].locked++;
|
|
137
|
+
enqueueJobs(job);
|
|
138
|
+
jobProcessing();
|
|
139
|
+
}
|
|
174
140
|
|
|
175
|
-
|
|
176
|
-
|
|
141
|
+
// Mark lock on fly is done for now
|
|
142
|
+
self._isLockingOnTheFly = false;
|
|
177
143
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
144
|
+
// Re-run in case anything is in the queue
|
|
145
|
+
await lockOnTheFly();
|
|
181
146
|
}
|
|
182
147
|
|
|
183
148
|
/**
|
|
@@ -185,7 +150,7 @@ module.exports = function(extraJob) {
|
|
|
185
150
|
* @param {String} name fill a queue with specific job name
|
|
186
151
|
* @returns {undefined}
|
|
187
152
|
*/
|
|
188
|
-
function jobQueueFilling(name) {
|
|
153
|
+
async function jobQueueFilling(name) {
|
|
189
154
|
// Don't lock because of a limit we have set (lockLimit, etc)
|
|
190
155
|
if (!shouldLock(name)) {
|
|
191
156
|
debug('lock limit reached in queue filling for [%s]', name);
|
|
@@ -197,12 +162,8 @@ module.exports = function(extraJob) {
|
|
|
197
162
|
self._nextScanAt = new Date(now.valueOf() + self._processEvery);
|
|
198
163
|
|
|
199
164
|
// For this job name, find the next job to run and lock it!
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
debug('[%s] job lock failed while filling queue', name);
|
|
203
|
-
throw err;
|
|
204
|
-
}
|
|
205
|
-
|
|
165
|
+
try {
|
|
166
|
+
const job = await self._findAndLockNextJob(name, definitions[name]);
|
|
206
167
|
// Still have the job?
|
|
207
168
|
// 1. Add it to lock list
|
|
208
169
|
// 2. Add count of locked jobs
|
|
@@ -213,10 +174,12 @@ module.exports = function(extraJob) {
|
|
|
213
174
|
self._lockedJobs.push(job);
|
|
214
175
|
definitions[job.attrs.name].locked++;
|
|
215
176
|
enqueueJobs(job);
|
|
216
|
-
jobQueueFilling(name);
|
|
177
|
+
await jobQueueFilling(name);
|
|
217
178
|
jobProcessing();
|
|
218
179
|
}
|
|
219
|
-
})
|
|
180
|
+
} catch (error) {
|
|
181
|
+
debug('[%s] job lock failed while filling queue', name, error);
|
|
182
|
+
}
|
|
220
183
|
}
|
|
221
184
|
|
|
222
185
|
/**
|
|
@@ -233,29 +196,19 @@ module.exports = function(extraJob) {
|
|
|
233
196
|
const now = new Date();
|
|
234
197
|
|
|
235
198
|
// Get the next job that is not blocked by concurrency
|
|
236
|
-
|
|
237
|
-
for (next = jobQueue.length - 1; next > 0; next -= 1) {
|
|
238
|
-
const def = definitions[jobQueue[next].attrs.name];
|
|
239
|
-
if (def.concurrency > def.running) {
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// We now have the job we are going to process and its definition
|
|
245
|
-
const job = jobQueue.splice(next, 1)[0];
|
|
246
|
-
const jobDefinition = definitions[job.attrs.name];
|
|
199
|
+
const job = jobQueue.returnNextConcurrencyFreeJob(definitions);
|
|
247
200
|
|
|
248
201
|
debug('[%s:%s] about to process job', job.attrs.name, job.attrs._id);
|
|
249
202
|
|
|
250
203
|
// If the 'nextRunAt' time is older than the current time, run the job
|
|
251
204
|
// Otherwise, setTimeout that gets called at the time of 'nextRunAt'
|
|
252
|
-
if (job.attrs.nextRunAt
|
|
205
|
+
if (job.attrs.nextRunAt <= now) {
|
|
253
206
|
debug('[%s:%s] nextRunAt is in the past, run the job immediately', job.attrs.name, job.attrs._id);
|
|
254
207
|
runOrRetry();
|
|
255
208
|
} else {
|
|
256
209
|
const runIn = job.attrs.nextRunAt - now;
|
|
257
210
|
debug('[%s:%s] nextRunAt is in the future, calling setTimeout(%d)', job.attrs.name, job.attrs._id, runIn);
|
|
258
|
-
setTimeout(
|
|
211
|
+
setTimeout(jobProcessing, runIn);
|
|
259
212
|
}
|
|
260
213
|
|
|
261
214
|
/**
|
|
@@ -264,6 +217,8 @@ module.exports = function(extraJob) {
|
|
|
264
217
|
*/
|
|
265
218
|
async function runOrRetry() {
|
|
266
219
|
if (self._processInterval) {
|
|
220
|
+
const job = jobQueue.pop();
|
|
221
|
+
const jobDefinition = definitions[job.attrs.name];
|
|
267
222
|
if (jobDefinition.concurrency > jobDefinition.running && self._runningJobs.length < self._maxConcurrency) {
|
|
268
223
|
// Get the deadline of when the job is not supposed to go past for locking
|
|
269
224
|
const lockDeadline = new Date(Date.now() - jobDefinition.lockLifetime);
|
|
@@ -287,15 +242,12 @@ module.exports = function(extraJob) {
|
|
|
287
242
|
debug('[%s:%s] processing job', job.attrs.name, job.attrs._id);
|
|
288
243
|
|
|
289
244
|
job.run()
|
|
290
|
-
.catch(
|
|
245
|
+
.catch(error => [error, job])
|
|
291
246
|
.then(job => processJobResult(...Array.isArray(job) ? job : [null, job])); // eslint-disable-line promise/prefer-await-to-then
|
|
292
|
-
|
|
293
|
-
// Re-run the loop to check for more jobs to process (locally)
|
|
294
|
-
jobProcessing();
|
|
295
247
|
} else {
|
|
296
248
|
// Run the job immediately by putting it on the top of the queue
|
|
297
249
|
debug('[%s:%s] concurrency preventing immediate run, pushing job to top of queue', job.attrs.name, job.attrs._id);
|
|
298
|
-
enqueueJobs(job
|
|
250
|
+
enqueueJobs(job);
|
|
299
251
|
}
|
|
300
252
|
}
|
|
301
253
|
}
|
|
@@ -311,10 +263,11 @@ module.exports = function(extraJob) {
|
|
|
311
263
|
if (err) {
|
|
312
264
|
return job.agenda.emit('error', err);
|
|
313
265
|
}
|
|
266
|
+
|
|
314
267
|
const {name} = job.attrs;
|
|
315
268
|
|
|
316
269
|
// Job isn't in running jobs so throw an error
|
|
317
|
-
if (self._runningJobs.
|
|
270
|
+
if (!self._runningJobs.includes(job)) {
|
|
318
271
|
debug('[%s] callback was called, job must have been marked as complete already', job.attrs._id);
|
|
319
272
|
throw new Error('callback already called - job ' + name + ' already marked complete');
|
|
320
273
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenda",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Light weight job scheduler for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"lib"
|
|
8
8
|
],
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
10
|
+
"node": ">=10.0.0"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "npm run lint && npm run mocha",
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
"mocha-debug": "DEBUG=agenda:**,-agenda:internal:** mocha --reporter spec --timeout 8000 -b",
|
|
17
17
|
"mocha-debug-internal": "DEBUG=agenda:internal:** mocha --reporter spec --timeout 8000 -b",
|
|
18
18
|
"mocha-debug-all": "DEBUG=agenda:** mocha --reporter spec --timeout 8000 -b",
|
|
19
|
+
"docs": "jsdoc --configure .jsdoc.json --verbose"
|
|
20
|
+
},
|
|
21
|
+
"config": {
|
|
19
22
|
"blanket": {
|
|
20
23
|
"pattern": "lib",
|
|
21
24
|
"data-cover-never": "node_modules"
|
|
22
|
-
}
|
|
23
|
-
"docs": "jsdoc --configure .jsdoc.json --verbose"
|
|
25
|
+
}
|
|
24
26
|
},
|
|
25
27
|
"repository": {
|
|
26
28
|
"type": "git",
|
|
@@ -40,29 +42,38 @@
|
|
|
40
42
|
"url": "https://github.com/agenda/agenda/issues"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"cron": "~1.
|
|
45
|
+
"cron": "~1.8.0",
|
|
44
46
|
"date.js": "~0.3.3",
|
|
45
47
|
"debug": "~4.1.1",
|
|
46
|
-
"human-interval": "~0.
|
|
47
|
-
"moment-timezone": "~0.5.
|
|
48
|
-
"mongodb": "~3.
|
|
48
|
+
"human-interval": "~1.0.0",
|
|
49
|
+
"moment-timezone": "~0.5.27",
|
|
50
|
+
"mongodb": "~3.5.0"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
53
|
"blanket": "1.2.3",
|
|
52
|
-
"coveralls": "3.0.
|
|
54
|
+
"coveralls": "3.0.11",
|
|
53
55
|
"delay": "4.3.0",
|
|
54
|
-
"eslint": "
|
|
56
|
+
"eslint": "6.8.0",
|
|
57
|
+
"eslint-config-xo": "0.27.2",
|
|
58
|
+
"eslint-plugin-ava": "10.2.0",
|
|
59
|
+
"eslint-plugin-eslint-comments": "3.1.2",
|
|
60
|
+
"eslint-plugin-import": "2.20.2",
|
|
61
|
+
"eslint-plugin-node": "11.1.0",
|
|
62
|
+
"eslint-plugin-unicorn": "16.1.1",
|
|
55
63
|
"expect.js": "0.3.1",
|
|
56
|
-
"jsdoc": "3.6.
|
|
64
|
+
"jsdoc": "3.6.4",
|
|
57
65
|
"jsdoc-template": "https://github.com/braintree/jsdoc-template",
|
|
58
|
-
"mocha": "
|
|
66
|
+
"mocha": "7.1.1",
|
|
59
67
|
"mocha-lcov-reporter": "1.3.0",
|
|
60
68
|
"q": "1.5.1",
|
|
61
|
-
"sinon": "
|
|
62
|
-
"xo": "0.
|
|
69
|
+
"sinon": "9.0.1",
|
|
70
|
+
"xo": "0.27.2"
|
|
63
71
|
},
|
|
64
72
|
"xo": {
|
|
65
73
|
"space": 2,
|
|
74
|
+
"ignores": [
|
|
75
|
+
"docs/**"
|
|
76
|
+
],
|
|
66
77
|
"rules": {
|
|
67
78
|
"space-before-function-paren": [
|
|
68
79
|
"error",
|