@abtnode/queue 1.15.17 → 1.16.0-beta-b16cb035
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/lib/index.js +109 -7
- package/lib/logger.js +6 -6
- package/lib/store.js +12 -8
- package/package.json +7 -8
package/lib/index.js
CHANGED
|
@@ -3,12 +3,14 @@ const uuid = require('uuid');
|
|
|
3
3
|
const Queue = require('fastq');
|
|
4
4
|
const EventEmitter = require('events');
|
|
5
5
|
const tryWithTimeout = require('@abtnode/util/lib/try-with-timeout');
|
|
6
|
+
const sleep = require('@abtnode/util/lib/sleep');
|
|
6
7
|
const debug = require('debug')('core/queue');
|
|
7
8
|
|
|
8
9
|
const JobStore = require('./store');
|
|
9
10
|
const logger = require('./logger');
|
|
10
11
|
|
|
11
12
|
const CANCELLED = '__CANCELLED__';
|
|
13
|
+
const MIN_DELAY = 5;
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
*
|
|
@@ -21,6 +23,7 @@ const CANCELLED = '__CANCELLED__';
|
|
|
21
23
|
* @param {number} Options.options.maxRetries [param=1] number of max retries, default 1
|
|
22
24
|
* @param {number} Options.options.maxTimeout [param=86400000] max timeout, in ms, default 86400000ms(1d)
|
|
23
25
|
* @param {number} Options.options.retryDelay [param=0] retry delay, in ms, default 0ms
|
|
26
|
+
* @param {boolean} Options.options.enableScheduledJob [param=false] enable scheduled job or not, default is false
|
|
24
27
|
*/
|
|
25
28
|
module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
26
29
|
if (typeof onJob !== 'function') {
|
|
@@ -38,6 +41,7 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
38
41
|
const concurrency = Math.max(options.concurrency || defaults.concurrency, 1);
|
|
39
42
|
const maxTimeout = Math.max(options.maxTimeout || defaults.maxTimeout, 0);
|
|
40
43
|
const retryDelay = Math.max(options.retryDelay || defaults.retryDelay, 0);
|
|
44
|
+
const enableScheduledJob = typeof options.enableScheduledJob === 'boolean' ? options.enableScheduledJob : false;
|
|
41
45
|
|
|
42
46
|
if (typeof options.maxRetries === 'number' && options.maxRetries >= 0) {
|
|
43
47
|
maxRetries = options.maxRetries;
|
|
@@ -46,6 +50,9 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
46
50
|
const store = new JobStore(file);
|
|
47
51
|
const queueEvents = new EventEmitter();
|
|
48
52
|
|
|
53
|
+
const getJobId = (jobId, job) =>
|
|
54
|
+
jobId || (typeof options.id === 'function' ? options.id(job) : uuid.v4()) || uuid.v4();
|
|
55
|
+
|
|
49
56
|
const queue = Queue(async ({ job, id, persist }, cb) => {
|
|
50
57
|
if (persist) {
|
|
51
58
|
try {
|
|
@@ -68,7 +75,32 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
68
75
|
}
|
|
69
76
|
}, concurrency);
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Push job to the queue, the old way of calling `push(job, jobId, persist)` will be deprecated
|
|
80
|
+
* @param {object} args
|
|
81
|
+
* @param {object} args.job The data of the job
|
|
82
|
+
* @param {string} args.jobId Optional, custom jobId
|
|
83
|
+
* @param {boolean} args.persist [persist=true] Persisting the job to the database
|
|
84
|
+
* @param {number} args.delay Optional, default with no delay, unit is second, for example: 10 will run after 10s
|
|
85
|
+
* @returns
|
|
86
|
+
*/
|
|
87
|
+
const push = (...args) => {
|
|
88
|
+
let job;
|
|
89
|
+
let jobId;
|
|
90
|
+
let persist;
|
|
91
|
+
let delay;
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
args.length === 1 &&
|
|
95
|
+
args[0] &&
|
|
96
|
+
typeof args[0] === 'object' &&
|
|
97
|
+
(args[0].job || args[0].jobId || args[0].persist)
|
|
98
|
+
) {
|
|
99
|
+
[{ job, jobId, persist = true, delay }] = args;
|
|
100
|
+
} else {
|
|
101
|
+
[job, jobId, persist = true] = args;
|
|
102
|
+
}
|
|
103
|
+
|
|
72
104
|
const jobEvents = new EventEmitter();
|
|
73
105
|
const emit = (e, data) => {
|
|
74
106
|
queueEvents.emit(e, data);
|
|
@@ -79,11 +111,46 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
79
111
|
throw new Error('Can not queue empty job');
|
|
80
112
|
}
|
|
81
113
|
|
|
82
|
-
const id = jobId
|
|
114
|
+
const id = getJobId(jobId, job);
|
|
115
|
+
|
|
116
|
+
if (delay) {
|
|
117
|
+
if (!enableScheduledJob) {
|
|
118
|
+
throw new Error('The queue must have options.enableScheduledJob set to true to run delay jobs');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 这里不是精确的 delay, 延迟的时间太短没有意义,所以这里限制了最小 delay
|
|
122
|
+
if (delay < MIN_DELAY) {
|
|
123
|
+
throw new Error(`minimum delay is ${MIN_DELAY}s`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
store.addJob(
|
|
127
|
+
id,
|
|
128
|
+
job,
|
|
129
|
+
(err) => {
|
|
130
|
+
if (err) {
|
|
131
|
+
logger.error('failed to add job', err);
|
|
132
|
+
emit('failed', { id, job, error: err });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
emit('queued', { id, job });
|
|
137
|
+
},
|
|
138
|
+
{ delay, willRunAt: Date.now() + delay * 1000 }
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
jobEvents.id = id;
|
|
142
|
+
|
|
143
|
+
return jobEvents;
|
|
144
|
+
}
|
|
83
145
|
|
|
84
146
|
// eslint-disable-next-line no-shadow
|
|
85
|
-
const clearJob = (id) => {
|
|
147
|
+
const clearJob = (id, cb) => {
|
|
86
148
|
store.deleteJob(id, (e) => {
|
|
149
|
+
if (typeof cb === 'function') {
|
|
150
|
+
cb(e);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
87
154
|
if (e) {
|
|
88
155
|
logger.error('Failed to delete job from persistent store', { error: e, id });
|
|
89
156
|
}
|
|
@@ -112,8 +179,9 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
112
179
|
|
|
113
180
|
if (attrs.retryCount >= maxRetries) {
|
|
114
181
|
logger.info('fail job', { id });
|
|
115
|
-
|
|
116
|
-
|
|
182
|
+
clearJob(id, () => {
|
|
183
|
+
emit('failed', { id, job, error: err });
|
|
184
|
+
});
|
|
117
185
|
return;
|
|
118
186
|
}
|
|
119
187
|
|
|
@@ -162,11 +230,22 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
162
230
|
|
|
163
231
|
const cancel = async (id) =>
|
|
164
232
|
new Promise((resolve, reject) => {
|
|
165
|
-
store.updateJob(id, { cancelled: true }, (e,
|
|
233
|
+
store.updateJob(id, { cancelled: true }, (e, doc) => {
|
|
166
234
|
if (e) {
|
|
167
235
|
return reject(e);
|
|
168
236
|
}
|
|
169
|
-
return resolve(
|
|
237
|
+
return resolve(doc ? doc.job : null);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const getJob = async (id) =>
|
|
242
|
+
new Promise((resolve, reject) => {
|
|
243
|
+
store.getJob(id, (e, doc) => {
|
|
244
|
+
if (e) {
|
|
245
|
+
return reject(e);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return resolve(doc ? doc.job : null);
|
|
170
249
|
});
|
|
171
250
|
});
|
|
172
251
|
|
|
@@ -195,15 +274,38 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
|
|
|
195
274
|
});
|
|
196
275
|
});
|
|
197
276
|
|
|
277
|
+
const loop = async () => {
|
|
278
|
+
if (enableScheduledJob === true) {
|
|
279
|
+
// eslint-disable-next-line no-constant-condition
|
|
280
|
+
while (true) {
|
|
281
|
+
/* eslint-disable no-await-in-loop */
|
|
282
|
+
const jobs = await store.getScheduledJobs();
|
|
283
|
+
jobs.forEach((x) => {
|
|
284
|
+
if (x.job && x._id) {
|
|
285
|
+
push(x.job, x._id, false);
|
|
286
|
+
} else {
|
|
287
|
+
logger.info('skip invalid job from db', { job: x });
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await sleep(2000);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
loop();
|
|
297
|
+
|
|
198
298
|
return Object.assign(queueEvents, {
|
|
199
299
|
store,
|
|
200
300
|
push,
|
|
301
|
+
get: getJob,
|
|
201
302
|
cancel,
|
|
202
303
|
options: {
|
|
203
304
|
concurrency,
|
|
204
305
|
maxRetries,
|
|
205
306
|
maxTimeout,
|
|
206
307
|
retryDelay,
|
|
308
|
+
enableScheduledJob,
|
|
207
309
|
},
|
|
208
310
|
});
|
|
209
311
|
};
|
package/lib/logger.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
const debug = require('debug')(require('../package.json').name);
|
|
2
2
|
|
|
3
3
|
const noop = () => {};
|
|
4
|
-
const isTest = process.env.NODE_ENV
|
|
4
|
+
const isTest = !!process.env.NODE_ENV && process.env.NODE_ENV.startsWith('test');
|
|
5
5
|
|
|
6
|
-
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
};
|
|
6
|
+
module.exports = ['error', 'warn', 'info', 'debug', 'verbose', 'silly'].reduce((acc, fn) => {
|
|
7
|
+
acc[fn] = isTest ? noop : debug;
|
|
8
|
+
return acc;
|
|
9
|
+
}, {});
|
package/lib/store.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
2
|
/* eslint-disable func-names */
|
|
3
3
|
/* eslint-disable consistent-return */
|
|
4
|
-
const DataStore =
|
|
5
|
-
process.env.NODE_ENV === 'test' || !process.env.NEDB_MULTI_PORT
|
|
6
|
-
? require('@nedb/core')
|
|
7
|
-
: require('@nedb/multi')(Number(process.env.NEDB_MULTI_PORT));
|
|
4
|
+
const { DataStore } = require('@abtnode/db/lib/base');
|
|
8
5
|
|
|
9
6
|
class JobStore {
|
|
10
7
|
constructor(dbPath) {
|
|
@@ -17,9 +14,9 @@ class JobStore {
|
|
|
17
14
|
|
|
18
15
|
isCancelled(id) {
|
|
19
16
|
return new Promise((resolve, reject) => {
|
|
20
|
-
this.db.findOne({ _id: id }, (e,
|
|
17
|
+
this.db.findOne({ _id: id }, (e, job) => {
|
|
21
18
|
if (e) return reject(e);
|
|
22
|
-
return resolve(!!
|
|
19
|
+
return resolve(!!job && !!job.cancelled);
|
|
23
20
|
});
|
|
24
21
|
});
|
|
25
22
|
}
|
|
@@ -30,7 +27,7 @@ class JobStore {
|
|
|
30
27
|
|
|
31
28
|
getJobs(cb) {
|
|
32
29
|
this.db
|
|
33
|
-
.
|
|
30
|
+
.cursor({ delay: { $exists: false } })
|
|
34
31
|
.sort({ createdAt: 1 })
|
|
35
32
|
.exec((err, result) => {
|
|
36
33
|
if (err) {
|
|
@@ -40,6 +37,13 @@ class JobStore {
|
|
|
40
37
|
});
|
|
41
38
|
}
|
|
42
39
|
|
|
40
|
+
getScheduledJobs() {
|
|
41
|
+
return this.db
|
|
42
|
+
.cursor({ delay: { $exists: true }, willRunAt: { $lte: Date.now() } })
|
|
43
|
+
.sort({ createdAt: 1 })
|
|
44
|
+
.exec();
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
updateJob(id, updates, cb) {
|
|
44
48
|
this.db.findOne({ _id: id }, (err, exist) => {
|
|
45
49
|
if (err) return cb(err);
|
|
@@ -58,7 +62,7 @@ class JobStore {
|
|
|
58
62
|
},
|
|
59
63
|
(e) => {
|
|
60
64
|
if (e) {
|
|
61
|
-
console.error('error updating job',
|
|
65
|
+
console.error('error updating job', e);
|
|
62
66
|
return cb(e, null);
|
|
63
67
|
}
|
|
64
68
|
return cb(null, Object.assign(exist, update));
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.16.0-beta-b16cb035",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,15 +19,14 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/
|
|
23
|
-
"@
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"fastq": "^1.8.0",
|
|
22
|
+
"@abtnode/db": "1.16.0-beta-b16cb035",
|
|
23
|
+
"@abtnode/util": "1.16.0-beta-b16cb035",
|
|
24
|
+
"debug": "^4.3.4",
|
|
25
|
+
"fastq": "^1.13.0",
|
|
27
26
|
"uuid": "7.0.3"
|
|
28
27
|
},
|
|
29
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "138bd91aee5a35b28c2de4e1e924c44932efaf3a",
|
|
30
29
|
"devDependencies": {
|
|
31
|
-
"jest": "^27.
|
|
30
|
+
"jest": "^27.5.1"
|
|
32
31
|
}
|
|
33
32
|
}
|