queue-jobs-worker 1.0.2 → 1.0.3
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/CHANGELOG.md +155 -128
- package/README.md +133 -291
- package/assets/queue-jobs-worker-github.png +0 -0
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/index.cjs +321 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +321 -92
- package/dist/index.js.map +1 -1
- package/dist/storage/in-memory.adapter.d.ts +3 -2
- package/dist/storage/in-memory.adapter.d.ts.map +1 -1
- package/dist/storage/mysql.adapter.d.ts +3 -2
- package/dist/storage/mysql.adapter.d.ts.map +1 -1
- package/dist/storage/postgres.adapter.d.ts +3 -2
- package/dist/storage/postgres.adapter.d.ts.map +1 -1
- package/dist/storage/redis.adapter.d.ts +3 -2
- package/dist/storage/redis.adapter.d.ts.map +1 -1
- package/dist/types/storage.types.d.ts +20 -2
- package/dist/types/storage.types.d.ts.map +1 -1
- package/dist/types/worker.types.d.ts +1 -1
- package/dist/types/worker.types.d.ts.map +1 -1
- package/package.json +110 -109
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/core/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,KAAK,gBAAgB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAyBjD,qBAAa,MAAM;IACjB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAAK;IAExB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAA+B;IAEnD,4DAA4D;IAC5D,OAAO,CAAC,YAAY,CAA6B;gBAG/C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAC3C,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,gBAAgB;IAc5B,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,8BAA8B;IAC9B,KAAK,IAAI,IAAI;IAab;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD3B,OAAO,CAAC,YAAY;YAQN,IAAI;YAsBJ,SAAS;YAyCT,UAAU;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/core/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,KAAK,gBAAgB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAyBjD,qBAAa,MAAM;IACjB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAAK;IAExB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAA+B;IAEnD,4DAA4D;IAC5D,OAAO,CAAC,YAAY,CAA6B;gBAG/C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAC3C,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,gBAAgB;IAc5B,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,8BAA8B;IAC9B,KAAK,IAAI,IAAI;IAab;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD3B,OAAO,CAAC,YAAY;YAQN,IAAI;YAsBJ,SAAS;YAyCT,UAAU;YAqFV,eAAe;YA4Df,aAAa;IA4C3B,OAAO,CAAC,kBAAkB;YAQZ,kBAAkB;CAkBjC"}
|
package/dist/index.cjs
CHANGED
|
@@ -77,7 +77,7 @@ function hashToJob(h) {
|
|
|
77
77
|
if (h["cron"]) job.cron = h["cron"];
|
|
78
78
|
return job;
|
|
79
79
|
}
|
|
80
|
-
var PREFIX, k, CLAIM_LUA, RECOVER_STALLED_LUA; exports.RedisStorageAdapter = void 0;
|
|
80
|
+
var PREFIX, k, CLAIM_LUA, RECOVER_STALLED_LUA, RENEW_LOCK_LUA; exports.RedisStorageAdapter = void 0;
|
|
81
81
|
var init_redis_adapter = __esm({
|
|
82
82
|
"src/storage/redis.adapter.ts"() {
|
|
83
83
|
PREFIX = "qjw:";
|
|
@@ -161,6 +161,24 @@ redis.call('HSET', job_key,
|
|
|
161
161
|
redis.call('SREM', active_set, job_id)
|
|
162
162
|
redis.call('ZADD', waiting_zset, priority_score, job_id)
|
|
163
163
|
return 1
|
|
164
|
+
`;
|
|
165
|
+
RENEW_LOCK_LUA = `
|
|
166
|
+
local job_key = KEYS[1]
|
|
167
|
+
local lock_id = ARGV[1]
|
|
168
|
+
local new_exp = ARGV[2]
|
|
169
|
+
local now_iso = ARGV[3]
|
|
170
|
+
|
|
171
|
+
local fields = redis.call('HMGET', job_key, 'status', 'lockId', 'lockExpiresAt')
|
|
172
|
+
local status = fields[1] or ''
|
|
173
|
+
local current_lock = fields[2] or ''
|
|
174
|
+
local current_exp = fields[3] or ''
|
|
175
|
+
|
|
176
|
+
if status ~= 'active' then return 0 end
|
|
177
|
+
if current_lock ~= lock_id then return 0 end
|
|
178
|
+
if current_exp == '' or current_exp <= now_iso then return 0 end
|
|
179
|
+
|
|
180
|
+
redis.call('HSET', job_key, 'lockExpiresAt', new_exp, 'updatedAt', now_iso)
|
|
181
|
+
return 1
|
|
164
182
|
`;
|
|
165
183
|
exports.RedisStorageAdapter = class {
|
|
166
184
|
client;
|
|
@@ -251,12 +269,28 @@ return 1
|
|
|
251
269
|
return hashToJob(hash);
|
|
252
270
|
}
|
|
253
271
|
// -------------------------------------------------------------------------
|
|
272
|
+
// Renew lock
|
|
273
|
+
// -------------------------------------------------------------------------
|
|
274
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
275
|
+
const nowMs = Date.now();
|
|
276
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
277
|
+
const newExpiresAt = new Date(nowMs + lockDuration).toISOString();
|
|
278
|
+
const res = await this.client.eval(RENEW_LOCK_LUA, {
|
|
279
|
+
keys: [k.job(jobId)],
|
|
280
|
+
arguments: [lockId, newExpiresAt, nowIso]
|
|
281
|
+
});
|
|
282
|
+
return Number(res) === 1;
|
|
283
|
+
}
|
|
284
|
+
// -------------------------------------------------------------------------
|
|
254
285
|
// Complete
|
|
255
286
|
// -------------------------------------------------------------------------
|
|
256
|
-
async complete(jobId) {
|
|
287
|
+
async complete(jobId, lockId) {
|
|
257
288
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
258
289
|
const hash = await this.client.hGetAll(k.job(jobId));
|
|
259
|
-
if (!hash) return;
|
|
290
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
291
|
+
if (lockId !== void 0) {
|
|
292
|
+
if (hash["status"] !== "active" || hash["lockId"] !== lockId) return;
|
|
293
|
+
}
|
|
260
294
|
const queue = hash["queue"] ?? "";
|
|
261
295
|
const multi = this.client.multi();
|
|
262
296
|
multi.hSet(k.job(jobId), {
|
|
@@ -276,7 +310,10 @@ return 1
|
|
|
276
310
|
async requeue(input) {
|
|
277
311
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
278
312
|
const hash = await this.client.hGetAll(k.job(input.jobId));
|
|
279
|
-
if (!hash) return;
|
|
313
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
314
|
+
if (input.lockId !== void 0) {
|
|
315
|
+
if (hash["status"] !== "active" || hash["lockId"] !== input.lockId) return;
|
|
316
|
+
}
|
|
280
317
|
const queue = hash["queue"] ?? "";
|
|
281
318
|
const attempts = JSON.parse(hash["attempts"] ?? "[]");
|
|
282
319
|
attempts.push({
|
|
@@ -309,7 +346,10 @@ return 1
|
|
|
309
346
|
async moveToDlq(input) {
|
|
310
347
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
311
348
|
const hash = await this.client.hGetAll(k.job(input.jobId));
|
|
312
|
-
if (!hash) return;
|
|
349
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
350
|
+
if (input.lockId !== void 0) {
|
|
351
|
+
if (hash["status"] !== "active" || hash["lockId"] !== input.lockId) return;
|
|
352
|
+
}
|
|
313
353
|
const queue = hash["queue"] ?? "";
|
|
314
354
|
const attempts = JSON.parse(hash["attempts"] ?? "[]");
|
|
315
355
|
attempts.push({
|
|
@@ -336,7 +376,12 @@ return 1
|
|
|
336
376
|
// -------------------------------------------------------------------------
|
|
337
377
|
// Release lock
|
|
338
378
|
// -------------------------------------------------------------------------
|
|
339
|
-
async releaseLock(jobId) {
|
|
379
|
+
async releaseLock(jobId, lockId) {
|
|
380
|
+
const hash = await this.client.hGetAll(k.job(jobId));
|
|
381
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
382
|
+
if (lockId !== void 0) {
|
|
383
|
+
if (hash["status"] !== "active" || hash["lockId"] !== lockId) return;
|
|
384
|
+
}
|
|
340
385
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
341
386
|
await this.client.hSet(k.job(jobId), {
|
|
342
387
|
lockId: "",
|
|
@@ -663,16 +708,40 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
663
708
|
}
|
|
664
709
|
}
|
|
665
710
|
// -------------------------------------------------------------------------
|
|
666
|
-
//
|
|
711
|
+
// Renew lock
|
|
667
712
|
// -------------------------------------------------------------------------
|
|
668
|
-
async
|
|
669
|
-
|
|
713
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
714
|
+
const newLockExpiresAt = new Date(Date.now() + lockDuration).toISOString();
|
|
715
|
+
const res = await this.pool.query(
|
|
670
716
|
`UPDATE qjw_jobs
|
|
671
|
-
SET
|
|
672
|
-
|
|
673
|
-
WHERE id = $
|
|
674
|
-
[jobId]
|
|
717
|
+
SET lock_expires_at = $1::timestamptz,
|
|
718
|
+
updated_at = NOW()
|
|
719
|
+
WHERE id = $2 AND status = 'active' AND lock_id = $3 AND lock_expires_at > NOW()`,
|
|
720
|
+
[newLockExpiresAt, jobId, lockId]
|
|
675
721
|
);
|
|
722
|
+
return (res.rowCount ?? 0) > 0;
|
|
723
|
+
}
|
|
724
|
+
// -------------------------------------------------------------------------
|
|
725
|
+
// Complete
|
|
726
|
+
// -------------------------------------------------------------------------
|
|
727
|
+
async complete(jobId, lockId) {
|
|
728
|
+
if (lockId !== void 0) {
|
|
729
|
+
await this.pool.query(
|
|
730
|
+
`UPDATE qjw_jobs
|
|
731
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
732
|
+
completed_at = NOW(), updated_at = NOW()
|
|
733
|
+
WHERE id = $1 AND status = 'active' AND lock_id = $2`,
|
|
734
|
+
[jobId, lockId]
|
|
735
|
+
);
|
|
736
|
+
} else {
|
|
737
|
+
await this.pool.query(
|
|
738
|
+
`UPDATE qjw_jobs
|
|
739
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
740
|
+
completed_at = NOW(), updated_at = NOW()
|
|
741
|
+
WHERE id = $1`,
|
|
742
|
+
[jobId]
|
|
743
|
+
);
|
|
744
|
+
}
|
|
676
745
|
}
|
|
677
746
|
// -------------------------------------------------------------------------
|
|
678
747
|
// Requeue
|
|
@@ -686,18 +755,33 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
686
755
|
error: input.error,
|
|
687
756
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
688
757
|
};
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
758
|
+
if (input.lockId !== void 0) {
|
|
759
|
+
await this.pool.query(
|
|
760
|
+
`UPDATE qjw_jobs
|
|
761
|
+
SET status = 'waiting',
|
|
762
|
+
attempts_made = $1,
|
|
763
|
+
attempts = attempts || $2::jsonb,
|
|
764
|
+
run_at = $3::timestamptz,
|
|
765
|
+
lock_id = NULL,
|
|
766
|
+
lock_expires_at = NULL,
|
|
767
|
+
updated_at = NOW()
|
|
768
|
+
WHERE id = $4 AND status = 'active' AND lock_id = $5`,
|
|
769
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.runAt, input.jobId, input.lockId]
|
|
770
|
+
);
|
|
771
|
+
} else {
|
|
772
|
+
await this.pool.query(
|
|
773
|
+
`UPDATE qjw_jobs
|
|
774
|
+
SET status = 'waiting',
|
|
775
|
+
attempts_made = $1,
|
|
776
|
+
attempts = attempts || $2::jsonb,
|
|
777
|
+
run_at = $3::timestamptz,
|
|
778
|
+
lock_id = NULL,
|
|
779
|
+
lock_expires_at = NULL,
|
|
780
|
+
updated_at = NOW()
|
|
781
|
+
WHERE id = $4`,
|
|
782
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.runAt, input.jobId]
|
|
783
|
+
);
|
|
784
|
+
}
|
|
701
785
|
}
|
|
702
786
|
// -------------------------------------------------------------------------
|
|
703
787
|
// Move to DLQ
|
|
@@ -711,29 +795,53 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
711
795
|
error: input.error,
|
|
712
796
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
713
797
|
};
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
798
|
+
if (input.lockId !== void 0) {
|
|
799
|
+
await this.pool.query(
|
|
800
|
+
`UPDATE qjw_jobs
|
|
801
|
+
SET status = 'dead',
|
|
802
|
+
attempts_made = $1,
|
|
803
|
+
attempts = attempts || $2::jsonb,
|
|
804
|
+
lock_id = NULL,
|
|
805
|
+
lock_expires_at = NULL,
|
|
806
|
+
failed_at = NOW(),
|
|
807
|
+
updated_at = NOW()
|
|
808
|
+
WHERE id = $3 AND status = 'active' AND lock_id = $4`,
|
|
809
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.jobId, input.lockId]
|
|
810
|
+
);
|
|
811
|
+
} else {
|
|
812
|
+
await this.pool.query(
|
|
813
|
+
`UPDATE qjw_jobs
|
|
814
|
+
SET status = 'dead',
|
|
815
|
+
attempts_made = $1,
|
|
816
|
+
attempts = attempts || $2::jsonb,
|
|
817
|
+
lock_id = NULL,
|
|
818
|
+
lock_expires_at = NULL,
|
|
819
|
+
failed_at = NOW(),
|
|
820
|
+
updated_at = NOW()
|
|
821
|
+
WHERE id = $3`,
|
|
822
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.jobId]
|
|
823
|
+
);
|
|
824
|
+
}
|
|
726
825
|
}
|
|
727
826
|
// -------------------------------------------------------------------------
|
|
728
827
|
// Release lock
|
|
729
828
|
// -------------------------------------------------------------------------
|
|
730
|
-
async releaseLock(jobId) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
829
|
+
async releaseLock(jobId, lockId) {
|
|
830
|
+
if (lockId !== void 0) {
|
|
831
|
+
await this.pool.query(
|
|
832
|
+
`UPDATE qjw_jobs
|
|
833
|
+
SET lock_id = NULL, lock_expires_at = NOW(), updated_at = NOW()
|
|
834
|
+
WHERE id = $1 AND status = 'active' AND lock_id = $2`,
|
|
835
|
+
[jobId, lockId]
|
|
836
|
+
);
|
|
837
|
+
} else {
|
|
838
|
+
await this.pool.query(
|
|
839
|
+
`UPDATE qjw_jobs
|
|
840
|
+
SET lock_id = NULL, lock_expires_at = NOW(), updated_at = NOW()
|
|
841
|
+
WHERE id = $1`,
|
|
842
|
+
[jobId]
|
|
843
|
+
);
|
|
844
|
+
}
|
|
737
845
|
}
|
|
738
846
|
// -------------------------------------------------------------------------
|
|
739
847
|
// Recover stalled jobs
|
|
@@ -1014,16 +1122,39 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1014
1122
|
}
|
|
1015
1123
|
}
|
|
1016
1124
|
// -------------------------------------------------------------------------
|
|
1017
|
-
//
|
|
1125
|
+
// Renew lock
|
|
1018
1126
|
// -------------------------------------------------------------------------
|
|
1019
|
-
async
|
|
1020
|
-
|
|
1127
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
1128
|
+
const newLockExpiresAt = new Date(Date.now() + lockDuration).toISOString().slice(0, 23).replace("T", " ");
|
|
1129
|
+
const [result] = await this.pool.query(
|
|
1021
1130
|
`UPDATE qjw_jobs
|
|
1022
|
-
SET
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
[jobId]
|
|
1131
|
+
SET lock_expires_at = ?, updated_at = NOW(3)
|
|
1132
|
+
WHERE id = ? AND status = 'active' AND lock_id = ? AND lock_expires_at > NOW(3)`,
|
|
1133
|
+
[newLockExpiresAt, jobId, lockId]
|
|
1026
1134
|
);
|
|
1135
|
+
return result.affectedRows > 0;
|
|
1136
|
+
}
|
|
1137
|
+
// -------------------------------------------------------------------------
|
|
1138
|
+
// Complete
|
|
1139
|
+
// -------------------------------------------------------------------------
|
|
1140
|
+
async complete(jobId, lockId) {
|
|
1141
|
+
if (lockId !== void 0) {
|
|
1142
|
+
await this.pool.query(
|
|
1143
|
+
`UPDATE qjw_jobs
|
|
1144
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
1145
|
+
completed_at = NOW(3), updated_at = NOW(3)
|
|
1146
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1147
|
+
[jobId, lockId]
|
|
1148
|
+
);
|
|
1149
|
+
} else {
|
|
1150
|
+
await this.pool.query(
|
|
1151
|
+
`UPDATE qjw_jobs
|
|
1152
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
1153
|
+
completed_at = NOW(3), updated_at = NOW(3)
|
|
1154
|
+
WHERE id = ?`,
|
|
1155
|
+
[jobId]
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1027
1158
|
}
|
|
1028
1159
|
// -------------------------------------------------------------------------
|
|
1029
1160
|
// Requeue
|
|
@@ -1037,18 +1168,33 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1037
1168
|
error: input.error,
|
|
1038
1169
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
1039
1170
|
});
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1171
|
+
if (input.lockId !== void 0) {
|
|
1172
|
+
await this.pool.query(
|
|
1173
|
+
`UPDATE qjw_jobs
|
|
1174
|
+
SET status = 'waiting',
|
|
1175
|
+
attempts_made = attempts_made + 1,
|
|
1176
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1177
|
+
run_at = ?,
|
|
1178
|
+
lock_id = NULL,
|
|
1179
|
+
lock_expires_at = NULL,
|
|
1180
|
+
updated_at = NOW(3)
|
|
1181
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1182
|
+
[attempt, input.runAt, input.jobId, input.lockId]
|
|
1183
|
+
);
|
|
1184
|
+
} else {
|
|
1185
|
+
await this.pool.query(
|
|
1186
|
+
`UPDATE qjw_jobs
|
|
1187
|
+
SET status = 'waiting',
|
|
1188
|
+
attempts_made = attempts_made + 1,
|
|
1189
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1190
|
+
run_at = ?,
|
|
1191
|
+
lock_id = NULL,
|
|
1192
|
+
lock_expires_at = NULL,
|
|
1193
|
+
updated_at = NOW(3)
|
|
1194
|
+
WHERE id = ?`,
|
|
1195
|
+
[attempt, input.runAt, input.jobId]
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1052
1198
|
}
|
|
1053
1199
|
// -------------------------------------------------------------------------
|
|
1054
1200
|
// Move to DLQ
|
|
@@ -1062,29 +1208,53 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1062
1208
|
error: input.error,
|
|
1063
1209
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
1064
1210
|
});
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1211
|
+
if (input.lockId !== void 0) {
|
|
1212
|
+
await this.pool.query(
|
|
1213
|
+
`UPDATE qjw_jobs
|
|
1214
|
+
SET status = 'dead',
|
|
1215
|
+
attempts_made = attempts_made + 1,
|
|
1216
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1217
|
+
lock_id = NULL,
|
|
1218
|
+
lock_expires_at = NULL,
|
|
1219
|
+
failed_at = NOW(3),
|
|
1220
|
+
updated_at = NOW(3)
|
|
1221
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1222
|
+
[attempt, input.jobId, input.lockId]
|
|
1223
|
+
);
|
|
1224
|
+
} else {
|
|
1225
|
+
await this.pool.query(
|
|
1226
|
+
`UPDATE qjw_jobs
|
|
1227
|
+
SET status = 'dead',
|
|
1228
|
+
attempts_made = attempts_made + 1,
|
|
1229
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1230
|
+
lock_id = NULL,
|
|
1231
|
+
lock_expires_at = NULL,
|
|
1232
|
+
failed_at = NOW(3),
|
|
1233
|
+
updated_at = NOW(3)
|
|
1234
|
+
WHERE id = ?`,
|
|
1235
|
+
[attempt, input.jobId]
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1077
1238
|
}
|
|
1078
1239
|
// -------------------------------------------------------------------------
|
|
1079
1240
|
// Release lock
|
|
1080
1241
|
// -------------------------------------------------------------------------
|
|
1081
|
-
async releaseLock(jobId) {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1242
|
+
async releaseLock(jobId, lockId) {
|
|
1243
|
+
if (lockId !== void 0) {
|
|
1244
|
+
await this.pool.query(
|
|
1245
|
+
`UPDATE qjw_jobs
|
|
1246
|
+
SET lock_id = NULL, lock_expires_at = NOW(3), updated_at = NOW(3)
|
|
1247
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1248
|
+
[jobId, lockId]
|
|
1249
|
+
);
|
|
1250
|
+
} else {
|
|
1251
|
+
await this.pool.query(
|
|
1252
|
+
`UPDATE qjw_jobs
|
|
1253
|
+
SET lock_id = NULL, lock_expires_at = NOW(3), updated_at = NOW(3)
|
|
1254
|
+
WHERE id = ?`,
|
|
1255
|
+
[jobId]
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1088
1258
|
}
|
|
1089
1259
|
// -------------------------------------------------------------------------
|
|
1090
1260
|
// Recover stalled jobs (atomic — SELECT … FOR UPDATE inside transaction)
|
|
@@ -1475,7 +1645,7 @@ var Worker = class {
|
|
|
1475
1645
|
if (this.activeJobIds.size > 0) {
|
|
1476
1646
|
await Promise.all(
|
|
1477
1647
|
Array.from(this.activeJobIds).map(
|
|
1478
|
-
(jobId) => this.storage.releaseLock(jobId).catch(() => {
|
|
1648
|
+
(jobId) => this.storage.releaseLock(jobId, this.id).catch(() => {
|
|
1479
1649
|
})
|
|
1480
1650
|
)
|
|
1481
1651
|
);
|
|
@@ -1527,7 +1697,7 @@ var Worker = class {
|
|
|
1527
1697
|
now
|
|
1528
1698
|
);
|
|
1529
1699
|
if (!allowed) {
|
|
1530
|
-
await this.storage.releaseLock(raw.id);
|
|
1700
|
+
await this.storage.releaseLock(raw.id, this.id);
|
|
1531
1701
|
return false;
|
|
1532
1702
|
}
|
|
1533
1703
|
}
|
|
@@ -1547,16 +1717,35 @@ var Worker = class {
|
|
|
1547
1717
|
await this.handleFailure(job, error);
|
|
1548
1718
|
return;
|
|
1549
1719
|
}
|
|
1720
|
+
const abortController = new AbortController();
|
|
1550
1721
|
let timeoutHandle = null;
|
|
1722
|
+
let lockRenewTimer = null;
|
|
1723
|
+
const lockRenewInterval = Math.max(100, Math.floor(this.config.lockDuration / 2));
|
|
1724
|
+
lockRenewTimer = setInterval(async () => {
|
|
1725
|
+
try {
|
|
1726
|
+
const renewed = await this.storage.renewLock(job.id, this.id, this.config.lockDuration);
|
|
1727
|
+
if (!renewed && lockRenewTimer) {
|
|
1728
|
+
clearInterval(lockRenewTimer);
|
|
1729
|
+
lockRenewTimer = null;
|
|
1730
|
+
}
|
|
1731
|
+
} catch {
|
|
1732
|
+
}
|
|
1733
|
+
}, lockRenewInterval);
|
|
1551
1734
|
try {
|
|
1552
1735
|
await new Promise((resolve, reject) => {
|
|
1553
1736
|
timeoutHandle = setTimeout(() => {
|
|
1554
|
-
|
|
1737
|
+
const timeoutError = new Error(`Job timed out after ${job.timeout}ms`);
|
|
1738
|
+
abortController.abort(timeoutError);
|
|
1739
|
+
reject(timeoutError);
|
|
1555
1740
|
}, job.timeout);
|
|
1556
|
-
Promise.resolve(processor(job)).then(resolve, reject);
|
|
1741
|
+
Promise.resolve(processor(job, abortController.signal)).then(resolve, reject);
|
|
1557
1742
|
});
|
|
1558
1743
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1559
|
-
|
|
1744
|
+
if (lockRenewTimer) {
|
|
1745
|
+
clearInterval(lockRenewTimer);
|
|
1746
|
+
lockRenewTimer = null;
|
|
1747
|
+
}
|
|
1748
|
+
await this.storage.complete(job.id, this.id);
|
|
1560
1749
|
const completedData = await this.storage.getJob(job.id) ?? job._data;
|
|
1561
1750
|
this.emitter.emit("job:completed", completedData);
|
|
1562
1751
|
if (job.cron) {
|
|
@@ -1564,9 +1753,17 @@ var Worker = class {
|
|
|
1564
1753
|
}
|
|
1565
1754
|
} catch (err) {
|
|
1566
1755
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1756
|
+
if (lockRenewTimer) {
|
|
1757
|
+
clearInterval(lockRenewTimer);
|
|
1758
|
+
lockRenewTimer = null;
|
|
1759
|
+
}
|
|
1567
1760
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1568
1761
|
await this.handleFailure(job, error);
|
|
1569
1762
|
} finally {
|
|
1763
|
+
if (lockRenewTimer) {
|
|
1764
|
+
clearInterval(lockRenewTimer);
|
|
1765
|
+
lockRenewTimer = null;
|
|
1766
|
+
}
|
|
1570
1767
|
this.activeJobIds.delete(job.id);
|
|
1571
1768
|
this.activeCount -= 1;
|
|
1572
1769
|
if (this.activeCount === 0 && this.drainResolve) {
|
|
@@ -1632,6 +1829,7 @@ var Worker = class {
|
|
|
1632
1829
|
runAt,
|
|
1633
1830
|
error: error.message,
|
|
1634
1831
|
attemptNumber,
|
|
1832
|
+
lockId: this.id,
|
|
1635
1833
|
...error.stack !== void 0 && { stack: error.stack }
|
|
1636
1834
|
});
|
|
1637
1835
|
const updated = await this.storage.getJob(job.id);
|
|
@@ -1643,6 +1841,7 @@ var Worker = class {
|
|
|
1643
1841
|
jobId: job.id,
|
|
1644
1842
|
error: error.message,
|
|
1645
1843
|
attemptNumber,
|
|
1844
|
+
lockId: this.id,
|
|
1646
1845
|
...error.stack !== void 0 && { stack: error.stack }
|
|
1647
1846
|
});
|
|
1648
1847
|
const dead = await this.storage.getJob(job.id);
|
|
@@ -1902,10 +2101,29 @@ var InMemoryStorageAdapter = class {
|
|
|
1902
2101
|
return job;
|
|
1903
2102
|
}
|
|
1904
2103
|
// -------------------------------------------------------------------------
|
|
2104
|
+
// Renew lock
|
|
2105
|
+
// -------------------------------------------------------------------------
|
|
2106
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
2107
|
+
const job = this.jobs.get(jobId);
|
|
2108
|
+
if (!job) return false;
|
|
2109
|
+
if (job.status !== "active" || job.lockId !== lockId) return false;
|
|
2110
|
+
const nowMs = Date.now();
|
|
2111
|
+
if (job.lockExpiresAt !== null && new Date(job.lockExpiresAt).getTime() <= nowMs) {
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
job.lockExpiresAt = new Date(nowMs + lockDuration).toISOString();
|
|
2115
|
+
job.updatedAt = new Date(nowMs).toISOString();
|
|
2116
|
+
return true;
|
|
2117
|
+
}
|
|
2118
|
+
// -------------------------------------------------------------------------
|
|
1905
2119
|
// Complete
|
|
1906
2120
|
// -------------------------------------------------------------------------
|
|
1907
|
-
async complete(jobId) {
|
|
1908
|
-
const job = this.
|
|
2121
|
+
async complete(jobId, lockId) {
|
|
2122
|
+
const job = this.jobs.get(jobId);
|
|
2123
|
+
if (!job) return;
|
|
2124
|
+
if (lockId !== void 0 && (job.status !== "active" || job.lockId !== lockId)) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
1909
2127
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1910
2128
|
job.status = "completed";
|
|
1911
2129
|
job.lockId = null;
|
|
@@ -1917,7 +2135,11 @@ var InMemoryStorageAdapter = class {
|
|
|
1917
2135
|
// Requeue (retry)
|
|
1918
2136
|
// -------------------------------------------------------------------------
|
|
1919
2137
|
async requeue(input) {
|
|
1920
|
-
const job = this.
|
|
2138
|
+
const job = this.jobs.get(input.jobId);
|
|
2139
|
+
if (!job) return;
|
|
2140
|
+
if (input.lockId !== void 0 && (job.status !== "active" || job.lockId !== input.lockId)) {
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
1921
2143
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1922
2144
|
job.attempts.push({
|
|
1923
2145
|
attempt: input.attemptNumber,
|
|
@@ -1937,7 +2159,11 @@ var InMemoryStorageAdapter = class {
|
|
|
1937
2159
|
// Move to DLQ
|
|
1938
2160
|
// -------------------------------------------------------------------------
|
|
1939
2161
|
async moveToDlq(input) {
|
|
1940
|
-
const job = this.
|
|
2162
|
+
const job = this.jobs.get(input.jobId);
|
|
2163
|
+
if (!job) return;
|
|
2164
|
+
if (input.lockId !== void 0 && (job.status !== "active" || job.lockId !== input.lockId)) {
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
1941
2167
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1942
2168
|
job.attempts.push({
|
|
1943
2169
|
attempt: input.attemptNumber,
|
|
@@ -1956,9 +2182,12 @@ var InMemoryStorageAdapter = class {
|
|
|
1956
2182
|
// -------------------------------------------------------------------------
|
|
1957
2183
|
// Release lock
|
|
1958
2184
|
// -------------------------------------------------------------------------
|
|
1959
|
-
async releaseLock(jobId) {
|
|
2185
|
+
async releaseLock(jobId, lockId) {
|
|
1960
2186
|
const job = this.jobs.get(jobId);
|
|
1961
2187
|
if (!job) return;
|
|
2188
|
+
if (lockId !== void 0 && (job.status !== "active" || job.lockId !== lockId)) {
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
1962
2191
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1963
2192
|
job.lockId = null;
|
|
1964
2193
|
job.lockExpiresAt = now;
|