bullmq 5.58.7 → 5.58.8

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.
Files changed (63) hide show
  1. package/dist/cjs/classes/job-scheduler.js +22 -24
  2. package/dist/cjs/classes/job-scheduler.js.map +1 -1
  3. package/dist/cjs/classes/job.js +6 -2
  4. package/dist/cjs/classes/job.js.map +1 -1
  5. package/dist/cjs/classes/queue.js +1 -1
  6. package/dist/cjs/classes/queue.js.map +1 -1
  7. package/dist/cjs/classes/scripts.js +37 -11
  8. package/dist/cjs/classes/scripts.js.map +1 -1
  9. package/dist/cjs/classes/worker.js +53 -18
  10. package/dist/cjs/classes/worker.js.map +1 -1
  11. package/dist/cjs/commands/addJobScheduler-11.lua +81 -25
  12. package/dist/cjs/commands/addRepeatableJob-2.lua +1 -1
  13. package/dist/cjs/commands/includes/addJobFromScheduler.lua +5 -3
  14. package/dist/cjs/commands/includes/getJobSchedulerEveryNextMillis.lua +28 -0
  15. package/dist/cjs/commands/includes/storeJobScheduler.lua +15 -1
  16. package/dist/cjs/commands/moveStalledJobsToWait-8.lua +14 -1
  17. package/dist/cjs/commands/updateJobScheduler-12.lua +50 -14
  18. package/dist/cjs/enums/error-code.js +2 -0
  19. package/dist/cjs/enums/error-code.js.map +1 -1
  20. package/dist/cjs/scripts/addJobScheduler-11.js +108 -25
  21. package/dist/cjs/scripts/addJobScheduler-11.js.map +1 -1
  22. package/dist/cjs/scripts/addRepeatableJob-2.js +1 -1
  23. package/dist/cjs/scripts/moveStalledJobsToWait-8.js +11 -1
  24. package/dist/cjs/scripts/moveStalledJobsToWait-8.js.map +1 -1
  25. package/dist/cjs/scripts/updateJobScheduler-12.js +66 -17
  26. package/dist/cjs/scripts/updateJobScheduler-12.js.map +1 -1
  27. package/dist/cjs/tsconfig-cjs.tsbuildinfo +1 -1
  28. package/dist/cjs/version.js +1 -1
  29. package/dist/esm/classes/job-scheduler.js +22 -24
  30. package/dist/esm/classes/job-scheduler.js.map +1 -1
  31. package/dist/esm/classes/job.js +6 -2
  32. package/dist/esm/classes/job.js.map +1 -1
  33. package/dist/esm/classes/queue.d.ts +1 -1
  34. package/dist/esm/classes/queue.js +1 -1
  35. package/dist/esm/classes/queue.js.map +1 -1
  36. package/dist/esm/classes/scripts.d.ts +1 -1
  37. package/dist/esm/classes/scripts.js +37 -11
  38. package/dist/esm/classes/scripts.js.map +1 -1
  39. package/dist/esm/classes/worker.js +53 -18
  40. package/dist/esm/classes/worker.js.map +1 -1
  41. package/dist/esm/commands/addJobScheduler-11.lua +81 -25
  42. package/dist/esm/commands/addRepeatableJob-2.lua +1 -1
  43. package/dist/esm/commands/includes/addJobFromScheduler.lua +5 -3
  44. package/dist/esm/commands/includes/getJobSchedulerEveryNextMillis.lua +28 -0
  45. package/dist/esm/commands/includes/storeJobScheduler.lua +15 -1
  46. package/dist/esm/commands/moveStalledJobsToWait-8.lua +14 -1
  47. package/dist/esm/commands/updateJobScheduler-12.lua +50 -14
  48. package/dist/esm/enums/error-code.d.ts +3 -1
  49. package/dist/esm/enums/error-code.js +2 -0
  50. package/dist/esm/enums/error-code.js.map +1 -1
  51. package/dist/esm/interfaces/job-scheduler-json.d.ts +1 -0
  52. package/dist/esm/interfaces/repeatable-options.d.ts +1 -0
  53. package/dist/esm/scripts/addJobScheduler-11.js +108 -25
  54. package/dist/esm/scripts/addJobScheduler-11.js.map +1 -1
  55. package/dist/esm/scripts/addRepeatableJob-2.js +1 -1
  56. package/dist/esm/scripts/moveStalledJobsToWait-8.js +11 -1
  57. package/dist/esm/scripts/moveStalledJobsToWait-8.js.map +1 -1
  58. package/dist/esm/scripts/updateJobScheduler-12.js +66 -17
  59. package/dist/esm/scripts/updateJobScheduler-12.js.map +1 -1
  60. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  61. package/dist/esm/version.d.ts +1 -1
  62. package/dist/esm/version.js +1 -1
  63. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"error-code.js","sourceRoot":"","sources":["../../../src/enums/error-code.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,SAUX;AAVD,WAAY,SAAS;IACnB,wDAAgB,CAAA;IAChB,gEAAoB,CAAA;IACpB,4DAAkB,CAAA;IAClB,sEAAuB,CAAA;IACvB,oEAAsB,CAAA;IACtB,gEAAoB,CAAA;IACpB,oFAA8B,CAAA;IAC9B,kFAA6B,CAAA;IAC7B,0EAAyB,CAAA;AAC3B,CAAC,EAVW,SAAS,KAAT,SAAS,QAUpB"}
1
+ {"version":3,"file":"error-code.js","sourceRoot":"","sources":["../../../src/enums/error-code.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,SAYX;AAZD,WAAY,SAAS;IACnB,wDAAgB,CAAA;IAChB,gEAAoB,CAAA;IACpB,4DAAkB,CAAA;IAClB,sEAAuB,CAAA;IACvB,oEAAsB,CAAA;IACtB,gEAAoB,CAAA;IACpB,oFAA8B,CAAA;IAC9B,kFAA6B,CAAA;IAC7B,0EAAyB,CAAA;IACzB,iFAA6B,CAAA;IAC7B,6EAA2B,CAAA;AAC7B,CAAC,EAZW,SAAS,KAAT,SAAS,QAYpB"}
@@ -9,6 +9,7 @@ export interface JobSchedulerJson<D = any> {
9
9
  id?: string | null;
10
10
  iterationCount?: number;
11
11
  limit?: number;
12
+ startDate?: number;
12
13
  endDate?: number;
13
14
  tz?: string;
14
15
  pattern?: string;
@@ -1,5 +1,6 @@
1
1
  export type RepeatableOptions = {
2
2
  name: string;
3
+ startDate?: number;
3
4
  endDate?: number;
4
5
  tz?: string;
5
6
  limit?: number;
@@ -16,7 +16,7 @@ const content = `--[[
16
16
  ARGV[2] msgpacked options
17
17
  [1] name
18
18
  [2] tz?
19
- [3] patten?
19
+ [3] pattern?
20
20
  [4] endDate?
21
21
  [5] every?
22
22
  ARGV[3] jobs scheduler id
@@ -40,7 +40,9 @@ local eventsKey = KEYS[9]
40
40
  local nextMillis = ARGV[1]
41
41
  local jobSchedulerId = ARGV[3]
42
42
  local templateOpts = cmsgpack.unpack(ARGV[5])
43
+ local now = tonumber(ARGV[7])
43
44
  local prefixKey = ARGV[8]
45
+ local jobOpts = cmsgpack.unpack(ARGV[6])
44
46
  -- Includes
45
47
  --[[
46
48
  Add delay marker if needed.
@@ -200,10 +202,11 @@ local function addJobInTargetList(targetKey, markerKey, pushCmd, isPausedOrMaxed
200
202
  rcall(pushCmd, targetKey, jobId)
201
203
  addBaseMarkerIfNeeded(markerKey, isPausedOrMaxed)
202
204
  end
203
- local function addJobFromScheduler(jobKey, jobId, rawOpts, waitKey, pausedKey, activeKey, metaKey,
205
+ local function addJobFromScheduler(jobKey, jobId, opts, waitKey, pausedKey, activeKey, metaKey,
204
206
  prioritizedKey, priorityCounter, delayedKey, markerKey, eventsKey, name, maxEvents, timestamp,
205
- data, jobSchedulerId)
206
- local opts = cmsgpack.unpack(rawOpts)
207
+ data, jobSchedulerId, repeatDelay)
208
+ opts['delay'] = repeatDelay
209
+ opts['jobId'] = jobId
207
210
  local delay, priority = storeJob(eventsKey, jobKey, jobId, name, data,
208
211
  opts, timestamp, nil, nil, jobSchedulerId)
209
212
  if delay ~= 0 then
@@ -374,6 +377,10 @@ local function storeJobScheduler(schedulerId, schedulerKey, repeatKey, nextMilli
374
377
  table.insert(optionalValues, "pattern")
375
378
  table.insert(optionalValues, opts['pattern'])
376
379
  end
380
+ if opts['startDate'] then
381
+ table.insert(optionalValues, "startDate")
382
+ table.insert(optionalValues, opts['startDate'])
383
+ end
377
384
  if opts['endDate'] then
378
385
  table.insert(optionalValues, "endDate")
379
386
  table.insert(optionalValues, opts['endDate'])
@@ -385,6 +392,12 @@ local function storeJobScheduler(schedulerId, schedulerKey, repeatKey, nextMilli
385
392
  if opts['offset'] then
386
393
  table.insert(optionalValues, "offset")
387
394
  table.insert(optionalValues, opts['offset'])
395
+ else
396
+ local offset = rcall("HGET", schedulerKey, "offset")
397
+ if offset then
398
+ table.insert(optionalValues, "offset")
399
+ table.insert(optionalValues, tonumber(offset))
400
+ end
388
401
  end
389
402
  local jsonTemplateOpts = cjson.encode(templateOpts)
390
403
  if jsonTemplateOpts and jsonTemplateOpts ~= '{}' then
@@ -395,15 +408,55 @@ local function storeJobScheduler(schedulerId, schedulerKey, repeatKey, nextMilli
395
408
  table.insert(optionalValues, "data")
396
409
  table.insert(optionalValues, templateData)
397
410
  end
411
+ table.insert(optionalValues, "ic")
412
+ table.insert(optionalValues, rcall("HGET", schedulerKey, "ic") or 1)
398
413
  rcall("DEL", schedulerKey) -- remove all attributes and then re-insert new ones
399
- rcall("HMSET", schedulerKey, "name", opts['name'], "ic", 1, unpack(optionalValues))
414
+ rcall("HMSET", schedulerKey, "name", opts['name'], unpack(optionalValues))
415
+ end
416
+ local function getJobSchedulerEveryNextMillis(prevMillis, every, now, offset, startDate)
417
+ local nextMillis
418
+ if not prevMillis then
419
+ if startDate then
420
+ -- Assuming startDate is passed as milliseconds from JavaScript
421
+ nextMillis = tonumber(startDate)
422
+ nextMillis = nextMillis > now and nextMillis or now
423
+ else
424
+ nextMillis = now
425
+ end
426
+ else
427
+ nextMillis = prevMillis + every
428
+ -- check if we may have missed some iterations
429
+ if nextMillis < now then
430
+ nextMillis = math.floor(now / every) * every + every + (offset or 0)
431
+ end
432
+ end
433
+ if not offset or offset == 0 then
434
+ local timeSlot = math.floor(nextMillis / every) * every;
435
+ offset = nextMillis - timeSlot;
436
+ end
437
+ -- Return a tuple nextMillis, offset
438
+ return math.floor(nextMillis), math.floor(offset)
400
439
  end
401
440
  -- If we are overriding a repeatable job we must delete the delayed job for
402
441
  -- the next iteration.
403
442
  local schedulerKey = repeatKey .. ":" .. jobSchedulerId
404
- local nextDelayedJobKey = schedulerKey .. ":" .. nextMillis
405
- local nextDelayedJobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
406
443
  local maxEvents = getOrSetMaxEvents(metaKey)
444
+ local templateData = ARGV[4]
445
+ local prevMillis = rcall("ZSCORE", repeatKey, jobSchedulerId)
446
+ if prevMillis then
447
+ prevMillis = tonumber(prevMillis)
448
+ end
449
+ local schedulerOpts = cmsgpack.unpack(ARGV[2])
450
+ local every = schedulerOpts['every']
451
+ -- For backwards compatibility we also check the offset from the job itself.
452
+ -- could be removed in future major versions.
453
+ local jobOffset = jobOpts['repeat'] and jobOpts['repeat']['offset'] or 0
454
+ local offset = schedulerOpts['offset'] or jobOffset or 0
455
+ local newOffset = offset
456
+ if every then
457
+ local startDate = schedulerOpts['startDate']
458
+ nextMillis, newOffset = getJobSchedulerEveryNextMillis(prevMillis, every, now, offset, startDate)
459
+ end
407
460
  local function removeJobFromScheduler(prefixKey, delayedKey, prioritizedKey, waitKey, pausedKey, jobId,
408
461
  metaKey, eventsKey)
409
462
  if rcall("ZSCORE", delayedKey, jobId) then
@@ -426,33 +479,63 @@ local function removeJobFromScheduler(prefixKey, delayedKey, prioritizedKey, wai
426
479
  end
427
480
  return false
428
481
  end
429
- if rcall("EXISTS", nextDelayedJobKey) == 1 then
430
- if not removeJobFromScheduler(prefixKey, delayedKey, prioritizedKey, waitKey, pausedKey,
431
- nextDelayedJobId, metaKey, eventsKey) then
432
- rcall("XADD", eventsKey, "MAXLEN", "~", maxEvents, "*", "event",
433
- "duplicated", "jobId", nextDelayedJobId)
434
- return nextDelayedJobId .. "" -- convert to string
435
- end
436
- end
437
- local prevMillis = rcall("ZSCORE", repeatKey, jobSchedulerId)
482
+ local hadPrevJob = false
438
483
  if prevMillis then
439
484
  local currentJobId = "repeat:" .. jobSchedulerId .. ":" .. prevMillis
440
- local currentDelayedJobKey = schedulerKey .. ":" .. prevMillis
441
- if currentJobId ~= nextDelayedJobId and rcall("EXISTS", currentDelayedJobKey) == 1 then
442
- removeJobFromScheduler(prefixKey, delayedKey, prioritizedKey, waitKey, pausedKey,
485
+ local currentJobKey = schedulerKey .. ":" .. prevMillis
486
+ -- In theory it should always exist the currentJobKey if there is a prevMillis unless something has
487
+ -- gone really wrong.
488
+ if rcall("EXISTS", currentJobKey) == 1 then
489
+ hadPrevJob = removeJobFromScheduler(prefixKey, delayedKey, prioritizedKey, waitKey, pausedKey,
443
490
  currentJobId, metaKey, eventsKey)
444
491
  end
445
492
  end
446
- local schedulerOpts = cmsgpack.unpack(ARGV[2])
447
- storeJobScheduler(jobSchedulerId, schedulerKey, repeatKey, nextMillis, schedulerOpts, ARGV[4], templateOpts)
493
+ if hadPrevJob then
494
+ -- The jobs has been removed and we want to replace it, so lets use the same millis.
495
+ nextMillis = prevMillis
496
+ else
497
+ -- Special case where no job was removed, and we need to add the next iteration.
498
+ schedulerOpts['offset'] = newOffset
499
+ end
500
+ -- Check for job ID collision with existing jobs (in any state)
501
+ local jobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
502
+ local jobKey = prefixKey .. jobId
503
+ -- If there's already a job with this ID, handle the collision
504
+ if rcall("EXISTS", jobKey) == 1 then
505
+ if every then
506
+ -- For 'every' case: try next time slot to avoid collision
507
+ local nextSlotMillis = nextMillis + every
508
+ local nextSlotJobId = "repeat:" .. jobSchedulerId .. ":" .. nextSlotMillis
509
+ local nextSlotJobKey = prefixKey .. nextSlotJobId
510
+ if rcall("EXISTS", nextSlotJobKey) == 0 then
511
+ -- Next slot is free, use it
512
+ nextMillis = nextSlotMillis
513
+ jobId = nextSlotJobId
514
+ else
515
+ -- Next slot also has a job, return error code
516
+ return -11 -- SchedulerJobSlotsBusy
517
+ end
518
+ else
519
+ -- For 'pattern' case: return error code
520
+ return -10 -- SchedulerJobIdCollision
521
+ end
522
+ end
523
+ local delay = nextMillis - now
524
+ -- Fast Clamp delay to minimum of 0
525
+ if delay < 0 then
526
+ delay = 0
527
+ end
528
+ local nextJobKey = schedulerKey .. ":" .. nextMillis
529
+ -- jobId already calculated above during collision check
530
+ storeJobScheduler(jobSchedulerId, schedulerKey, repeatKey, nextMillis, schedulerOpts, templateData, templateOpts)
448
531
  rcall("INCR", KEYS[8])
449
- addJobFromScheduler(nextDelayedJobKey, nextDelayedJobId, ARGV[6], waitKey, pausedKey,
532
+ addJobFromScheduler(nextJobKey, jobId, jobOpts, waitKey, pausedKey,
450
533
  KEYS[11], metaKey, prioritizedKey, KEYS[10], delayedKey, KEYS[7], eventsKey,
451
- schedulerOpts['name'], maxEvents, ARGV[7], ARGV[4], jobSchedulerId)
534
+ schedulerOpts['name'], maxEvents, now, templateData, jobSchedulerId, delay)
452
535
  if ARGV[9] ~= "" then
453
- rcall("HSET", ARGV[9], "nrjid", nextDelayedJobId)
536
+ rcall("HSET", ARGV[9], "nrjid", jobId)
454
537
  end
455
- return nextDelayedJobId .. "" -- convert to string
538
+ return {jobId .. "", delay}
456
539
  `;
457
540
  export const addJobScheduler = {
458
541
  name: 'addJobScheduler',
@@ -1 +1 @@
1
- {"version":3,"file":"addJobScheduler-11.js","sourceRoot":"","sources":["../../../src/scripts/addJobScheduler-11.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAucf,CAAC;AACF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,iBAAiB;IACvB,OAAO;IACP,IAAI,EAAE,EAAE;CACT,CAAC"}
1
+ {"version":3,"file":"addJobScheduler-11.js","sourceRoot":"","sources":["../../../src/scripts/addJobScheduler-11.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0hBf,CAAC;AACF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,iBAAiB;IACvB,OAAO;IACP,IAAI,EAAE,EAAE;CACT,CAAC"}
@@ -7,7 +7,7 @@ const content = `--[[
7
7
  ARGV[2] msgpacked options
8
8
  [1] name
9
9
  [2] tz?
10
- [3] patten?
10
+ [3] pattern?
11
11
  [4] endDate?
12
12
  [5] every?
13
13
  ARGV[3] legacy custom key TODO: remove this logic in next breaking change
@@ -141,7 +141,17 @@ if (#stalling > 0) then
141
141
  if (removed > 0) then
142
142
  -- If this job has been stalled too many times, such as if it crashes the worker, then fail it.
143
143
  local stalledCount = rcall("HINCRBY", jobKey, "stc", 1)
144
- if stalledCount > maxStalledJobCount then
144
+ -- Check if this is a repeatable job by looking at job options
145
+ local jobOpts = rcall("HGET", jobKey, "opts")
146
+ local isRepeatableJob = false
147
+ if jobOpts then
148
+ local opts = cjson.decode(jobOpts)
149
+ if opts and opts["repeat"] then
150
+ isRepeatableJob = true
151
+ end
152
+ end
153
+ -- Only fail job if it exceeds stall limit AND is not a repeatable job
154
+ if stalledCount > maxStalledJobCount and not isRepeatableJob then
145
155
  local failedReason = "job stalled more than allowable limit"
146
156
  rcall("HSET", jobKey, "defa", failedReason)
147
157
  end
@@ -1 +1 @@
1
- {"version":3,"file":"moveStalledJobsToWait-8.js","sourceRoot":"","sources":["../../../src/scripts/moveStalledJobsToWait-8.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqKf,CAAC;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,uBAAuB;IAC7B,OAAO;IACP,IAAI,EAAE,CAAC;CACR,CAAC"}
1
+ {"version":3,"file":"moveStalledJobsToWait-8.js","sourceRoot":"","sources":["../../../src/scripts/moveStalledJobsToWait-8.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Kf,CAAC;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,uBAAuB;IAC7B,OAAO;IACP,IAAI,EAAE,CAAC;CACR,CAAC"}
@@ -22,19 +22,19 @@ const content = `--[[
22
22
  ARGV[7] producer id
23
23
  Output:
24
24
  next delayed job id - OK
25
- ]]
26
- local rcall = redis.call
25
+ ]] local rcall = redis.call
27
26
  local repeatKey = KEYS[1]
28
27
  local delayedKey = KEYS[2]
29
28
  local waitKey = KEYS[3]
30
29
  local pausedKey = KEYS[4]
31
30
  local metaKey = KEYS[5]
32
31
  local prioritizedKey = KEYS[6]
33
- local nextMillis = ARGV[1]
32
+ local nextMillis = tonumber(ARGV[1])
34
33
  local jobSchedulerId = ARGV[2]
35
- local timestamp = ARGV[5]
34
+ local timestamp = tonumber(ARGV[5])
36
35
  local prefixKey = ARGV[6]
37
36
  local producerId = ARGV[7]
37
+ local jobOpts = cmsgpack.unpack(ARGV[4])
38
38
  -- Includes
39
39
  --[[
40
40
  Add delay marker if needed.
@@ -194,10 +194,11 @@ local function addJobInTargetList(targetKey, markerKey, pushCmd, isPausedOrMaxed
194
194
  rcall(pushCmd, targetKey, jobId)
195
195
  addBaseMarkerIfNeeded(markerKey, isPausedOrMaxed)
196
196
  end
197
- local function addJobFromScheduler(jobKey, jobId, rawOpts, waitKey, pausedKey, activeKey, metaKey,
197
+ local function addJobFromScheduler(jobKey, jobId, opts, waitKey, pausedKey, activeKey, metaKey,
198
198
  prioritizedKey, priorityCounter, delayedKey, markerKey, eventsKey, name, maxEvents, timestamp,
199
- data, jobSchedulerId)
200
- local opts = cmsgpack.unpack(rawOpts)
199
+ data, jobSchedulerId, repeatDelay)
200
+ opts['delay'] = repeatDelay
201
+ opts['jobId'] = jobId
201
202
  local delay, priority = storeJob(eventsKey, jobKey, jobId, name, data,
202
203
  opts, timestamp, nil, nil, jobSchedulerId)
203
204
  if delay ~= 0 then
@@ -227,18 +228,61 @@ local function getOrSetMaxEvents(metaKey)
227
228
  end
228
229
  return maxEvents
229
230
  end
230
- local schedulerKey = repeatKey .. ":" .. jobSchedulerId
231
- local nextDelayedJobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
232
- local nextDelayedJobKey = schedulerKey .. ":" .. nextMillis
233
- -- Validate that scheduler exists.
231
+ local function getJobSchedulerEveryNextMillis(prevMillis, every, now, offset, startDate)
232
+ local nextMillis
233
+ if not prevMillis then
234
+ if startDate then
235
+ -- Assuming startDate is passed as milliseconds from JavaScript
236
+ nextMillis = tonumber(startDate)
237
+ nextMillis = nextMillis > now and nextMillis or now
238
+ else
239
+ nextMillis = now
240
+ end
241
+ else
242
+ nextMillis = prevMillis + every
243
+ -- check if we may have missed some iterations
244
+ if nextMillis < now then
245
+ nextMillis = math.floor(now / every) * every + every + (offset or 0)
246
+ end
247
+ end
248
+ if not offset or offset == 0 then
249
+ local timeSlot = math.floor(nextMillis / every) * every;
250
+ offset = nextMillis - timeSlot;
251
+ end
252
+ -- Return a tuple nextMillis, offset
253
+ return math.floor(nextMillis), math.floor(offset)
254
+ end
234
255
  local prevMillis = rcall("ZSCORE", repeatKey, jobSchedulerId)
256
+ -- Validate that scheduler exists.
257
+ -- If it does not exist we should not iterate anymore.
235
258
  if prevMillis then
259
+ prevMillis = tonumber(prevMillis)
260
+ local schedulerKey = repeatKey .. ":" .. jobSchedulerId
261
+ local schedulerAttributes = rcall("HMGET", schedulerKey, "name", "data", "every", "startDate", "offset")
262
+ local every = tonumber(schedulerAttributes[3])
263
+ local now = tonumber(timestamp)
264
+ -- If every is not found in scheduler attributes, try to get it from job options
265
+ if not every and jobOpts['repeat'] and jobOpts['repeat']['every'] then
266
+ every = tonumber(jobOpts['repeat']['every'])
267
+ end
268
+ if every then
269
+ local startDate = schedulerAttributes[4]
270
+ local jobOptsOffset = jobOpts['repeat'] and jobOpts['repeat']['offset'] or 0
271
+ local offset = schedulerAttributes[5] or jobOptsOffset or 0
272
+ local newOffset
273
+ nextMillis, newOffset = getJobSchedulerEveryNextMillis(prevMillis, every, now, offset, startDate)
274
+ if not offset then
275
+ rcall("HSET", schedulerKey, "offset", newOffset)
276
+ jobOpts['repeat']['offset'] = newOffset
277
+ end
278
+ end
279
+ local nextDelayedJobId = "repeat:" .. jobSchedulerId .. ":" .. nextMillis
280
+ local nextDelayedJobKey = schedulerKey .. ":" .. nextMillis
236
281
  local currentDelayedJobId = "repeat:" .. jobSchedulerId .. ":" .. prevMillis
237
282
  if producerId == currentDelayedJobId then
238
283
  local eventsKey = KEYS[9]
239
284
  local maxEvents = getOrSetMaxEvents(metaKey)
240
285
  if rcall("EXISTS", nextDelayedJobKey) ~= 1 then
241
- local schedulerAttributes = rcall("HMGET", schedulerKey, "name", "data")
242
286
  rcall("ZADD", repeatKey, nextMillis, jobSchedulerId)
243
287
  rcall("HINCRBY", schedulerKey, "ic", 1)
244
288
  rcall("INCR", KEYS[8])
@@ -248,17 +292,22 @@ if prevMillis then
248
292
  if templateData and templateData ~= '{}' then
249
293
  rcall("HSET", schedulerKey, "data", templateData)
250
294
  end
251
- addJobFromScheduler(nextDelayedJobKey, nextDelayedJobId, ARGV[4], waitKey, pausedKey,
252
- KEYS[12], metaKey, prioritizedKey, KEYS[10], delayedKey, KEYS[7], eventsKey,
253
- schedulerAttributes[1], maxEvents, ARGV[5], templateData or '{}', jobSchedulerId)
295
+ local delay = nextMillis - now
296
+ -- Fast Clamp delay to minimum of 0
297
+ if delay < 0 then
298
+ delay = 0
299
+ end
300
+ jobOpts["delay"] = delay
301
+ addJobFromScheduler(nextDelayedJobKey, nextDelayedJobId, jobOpts, waitKey, pausedKey, KEYS[12], metaKey,
302
+ prioritizedKey, KEYS[10], delayedKey, KEYS[7], eventsKey, schedulerAttributes[1], maxEvents, ARGV[5],
303
+ templateData or '{}', jobSchedulerId, delay)
254
304
  -- TODO: remove this workaround in next breaking change
255
305
  if KEYS[11] ~= "" then
256
306
  rcall("HSET", KEYS[11], "nrjid", nextDelayedJobId)
257
307
  end
258
308
  return nextDelayedJobId .. "" -- convert to string
259
309
  else
260
- rcall("XADD", eventsKey, "MAXLEN", "~", maxEvents, "*", "event",
261
- "duplicated", "jobId", nextDelayedJobId)
310
+ rcall("XADD", eventsKey, "MAXLEN", "~", maxEvents, "*", "event", "duplicated", "jobId", nextDelayedJobId)
262
311
  end
263
312
  end
264
313
  end
@@ -1 +1 @@
1
- {"version":3,"file":"updateJobScheduler-12.js","sourceRoot":"","sources":["../../../src/scripts/updateJobScheduler-12.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwQf,CAAC;AACF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,oBAAoB;IAC1B,OAAO;IACP,IAAI,EAAE,EAAE;CACT,CAAC"}
1
+ {"version":3,"file":"updateJobScheduler-12.js","sourceRoot":"","sources":["../../../src/scripts/updateJobScheduler-12.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyTf,CAAC;AACF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,oBAAoB;IAC1B,OAAO;IACP,IAAI,EAAE,EAAE;CACT,CAAC"}