qdone 2.2.5 → 2.2.6
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.
|
@@ -71,6 +71,9 @@ class JobExecutor {
|
|
|
71
71
|
getExecutionTimeMs(job, start = new Date()) {
|
|
72
72
|
return start - job.executionStart;
|
|
73
73
|
}
|
|
74
|
+
shouldEnforceKillAfter(job) {
|
|
75
|
+
return !!(this.opt.killAfter && job.executionMode !== 'inline');
|
|
76
|
+
}
|
|
74
77
|
scheduleKillAfter(job) {
|
|
75
78
|
if (!this.opt.killAfter)
|
|
76
79
|
return;
|
|
@@ -86,6 +89,8 @@ class JobExecutor {
|
|
|
86
89
|
return;
|
|
87
90
|
if (job.killed)
|
|
88
91
|
return;
|
|
92
|
+
if (!this.shouldEnforceKillAfter(job))
|
|
93
|
+
return;
|
|
89
94
|
const executionTimeMs = this.getExecutionTimeMs(job, start);
|
|
90
95
|
if (executionTimeMs < this.opt.killAfter * 1000)
|
|
91
96
|
return;
|
|
@@ -134,14 +139,10 @@ class JobExecutor {
|
|
|
134
139
|
}, SIGKILL_DELAY_MS);
|
|
135
140
|
job.killSignalTimer.unref?.();
|
|
136
141
|
}
|
|
137
|
-
async
|
|
138
|
-
if (!this.opt.killAfter)
|
|
139
|
-
return;
|
|
140
|
-
const visibilityTimeout = Math.max(1, Math.min(job.visibilityTimeout, this.opt.killAfter));
|
|
141
|
-
if (visibilityTimeout >= job.visibilityTimeout)
|
|
142
|
-
return;
|
|
142
|
+
async setJobVisibilityTimeout(job, visibilityTimeout, start = new Date()) {
|
|
143
143
|
job.visibilityTimeout = visibilityTimeout;
|
|
144
|
-
|
|
144
|
+
const jobRunTime = Math.round((start - job.start) / 1000);
|
|
145
|
+
job.extendAtSecond = Math.round(jobRunTime + job.visibilityTimeout / 2);
|
|
145
146
|
const input = {
|
|
146
147
|
QueueUrl: job.qrl,
|
|
147
148
|
ReceiptHandle: job.message.ReceiptHandle,
|
|
@@ -161,6 +162,51 @@ class JobExecutor {
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
}
|
|
165
|
+
async setRunningVisibilityTimeout(job) {
|
|
166
|
+
if (!this.shouldEnforceKillAfter(job))
|
|
167
|
+
return;
|
|
168
|
+
const visibilityTimeout = Math.max(1, Math.min(job.visibilityTimeout, this.opt.killAfter));
|
|
169
|
+
if (visibilityTimeout >= job.visibilityTimeout)
|
|
170
|
+
return;
|
|
171
|
+
await this.setJobVisibilityTimeout(job, visibilityTimeout);
|
|
172
|
+
}
|
|
173
|
+
async registerInlineExecution(job) {
|
|
174
|
+
if (job.executionMode === 'inline')
|
|
175
|
+
return;
|
|
176
|
+
if (job.executionMode === 'child_process') {
|
|
177
|
+
debug('registerInlineExecution ignored after registerPid', { messageId: job.message?.MessageId });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
job.executionMode = 'inline';
|
|
181
|
+
job.killDue = false;
|
|
182
|
+
this.clearJobTimers(job);
|
|
183
|
+
if (job.status === 'running' && job.visibilityTimeout < defaultVisibilityTimeout) {
|
|
184
|
+
await this.setJobVisibilityTimeout(job, defaultVisibilityTimeout);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
logInlineKillAfterOverrun(job, start = new Date()) {
|
|
188
|
+
if (!this.opt.killAfter || !job.executionStart || job.inlineKillAfterLogged)
|
|
189
|
+
return;
|
|
190
|
+
const executionTimeMs = this.getExecutionTimeMs(job, start);
|
|
191
|
+
if (executionTimeMs < this.opt.killAfter * 1000)
|
|
192
|
+
return;
|
|
193
|
+
job.inlineKillAfterLogged = true;
|
|
194
|
+
const executionTime = Math.floor(executionTimeMs / 1000);
|
|
195
|
+
if (this.opt.verbose) {
|
|
196
|
+
console.error(chalk_1.default.yellow('INLINE_JOB_EXCEEDED_KILL_AFTER'), job.prettyQname, chalk_1.default.yellow('after'), executionTime, chalk_1.default.yellow('seconds (limit:'), this.opt.killAfter + ')');
|
|
197
|
+
}
|
|
198
|
+
else if (!this.opt.disableLog) {
|
|
199
|
+
console.log(JSON.stringify({
|
|
200
|
+
event: 'INLINE_JOB_EXCEEDED_KILL_AFTER',
|
|
201
|
+
timestamp: start,
|
|
202
|
+
queue: job.qname,
|
|
203
|
+
messageId: job.message.MessageId,
|
|
204
|
+
executionTime,
|
|
205
|
+
killAfter: this.opt.killAfter,
|
|
206
|
+
payload: job.payload
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
164
210
|
/**
|
|
165
211
|
* Changes message visibility on all running jobs using as few calls as possible.
|
|
166
212
|
*/
|
|
@@ -178,7 +224,6 @@ class JobExecutor {
|
|
|
178
224
|
this.maintainVisibilityTimeout = setTimeout(() => {
|
|
179
225
|
this.maintainPromise = this.maintainVisibility();
|
|
180
226
|
}, nextCheckInMs);
|
|
181
|
-
// debug('maintainVisibility', this.jobs)
|
|
182
227
|
const start = new Date();
|
|
183
228
|
const jobsToExtendByQrl = {};
|
|
184
229
|
const jobsToDeleteByQrl = {};
|
|
@@ -189,7 +234,6 @@ class JobExecutor {
|
|
|
189
234
|
const job = this.jobs[i];
|
|
190
235
|
const jobRunTime = Math.round((start - job.start) / 1000);
|
|
191
236
|
jobStatuses[job.status] = (jobStatuses[job.status] || 0) + 1;
|
|
192
|
-
// debug('considering job', job)
|
|
193
237
|
if (job.status === 'complete') {
|
|
194
238
|
const jobsToDelete = jobsToDeleteByQrl[job.qrl] || [];
|
|
195
239
|
job.status = 'deleting';
|
|
@@ -205,13 +249,16 @@ class JobExecutor {
|
|
|
205
249
|
// Kill-after enforcement: terminate child process if it exceeds the deadline.
|
|
206
250
|
// Uses executionStart (when runJob began) so FIFO serial jobs aren't
|
|
207
251
|
// penalized for queue wait time.
|
|
208
|
-
if (this.
|
|
252
|
+
if (this.shouldEnforceKillAfter(job) && job.executionStart && !job.killed) {
|
|
209
253
|
const executionTimeMs = this.getExecutionTimeMs(job, start);
|
|
210
254
|
if (executionTimeMs >= this.opt.killAfter * 1000) {
|
|
211
255
|
job.killDue = true;
|
|
212
256
|
this.killJob(job, start);
|
|
213
257
|
}
|
|
214
258
|
}
|
|
259
|
+
else if (job.executionMode === 'inline') {
|
|
260
|
+
this.logInlineKillAfterOverrun(job, start);
|
|
261
|
+
}
|
|
215
262
|
if (jobRunTime >= job.extendAtSecond) {
|
|
216
263
|
// Add it to our organized list of jobs
|
|
217
264
|
const jobsToExtend = jobsToExtendByQrl[job.qrl] || [];
|
|
@@ -223,7 +270,7 @@ class JobExecutor {
|
|
|
223
270
|
const doubled = job.visibilityTimeout * 2;
|
|
224
271
|
const secondsUntilMax = Math.max(1, maxJobSeconds - jobRunTime);
|
|
225
272
|
const executionTimeMs = job.executionStart ? this.getExecutionTimeMs(job, start) : 0;
|
|
226
|
-
const secondsUntilKill = (this.
|
|
273
|
+
const secondsUntilKill = (this.shouldEnforceKillAfter(job) && job.executionStart)
|
|
227
274
|
? Math.max(1, Math.ceil((this.opt.killAfter * 1000 - executionTimeMs) / 1000))
|
|
228
275
|
: Infinity;
|
|
229
276
|
job.visibilityTimeout = Math.min(doubled, secondsUntilMax, secondsUntilKill);
|
|
@@ -438,13 +485,22 @@ class JobExecutor {
|
|
|
438
485
|
messageGroupId: job.message.Attributes?.MessageGroupId || '',
|
|
439
486
|
/** Call with a child process PID to enable kill-after process termination. */
|
|
440
487
|
registerPid: (pid) => {
|
|
488
|
+
if (job.executionMode === 'inline') {
|
|
489
|
+
debug('registerPid ignored after registerInlineExecution', { messageId: job.message?.MessageId });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
441
492
|
if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 1 || pid === process.pid) {
|
|
442
493
|
debug('registerPid: rejected invalid PID', pid);
|
|
443
494
|
return;
|
|
444
495
|
}
|
|
496
|
+
job.executionMode = 'child_process';
|
|
445
497
|
job.pid = pid;
|
|
446
498
|
if (job.killDue && !job.killed)
|
|
447
499
|
this.killJob(job, new Date());
|
|
500
|
+
},
|
|
501
|
+
/** Call before inline work starts to opt out of kill-after visibility expiry. */
|
|
502
|
+
registerInlineExecution: async () => {
|
|
503
|
+
await this.registerInlineExecution(job);
|
|
448
504
|
}
|
|
449
505
|
};
|
|
450
506
|
const result = await job.callback(queue, job.payload, attributes);
|
|
@@ -503,13 +559,11 @@ class JobExecutor {
|
|
|
503
559
|
const jobs = messages.map(message => this.addJob(message, callback, qname, qrl));
|
|
504
560
|
const isFifo = qrl.endsWith('.fifo');
|
|
505
561
|
const runningJobs = [];
|
|
506
|
-
// console.log(jobs)
|
|
507
562
|
// Begin executing
|
|
508
563
|
for (const [job, i] of jobs.map((job, i) => [job, i])) {
|
|
509
564
|
// Figure out if the next job needs to happen in serial, otherwise we can parallel execute
|
|
510
565
|
const nextJob = jobs[i + 1];
|
|
511
566
|
const nextJobIsSerial = isFifo && nextJob && job.message?.Attributes?.MessageGroupId === nextJob.message?.Attributes?.MessageGroupId;
|
|
512
|
-
// console.log({ i, nextJobAtt: nextJob?.message?.Attributes, nextJobIsSerial })
|
|
513
567
|
// Execute serial or parallel
|
|
514
568
|
if (nextJobIsSerial)
|
|
515
569
|
await this.runJob(job);
|
package/package.json
CHANGED
|
@@ -81,6 +81,10 @@ export class JobExecutor {
|
|
|
81
81
|
return start - job.executionStart
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
shouldEnforceKillAfter (job) {
|
|
85
|
+
return !!(this.opt.killAfter && job.executionMode !== 'inline')
|
|
86
|
+
}
|
|
87
|
+
|
|
84
88
|
scheduleKillAfter (job) {
|
|
85
89
|
if (!this.opt.killAfter) return
|
|
86
90
|
clearTimeout(job.killTimer)
|
|
@@ -94,6 +98,7 @@ export class JobExecutor {
|
|
|
94
98
|
killJob (job, start = new Date()) {
|
|
95
99
|
if (!job.executionStart || job.status !== 'running') return
|
|
96
100
|
if (job.killed) return
|
|
101
|
+
if (!this.shouldEnforceKillAfter(job)) return
|
|
97
102
|
|
|
98
103
|
const executionTimeMs = this.getExecutionTimeMs(job, start)
|
|
99
104
|
if (executionTimeMs < this.opt.killAfter * 1000) return
|
|
@@ -140,14 +145,10 @@ export class JobExecutor {
|
|
|
140
145
|
job.killSignalTimer.unref?.()
|
|
141
146
|
}
|
|
142
147
|
|
|
143
|
-
async
|
|
144
|
-
if (!this.opt.killAfter) return
|
|
145
|
-
|
|
146
|
-
const visibilityTimeout = Math.max(1, Math.min(job.visibilityTimeout, this.opt.killAfter))
|
|
147
|
-
if (visibilityTimeout >= job.visibilityTimeout) return
|
|
148
|
-
|
|
148
|
+
async setJobVisibilityTimeout (job, visibilityTimeout, start = new Date()) {
|
|
149
149
|
job.visibilityTimeout = visibilityTimeout
|
|
150
|
-
|
|
150
|
+
const jobRunTime = Math.round((start - job.start) / 1000)
|
|
151
|
+
job.extendAtSecond = Math.round(jobRunTime + job.visibilityTimeout / 2)
|
|
151
152
|
|
|
152
153
|
const input = {
|
|
153
154
|
QueueUrl: job.qrl,
|
|
@@ -169,6 +170,55 @@ export class JobExecutor {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
async setRunningVisibilityTimeout (job) {
|
|
174
|
+
if (!this.shouldEnforceKillAfter(job)) return
|
|
175
|
+
|
|
176
|
+
const visibilityTimeout = Math.max(1, Math.min(job.visibilityTimeout, this.opt.killAfter))
|
|
177
|
+
if (visibilityTimeout >= job.visibilityTimeout) return
|
|
178
|
+
|
|
179
|
+
await this.setJobVisibilityTimeout(job, visibilityTimeout)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async registerInlineExecution (job) {
|
|
183
|
+
if (job.executionMode === 'inline') return
|
|
184
|
+
if (job.executionMode === 'child_process') {
|
|
185
|
+
debug('registerInlineExecution ignored after registerPid', { messageId: job.message?.MessageId })
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
job.executionMode = 'inline'
|
|
190
|
+
job.killDue = false
|
|
191
|
+
this.clearJobTimers(job)
|
|
192
|
+
|
|
193
|
+
if (job.status === 'running' && job.visibilityTimeout < defaultVisibilityTimeout) {
|
|
194
|
+
await this.setJobVisibilityTimeout(job, defaultVisibilityTimeout)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logInlineKillAfterOverrun (job, start = new Date()) {
|
|
199
|
+
if (!this.opt.killAfter || !job.executionStart || job.inlineKillAfterLogged) return
|
|
200
|
+
|
|
201
|
+
const executionTimeMs = this.getExecutionTimeMs(job, start)
|
|
202
|
+
if (executionTimeMs < this.opt.killAfter * 1000) return
|
|
203
|
+
|
|
204
|
+
job.inlineKillAfterLogged = true
|
|
205
|
+
const executionTime = Math.floor(executionTimeMs / 1000)
|
|
206
|
+
if (this.opt.verbose) {
|
|
207
|
+
console.error(chalk.yellow('INLINE_JOB_EXCEEDED_KILL_AFTER'), job.prettyQname,
|
|
208
|
+
chalk.yellow('after'), executionTime, chalk.yellow('seconds (limit:'), this.opt.killAfter + ')')
|
|
209
|
+
} else if (!this.opt.disableLog) {
|
|
210
|
+
console.log(JSON.stringify({
|
|
211
|
+
event: 'INLINE_JOB_EXCEEDED_KILL_AFTER',
|
|
212
|
+
timestamp: start,
|
|
213
|
+
queue: job.qname,
|
|
214
|
+
messageId: job.message.MessageId,
|
|
215
|
+
executionTime,
|
|
216
|
+
killAfter: this.opt.killAfter,
|
|
217
|
+
payload: job.payload
|
|
218
|
+
}))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
172
222
|
/**
|
|
173
223
|
* Changes message visibility on all running jobs using as few calls as possible.
|
|
174
224
|
*/
|
|
@@ -188,7 +238,6 @@ export class JobExecutor {
|
|
|
188
238
|
this.maintainPromise = this.maintainVisibility()
|
|
189
239
|
}, nextCheckInMs)
|
|
190
240
|
|
|
191
|
-
// debug('maintainVisibility', this.jobs)
|
|
192
241
|
const start = new Date()
|
|
193
242
|
const jobsToExtendByQrl = {}
|
|
194
243
|
const jobsToDeleteByQrl = {}
|
|
@@ -200,7 +249,6 @@ export class JobExecutor {
|
|
|
200
249
|
const job = this.jobs[i]
|
|
201
250
|
const jobRunTime = Math.round((start - job.start) / 1000)
|
|
202
251
|
jobStatuses[job.status] = (jobStatuses[job.status] || 0) + 1
|
|
203
|
-
// debug('considering job', job)
|
|
204
252
|
if (job.status === 'complete') {
|
|
205
253
|
const jobsToDelete = jobsToDeleteByQrl[job.qrl] || []
|
|
206
254
|
job.status = 'deleting'
|
|
@@ -215,12 +263,14 @@ export class JobExecutor {
|
|
|
215
263
|
// Kill-after enforcement: terminate child process if it exceeds the deadline.
|
|
216
264
|
// Uses executionStart (when runJob began) so FIFO serial jobs aren't
|
|
217
265
|
// penalized for queue wait time.
|
|
218
|
-
if (this.
|
|
266
|
+
if (this.shouldEnforceKillAfter(job) && job.executionStart && !job.killed) {
|
|
219
267
|
const executionTimeMs = this.getExecutionTimeMs(job, start)
|
|
220
268
|
if (executionTimeMs >= this.opt.killAfter * 1000) {
|
|
221
269
|
job.killDue = true
|
|
222
270
|
this.killJob(job, start)
|
|
223
271
|
}
|
|
272
|
+
} else if (job.executionMode === 'inline') {
|
|
273
|
+
this.logInlineKillAfterOverrun(job, start)
|
|
224
274
|
}
|
|
225
275
|
|
|
226
276
|
if (jobRunTime >= job.extendAtSecond) {
|
|
@@ -235,7 +285,7 @@ export class JobExecutor {
|
|
|
235
285
|
const doubled = job.visibilityTimeout * 2
|
|
236
286
|
const secondsUntilMax = Math.max(1, maxJobSeconds - jobRunTime)
|
|
237
287
|
const executionTimeMs = job.executionStart ? this.getExecutionTimeMs(job, start) : 0
|
|
238
|
-
const secondsUntilKill = (this.
|
|
288
|
+
const secondsUntilKill = (this.shouldEnforceKillAfter(job) && job.executionStart)
|
|
239
289
|
? Math.max(1, Math.ceil((this.opt.killAfter * 1000 - executionTimeMs) / 1000))
|
|
240
290
|
: Infinity
|
|
241
291
|
job.visibilityTimeout = Math.min(doubled, secondsUntilMax, secondsUntilKill)
|
|
@@ -455,12 +505,21 @@ export class JobExecutor {
|
|
|
455
505
|
messageGroupId: job.message.Attributes?.MessageGroupId || '',
|
|
456
506
|
/** Call with a child process PID to enable kill-after process termination. */
|
|
457
507
|
registerPid: (pid) => {
|
|
508
|
+
if (job.executionMode === 'inline') {
|
|
509
|
+
debug('registerPid ignored after registerInlineExecution', { messageId: job.message?.MessageId })
|
|
510
|
+
return
|
|
511
|
+
}
|
|
458
512
|
if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 1 || pid === process.pid) {
|
|
459
513
|
debug('registerPid: rejected invalid PID', pid)
|
|
460
514
|
return
|
|
461
515
|
}
|
|
516
|
+
job.executionMode = 'child_process'
|
|
462
517
|
job.pid = pid
|
|
463
518
|
if (job.killDue && !job.killed) this.killJob(job, new Date())
|
|
519
|
+
},
|
|
520
|
+
/** Call before inline work starts to opt out of kill-after visibility expiry. */
|
|
521
|
+
registerInlineExecution: async () => {
|
|
522
|
+
await this.registerInlineExecution(job)
|
|
464
523
|
}
|
|
465
524
|
}
|
|
466
525
|
const result = await job.callback(queue, job.payload, attributes)
|
|
@@ -518,15 +577,12 @@ export class JobExecutor {
|
|
|
518
577
|
const isFifo = qrl.endsWith('.fifo')
|
|
519
578
|
const runningJobs = []
|
|
520
579
|
|
|
521
|
-
// console.log(jobs)
|
|
522
|
-
|
|
523
580
|
// Begin executing
|
|
524
581
|
for (const [job, i] of jobs.map((job, i) => [job, i])) {
|
|
525
582
|
// Figure out if the next job needs to happen in serial, otherwise we can parallel execute
|
|
526
583
|
const nextJob = jobs[i + 1]
|
|
527
584
|
const nextJobIsSerial = isFifo && nextJob && job.message?.Attributes?.MessageGroupId === nextJob.message?.Attributes?.MessageGroupId
|
|
528
585
|
|
|
529
|
-
// console.log({ i, nextJobAtt: nextJob?.message?.Attributes, nextJobIsSerial })
|
|
530
586
|
// Execute serial or parallel
|
|
531
587
|
if (nextJobIsSerial) await this.runJob(job)
|
|
532
588
|
else runningJobs.push(this.runJob(job))
|