bullmq 2.3.0 → 2.3.2

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 (109) hide show
  1. package/dist/cjs/classes/redis-connection.js +2 -2
  2. package/dist/cjs/classes/redis-connection.js.map +1 -1
  3. package/dist/cjs/commands/addJob-8.lua +174 -0
  4. package/dist/cjs/commands/changeDelay-4.lua +43 -0
  5. package/dist/cjs/commands/cleanJobsInSet-2.lua +46 -0
  6. package/dist/cjs/commands/drain-4.lua +25 -0
  7. package/dist/cjs/commands/extendLock-2.lua +23 -0
  8. package/dist/cjs/commands/getState-7.lua +57 -0
  9. package/dist/cjs/commands/getStateV2-7.lua +51 -0
  10. package/dist/cjs/commands/includes/addJobWithPriority.lua +16 -0
  11. package/dist/cjs/commands/includes/batches.lua +18 -0
  12. package/dist/cjs/commands/includes/checkItemInList.lua +12 -0
  13. package/dist/cjs/commands/includes/checkStalledJobs.lua +137 -0
  14. package/dist/cjs/commands/includes/cleanList.lua +50 -0
  15. package/dist/cjs/commands/includes/cleanSet.lua +50 -0
  16. package/dist/cjs/commands/includes/collectMetrics.lua +46 -0
  17. package/dist/cjs/commands/includes/destructureJobKey.lua +12 -0
  18. package/dist/cjs/commands/includes/getNextDelayedTimestamp.lua +13 -0
  19. package/dist/cjs/commands/includes/getTargetQueueList.lua +12 -0
  20. package/dist/cjs/commands/includes/getTimestamp.lua +19 -0
  21. package/dist/cjs/commands/includes/getZSetItems.lua +7 -0
  22. package/dist/cjs/commands/includes/isLocked.lua +31 -0
  23. package/dist/cjs/commands/includes/moveJobFromWaitToActive.lua +84 -0
  24. package/dist/cjs/commands/includes/moveParentFromWaitingChildrenToFailed.lua +28 -0
  25. package/dist/cjs/commands/includes/promoteDelayedJobs.lua +49 -0
  26. package/dist/cjs/commands/includes/removeJob.lua +13 -0
  27. package/dist/cjs/commands/includes/removeJobFromAnyState.lua +26 -0
  28. package/dist/cjs/commands/includes/removeJobs.lua +38 -0
  29. package/dist/cjs/commands/includes/removeJobsByMaxAge.lua +15 -0
  30. package/dist/cjs/commands/includes/removeJobsByMaxCount.lua +15 -0
  31. package/dist/cjs/commands/includes/removeParentDependencyKey.lua +81 -0
  32. package/dist/cjs/commands/includes/trimEvents.lua +12 -0
  33. package/dist/cjs/commands/includes/updateParentDepsIfNeeded.lua +28 -0
  34. package/dist/cjs/commands/isFinished-3.lua +48 -0
  35. package/dist/cjs/commands/isJobInList-1.lua +16 -0
  36. package/dist/cjs/commands/moveStalledJobsToWait-8.lua +24 -0
  37. package/dist/cjs/commands/moveToActive-9.lua +67 -0
  38. package/dist/cjs/commands/moveToDelayed-5.lua +54 -0
  39. package/dist/cjs/commands/moveToFinished-12.lua +201 -0
  40. package/dist/cjs/commands/moveToWaitingChildren-4.lua +62 -0
  41. package/dist/cjs/commands/obliterate-2.lua +94 -0
  42. package/dist/cjs/commands/pause-4.lua +27 -0
  43. package/dist/cjs/commands/promote-6.lua +59 -0
  44. package/dist/cjs/commands/releaseLock-1.lua +19 -0
  45. package/dist/cjs/commands/removeJob-1.lua +72 -0
  46. package/dist/cjs/commands/removeRepeatable-2.lua +32 -0
  47. package/dist/cjs/commands/reprocessJob-4.lua +35 -0
  48. package/dist/cjs/commands/retryJob-6.lua +51 -0
  49. package/dist/cjs/commands/retryJobs-6.lua +53 -0
  50. package/dist/cjs/commands/takeLock-1.lua +17 -0
  51. package/dist/cjs/commands/updateData-1.lua +16 -0
  52. package/dist/cjs/commands/updateProgress-2.lua +22 -0
  53. package/dist/cjs/scripts/moveToFinished-12.js +5 -2
  54. package/dist/cjs/scripts/moveToFinished-12.js.map +1 -1
  55. package/dist/esm/classes/redis-connection.js +2 -2
  56. package/dist/esm/classes/redis-connection.js.map +1 -1
  57. package/dist/esm/commands/addJob-8.lua +174 -0
  58. package/dist/esm/commands/changeDelay-4.lua +43 -0
  59. package/dist/esm/commands/cleanJobsInSet-2.lua +46 -0
  60. package/dist/esm/commands/drain-4.lua +25 -0
  61. package/dist/esm/commands/extendLock-2.lua +23 -0
  62. package/dist/esm/commands/getState-7.lua +57 -0
  63. package/dist/esm/commands/getStateV2-7.lua +51 -0
  64. package/dist/esm/commands/includes/addJobWithPriority.lua +16 -0
  65. package/dist/esm/commands/includes/batches.lua +18 -0
  66. package/dist/esm/commands/includes/checkItemInList.lua +12 -0
  67. package/dist/esm/commands/includes/checkStalledJobs.lua +137 -0
  68. package/dist/esm/commands/includes/cleanList.lua +50 -0
  69. package/dist/esm/commands/includes/cleanSet.lua +50 -0
  70. package/dist/esm/commands/includes/collectMetrics.lua +46 -0
  71. package/dist/esm/commands/includes/destructureJobKey.lua +12 -0
  72. package/dist/esm/commands/includes/getNextDelayedTimestamp.lua +13 -0
  73. package/dist/esm/commands/includes/getTargetQueueList.lua +12 -0
  74. package/dist/esm/commands/includes/getTimestamp.lua +19 -0
  75. package/dist/esm/commands/includes/getZSetItems.lua +7 -0
  76. package/dist/esm/commands/includes/isLocked.lua +31 -0
  77. package/dist/esm/commands/includes/moveJobFromWaitToActive.lua +84 -0
  78. package/dist/esm/commands/includes/moveParentFromWaitingChildrenToFailed.lua +28 -0
  79. package/dist/esm/commands/includes/promoteDelayedJobs.lua +49 -0
  80. package/dist/esm/commands/includes/removeJob.lua +13 -0
  81. package/dist/esm/commands/includes/removeJobFromAnyState.lua +26 -0
  82. package/dist/esm/commands/includes/removeJobs.lua +38 -0
  83. package/dist/esm/commands/includes/removeJobsByMaxAge.lua +15 -0
  84. package/dist/esm/commands/includes/removeJobsByMaxCount.lua +15 -0
  85. package/dist/esm/commands/includes/removeParentDependencyKey.lua +81 -0
  86. package/dist/esm/commands/includes/trimEvents.lua +12 -0
  87. package/dist/esm/commands/includes/updateParentDepsIfNeeded.lua +28 -0
  88. package/dist/esm/commands/isFinished-3.lua +48 -0
  89. package/dist/esm/commands/isJobInList-1.lua +16 -0
  90. package/dist/esm/commands/moveStalledJobsToWait-8.lua +24 -0
  91. package/dist/esm/commands/moveToActive-9.lua +67 -0
  92. package/dist/esm/commands/moveToDelayed-5.lua +54 -0
  93. package/dist/esm/commands/moveToFinished-12.lua +201 -0
  94. package/dist/esm/commands/moveToWaitingChildren-4.lua +62 -0
  95. package/dist/esm/commands/obliterate-2.lua +94 -0
  96. package/dist/esm/commands/pause-4.lua +27 -0
  97. package/dist/esm/commands/promote-6.lua +59 -0
  98. package/dist/esm/commands/releaseLock-1.lua +19 -0
  99. package/dist/esm/commands/removeJob-1.lua +72 -0
  100. package/dist/esm/commands/removeRepeatable-2.lua +32 -0
  101. package/dist/esm/commands/reprocessJob-4.lua +35 -0
  102. package/dist/esm/commands/retryJob-6.lua +51 -0
  103. package/dist/esm/commands/retryJobs-6.lua +53 -0
  104. package/dist/esm/commands/takeLock-1.lua +17 -0
  105. package/dist/esm/commands/updateData-1.lua +16 -0
  106. package/dist/esm/commands/updateProgress-2.lua +22 -0
  107. package/dist/esm/scripts/moveToFinished-12.js +5 -2
  108. package/dist/esm/scripts/moveToFinished-12.js.map +1 -1
  109. package/package.json +2 -2
@@ -0,0 +1,23 @@
1
+ --[[
2
+ Extend lock and removes the job from the stalled set.
3
+
4
+ Input:
5
+ KEYS[1] 'lock',
6
+ KEYS[2] 'stalled'
7
+
8
+ ARGV[1] token
9
+ ARGV[2] lock duration in milliseconds
10
+ ARGV[3] jobid
11
+
12
+ Output:
13
+ "1" if lock extented succesfully.
14
+ ]]
15
+ local rcall = redis.call
16
+ if rcall("GET", KEYS[1]) == ARGV[1] then
17
+ -- if rcall("SET", KEYS[1], ARGV[1], "PX", ARGV[2], "XX") then
18
+ if rcall("SET", KEYS[1], ARGV[1], "PX", ARGV[2]) then
19
+ rcall("SREM", KEYS[2], ARGV[3])
20
+ return 1
21
+ end
22
+ end
23
+ return 0
@@ -0,0 +1,57 @@
1
+ --[[
2
+ Get a job state
3
+
4
+ Input:
5
+ KEYS[1] 'completed' key,
6
+ KEYS[2] 'failed' key
7
+ KEYS[3] 'delayed' key
8
+ KEYS[4] 'active' key
9
+ KEYS[5] 'wait' key
10
+ KEYS[6] 'paused' key
11
+ KEYS[7] waitChildrenKey key
12
+
13
+ ARGV[1] job id
14
+ Output:
15
+ 'completed'
16
+ 'failed'
17
+ 'delayed'
18
+ 'active'
19
+ 'waiting'
20
+ 'waiting-children'
21
+ 'unknown'
22
+ ]]
23
+ if redis.call("ZSCORE", KEYS[1], ARGV[1]) ~= false then
24
+ return "completed"
25
+ end
26
+
27
+ if redis.call("ZSCORE", KEYS[2], ARGV[1]) ~= false then
28
+ return "failed"
29
+ end
30
+
31
+ if redis.call("ZSCORE", KEYS[3], ARGV[1]) ~= false then
32
+ return "delayed"
33
+ end
34
+
35
+ -- Includes
36
+ --- @include "includes/checkItemInList"
37
+
38
+ local active_items = redis.call("LRANGE", KEYS[4] , 0, -1)
39
+ if checkItemInList(active_items, ARGV[1]) ~= nil then
40
+ return "active"
41
+ end
42
+
43
+ local wait_items = redis.call("LRANGE", KEYS[5] , 0, -1)
44
+ if checkItemInList(wait_items, ARGV[1]) ~= nil then
45
+ return "waiting"
46
+ end
47
+
48
+ local paused_items = redis.call("LRANGE", KEYS[6] , 0, -1)
49
+ if checkItemInList(paused_items, ARGV[1]) ~= nil then
50
+ return "waiting"
51
+ end
52
+
53
+ if redis.call("ZSCORE", KEYS[7], ARGV[1]) ~= false then
54
+ return "waiting-children"
55
+ end
56
+
57
+ return "unknown"
@@ -0,0 +1,51 @@
1
+ --[[
2
+ Get a job state
3
+
4
+ Input:
5
+ KEYS[1] 'completed' key,
6
+ KEYS[2] 'failed' key
7
+ KEYS[3] 'delayed' key
8
+ KEYS[4] 'active' key
9
+ KEYS[5] 'wait' key
10
+ KEYS[6] 'paused' key
11
+ KEYS[7] waitChildrenKey key
12
+
13
+ ARGV[1] job id
14
+ Output:
15
+ 'completed'
16
+ 'failed'
17
+ 'delayed'
18
+ 'active'
19
+ 'waiting'
20
+ 'waiting-children'
21
+ 'unknown'
22
+ ]]
23
+ if redis.call("ZSCORE", KEYS[1], ARGV[1]) ~= false then
24
+ return "completed"
25
+ end
26
+
27
+ if redis.call("ZSCORE", KEYS[2], ARGV[1]) ~= false then
28
+ return "failed"
29
+ end
30
+
31
+ if redis.call("ZSCORE", KEYS[3], ARGV[1]) ~= false then
32
+ return "delayed"
33
+ end
34
+
35
+ if redis.call("LPOS", KEYS[4] , ARGV[1]) ~= false then
36
+ return "active"
37
+ end
38
+
39
+ if redis.call("LPOS", KEYS[5] , ARGV[1]) ~= false then
40
+ return "waiting"
41
+ end
42
+
43
+ if redis.call("LPOS", KEYS[6] , ARGV[1]) ~= false then
44
+ return "waiting"
45
+ end
46
+
47
+ if redis.call("ZSCORE", KEYS[7] , ARGV[1]) ~= false then
48
+ return "waiting-children"
49
+ end
50
+
51
+ return "unknown"
@@ -0,0 +1,16 @@
1
+ --[[
2
+ Function to add job considering priority.
3
+ ]]
4
+
5
+ local function addJobWithPriority(priorityKey, priority, targetKey, jobId)
6
+ rcall("ZADD", priorityKey, priority, jobId)
7
+ local count = rcall("ZCOUNT", priorityKey, 0, priority)
8
+
9
+ local len = rcall("LLEN", targetKey)
10
+ local id = rcall("LINDEX", targetKey, len - (count - 1))
11
+ if id then
12
+ rcall("LINSERT", targetKey, "BEFORE", id, jobId)
13
+ else
14
+ rcall("RPUSH", targetKey, jobId)
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ --[[
2
+ Function to loop in batches.
3
+ Just a bit of warning, some commands as ZREM
4
+ could receive a maximum of 7000 parameters per call.
5
+ ]]
6
+
7
+ local function batches(n, batchSize)
8
+ local i = 0
9
+
10
+ return function()
11
+ local from = i * batchSize + 1
12
+ i = i + 1
13
+ if (from <= n) then
14
+ local to = math.min(from + batchSize - 1, n)
15
+ return from, to
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ --[[
2
+ Functions to check if a item belongs to a list.
3
+ ]]
4
+
5
+ local function checkItemInList(list, item)
6
+ for _, v in pairs(list) do
7
+ if v == item then
8
+ return 1
9
+ end
10
+ end
11
+ return nil
12
+ end
@@ -0,0 +1,137 @@
1
+ --[[
2
+ Move stalled jobs to wait.
3
+
4
+ Input:
5
+ stalledKey 'stalled' (SET)
6
+ waitKey 'wait', (LIST)
7
+ activeKey 'active', (LIST)
8
+ failedKey 'failed', (ZSET)
9
+ stalledCheckKey 'stalled-check', (KEY)
10
+ metaKey 'meta', (KEY)
11
+ pausedKey 'paused', (LIST)
12
+ eventStreamKey 'event stream' (STREAM)
13
+
14
+ maxStalledJobCount Max stalled job count
15
+ queueKeyPrefix queue.toKey('')
16
+ timestamp timestamp
17
+ maxCheckTime max check time
18
+
19
+ Events:
20
+ 'stalled' with stalled job id.
21
+ ]] local rcall = redis.call
22
+
23
+ -- Includes
24
+ --- @include "batches"
25
+ --- @include "getTargetQueueList"
26
+ --- @include "removeJob"
27
+ --- @include "removeJobsByMaxAge"
28
+ --- @include "removeJobsByMaxCount"
29
+ --- @include "trimEvents"
30
+
31
+ -- Check if we need to check for stalled jobs now.
32
+
33
+ local function checkStalledJobs(stalledKey, waitKey, activeKey, failedKey, stalledCheckKey,
34
+ metaKey, pausedKey, eventStreamKey, maxStalledJobCount, queueKeyPrefix, timestamp, maxCheckTime)
35
+ if rcall("EXISTS", stalledCheckKey) == 1 then
36
+ return {{}, {}}
37
+ end
38
+
39
+ rcall("SET", stalledCheckKey, timestamp, "PX", maxCheckTime)
40
+
41
+ -- Trim events before emiting them to avoid trimming events emitted in this script
42
+ trimEvents(metaKey, eventStreamKey)
43
+
44
+ -- Move all stalled jobs to wait
45
+ local stalling = rcall('SMEMBERS', stalledKey)
46
+ local stalled = {}
47
+ local failed = {}
48
+ if (#stalling > 0) then
49
+ rcall('DEL', stalledKey)
50
+
51
+ local MAX_STALLED_JOB_COUNT = tonumber(maxStalledJobCount)
52
+
53
+ -- Remove from active list
54
+ for i, jobId in ipairs(stalling) do
55
+
56
+ if jobId == '0' then
57
+ -- If the jobId is a delay marker ID we just remove it.
58
+ local removed = rcall("LREM", activeKey, 1, jobId)
59
+ else
60
+ local jobKey = queueKeyPrefix .. jobId
61
+
62
+ -- Check that the lock is also missing, then we can handle this job as really stalled.
63
+ if (rcall("EXISTS", jobKey .. ":lock") == 0) then
64
+ -- Remove from the active queue.
65
+ local removed = rcall("LREM", activeKey, 1, jobId)
66
+
67
+ if (removed > 0) then
68
+ -- If this job has been stalled too many times, such as if it crashes the worker, then fail it.
69
+ local stalledCount =
70
+ rcall("HINCRBY", jobKey, "stalledCounter", 1)
71
+ if (stalledCount > MAX_STALLED_JOB_COUNT) then
72
+ local rawOpts = rcall("HGET", jobKey, "opts")
73
+ local opts = cjson.decode(rawOpts)
74
+ local removeOnFailType = type(opts["removeOnFail"])
75
+ rcall("ZADD", failedKey, timestamp, jobId)
76
+ local failedReason =
77
+ "job stalled more than allowable limit"
78
+ rcall("HMSET", jobKey, "failedReason", failedReason,
79
+ "finishedOn", timestamp)
80
+ rcall("XADD", eventStreamKey, "*", "event", "failed", "jobId",
81
+ jobId, 'prev', 'active', 'failedReason',
82
+ failedReason)
83
+
84
+ if removeOnFailType == "number" then
85
+ removeJobsByMaxCount(opts["removeOnFail"], failedKey,
86
+ queueKeyPrefix)
87
+ elseif removeOnFailType == "boolean" then
88
+ if opts["removeOnFail"] then
89
+ removeJob(jobId, false, queueKeyPrefix)
90
+ rcall("ZREM", failedKey, jobId)
91
+ end
92
+ elseif removeOnFailType ~= "nil" then
93
+ local maxAge = opts["removeOnFail"]["age"]
94
+ local maxCount = opts["removeOnFail"]["count"]
95
+
96
+ if maxAge ~= nil then
97
+ removeJobsByMaxAge(timestamp, maxAge, failedKey,
98
+ queueKeyPrefix)
99
+ end
100
+
101
+ if maxCount ~= nil and maxCount > 0 then
102
+ removeJobsByMaxCount(maxCount, failedKey, queueKeyPrefix)
103
+ end
104
+ end
105
+
106
+ table.insert(failed, jobId)
107
+ else
108
+ local target = getTargetQueueList(metaKey, waitKey,
109
+ pausedKey)
110
+
111
+ -- Move the job back to the wait queue, to immediately be picked up by a waiting worker.
112
+ rcall("RPUSH", target, jobId)
113
+ rcall("XADD", eventStreamKey, "*", "event", "waiting", "jobId",
114
+ jobId, 'prev', 'active')
115
+
116
+ -- Emit the stalled event
117
+ rcall("XADD", eventStreamKey, "*", "event", "stalled", "jobId",
118
+ jobId)
119
+ table.insert(stalled, jobId)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ -- Mark potentially stalled jobs
128
+ local active = rcall('LRANGE', activeKey, 0, -1)
129
+
130
+ if (#active > 0) then
131
+ for from, to in batches(#active, 7000) do
132
+ rcall('SADD', stalledKey, unpack(active, from, to))
133
+ end
134
+ end
135
+
136
+ return {failed, stalled}
137
+ end
@@ -0,0 +1,50 @@
1
+ --[[
2
+ Function to clean job list.
3
+ Returns jobIds and deleted count number.
4
+ ]]
5
+
6
+ -- Includes
7
+ --- @include "getTimestamp"
8
+ --- @include "removeJob"
9
+
10
+ local function cleanList(listKey, jobKeyPrefix, rangeStart, rangeEnd,
11
+ timestamp, isWaiting)
12
+ local jobs = rcall("LRANGE", listKey, rangeStart, rangeEnd)
13
+ local deleted = {}
14
+ local deletedCount = 0
15
+ local jobTS
16
+ local deletionMarker = ''
17
+ local jobIdsLen = #jobs
18
+ for i, job in ipairs(jobs) do
19
+ if limit > 0 and deletedCount >= limit then
20
+ break
21
+ end
22
+
23
+ local jobKey = jobKeyPrefix .. job
24
+ if (isWaiting or rcall("EXISTS", jobKey .. ":lock") == 0) then
25
+ -- Find the right timestamp of the job to compare to maxTimestamp:
26
+ -- * finishedOn says when the job was completed, but it isn't set unless the job has actually completed
27
+ -- * processedOn represents when the job was last attempted, but it doesn't get populated until
28
+ -- the job is first tried
29
+ -- * timestamp is the original job submission time
30
+ -- Fetch all three of these (in that order) and use the first one that is set so that we'll leave jobs
31
+ -- that have been active within the grace period:
32
+ jobTS = getTimestamp(jobKey, {"finishedOn", "processedOn", "timestamp"})
33
+ if (not jobTS or jobTS < timestamp) then
34
+ -- replace the entry with a deletion marker; the actual deletion will
35
+ -- occur at the end of the script
36
+ rcall("LSET", listKey, rangeEnd - jobIdsLen + i, deletionMarker)
37
+ removeJob(job, true, jobKeyPrefix)
38
+ if isWaiting then
39
+ rcall("ZREM", jobKeyPrefix .. "priority", job)
40
+ end
41
+ deletedCount = deletedCount + 1
42
+ table.insert(deleted, job)
43
+ end
44
+ end
45
+ end
46
+
47
+ rcall("LREM", listKey, 0, deletionMarker)
48
+
49
+ return {deleted, deletedCount}
50
+ end
@@ -0,0 +1,50 @@
1
+ --[[
2
+ Function to clean job set.
3
+ Returns jobIds and deleted count number.
4
+ ]]
5
+
6
+ -- Includes
7
+ --- @include "batches"
8
+ --- @include "getTimestamp"
9
+ --- @include "removeJob"
10
+
11
+ -- We use ZRANGEBYSCORE to make the case where we're deleting a limited number
12
+ -- of items in a sorted set only run a single iteration. If we simply used
13
+ -- ZRANGE, we may take a long time traversing through jobs that are within the
14
+ -- grace period.
15
+ local function getJobs(setKey, rangeStart, rangeEnd, maxTimestamp, limit)
16
+ if limit > 0 then
17
+ return rcall("ZRANGEBYSCORE", setKey, 0, maxTimestamp, "LIMIT", 0, limit)
18
+ else
19
+ return rcall("ZRANGE", setKey, rangeStart, rangeEnd)
20
+ end
21
+ end
22
+
23
+ local function cleanSet(setKey, jobKeyPrefix, rangeStart, rangeEnd, timestamp, limit, attributes)
24
+ local jobs = getJobs(setKey, rangeStart, rangeEnd, timestamp, limit)
25
+ local deleted = {}
26
+ local deletedCount = 0
27
+ local jobTS
28
+ for i, job in ipairs(jobs) do
29
+ if limit > 0 and deletedCount >= limit then
30
+ break
31
+ end
32
+
33
+ local jobKey = jobKeyPrefix .. job
34
+ -- * finishedOn says when the job was completed, but it isn't set unless the job has actually completed
35
+ jobTS = getTimestamp(jobKey, attributes)
36
+ if (not jobTS or jobTS < timestamp) then
37
+ removeJob(job, true, jobKeyPrefix)
38
+ deletedCount = deletedCount + 1
39
+ table.insert(deleted, job)
40
+ end
41
+ end
42
+
43
+ if(#deleted > 0) then
44
+ for from, to in batches(#deleted, 7000) do
45
+ rcall("ZREM", setKey, unpack(deleted, from, to))
46
+ end
47
+ end
48
+
49
+ return {deleted, deletedCount}
50
+ end
@@ -0,0 +1,46 @@
1
+ --[[
2
+ Functions to collect metrics based on a current and previous count of jobs.
3
+ Granualarity is fixed at 1 minute.
4
+ ]]
5
+ --- @include "batches"
6
+ local function collectMetrics(metaKey, dataPointsList, maxDataPoints,
7
+ timestamp)
8
+ -- Increment current count
9
+ local count = rcall("HINCRBY", metaKey, "count", 1) - 1
10
+
11
+ -- Compute how many data points we need to add to the list, N.
12
+ local prevTS = rcall("HGET", metaKey, "prevTS")
13
+
14
+ if not prevTS then
15
+ -- If prevTS is nil, set it to the current timestamp
16
+ rcall("HSET", metaKey, "prevTS", timestamp, "prevCount", 0)
17
+ return
18
+ end
19
+
20
+ local N = math.floor((timestamp - prevTS) / 60000)
21
+
22
+ if N > 0 then
23
+ local delta = count - rcall("HGET", metaKey, "prevCount")
24
+ -- If N > 1, add N-1 zeros to the list
25
+ if N > 1 then
26
+ local points = {}
27
+ points[1] = delta
28
+ for i = 2, N do
29
+ points[i] = 0
30
+ end
31
+
32
+ for from, to in batches(#points, 7000) do
33
+ rcall("LPUSH", dataPointsList, unpack(points, from, to))
34
+ end
35
+ else
36
+ -- LPUSH delta to the list
37
+ rcall("LPUSH", dataPointsList, delta)
38
+ end
39
+
40
+ -- LTRIM to keep list to its max size
41
+ rcall("LTRIM", dataPointsList, 0, maxDataPoints - 1)
42
+
43
+ -- update prev count with current count
44
+ rcall("HSET", metaKey, "prevCount", count, "prevTS", timestamp)
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ --[[
2
+ Functions to destructure job key.
3
+ Just a bit of warning, these functions may be a bit slow and affect performance significantly.
4
+ ]]
5
+
6
+ local getJobIdFromKey = function (jobKey)
7
+ return string.match(jobKey, ".*:(.*)")
8
+ end
9
+
10
+ local getJobKeyPrefix = function (jobKey, jobId)
11
+ return string.sub(jobKey, 0, #jobKey - #jobId)
12
+ end
@@ -0,0 +1,13 @@
1
+ --[[
2
+ Function to return the next delayed job timestamp.
3
+ ]]
4
+ local function getNextDelayedTimestamp(delayedKey)
5
+ local result = rcall("ZRANGE", delayedKey, 0, 0, "WITHSCORES")
6
+ if #result then
7
+ local nextTimestamp = tonumber(result[2])
8
+ if (nextTimestamp ~= nil) then
9
+ nextTimestamp = nextTimestamp / 0x1000
10
+ end
11
+ return nextTimestamp
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ --[[
2
+ Function to check for the meta.paused key to decide if we are paused or not
3
+ (since an empty list and !EXISTS are not really the same).
4
+ ]]
5
+
6
+ local function getTargetQueueList(queueMetaKey, waitKey, pausedKey)
7
+ if rcall("HEXISTS", queueMetaKey, "paused") ~= 1 then
8
+ return waitKey
9
+ else
10
+ return pausedKey
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ --[[
2
+ Function to get the latest saved timestamp.
3
+ ]]
4
+
5
+ local function getTimestamp(jobKey, attributes)
6
+ if #attributes == 1 then
7
+ return rcall("HGET", jobKey, attributes[1])
8
+ end
9
+
10
+ local jobTs
11
+ for _, ts in ipairs(rcall("HMGET", jobKey, unpack(attributes))) do
12
+ if (ts) then
13
+ jobTs = ts
14
+ break
15
+ end
16
+ end
17
+
18
+ return jobTs
19
+ end
@@ -0,0 +1,7 @@
1
+ --[[
2
+ Function to get ZSet items.
3
+ ]]
4
+
5
+ local function getZSetItems(keyName, max)
6
+ return rcall('ZRANGE', keyName, 0, max - 1)
7
+ end
@@ -0,0 +1,31 @@
1
+ --[[
2
+ Function to recursively check if there are no locks
3
+ on the jobs to be removed.
4
+
5
+ returns:
6
+ boolean
7
+ ]]
8
+
9
+ local function isLocked( prefix, jobId)
10
+ local jobKey = prefix .. jobId;
11
+
12
+ -- Check if this job is locked
13
+ local lockKey = jobKey .. ':lock'
14
+ local lock = rcall("GET", lockKey)
15
+ if not lock then
16
+ local dependencies = rcall("SMEMBERS", jobKey .. ":dependencies")
17
+ if (#dependencies > 0) then
18
+ for i, childJobKey in ipairs(dependencies) do
19
+ -- We need to get the jobId for this job.
20
+ local childJobId = getJobIdFromKey(childJobKey)
21
+ local childJobPrefix = getJobKeyPrefix(childJobKey, childJobId)
22
+ local result = isLocked( childJobPrefix, childJobId )
23
+ if result then
24
+ return true
25
+ end
26
+ end
27
+ end
28
+ return false
29
+ end
30
+ return true
31
+ end
@@ -0,0 +1,84 @@
1
+
2
+ --[[
3
+ Function to move job from wait state to active.
4
+ Input:
5
+ keys[1] wait key
6
+ keys[2] active key
7
+ keys[3] priority key
8
+ keys[4] stream events key
9
+ keys[5] stalled key
10
+
11
+ -- Rate limiting
12
+ keys[6] rate limiter key
13
+ keys[7] delayed key
14
+
15
+ opts - token - lock token
16
+ opts - lockDuration
17
+ opts - limiter
18
+ ]]
19
+
20
+ local function moveJobFromWaitToActive(keys, keyPrefix, jobId, processedOn, opts)
21
+ -- Check if we need to perform rate limiting.
22
+ local maxJobs = tonumber(opts['limiter'] and opts['limiter']['max'])
23
+
24
+ if(maxJobs) then
25
+ local rateLimiterKey = keys[6];
26
+
27
+ local groupKey
28
+ local groupKeyOpt = opts['limiter'] and opts['limiter']['groupKey'] or ""
29
+ if groupKeyOpt ~= "" then
30
+ groupKey = string.match(jobId, "[^:]+$")
31
+ if groupKey ~= jobId then
32
+ rateLimiterKey = rateLimiterKey .. ":" .. groupKey
33
+ end
34
+ end
35
+
36
+ local jobCounter
37
+
38
+ if groupKey ~= nil then
39
+ if rateLimiterKey ~= keys[6] then
40
+ jobCounter = tonumber(rcall("INCR", rateLimiterKey))
41
+ end
42
+ else
43
+ jobCounter = tonumber(rcall("INCR", rateLimiterKey))
44
+ end
45
+
46
+ local limiterDuration = opts['limiter'] and opts['limiter']['duration']
47
+ -- check if rate limit hit
48
+ if jobCounter ~= nil and jobCounter > maxJobs then
49
+ local exceedingJobs = jobCounter - maxJobs
50
+ local expireTime = tonumber(rcall("PTTL", rateLimiterKey))
51
+ local delay = expireTime + ((exceedingJobs - 1) * limiterDuration) / maxJobs;
52
+ local timestamp = delay + tonumber(processedOn)
53
+
54
+ -- put job into delayed queue
55
+ rcall("ZADD", keys[7], timestamp * 0x1000 + bit.band(jobCounter, 0xfff), jobId);
56
+ rcall("XADD", keys[4], "*", "event", "delayed", "jobId", jobId, "delay", timestamp);
57
+
58
+ -- remove from active queue
59
+ rcall("LREM", keys[2], 1, jobId)
60
+
61
+ -- Return when we can process more jobs
62
+ return expireTime
63
+ else
64
+ if jobCounter == 1 then
65
+ rcall("PEXPIRE", rateLimiterKey, limiterDuration)
66
+ end
67
+ end
68
+ end
69
+
70
+ local jobKey = keyPrefix .. jobId
71
+ local lockKey = jobKey .. ':lock'
72
+
73
+ -- get a lock
74
+ if opts['token'] ~= "0" then
75
+ rcall("SET", lockKey, opts['token'], "PX", opts['lockDuration'])
76
+ end
77
+
78
+ rcall("ZREM", keys[3], jobId) -- remove from priority
79
+ rcall("XADD", keys[4], "*", "event", "active", "jobId", jobId, "prev", "waiting")
80
+ rcall("HSET", jobKey, "processedOn", processedOn)
81
+ rcall("HINCRBY", jobKey, "attemptsMade", 1)
82
+
83
+ return {rcall("HGETALL", jobKey), jobId} -- get job data
84
+ end
@@ -0,0 +1,28 @@
1
+ --[[
2
+ Function to recursively move from waitingChildren to failed.
3
+ ]]
4
+
5
+ local function moveParentFromWaitingChildrenToFailed( parentQueueKey, parentKey, parentId, jobIdKey, timestamp)
6
+ if rcall("ZREM", parentQueueKey .. ":waiting-children", parentId) == 1 then
7
+ rcall("ZADD", parentQueueKey .. ":failed", timestamp, parentId)
8
+ local failedReason = "child " .. jobIdKey .. " failed"
9
+ rcall("HMSET", parentKey, "failedReason", failedReason, "finishedOn", timestamp)
10
+ rcall("XADD", parentQueueKey .. ":events", "*", "event", "failed", "jobId", parentId, "failedReason",
11
+ failedReason, "prev", "waiting-children")
12
+
13
+ local rawParentData = rcall("HGET", parentKey, "parent")
14
+
15
+ if rawParentData ~= false then
16
+ local parentData = cjson.decode(rawParentData)
17
+ if parentData['fpof'] then
18
+ moveParentFromWaitingChildrenToFailed(
19
+ parentData['queueKey'],
20
+ parentData['queueKey'] .. ':' .. parentData['id'],
21
+ parentData['id'],
22
+ parentKey,
23
+ timestamp
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end