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
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ function hashToJob(h) {
|
|
|
75
75
|
if (h["cron"]) job.cron = h["cron"];
|
|
76
76
|
return job;
|
|
77
77
|
}
|
|
78
|
-
var PREFIX, k, CLAIM_LUA, RECOVER_STALLED_LUA, RedisStorageAdapter;
|
|
78
|
+
var PREFIX, k, CLAIM_LUA, RECOVER_STALLED_LUA, RENEW_LOCK_LUA, RedisStorageAdapter;
|
|
79
79
|
var init_redis_adapter = __esm({
|
|
80
80
|
"src/storage/redis.adapter.ts"() {
|
|
81
81
|
PREFIX = "qjw:";
|
|
@@ -159,6 +159,24 @@ redis.call('HSET', job_key,
|
|
|
159
159
|
redis.call('SREM', active_set, job_id)
|
|
160
160
|
redis.call('ZADD', waiting_zset, priority_score, job_id)
|
|
161
161
|
return 1
|
|
162
|
+
`;
|
|
163
|
+
RENEW_LOCK_LUA = `
|
|
164
|
+
local job_key = KEYS[1]
|
|
165
|
+
local lock_id = ARGV[1]
|
|
166
|
+
local new_exp = ARGV[2]
|
|
167
|
+
local now_iso = ARGV[3]
|
|
168
|
+
|
|
169
|
+
local fields = redis.call('HMGET', job_key, 'status', 'lockId', 'lockExpiresAt')
|
|
170
|
+
local status = fields[1] or ''
|
|
171
|
+
local current_lock = fields[2] or ''
|
|
172
|
+
local current_exp = fields[3] or ''
|
|
173
|
+
|
|
174
|
+
if status ~= 'active' then return 0 end
|
|
175
|
+
if current_lock ~= lock_id then return 0 end
|
|
176
|
+
if current_exp == '' or current_exp <= now_iso then return 0 end
|
|
177
|
+
|
|
178
|
+
redis.call('HSET', job_key, 'lockExpiresAt', new_exp, 'updatedAt', now_iso)
|
|
179
|
+
return 1
|
|
162
180
|
`;
|
|
163
181
|
RedisStorageAdapter = class {
|
|
164
182
|
client;
|
|
@@ -249,12 +267,28 @@ return 1
|
|
|
249
267
|
return hashToJob(hash);
|
|
250
268
|
}
|
|
251
269
|
// -------------------------------------------------------------------------
|
|
270
|
+
// Renew lock
|
|
271
|
+
// -------------------------------------------------------------------------
|
|
272
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
273
|
+
const nowMs = Date.now();
|
|
274
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
275
|
+
const newExpiresAt = new Date(nowMs + lockDuration).toISOString();
|
|
276
|
+
const res = await this.client.eval(RENEW_LOCK_LUA, {
|
|
277
|
+
keys: [k.job(jobId)],
|
|
278
|
+
arguments: [lockId, newExpiresAt, nowIso]
|
|
279
|
+
});
|
|
280
|
+
return Number(res) === 1;
|
|
281
|
+
}
|
|
282
|
+
// -------------------------------------------------------------------------
|
|
252
283
|
// Complete
|
|
253
284
|
// -------------------------------------------------------------------------
|
|
254
|
-
async complete(jobId) {
|
|
285
|
+
async complete(jobId, lockId) {
|
|
255
286
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
256
287
|
const hash = await this.client.hGetAll(k.job(jobId));
|
|
257
|
-
if (!hash) return;
|
|
288
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
289
|
+
if (lockId !== void 0) {
|
|
290
|
+
if (hash["status"] !== "active" || hash["lockId"] !== lockId) return;
|
|
291
|
+
}
|
|
258
292
|
const queue = hash["queue"] ?? "";
|
|
259
293
|
const multi = this.client.multi();
|
|
260
294
|
multi.hSet(k.job(jobId), {
|
|
@@ -274,7 +308,10 @@ return 1
|
|
|
274
308
|
async requeue(input) {
|
|
275
309
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
276
310
|
const hash = await this.client.hGetAll(k.job(input.jobId));
|
|
277
|
-
if (!hash) return;
|
|
311
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
312
|
+
if (input.lockId !== void 0) {
|
|
313
|
+
if (hash["status"] !== "active" || hash["lockId"] !== input.lockId) return;
|
|
314
|
+
}
|
|
278
315
|
const queue = hash["queue"] ?? "";
|
|
279
316
|
const attempts = JSON.parse(hash["attempts"] ?? "[]");
|
|
280
317
|
attempts.push({
|
|
@@ -307,7 +344,10 @@ return 1
|
|
|
307
344
|
async moveToDlq(input) {
|
|
308
345
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
309
346
|
const hash = await this.client.hGetAll(k.job(input.jobId));
|
|
310
|
-
if (!hash) return;
|
|
347
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
348
|
+
if (input.lockId !== void 0) {
|
|
349
|
+
if (hash["status"] !== "active" || hash["lockId"] !== input.lockId) return;
|
|
350
|
+
}
|
|
311
351
|
const queue = hash["queue"] ?? "";
|
|
312
352
|
const attempts = JSON.parse(hash["attempts"] ?? "[]");
|
|
313
353
|
attempts.push({
|
|
@@ -334,7 +374,12 @@ return 1
|
|
|
334
374
|
// -------------------------------------------------------------------------
|
|
335
375
|
// Release lock
|
|
336
376
|
// -------------------------------------------------------------------------
|
|
337
|
-
async releaseLock(jobId) {
|
|
377
|
+
async releaseLock(jobId, lockId) {
|
|
378
|
+
const hash = await this.client.hGetAll(k.job(jobId));
|
|
379
|
+
if (!hash || Object.keys(hash).length === 0) return;
|
|
380
|
+
if (lockId !== void 0) {
|
|
381
|
+
if (hash["status"] !== "active" || hash["lockId"] !== lockId) return;
|
|
382
|
+
}
|
|
338
383
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
339
384
|
await this.client.hSet(k.job(jobId), {
|
|
340
385
|
lockId: "",
|
|
@@ -661,16 +706,40 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
661
706
|
}
|
|
662
707
|
}
|
|
663
708
|
// -------------------------------------------------------------------------
|
|
664
|
-
//
|
|
709
|
+
// Renew lock
|
|
665
710
|
// -------------------------------------------------------------------------
|
|
666
|
-
async
|
|
667
|
-
|
|
711
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
712
|
+
const newLockExpiresAt = new Date(Date.now() + lockDuration).toISOString();
|
|
713
|
+
const res = await this.pool.query(
|
|
668
714
|
`UPDATE qjw_jobs
|
|
669
|
-
SET
|
|
670
|
-
|
|
671
|
-
WHERE id = $
|
|
672
|
-
[jobId]
|
|
715
|
+
SET lock_expires_at = $1::timestamptz,
|
|
716
|
+
updated_at = NOW()
|
|
717
|
+
WHERE id = $2 AND status = 'active' AND lock_id = $3 AND lock_expires_at > NOW()`,
|
|
718
|
+
[newLockExpiresAt, jobId, lockId]
|
|
673
719
|
);
|
|
720
|
+
return (res.rowCount ?? 0) > 0;
|
|
721
|
+
}
|
|
722
|
+
// -------------------------------------------------------------------------
|
|
723
|
+
// Complete
|
|
724
|
+
// -------------------------------------------------------------------------
|
|
725
|
+
async complete(jobId, lockId) {
|
|
726
|
+
if (lockId !== void 0) {
|
|
727
|
+
await this.pool.query(
|
|
728
|
+
`UPDATE qjw_jobs
|
|
729
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
730
|
+
completed_at = NOW(), updated_at = NOW()
|
|
731
|
+
WHERE id = $1 AND status = 'active' AND lock_id = $2`,
|
|
732
|
+
[jobId, lockId]
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
await this.pool.query(
|
|
736
|
+
`UPDATE qjw_jobs
|
|
737
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
738
|
+
completed_at = NOW(), updated_at = NOW()
|
|
739
|
+
WHERE id = $1`,
|
|
740
|
+
[jobId]
|
|
741
|
+
);
|
|
742
|
+
}
|
|
674
743
|
}
|
|
675
744
|
// -------------------------------------------------------------------------
|
|
676
745
|
// Requeue
|
|
@@ -684,18 +753,33 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
684
753
|
error: input.error,
|
|
685
754
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
686
755
|
};
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
756
|
+
if (input.lockId !== void 0) {
|
|
757
|
+
await this.pool.query(
|
|
758
|
+
`UPDATE qjw_jobs
|
|
759
|
+
SET status = 'waiting',
|
|
760
|
+
attempts_made = $1,
|
|
761
|
+
attempts = attempts || $2::jsonb,
|
|
762
|
+
run_at = $3::timestamptz,
|
|
763
|
+
lock_id = NULL,
|
|
764
|
+
lock_expires_at = NULL,
|
|
765
|
+
updated_at = NOW()
|
|
766
|
+
WHERE id = $4 AND status = 'active' AND lock_id = $5`,
|
|
767
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.runAt, input.jobId, input.lockId]
|
|
768
|
+
);
|
|
769
|
+
} else {
|
|
770
|
+
await this.pool.query(
|
|
771
|
+
`UPDATE qjw_jobs
|
|
772
|
+
SET status = 'waiting',
|
|
773
|
+
attempts_made = $1,
|
|
774
|
+
attempts = attempts || $2::jsonb,
|
|
775
|
+
run_at = $3::timestamptz,
|
|
776
|
+
lock_id = NULL,
|
|
777
|
+
lock_expires_at = NULL,
|
|
778
|
+
updated_at = NOW()
|
|
779
|
+
WHERE id = $4`,
|
|
780
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.runAt, input.jobId]
|
|
781
|
+
);
|
|
782
|
+
}
|
|
699
783
|
}
|
|
700
784
|
// -------------------------------------------------------------------------
|
|
701
785
|
// Move to DLQ
|
|
@@ -709,29 +793,53 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
709
793
|
error: input.error,
|
|
710
794
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
711
795
|
};
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
796
|
+
if (input.lockId !== void 0) {
|
|
797
|
+
await this.pool.query(
|
|
798
|
+
`UPDATE qjw_jobs
|
|
799
|
+
SET status = 'dead',
|
|
800
|
+
attempts_made = $1,
|
|
801
|
+
attempts = attempts || $2::jsonb,
|
|
802
|
+
lock_id = NULL,
|
|
803
|
+
lock_expires_at = NULL,
|
|
804
|
+
failed_at = NOW(),
|
|
805
|
+
updated_at = NOW()
|
|
806
|
+
WHERE id = $3 AND status = 'active' AND lock_id = $4`,
|
|
807
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.jobId, input.lockId]
|
|
808
|
+
);
|
|
809
|
+
} else {
|
|
810
|
+
await this.pool.query(
|
|
811
|
+
`UPDATE qjw_jobs
|
|
812
|
+
SET status = 'dead',
|
|
813
|
+
attempts_made = $1,
|
|
814
|
+
attempts = attempts || $2::jsonb,
|
|
815
|
+
lock_id = NULL,
|
|
816
|
+
lock_expires_at = NULL,
|
|
817
|
+
failed_at = NOW(),
|
|
818
|
+
updated_at = NOW()
|
|
819
|
+
WHERE id = $3`,
|
|
820
|
+
[input.attemptNumber, JSON.stringify([attempt]), input.jobId]
|
|
821
|
+
);
|
|
822
|
+
}
|
|
724
823
|
}
|
|
725
824
|
// -------------------------------------------------------------------------
|
|
726
825
|
// Release lock
|
|
727
826
|
// -------------------------------------------------------------------------
|
|
728
|
-
async releaseLock(jobId) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
827
|
+
async releaseLock(jobId, lockId) {
|
|
828
|
+
if (lockId !== void 0) {
|
|
829
|
+
await this.pool.query(
|
|
830
|
+
`UPDATE qjw_jobs
|
|
831
|
+
SET lock_id = NULL, lock_expires_at = NOW(), updated_at = NOW()
|
|
832
|
+
WHERE id = $1 AND status = 'active' AND lock_id = $2`,
|
|
833
|
+
[jobId, lockId]
|
|
834
|
+
);
|
|
835
|
+
} else {
|
|
836
|
+
await this.pool.query(
|
|
837
|
+
`UPDATE qjw_jobs
|
|
838
|
+
SET lock_id = NULL, lock_expires_at = NOW(), updated_at = NOW()
|
|
839
|
+
WHERE id = $1`,
|
|
840
|
+
[jobId]
|
|
841
|
+
);
|
|
842
|
+
}
|
|
735
843
|
}
|
|
736
844
|
// -------------------------------------------------------------------------
|
|
737
845
|
// Recover stalled jobs
|
|
@@ -1012,16 +1120,39 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1012
1120
|
}
|
|
1013
1121
|
}
|
|
1014
1122
|
// -------------------------------------------------------------------------
|
|
1015
|
-
//
|
|
1123
|
+
// Renew lock
|
|
1016
1124
|
// -------------------------------------------------------------------------
|
|
1017
|
-
async
|
|
1018
|
-
|
|
1125
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
1126
|
+
const newLockExpiresAt = new Date(Date.now() + lockDuration).toISOString().slice(0, 23).replace("T", " ");
|
|
1127
|
+
const [result] = await this.pool.query(
|
|
1019
1128
|
`UPDATE qjw_jobs
|
|
1020
|
-
SET
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
[jobId]
|
|
1129
|
+
SET lock_expires_at = ?, updated_at = NOW(3)
|
|
1130
|
+
WHERE id = ? AND status = 'active' AND lock_id = ? AND lock_expires_at > NOW(3)`,
|
|
1131
|
+
[newLockExpiresAt, jobId, lockId]
|
|
1024
1132
|
);
|
|
1133
|
+
return result.affectedRows > 0;
|
|
1134
|
+
}
|
|
1135
|
+
// -------------------------------------------------------------------------
|
|
1136
|
+
// Complete
|
|
1137
|
+
// -------------------------------------------------------------------------
|
|
1138
|
+
async complete(jobId, lockId) {
|
|
1139
|
+
if (lockId !== void 0) {
|
|
1140
|
+
await this.pool.query(
|
|
1141
|
+
`UPDATE qjw_jobs
|
|
1142
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
1143
|
+
completed_at = NOW(3), updated_at = NOW(3)
|
|
1144
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1145
|
+
[jobId, lockId]
|
|
1146
|
+
);
|
|
1147
|
+
} else {
|
|
1148
|
+
await this.pool.query(
|
|
1149
|
+
`UPDATE qjw_jobs
|
|
1150
|
+
SET status = 'completed', lock_id = NULL, lock_expires_at = NULL,
|
|
1151
|
+
completed_at = NOW(3), updated_at = NOW(3)
|
|
1152
|
+
WHERE id = ?`,
|
|
1153
|
+
[jobId]
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1025
1156
|
}
|
|
1026
1157
|
// -------------------------------------------------------------------------
|
|
1027
1158
|
// Requeue
|
|
@@ -1035,18 +1166,33 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1035
1166
|
error: input.error,
|
|
1036
1167
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
1037
1168
|
});
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1169
|
+
if (input.lockId !== void 0) {
|
|
1170
|
+
await this.pool.query(
|
|
1171
|
+
`UPDATE qjw_jobs
|
|
1172
|
+
SET status = 'waiting',
|
|
1173
|
+
attempts_made = attempts_made + 1,
|
|
1174
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1175
|
+
run_at = ?,
|
|
1176
|
+
lock_id = NULL,
|
|
1177
|
+
lock_expires_at = NULL,
|
|
1178
|
+
updated_at = NOW(3)
|
|
1179
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1180
|
+
[attempt, input.runAt, input.jobId, input.lockId]
|
|
1181
|
+
);
|
|
1182
|
+
} else {
|
|
1183
|
+
await this.pool.query(
|
|
1184
|
+
`UPDATE qjw_jobs
|
|
1185
|
+
SET status = 'waiting',
|
|
1186
|
+
attempts_made = attempts_made + 1,
|
|
1187
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1188
|
+
run_at = ?,
|
|
1189
|
+
lock_id = NULL,
|
|
1190
|
+
lock_expires_at = NULL,
|
|
1191
|
+
updated_at = NOW(3)
|
|
1192
|
+
WHERE id = ?`,
|
|
1193
|
+
[attempt, input.runAt, input.jobId]
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1050
1196
|
}
|
|
1051
1197
|
// -------------------------------------------------------------------------
|
|
1052
1198
|
// Move to DLQ
|
|
@@ -1060,29 +1206,53 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
|
|
|
1060
1206
|
error: input.error,
|
|
1061
1207
|
...input.stack !== void 0 ? { stack: input.stack } : {}
|
|
1062
1208
|
});
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1209
|
+
if (input.lockId !== void 0) {
|
|
1210
|
+
await this.pool.query(
|
|
1211
|
+
`UPDATE qjw_jobs
|
|
1212
|
+
SET status = 'dead',
|
|
1213
|
+
attempts_made = attempts_made + 1,
|
|
1214
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1215
|
+
lock_id = NULL,
|
|
1216
|
+
lock_expires_at = NULL,
|
|
1217
|
+
failed_at = NOW(3),
|
|
1218
|
+
updated_at = NOW(3)
|
|
1219
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1220
|
+
[attempt, input.jobId, input.lockId]
|
|
1221
|
+
);
|
|
1222
|
+
} else {
|
|
1223
|
+
await this.pool.query(
|
|
1224
|
+
`UPDATE qjw_jobs
|
|
1225
|
+
SET status = 'dead',
|
|
1226
|
+
attempts_made = attempts_made + 1,
|
|
1227
|
+
attempts = JSON_ARRAY_APPEND(attempts, '$', CAST(? AS JSON)),
|
|
1228
|
+
lock_id = NULL,
|
|
1229
|
+
lock_expires_at = NULL,
|
|
1230
|
+
failed_at = NOW(3),
|
|
1231
|
+
updated_at = NOW(3)
|
|
1232
|
+
WHERE id = ?`,
|
|
1233
|
+
[attempt, input.jobId]
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1075
1236
|
}
|
|
1076
1237
|
// -------------------------------------------------------------------------
|
|
1077
1238
|
// Release lock
|
|
1078
1239
|
// -------------------------------------------------------------------------
|
|
1079
|
-
async releaseLock(jobId) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1240
|
+
async releaseLock(jobId, lockId) {
|
|
1241
|
+
if (lockId !== void 0) {
|
|
1242
|
+
await this.pool.query(
|
|
1243
|
+
`UPDATE qjw_jobs
|
|
1244
|
+
SET lock_id = NULL, lock_expires_at = NOW(3), updated_at = NOW(3)
|
|
1245
|
+
WHERE id = ? AND status = 'active' AND lock_id = ?`,
|
|
1246
|
+
[jobId, lockId]
|
|
1247
|
+
);
|
|
1248
|
+
} else {
|
|
1249
|
+
await this.pool.query(
|
|
1250
|
+
`UPDATE qjw_jobs
|
|
1251
|
+
SET lock_id = NULL, lock_expires_at = NOW(3), updated_at = NOW(3)
|
|
1252
|
+
WHERE id = ?`,
|
|
1253
|
+
[jobId]
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1086
1256
|
}
|
|
1087
1257
|
// -------------------------------------------------------------------------
|
|
1088
1258
|
// Recover stalled jobs (atomic — SELECT … FOR UPDATE inside transaction)
|
|
@@ -1473,7 +1643,7 @@ var Worker = class {
|
|
|
1473
1643
|
if (this.activeJobIds.size > 0) {
|
|
1474
1644
|
await Promise.all(
|
|
1475
1645
|
Array.from(this.activeJobIds).map(
|
|
1476
|
-
(jobId) => this.storage.releaseLock(jobId).catch(() => {
|
|
1646
|
+
(jobId) => this.storage.releaseLock(jobId, this.id).catch(() => {
|
|
1477
1647
|
})
|
|
1478
1648
|
)
|
|
1479
1649
|
);
|
|
@@ -1525,7 +1695,7 @@ var Worker = class {
|
|
|
1525
1695
|
now
|
|
1526
1696
|
);
|
|
1527
1697
|
if (!allowed) {
|
|
1528
|
-
await this.storage.releaseLock(raw.id);
|
|
1698
|
+
await this.storage.releaseLock(raw.id, this.id);
|
|
1529
1699
|
return false;
|
|
1530
1700
|
}
|
|
1531
1701
|
}
|
|
@@ -1545,16 +1715,35 @@ var Worker = class {
|
|
|
1545
1715
|
await this.handleFailure(job, error);
|
|
1546
1716
|
return;
|
|
1547
1717
|
}
|
|
1718
|
+
const abortController = new AbortController();
|
|
1548
1719
|
let timeoutHandle = null;
|
|
1720
|
+
let lockRenewTimer = null;
|
|
1721
|
+
const lockRenewInterval = Math.max(100, Math.floor(this.config.lockDuration / 2));
|
|
1722
|
+
lockRenewTimer = setInterval(async () => {
|
|
1723
|
+
try {
|
|
1724
|
+
const renewed = await this.storage.renewLock(job.id, this.id, this.config.lockDuration);
|
|
1725
|
+
if (!renewed && lockRenewTimer) {
|
|
1726
|
+
clearInterval(lockRenewTimer);
|
|
1727
|
+
lockRenewTimer = null;
|
|
1728
|
+
}
|
|
1729
|
+
} catch {
|
|
1730
|
+
}
|
|
1731
|
+
}, lockRenewInterval);
|
|
1549
1732
|
try {
|
|
1550
1733
|
await new Promise((resolve, reject) => {
|
|
1551
1734
|
timeoutHandle = setTimeout(() => {
|
|
1552
|
-
|
|
1735
|
+
const timeoutError = new Error(`Job timed out after ${job.timeout}ms`);
|
|
1736
|
+
abortController.abort(timeoutError);
|
|
1737
|
+
reject(timeoutError);
|
|
1553
1738
|
}, job.timeout);
|
|
1554
|
-
Promise.resolve(processor(job)).then(resolve, reject);
|
|
1739
|
+
Promise.resolve(processor(job, abortController.signal)).then(resolve, reject);
|
|
1555
1740
|
});
|
|
1556
1741
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1557
|
-
|
|
1742
|
+
if (lockRenewTimer) {
|
|
1743
|
+
clearInterval(lockRenewTimer);
|
|
1744
|
+
lockRenewTimer = null;
|
|
1745
|
+
}
|
|
1746
|
+
await this.storage.complete(job.id, this.id);
|
|
1558
1747
|
const completedData = await this.storage.getJob(job.id) ?? job._data;
|
|
1559
1748
|
this.emitter.emit("job:completed", completedData);
|
|
1560
1749
|
if (job.cron) {
|
|
@@ -1562,9 +1751,17 @@ var Worker = class {
|
|
|
1562
1751
|
}
|
|
1563
1752
|
} catch (err) {
|
|
1564
1753
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1754
|
+
if (lockRenewTimer) {
|
|
1755
|
+
clearInterval(lockRenewTimer);
|
|
1756
|
+
lockRenewTimer = null;
|
|
1757
|
+
}
|
|
1565
1758
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1566
1759
|
await this.handleFailure(job, error);
|
|
1567
1760
|
} finally {
|
|
1761
|
+
if (lockRenewTimer) {
|
|
1762
|
+
clearInterval(lockRenewTimer);
|
|
1763
|
+
lockRenewTimer = null;
|
|
1764
|
+
}
|
|
1568
1765
|
this.activeJobIds.delete(job.id);
|
|
1569
1766
|
this.activeCount -= 1;
|
|
1570
1767
|
if (this.activeCount === 0 && this.drainResolve) {
|
|
@@ -1630,6 +1827,7 @@ var Worker = class {
|
|
|
1630
1827
|
runAt,
|
|
1631
1828
|
error: error.message,
|
|
1632
1829
|
attemptNumber,
|
|
1830
|
+
lockId: this.id,
|
|
1633
1831
|
...error.stack !== void 0 && { stack: error.stack }
|
|
1634
1832
|
});
|
|
1635
1833
|
const updated = await this.storage.getJob(job.id);
|
|
@@ -1641,6 +1839,7 @@ var Worker = class {
|
|
|
1641
1839
|
jobId: job.id,
|
|
1642
1840
|
error: error.message,
|
|
1643
1841
|
attemptNumber,
|
|
1842
|
+
lockId: this.id,
|
|
1644
1843
|
...error.stack !== void 0 && { stack: error.stack }
|
|
1645
1844
|
});
|
|
1646
1845
|
const dead = await this.storage.getJob(job.id);
|
|
@@ -1900,10 +2099,29 @@ var InMemoryStorageAdapter = class {
|
|
|
1900
2099
|
return job;
|
|
1901
2100
|
}
|
|
1902
2101
|
// -------------------------------------------------------------------------
|
|
2102
|
+
// Renew lock
|
|
2103
|
+
// -------------------------------------------------------------------------
|
|
2104
|
+
async renewLock(jobId, lockId, lockDuration) {
|
|
2105
|
+
const job = this.jobs.get(jobId);
|
|
2106
|
+
if (!job) return false;
|
|
2107
|
+
if (job.status !== "active" || job.lockId !== lockId) return false;
|
|
2108
|
+
const nowMs = Date.now();
|
|
2109
|
+
if (job.lockExpiresAt !== null && new Date(job.lockExpiresAt).getTime() <= nowMs) {
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2112
|
+
job.lockExpiresAt = new Date(nowMs + lockDuration).toISOString();
|
|
2113
|
+
job.updatedAt = new Date(nowMs).toISOString();
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
// -------------------------------------------------------------------------
|
|
1903
2117
|
// Complete
|
|
1904
2118
|
// -------------------------------------------------------------------------
|
|
1905
|
-
async complete(jobId) {
|
|
1906
|
-
const job = this.
|
|
2119
|
+
async complete(jobId, lockId) {
|
|
2120
|
+
const job = this.jobs.get(jobId);
|
|
2121
|
+
if (!job) return;
|
|
2122
|
+
if (lockId !== void 0 && (job.status !== "active" || job.lockId !== lockId)) {
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
1907
2125
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1908
2126
|
job.status = "completed";
|
|
1909
2127
|
job.lockId = null;
|
|
@@ -1915,7 +2133,11 @@ var InMemoryStorageAdapter = class {
|
|
|
1915
2133
|
// Requeue (retry)
|
|
1916
2134
|
// -------------------------------------------------------------------------
|
|
1917
2135
|
async requeue(input) {
|
|
1918
|
-
const job = this.
|
|
2136
|
+
const job = this.jobs.get(input.jobId);
|
|
2137
|
+
if (!job) return;
|
|
2138
|
+
if (input.lockId !== void 0 && (job.status !== "active" || job.lockId !== input.lockId)) {
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
1919
2141
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1920
2142
|
job.attempts.push({
|
|
1921
2143
|
attempt: input.attemptNumber,
|
|
@@ -1935,7 +2157,11 @@ var InMemoryStorageAdapter = class {
|
|
|
1935
2157
|
// Move to DLQ
|
|
1936
2158
|
// -------------------------------------------------------------------------
|
|
1937
2159
|
async moveToDlq(input) {
|
|
1938
|
-
const job = this.
|
|
2160
|
+
const job = this.jobs.get(input.jobId);
|
|
2161
|
+
if (!job) return;
|
|
2162
|
+
if (input.lockId !== void 0 && (job.status !== "active" || job.lockId !== input.lockId)) {
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
1939
2165
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1940
2166
|
job.attempts.push({
|
|
1941
2167
|
attempt: input.attemptNumber,
|
|
@@ -1954,9 +2180,12 @@ var InMemoryStorageAdapter = class {
|
|
|
1954
2180
|
// -------------------------------------------------------------------------
|
|
1955
2181
|
// Release lock
|
|
1956
2182
|
// -------------------------------------------------------------------------
|
|
1957
|
-
async releaseLock(jobId) {
|
|
2183
|
+
async releaseLock(jobId, lockId) {
|
|
1958
2184
|
const job = this.jobs.get(jobId);
|
|
1959
2185
|
if (!job) return;
|
|
2186
|
+
if (lockId !== void 0 && (job.status !== "active" || job.lockId !== lockId)) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
1960
2189
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1961
2190
|
job.lockId = null;
|
|
1962
2191
|
job.lockExpiresAt = now;
|