qless 0.9.1
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.
- data/Gemfile +8 -0
- data/HISTORY.md +168 -0
- data/README.md +571 -0
- data/Rakefile +28 -0
- data/bin/qless-campfire +106 -0
- data/bin/qless-growl +99 -0
- data/bin/qless-web +23 -0
- data/lib/qless.rb +185 -0
- data/lib/qless/config.rb +31 -0
- data/lib/qless/job.rb +259 -0
- data/lib/qless/job_reservers/ordered.rb +23 -0
- data/lib/qless/job_reservers/round_robin.rb +34 -0
- data/lib/qless/lua.rb +25 -0
- data/lib/qless/qless-core/cancel.lua +71 -0
- data/lib/qless/qless-core/complete.lua +218 -0
- data/lib/qless/qless-core/config.lua +44 -0
- data/lib/qless/qless-core/depends.lua +65 -0
- data/lib/qless/qless-core/fail.lua +107 -0
- data/lib/qless/qless-core/failed.lua +83 -0
- data/lib/qless/qless-core/get.lua +37 -0
- data/lib/qless/qless-core/heartbeat.lua +50 -0
- data/lib/qless/qless-core/jobs.lua +41 -0
- data/lib/qless/qless-core/peek.lua +155 -0
- data/lib/qless/qless-core/pop.lua +278 -0
- data/lib/qless/qless-core/priority.lua +32 -0
- data/lib/qless/qless-core/put.lua +156 -0
- data/lib/qless/qless-core/queues.lua +58 -0
- data/lib/qless/qless-core/recur.lua +181 -0
- data/lib/qless/qless-core/retry.lua +73 -0
- data/lib/qless/qless-core/ruby/lib/qless-core.rb +1 -0
- data/lib/qless/qless-core/ruby/lib/qless/core.rb +13 -0
- data/lib/qless/qless-core/ruby/lib/qless/core/version.rb +5 -0
- data/lib/qless/qless-core/ruby/spec/qless_core_spec.rb +13 -0
- data/lib/qless/qless-core/stats.lua +92 -0
- data/lib/qless/qless-core/tag.lua +100 -0
- data/lib/qless/qless-core/track.lua +79 -0
- data/lib/qless/qless-core/workers.lua +69 -0
- data/lib/qless/queue.rb +141 -0
- data/lib/qless/server.rb +411 -0
- data/lib/qless/tasks.rb +10 -0
- data/lib/qless/version.rb +3 -0
- data/lib/qless/worker.rb +195 -0
- metadata +239 -0
@@ -0,0 +1,278 @@
|
|
1
|
+
-- This script takes the name of the queue and then checks
|
2
|
+
-- for any expired locks, then inserts any scheduled items
|
3
|
+
-- that are now valid, and lastly returns any work items
|
4
|
+
-- that can be handed over.
|
5
|
+
--
|
6
|
+
-- Keys:
|
7
|
+
-- 1) queue name
|
8
|
+
-- Args:
|
9
|
+
-- 1) worker name
|
10
|
+
-- 2) the number of items to return
|
11
|
+
-- 3) the current time
|
12
|
+
|
13
|
+
if #KEYS ~= 1 then
|
14
|
+
if #KEYS < 1 then
|
15
|
+
error('Pop(): Expected 1 KEYS argument')
|
16
|
+
else
|
17
|
+
error('Pop(): Got ' .. #KEYS .. ', expected 1 KEYS argument')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
local queue = assert(KEYS[1] , 'Pop(): Key "queue" missing')
|
22
|
+
local key = 'ql:q:' .. queue
|
23
|
+
local worker = assert(ARGV[1] , 'Pop(): Arg "worker" missing')
|
24
|
+
local count = assert(tonumber(ARGV[2]) , 'Pop(): Arg "count" missing or not a number: ' .. (ARGV[2] or 'nil'))
|
25
|
+
local now = assert(tonumber(ARGV[3]) , 'Pop(): Arg "now" missing or not a number: ' .. (ARGV[3] or 'nil'))
|
26
|
+
|
27
|
+
-- We should find the heartbeat interval for this queue
|
28
|
+
-- heartbeat
|
29
|
+
local _hb, _qhb = unpack(redis.call('hmget', 'ql:config', 'heartbeat', queue .. '-heartbeat'))
|
30
|
+
local expires = now + tonumber(_qhb or _hb or 60)
|
31
|
+
|
32
|
+
-- The bin is midnight of the provided day
|
33
|
+
-- 24 * 60 * 60 = 86400
|
34
|
+
local bin = now - (now % 86400)
|
35
|
+
|
36
|
+
-- These are the ids that we're going to return
|
37
|
+
local keys = {}
|
38
|
+
|
39
|
+
-- Make sure we this worker to the list of seen workers
|
40
|
+
redis.call('zadd', 'ql:workers', now, worker)
|
41
|
+
|
42
|
+
-- Iterate through all the expired locks and add them to the list
|
43
|
+
-- of keys that we'll return
|
44
|
+
for index, jid in ipairs(redis.call('zrangebyscore', key .. '-locks', 0, now, 'LIMIT', 0, count)) do
|
45
|
+
-- For each of these, decrement their retries. If any of them
|
46
|
+
-- have exhausted their retries, then we should mark them as
|
47
|
+
-- failed.
|
48
|
+
if redis.call('hincrby', 'ql:j:' .. jid, 'remaining', -1) < 0 then
|
49
|
+
-- Now remove the instance from the schedule, and work queues for the queue it's in
|
50
|
+
redis.call('zrem', 'ql:q:' .. queue .. '-work', jid)
|
51
|
+
redis.call('zrem', 'ql:q:' .. queue .. '-locks', jid)
|
52
|
+
redis.call('zrem', 'ql:q:' .. queue .. '-scheduled', jid)
|
53
|
+
|
54
|
+
local group = 'failed-retries-' .. queue
|
55
|
+
-- First things first, we should get the history
|
56
|
+
local history = redis.call('hget', 'ql:j:' .. jid, 'history')
|
57
|
+
|
58
|
+
-- Now, take the element of the history for which our provided worker is the worker, and update 'failed'
|
59
|
+
history = cjson.decode(history or '[]')
|
60
|
+
history[#history]['failed'] = now
|
61
|
+
|
62
|
+
redis.call('hmset', 'ql:j:' .. jid, 'state', 'failed', 'worker', '',
|
63
|
+
'expires', '', 'history', cjson.encode(history), 'failure', cjson.encode({
|
64
|
+
['group'] = group,
|
65
|
+
['message'] = 'Job exhuasted retries in queue "' .. queue .. '"',
|
66
|
+
['when'] = now,
|
67
|
+
['worker'] = history[#history]['worker']
|
68
|
+
}))
|
69
|
+
|
70
|
+
-- Add this type of failure to the list of failures
|
71
|
+
redis.call('sadd', 'ql:failures', group)
|
72
|
+
-- And add this particular instance to the failed types
|
73
|
+
redis.call('lpush', 'ql:f:' .. group, jid)
|
74
|
+
|
75
|
+
if redis.call('zscore', 'ql:tracked', jid) ~= false then
|
76
|
+
redis.call('publish', 'failed', jid)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
table.insert(keys, jid)
|
80
|
+
|
81
|
+
if redis.call('zscore', 'ql:tracked', jid) ~= false then
|
82
|
+
redis.call('publish', 'stalled', jid)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
-- Remove this job from the jobs that the worker that was running it has
|
87
|
+
local w = redis.call('hget', 'ql:j:' .. jid, 'worker')
|
88
|
+
redis.call('zrem', 'ql:w:' .. w .. ':jobs', jid)
|
89
|
+
end
|
90
|
+
-- Now we've checked __all__ the locks for this queue the could
|
91
|
+
-- have expired, and are no more than the number requested.
|
92
|
+
|
93
|
+
-- If we got any expired locks, then we should increment the
|
94
|
+
-- number of retries for this stage for this bin
|
95
|
+
redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. queue, 'retries', #keys)
|
96
|
+
|
97
|
+
-- If we still need jobs in order to meet demand, then we should
|
98
|
+
-- look for all the recurring jobs that need jobs run
|
99
|
+
if #keys < count then
|
100
|
+
local r = redis.call('zrangebyscore', key .. '-recur', 0, now)
|
101
|
+
for index, jid in ipairs(r) do
|
102
|
+
-- For each of the jids that need jobs scheduled, first
|
103
|
+
-- get the last time each of them was run, and then increment
|
104
|
+
-- it by its interval. While this time is less than now,
|
105
|
+
-- we need to keep putting jobs on the queue
|
106
|
+
local klass, data, priority, tags, retries, interval = unpack(redis.call('hmget', 'ql:r:' .. jid, 'klass', 'data', 'priority', 'tags', 'retries', 'interval'))
|
107
|
+
local _tags = cjson.decode(tags)
|
108
|
+
|
109
|
+
while math.floor(tonumber(redis.call('zscore', key .. '-recur', jid))) <= now do
|
110
|
+
local count = redis.call('hincrby', 'ql:r:' .. jid, 'count', 1)
|
111
|
+
|
112
|
+
-- Add this job to the list of jobs tagged with whatever tags were supplied
|
113
|
+
for i, tag in ipairs(_tags) do
|
114
|
+
redis.call('zadd', 'ql:t:' .. tag, now, jid .. '-' .. count)
|
115
|
+
redis.call('zincrby', 'ql:tags', 1, tag)
|
116
|
+
end
|
117
|
+
|
118
|
+
-- First, let's save its data
|
119
|
+
redis.call('hmset', 'ql:j:' .. jid .. '-' .. count,
|
120
|
+
'jid' , jid .. '-' .. count,
|
121
|
+
'klass' , klass,
|
122
|
+
'data' , data,
|
123
|
+
'priority' , priority,
|
124
|
+
'tags' , tags,
|
125
|
+
'state' , 'waiting',
|
126
|
+
'worker' , '',
|
127
|
+
'expires' , 0,
|
128
|
+
'queue' , queue,
|
129
|
+
'retries' , retries,
|
130
|
+
'remaining', retries,
|
131
|
+
'history' , cjson.encode({{
|
132
|
+
q = queue,
|
133
|
+
put = math.floor(now)
|
134
|
+
}}))
|
135
|
+
|
136
|
+
-- Now, if a delay was provided, and if it's in the future,
|
137
|
+
-- then we'll have to schedule it. Otherwise, we're just
|
138
|
+
-- going to add it to the work queue.
|
139
|
+
redis.call('zadd', key .. '-work', priority - (now / 10000000000), jid .. '-' .. count)
|
140
|
+
|
141
|
+
redis.call('zincrby', key .. '-recur', interval, jid)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
-- If we still need values in order to meet the demand, then we
|
147
|
+
-- should check if any scheduled items, and if so, we should
|
148
|
+
-- insert them to ensure correctness when pulling off the next
|
149
|
+
-- unit of work.
|
150
|
+
if #keys < count then
|
151
|
+
-- zadd is a list of arguments that we'll be able to use to
|
152
|
+
-- insert into the work queue
|
153
|
+
local zadd = {}
|
154
|
+
local r = redis.call('zrangebyscore', key .. '-scheduled', 0, now, 'LIMIT', 0, (count - #keys))
|
155
|
+
for index, jid in ipairs(r) do
|
156
|
+
-- With these in hand, we'll have to go out and find the
|
157
|
+
-- priorities of these jobs, and then we'll insert them
|
158
|
+
-- into the work queue and then when that's complete, we'll
|
159
|
+
-- remove them from the scheduled queue
|
160
|
+
table.insert(zadd, tonumber(redis.call('hget', 'ql:j:' .. jid, 'priority') or 0))
|
161
|
+
table.insert(zadd, jid)
|
162
|
+
end
|
163
|
+
|
164
|
+
-- Now add these to the work list, and then remove them
|
165
|
+
-- from the scheduled list
|
166
|
+
if #zadd > 0 then
|
167
|
+
redis.call('zadd', key .. '-work', unpack(zadd))
|
168
|
+
redis.call('zrem', key .. '-scheduled', unpack(r))
|
169
|
+
end
|
170
|
+
|
171
|
+
-- And now we should get up to the maximum number of requested
|
172
|
+
-- work items from the work queue.
|
173
|
+
for index, jid in ipairs(redis.call('zrevrange', key .. '-work', 0, (count - #keys) - 1)) do
|
174
|
+
table.insert(keys, jid)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
-- Alright, now the `keys` table is filled with all the job
|
179
|
+
-- ids which we'll be returning. Now we need to get the
|
180
|
+
-- metadeata about each of these, update their metadata to
|
181
|
+
-- reflect which worker they're on, when the lock expires,
|
182
|
+
-- etc., add them to the locks queue and then we have to
|
183
|
+
-- finally return a list of json blobs
|
184
|
+
|
185
|
+
local response = {}
|
186
|
+
local state
|
187
|
+
local history
|
188
|
+
for index, jid in ipairs(keys) do
|
189
|
+
-- First, we should get the state and history of the item
|
190
|
+
state, history = unpack(redis.call('hmget', 'ql:j:' .. jid, 'state', 'history'))
|
191
|
+
|
192
|
+
history = cjson.decode(history or '{}')
|
193
|
+
history[#history]['worker'] = worker
|
194
|
+
history[#history]['popped'] = math.floor(now)
|
195
|
+
|
196
|
+
----------------------------------------------------------
|
197
|
+
-- This is the massive stats update that we have to do
|
198
|
+
----------------------------------------------------------
|
199
|
+
-- This is how long we've been waiting to get popped
|
200
|
+
local waiting = math.floor(now) - history[#history]['put']
|
201
|
+
-- Now we'll go through the apparently long and arduous process of update
|
202
|
+
local count, mean, vk = unpack(redis.call('hmget', 'ql:s:wait:' .. bin .. ':' .. queue, 'total', 'mean', 'vk'))
|
203
|
+
count = count or 0
|
204
|
+
if count == 0 then
|
205
|
+
mean = waiting
|
206
|
+
vk = 0
|
207
|
+
count = 1
|
208
|
+
else
|
209
|
+
count = count + 1
|
210
|
+
local oldmean = mean
|
211
|
+
mean = mean + (waiting - mean) / count
|
212
|
+
vk = vk + (waiting - mean) * (waiting - oldmean)
|
213
|
+
end
|
214
|
+
-- Now, update the histogram
|
215
|
+
-- - `s1`, `s2`, ..., -- second-resolution histogram counts
|
216
|
+
-- - `m1`, `m2`, ..., -- minute-resolution
|
217
|
+
-- - `h1`, `h2`, ..., -- hour-resolution
|
218
|
+
-- - `d1`, `d2`, ..., -- day-resolution
|
219
|
+
waiting = math.floor(waiting)
|
220
|
+
if waiting < 60 then -- seconds
|
221
|
+
redis.call('hincrby', 'ql:s:wait:' .. bin .. ':' .. queue, 's' .. waiting, 1)
|
222
|
+
elseif waiting < 3600 then -- minutes
|
223
|
+
redis.call('hincrby', 'ql:s:wait:' .. bin .. ':' .. queue, 'm' .. math.floor(waiting / 60), 1)
|
224
|
+
elseif waiting < 86400 then -- hours
|
225
|
+
redis.call('hincrby', 'ql:s:wait:' .. bin .. ':' .. queue, 'h' .. math.floor(waiting / 3600), 1)
|
226
|
+
else -- days
|
227
|
+
redis.call('hincrby', 'ql:s:wait:' .. bin .. ':' .. queue, 'd' .. math.floor(waiting / 86400), 1)
|
228
|
+
end
|
229
|
+
redis.call('hmset', 'ql:s:wait:' .. bin .. ':' .. queue, 'total', count, 'mean', mean, 'vk', vk)
|
230
|
+
----------------------------------------------------------
|
231
|
+
|
232
|
+
-- Add this job to the list of jobs handled by this worker
|
233
|
+
redis.call('zadd', 'ql:w:' .. worker .. ':jobs', expires, jid)
|
234
|
+
|
235
|
+
-- Update the jobs data, and add its locks, and return the job
|
236
|
+
redis.call(
|
237
|
+
'hmset', 'ql:j:' .. jid, 'worker', worker, 'expires', expires,
|
238
|
+
'state', 'running', 'history', cjson.encode(history))
|
239
|
+
|
240
|
+
redis.call('zadd', key .. '-locks', expires, jid)
|
241
|
+
local job = redis.call(
|
242
|
+
'hmget', 'ql:j:' .. jid, 'jid', 'klass', 'state', 'queue', 'worker', 'priority',
|
243
|
+
'expires', 'retries', 'remaining', 'data', 'tags', 'history', 'failure')
|
244
|
+
|
245
|
+
local tracked = redis.call('zscore', 'ql:tracked', jid) ~= false
|
246
|
+
if tracked then
|
247
|
+
redis.call('publish', 'popped', jid)
|
248
|
+
end
|
249
|
+
|
250
|
+
table.insert(response, cjson.encode({
|
251
|
+
jid = job[1],
|
252
|
+
klass = job[2],
|
253
|
+
state = job[3],
|
254
|
+
queue = job[4],
|
255
|
+
worker = job[5] or '',
|
256
|
+
tracked = tracked,
|
257
|
+
priority = tonumber(job[6]),
|
258
|
+
expires = tonumber(job[7]) or 0,
|
259
|
+
retries = tonumber(job[8]),
|
260
|
+
remaining = tonumber(job[9]),
|
261
|
+
data = cjson.decode(job[10]),
|
262
|
+
tags = cjson.decode(job[11]),
|
263
|
+
history = cjson.decode(job[12]),
|
264
|
+
failure = cjson.decode(job[13] or '{}'),
|
265
|
+
dependents = redis.call('smembers', 'ql:j:' .. jid .. '-dependents'),
|
266
|
+
-- A job in the waiting state can not have dependencies
|
267
|
+
-- because it has been popped off of a queue, which
|
268
|
+
-- means all of its dependencies have been satisfied
|
269
|
+
dependencies = {}
|
270
|
+
|
271
|
+
}))
|
272
|
+
end
|
273
|
+
|
274
|
+
if #keys > 0 then
|
275
|
+
redis.call('zrem', key .. '-work', unpack(keys))
|
276
|
+
end
|
277
|
+
|
278
|
+
return response
|
@@ -0,0 +1,32 @@
|
|
1
|
+
-- priority(0, jid, priority)
|
2
|
+
-- --------------------------
|
3
|
+
-- Accepts a jid, and a new priority for the job. If the job
|
4
|
+
-- doesn't exist, then return false. Otherwise, return the
|
5
|
+
-- updated priority. If the job is waiting, then the change
|
6
|
+
-- will be reflected in the order in which it's popped
|
7
|
+
|
8
|
+
if #KEYS ~= 0 then
|
9
|
+
error('Priority(): Got ' .. #KEYS .. ', expected 0')
|
10
|
+
end
|
11
|
+
|
12
|
+
local jid = assert(ARGV[1] , 'Priority(): Arg "jid" missing')
|
13
|
+
local priority = assert(tonumber(ARGV[2]), 'Priority(): Arg "priority" missing or not a number: ' .. tostring(ARGV[2]))
|
14
|
+
|
15
|
+
-- Get the queue the job is currently in, if any
|
16
|
+
local queue = redis.call('hget', 'ql:j:' .. jid, 'queue')
|
17
|
+
|
18
|
+
if queue == nil then
|
19
|
+
return false
|
20
|
+
elseif queue == '' then
|
21
|
+
-- Just adjust the priority
|
22
|
+
redis.call('hset', 'ql:j:' .. jid, 'priority', priority)
|
23
|
+
return priority
|
24
|
+
else
|
25
|
+
-- Adjust the priority and see if it's a candidate for updating
|
26
|
+
-- its priority in the queue it's currently in
|
27
|
+
if redis.call('zscore', 'ql:q:' .. queue .. '-work', jid) then
|
28
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-work', priority, jid)
|
29
|
+
end
|
30
|
+
redis.call('hset', 'ql:j:' .. jid, 'priority', priority)
|
31
|
+
return priority
|
32
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
-- Put(1, queue, jid, klass, data, now, delay, [priority, p], [tags, t], [retries, r], [depends, '[...]'])
|
2
|
+
-- -------------------------------------------------------------------------------------------------------
|
3
|
+
-- This script takes the name of the queue and then the
|
4
|
+
-- info about the work item, and makes sure that it's
|
5
|
+
-- enqueued.
|
6
|
+
--
|
7
|
+
-- At some point, I'd like to able to provide functionality
|
8
|
+
-- that enables this to generate a unique ID for this piece
|
9
|
+
-- of work. As such, client libraries should not expose
|
10
|
+
-- setting the id from the user, as this is an implementation
|
11
|
+
-- detail that's likely to change and users should not grow
|
12
|
+
-- to depend on it.
|
13
|
+
--
|
14
|
+
-- Keys:
|
15
|
+
-- 1) queue name
|
16
|
+
-- Args:
|
17
|
+
-- 1) jid
|
18
|
+
-- 2) klass
|
19
|
+
-- 3) data
|
20
|
+
-- 4) now
|
21
|
+
-- 5) delay
|
22
|
+
-- *) [priority, p], [tags, t], [retries, r], [depends, '[...]']
|
23
|
+
|
24
|
+
if #KEYS ~= 1 then
|
25
|
+
if #KEYS < 1 then
|
26
|
+
error('Put(): Expected 1 KEYS argument')
|
27
|
+
else
|
28
|
+
error('Put(): Got ' .. #KEYS .. ', expected 1 KEYS argument')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
local queue = assert(KEYS[1] , 'Put(): Key "queue" missing')
|
33
|
+
local jid = assert(ARGV[1] , 'Put(): Arg "jid" missing')
|
34
|
+
local klass = assert(ARGV[2] , 'Put(): Arg "klass" missing')
|
35
|
+
local data = assert(cjson.decode(ARGV[3]) , 'Put(): Arg "data" missing or not JSON: ' .. tostring(ARGV[3]))
|
36
|
+
local now = assert(tonumber(ARGV[4]) , 'Put(): Arg "now" missing or not a number: ' .. tostring(ARGV[4]))
|
37
|
+
local delay = assert(tonumber(ARGV[5]) , 'Put(): Arg "delay" not a number: ' .. tostring(ARGV[5]))
|
38
|
+
|
39
|
+
-- Read in all the optional parameters
|
40
|
+
local options = {}
|
41
|
+
for i = 6, #ARGV, 2 do options[ARGV[i]] = ARGV[i + 1] end
|
42
|
+
|
43
|
+
-- Let's see what the old priority, history and tags were
|
44
|
+
local history, priority, tags, oldqueue, state, failure, retries, worker = unpack(redis.call('hmget', 'ql:j:' .. jid, 'history', 'priority', 'tags', 'queue', 'state', 'failure', 'retries', 'worker'))
|
45
|
+
|
46
|
+
-- Sanity check on optional args
|
47
|
+
retries = assert(tonumber(options['retries'] or retries or 5) , 'Put(): Arg "retries" not a number: ' .. tostring(options['retries']))
|
48
|
+
tags = assert(cjson.decode(options['tags'] or tags or '[]' ), 'Put(): Arg "tags" not JSON' .. tostring(options['tags']))
|
49
|
+
priority = assert(tonumber(options['priority'] or priority or 0), 'Put(): Arg "priority" not a number' .. tostring(options['priority']))
|
50
|
+
local depends = assert(cjson.decode(options['depends'] or '[]') , 'Put(): Arg "depends" not JSON: ' .. tostring(options['depends']))
|
51
|
+
|
52
|
+
-- Delay and depends are not allowed together
|
53
|
+
if delay > 0 and #depends > 0 then
|
54
|
+
error('Put(): "delay" and "depends" are not allowed to be used together')
|
55
|
+
end
|
56
|
+
|
57
|
+
-- Update the history to include this new change
|
58
|
+
local history = cjson.decode(history or '{}')
|
59
|
+
table.insert(history, {
|
60
|
+
q = queue,
|
61
|
+
put = math.floor(now)
|
62
|
+
})
|
63
|
+
|
64
|
+
-- If this item was previously in another queue, then we should remove it from there
|
65
|
+
if oldqueue then
|
66
|
+
redis.call('zrem', 'ql:q:' .. oldqueue .. '-work', jid)
|
67
|
+
redis.call('zrem', 'ql:q:' .. oldqueue .. '-locks', jid)
|
68
|
+
redis.call('zrem', 'ql:q:' .. oldqueue .. '-scheduled', jid)
|
69
|
+
redis.call('zrem', 'ql:q:' .. oldqueue .. '-depends', jid)
|
70
|
+
end
|
71
|
+
|
72
|
+
-- If this had previously been given out to a worker,
|
73
|
+
-- make sure to remove it from that worker's jobs
|
74
|
+
if worker then
|
75
|
+
redis.call('zrem', 'ql:w:' .. worker .. ':jobs', jid)
|
76
|
+
end
|
77
|
+
|
78
|
+
-- If the job was previously in the 'completed' state, then we should remove
|
79
|
+
-- it from being enqueued for destructination
|
80
|
+
if state == 'complete' then
|
81
|
+
redis.call('zrem', 'ql:completed', jid)
|
82
|
+
end
|
83
|
+
|
84
|
+
-- Add this job to the list of jobs tagged with whatever tags were supplied
|
85
|
+
for i, tag in ipairs(tags) do
|
86
|
+
redis.call('zadd', 'ql:t:' .. tag, now, jid)
|
87
|
+
redis.call('zincrby', 'ql:tags', 1, tag)
|
88
|
+
end
|
89
|
+
|
90
|
+
-- If we're in the failed state, remove all of our data
|
91
|
+
if state == 'failed' then
|
92
|
+
failure = cjson.decode(failure)
|
93
|
+
-- We need to make this remove it from the failed queues
|
94
|
+
redis.call('lrem', 'ql:f:' .. failure.group, 0, jid)
|
95
|
+
if redis.call('llen', 'ql:f:' .. failure.group) == 0 then
|
96
|
+
redis.call('srem', 'ql:failures', failure.group)
|
97
|
+
end
|
98
|
+
-- The bin is midnight of the provided day
|
99
|
+
-- 24 * 60 * 60 = 86400
|
100
|
+
local bin = failure.when - (failure.when % 86400)
|
101
|
+
-- We also need to decrement the stats about the queue on
|
102
|
+
-- the day that this failure actually happened.
|
103
|
+
redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. queue, 'failed' , -1)
|
104
|
+
end
|
105
|
+
|
106
|
+
-- First, let's save its data
|
107
|
+
redis.call('hmset', 'ql:j:' .. jid,
|
108
|
+
'jid' , jid,
|
109
|
+
'klass' , klass,
|
110
|
+
'data' , cjson.encode(data),
|
111
|
+
'priority' , priority,
|
112
|
+
'tags' , cjson.encode(tags),
|
113
|
+
'state' , ((delay > 0) and 'scheduled') or 'waiting',
|
114
|
+
'worker' , '',
|
115
|
+
'expires' , 0,
|
116
|
+
'queue' , queue,
|
117
|
+
'retries' , retries,
|
118
|
+
'remaining', retries,
|
119
|
+
'history' , cjson.encode(history))
|
120
|
+
|
121
|
+
-- These are the jids we legitimately have to wait on
|
122
|
+
for i, j in ipairs(depends) do
|
123
|
+
-- Make sure it's something other than 'nil' or complete.
|
124
|
+
local state = redis.call('hget', 'ql:j:' .. j, 'state')
|
125
|
+
if (state and state ~= 'complete') then
|
126
|
+
redis.call('sadd', 'ql:j:' .. j .. '-dependents' , jid)
|
127
|
+
redis.call('sadd', 'ql:j:' .. jid .. '-dependencies', j)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
-- Now, if a delay was provided, and if it's in the future,
|
132
|
+
-- then we'll have to schedule it. Otherwise, we're just
|
133
|
+
-- going to add it to the work queue.
|
134
|
+
if delay > 0 then
|
135
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-scheduled', now + delay, jid)
|
136
|
+
else
|
137
|
+
if redis.call('scard', 'ql:j:' .. jid .. '-dependencies') > 0 then
|
138
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-depends', now, jid)
|
139
|
+
redis.call('hset', 'ql:j:' .. jid, 'state', 'depends')
|
140
|
+
else
|
141
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-work', priority - (now / 10000000000), jid)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
-- Lastly, we're going to make sure that this item is in the
|
146
|
+
-- set of known queues. We should keep this sorted by the
|
147
|
+
-- order in which we saw each of these queues
|
148
|
+
if redis.call('zscore', 'ql:queues', queue) == false then
|
149
|
+
redis.call('zadd', 'ql:queues', now, queue)
|
150
|
+
end
|
151
|
+
|
152
|
+
if redis.call('zscore', 'ql:tracked', jid) ~= false then
|
153
|
+
redis.call('publish', 'put', jid)
|
154
|
+
end
|
155
|
+
|
156
|
+
return jid
|