qless 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/Gemfile +9 -3
  2. data/README.md +70 -25
  3. data/Rakefile +125 -9
  4. data/exe/install_phantomjs +21 -0
  5. data/lib/qless.rb +115 -76
  6. data/lib/qless/config.rb +11 -9
  7. data/lib/qless/failure_formatter.rb +43 -0
  8. data/lib/qless/job.rb +201 -102
  9. data/lib/qless/job_reservers/ordered.rb +7 -1
  10. data/lib/qless/job_reservers/round_robin.rb +16 -6
  11. data/lib/qless/job_reservers/shuffled_round_robin.rb +9 -2
  12. data/lib/qless/lua/qless-lib.lua +2463 -0
  13. data/lib/qless/lua/qless.lua +2012 -0
  14. data/lib/qless/lua_script.rb +63 -12
  15. data/lib/qless/middleware/memory_usage_monitor.rb +62 -0
  16. data/lib/qless/middleware/metriks.rb +45 -0
  17. data/lib/qless/middleware/redis_reconnect.rb +6 -3
  18. data/lib/qless/middleware/requeue_exceptions.rb +94 -0
  19. data/lib/qless/middleware/retry_exceptions.rb +38 -9
  20. data/lib/qless/middleware/sentry.rb +3 -7
  21. data/lib/qless/middleware/timeout.rb +64 -0
  22. data/lib/qless/queue.rb +90 -55
  23. data/lib/qless/server.rb +177 -130
  24. data/lib/qless/server/views/_job.erb +33 -15
  25. data/lib/qless/server/views/completed.erb +11 -0
  26. data/lib/qless/server/views/layout.erb +70 -11
  27. data/lib/qless/server/views/overview.erb +93 -53
  28. data/lib/qless/server/views/queue.erb +9 -8
  29. data/lib/qless/server/views/queues.erb +18 -1
  30. data/lib/qless/subscriber.rb +37 -22
  31. data/lib/qless/tasks.rb +5 -10
  32. data/lib/qless/test_helpers/worker_helpers.rb +55 -0
  33. data/lib/qless/version.rb +3 -1
  34. data/lib/qless/worker.rb +4 -413
  35. data/lib/qless/worker/base.rb +247 -0
  36. data/lib/qless/worker/forking.rb +245 -0
  37. data/lib/qless/worker/serial.rb +41 -0
  38. metadata +135 -52
  39. data/lib/qless/qless-core/cancel.lua +0 -101
  40. data/lib/qless/qless-core/complete.lua +0 -233
  41. data/lib/qless/qless-core/config.lua +0 -56
  42. data/lib/qless/qless-core/depends.lua +0 -65
  43. data/lib/qless/qless-core/deregister_workers.lua +0 -12
  44. data/lib/qless/qless-core/fail.lua +0 -117
  45. data/lib/qless/qless-core/failed.lua +0 -83
  46. data/lib/qless/qless-core/get.lua +0 -37
  47. data/lib/qless/qless-core/heartbeat.lua +0 -51
  48. data/lib/qless/qless-core/jobs.lua +0 -41
  49. data/lib/qless/qless-core/pause.lua +0 -18
  50. data/lib/qless/qless-core/peek.lua +0 -165
  51. data/lib/qless/qless-core/pop.lua +0 -314
  52. data/lib/qless/qless-core/priority.lua +0 -32
  53. data/lib/qless/qless-core/put.lua +0 -169
  54. data/lib/qless/qless-core/qless-lib.lua +0 -2354
  55. data/lib/qless/qless-core/qless.lua +0 -1862
  56. data/lib/qless/qless-core/queues.lua +0 -58
  57. data/lib/qless/qless-core/recur.lua +0 -190
  58. data/lib/qless/qless-core/retry.lua +0 -73
  59. data/lib/qless/qless-core/stats.lua +0 -92
  60. data/lib/qless/qless-core/tag.lua +0 -100
  61. data/lib/qless/qless-core/track.lua +0 -79
  62. data/lib/qless/qless-core/unfail.lua +0 -54
  63. data/lib/qless/qless-core/unpause.lua +0 -12
  64. data/lib/qless/qless-core/workers.lua +0 -69
  65. data/lib/qless/wait_until.rb +0 -19
@@ -1,1862 +0,0 @@
1
- local Qless = {
2
- ns = 'ql:'
3
- }
4
-
5
- local QlessQueue = {
6
- ns = Qless.ns .. 'q:'
7
- }
8
- QlessQueue.__index = QlessQueue
9
-
10
- local QlessWorker = {
11
- ns = Qless.ns .. 'w:'
12
- }
13
- QlessWorker.__index = QlessWorker
14
-
15
- local QlessJob = {
16
- ns = Qless.ns .. 'j:'
17
- }
18
- QlessJob.__index = QlessJob
19
-
20
- local QlessRecurringJob = {}
21
- QlessRecurringJob.__index = QlessRecurringJob
22
-
23
- Qless.config = {}
24
-
25
- function table.extend(self, other)
26
- for i, v in ipairs(other) do
27
- table.insert(self, v)
28
- end
29
- end
30
-
31
- function Qless.debug(message)
32
- redis.call('publish', 'debug', tostring(message))
33
- end
34
-
35
- function Qless.publish(channel, message)
36
- redis.call('publish', Qless.ns .. channel, message)
37
- end
38
-
39
- function Qless.job(jid)
40
- assert(jid, 'Job(): no jid provided')
41
- local job = {}
42
- setmetatable(job, QlessJob)
43
- job.jid = jid
44
- return job
45
- end
46
-
47
- function Qless.recurring(jid)
48
- assert(jid, 'Recurring(): no jid provided')
49
- local job = {}
50
- setmetatable(job, QlessRecurringJob)
51
- job.jid = jid
52
- return job
53
- end
54
-
55
- function Qless.failed(group, start, limit)
56
- start = assert(tonumber(start or 0),
57
- 'Failed(): Arg "start" is not a number: ' .. (start or 'nil'))
58
- limit = assert(tonumber(limit or 25),
59
- 'Failed(): Arg "limit" is not a number: ' .. (limit or 'nil'))
60
-
61
- if group then
62
- return {
63
- total = redis.call('llen', 'ql:f:' .. group),
64
- jobs = redis.call('lrange', 'ql:f:' .. group, start, limit - 1)
65
- }
66
- else
67
- local response = {}
68
- local groups = redis.call('smembers', 'ql:failures')
69
- for index, group in ipairs(groups) do
70
- response[group] = redis.call('llen', 'ql:f:' .. group)
71
- end
72
- return response
73
- end
74
- end
75
-
76
- function Qless.jobs(now, state, ...)
77
- assert(state, 'Jobs(): Arg "state" missing')
78
- if state == 'complete' then
79
- local offset = assert(tonumber(arg[1] or 0),
80
- 'Jobs(): Arg "offset" not a number: ' .. tostring(arg[1]))
81
- local count = assert(tonumber(arg[2] or 25),
82
- 'Jobs(): Arg "count" not a number: ' .. tostring(arg[2]))
83
- return redis.call('zrevrange', 'ql:completed', offset,
84
- offset + count - 1)
85
- else
86
- local name = assert(arg[1], 'Jobs(): Arg "queue" missing')
87
- local offset = assert(tonumber(arg[2] or 0),
88
- 'Jobs(): Arg "offset" not a number: ' .. tostring(arg[2]))
89
- local count = assert(tonumber(arg[3] or 25),
90
- 'Jobs(): Arg "count" not a number: ' .. tostring(arg[3]))
91
-
92
- local queue = Qless.queue(name)
93
- if state == 'running' then
94
- return queue.locks.peek(now, offset, count)
95
- elseif state == 'stalled' then
96
- return queue.locks.expired(now, offset, count)
97
- elseif state == 'scheduled' then
98
- return queue.scheduled.peek(now, offset, count)
99
- elseif state == 'depends' then
100
- return queue.depends.peek(now, offset, count)
101
- elseif state == 'recurring' then
102
- return queue.recurring.peek(now, offset, count)
103
- else
104
- error('Jobs(): Unknown type "' .. state .. '"')
105
- end
106
- end
107
- end
108
-
109
- function Qless.track(now, command, jid)
110
- if command ~= nil then
111
- assert(jid, 'Track(): Arg "jid" missing')
112
- if string.lower(ARGV[1]) == 'track' then
113
- Qless.publish('track', jid)
114
- return redis.call('zadd', 'ql:tracked', now, jid)
115
- elseif string.lower(ARGV[1]) == 'untrack' then
116
- Qless.publish('untrack', jid)
117
- return redis.call('zrem', 'ql:tracked', jid)
118
- else
119
- error('Track(): Unknown action "' .. command .. '"')
120
- end
121
- else
122
- local response = {
123
- jobs = {},
124
- expired = {}
125
- }
126
- local jids = redis.call('zrange', 'ql:tracked', 0, -1)
127
- for index, jid in ipairs(jids) do
128
- local data = Qless.job(jid):data()
129
- if data then
130
- table.insert(response.jobs, data)
131
- else
132
- table.insert(response.expired, jid)
133
- end
134
- end
135
- return response
136
- end
137
- end
138
-
139
- function Qless.tag(now, command, ...)
140
- assert(command, 'Tag(): Arg "command" must be "add", "remove", "get" or "top"')
141
-
142
- if command == 'add' then
143
- local jid = assert(arg[1], 'Tag(): Arg "jid" missing')
144
- local tags = redis.call('hget', QlessJob.ns .. jid, 'tags')
145
- if tags then
146
- tags = cjson.decode(tags)
147
- local _tags = {}
148
- for i,v in ipairs(tags) do _tags[v] = true end
149
-
150
- for i=2,#arg do
151
- local tag = arg[i]
152
- if _tags[tag] == nil then
153
- table.insert(tags, tag)
154
- end
155
- redis.call('zadd', 'ql:t:' .. tag, now, jid)
156
- redis.call('zincrby', 'ql:tags', 1, tag)
157
- end
158
-
159
- tags = cjson.encode(tags)
160
- redis.call('hset', QlessJob.ns .. jid, 'tags', tags)
161
- return tags
162
- else
163
- return false
164
- end
165
- elseif command == 'remove' then
166
- local jid = assert(arg[1], 'Tag(): Arg "jid" missing')
167
- local tags = redis.call('hget', QlessJob.ns .. jid, 'tags')
168
- if tags then
169
- tags = cjson.decode(tags)
170
- local _tags = {}
171
- for i,v in ipairs(tags) do _tags[v] = true end
172
-
173
- for i=2,#arg do
174
- local tag = arg[i]
175
- _tags[tag] = nil
176
- redis.call('zrem', 'ql:t:' .. tag, jid)
177
- redis.call('zincrby', 'ql:tags', -1, tag)
178
- end
179
-
180
- local results = {}
181
- for i,tag in ipairs(tags) do if _tags[tag] then table.insert(results, tag) end end
182
-
183
- tags = cjson.encode(results)
184
- redis.call('hset', QlessJob.ns .. jid, 'tags', tags)
185
- return results
186
- else
187
- return false
188
- end
189
- elseif command == 'get' then
190
- local tag = assert(arg[1], 'Tag(): Arg "tag" missing')
191
- local offset = assert(tonumber(arg[2] or 0),
192
- 'Tag(): Arg "offset" not a number: ' .. tostring(arg[2]))
193
- local count = assert(tonumber(arg[3] or 25),
194
- 'Tag(): Arg "count" not a number: ' .. tostring(arg[3]))
195
- return {
196
- total = redis.call('zcard', 'ql:t:' .. tag),
197
- jobs = redis.call('zrange', 'ql:t:' .. tag, offset, count)
198
- }
199
- elseif command == 'top' then
200
- local offset = assert(tonumber(arg[1] or 0) , 'Tag(): Arg "offset" not a number: ' .. tostring(arg[1]))
201
- local count = assert(tonumber(arg[2] or 25), 'Tag(): Arg "count" not a number: ' .. tostring(arg[2]))
202
- return redis.call('zrevrangebyscore', 'ql:tags', '+inf', 2, 'limit', offset, count)
203
- else
204
- error('Tag(): First argument must be "add", "remove" or "get"')
205
- end
206
- end
207
-
208
- function Qless.cancel(...)
209
- local dependents = {}
210
- for _, jid in ipairs(arg) do
211
- dependents[jid] = redis.call(
212
- 'smembers', QlessJob.ns .. jid .. '-dependents') or {}
213
- end
214
-
215
- for i, jid in ipairs(arg) do
216
- for j, dep in ipairs(dependents[jid]) do
217
- if dependents[dep] == nil then
218
- error('Cancel(): ' .. jid .. ' is a dependency of ' .. dep ..
219
- ' but is not mentioned to be canceled')
220
- end
221
- end
222
- end
223
-
224
- for _, jid in ipairs(arg) do
225
- local state, queue, failure, worker = unpack(redis.call(
226
- 'hmget', QlessJob.ns .. jid, 'state', 'queue', 'failure', 'worker'))
227
-
228
- if state ~= 'complete' then
229
- local encoded = cjson.encode({
230
- jid = jid,
231
- worker = worker,
232
- event = 'canceled',
233
- queue = queue
234
- })
235
- Qless.publish('log', encoded)
236
-
237
- if worker and (worker ~= '') then
238
- redis.call('zrem', 'ql:w:' .. worker .. ':jobs', jid)
239
- Qless.publish('w:', worker, encoded)
240
- end
241
-
242
- if queue then
243
- local queue = Qless.queue(queue)
244
- queue.work.remove(jid)
245
- queue.locks.remove(jid)
246
- queue.scheduled.remove(jid)
247
- queue.depends.remove(jid)
248
- end
249
-
250
- for i, j in ipairs(redis.call(
251
- 'smembers', QlessJob.ns .. jid .. '-dependencies')) do
252
- redis.call('srem', QlessJob.ns .. j .. '-dependents', jid)
253
- end
254
-
255
- redis.call('del', QlessJob.ns .. jid .. '-dependencies')
256
-
257
- if state == 'failed' then
258
- failure = cjson.decode(failure)
259
- redis.call('lrem', 'ql:f:' .. failure.group, 0, jid)
260
- if redis.call('llen', 'ql:f:' .. failure.group) == 0 then
261
- redis.call('srem', 'ql:failures', failure.group)
262
- end
263
- end
264
-
265
- local tags = cjson.decode(
266
- redis.call('hget', QlessJob.ns .. jid, 'tags') or '{}')
267
- for i, tag in ipairs(tags) do
268
- redis.call('zrem', 'ql:t:' .. tag, jid)
269
- redis.call('zincrby', 'ql:tags', -1, tag)
270
- end
271
-
272
- if redis.call('zscore', 'ql:tracked', jid) ~= false then
273
- Qless.publish('canceled', jid)
274
- end
275
-
276
- redis.call('del', QlessJob.ns .. jid)
277
- redis.call('del', QlessJob.ns .. jid .. '-history')
278
- end
279
- end
280
-
281
- return arg
282
- end
283
-
284
-
285
- Qless.config.defaults = {
286
- ['application'] = 'qless',
287
- ['heartbeat'] = 60,
288
- ['grace-period'] = 10,
289
- ['stats-history'] = 30,
290
- ['histogram-history'] = 7,
291
- ['jobs-history-count'] = 50000,
292
- ['jobs-history'] = 604800
293
- }
294
-
295
- Qless.config.get = function(key, default)
296
- if key then
297
- return redis.call('hget', 'ql:config', key) or
298
- Qless.config.defaults[key] or default
299
- else
300
- local reply = redis.call('hgetall', 'ql:config')
301
- for i = 1, #reply, 2 do
302
- Qless.config.defaults[reply[i]] = reply[i + 1]
303
- end
304
- return Qless.config.defaults
305
- end
306
- end
307
-
308
- Qless.config.set = function(option, value)
309
- assert(option, 'config.set(): Arg "option" missing')
310
- assert(value , 'config.set(): Arg "value" missing')
311
- Qless.publish('log', cjson.encode({
312
- event = 'config_set',
313
- option = option,
314
- value = value
315
- }))
316
-
317
- redis.call('hset', 'ql:config', option, value)
318
- end
319
-
320
- Qless.config.unset = function(option)
321
- assert(option, 'config.unset(): Arg "option" missing')
322
- Qless.publish('log', cjson.encode({
323
- event = 'config_unset',
324
- option = option
325
- }))
326
-
327
- redis.call('hdel', 'ql:config', option)
328
- end
329
-
330
- function QlessJob:data(...)
331
- local job = redis.call(
332
- 'hmget', QlessJob.ns .. self.jid, 'jid', 'klass', 'state', 'queue',
333
- 'worker', 'priority', 'expires', 'retries', 'remaining', 'data',
334
- 'tags', 'failure')
335
-
336
- if not job[1] then
337
- return nil
338
- end
339
-
340
- local data = {
341
- jid = job[1],
342
- klass = job[2],
343
- state = job[3],
344
- queue = job[4],
345
- worker = job[5] or '',
346
- tracked = redis.call(
347
- 'zscore', 'ql:tracked', self.jid) ~= false,
348
- priority = tonumber(job[6]),
349
- expires = tonumber(job[7]) or 0,
350
- retries = tonumber(job[8]),
351
- remaining = math.floor(tonumber(job[9])),
352
- data = cjson.decode(job[10]),
353
- tags = cjson.decode(job[11]),
354
- history = self:history(),
355
- failure = cjson.decode(job[12] or '{}'),
356
- dependents = redis.call(
357
- 'smembers', QlessJob.ns .. self.jid .. '-dependents'),
358
- dependencies = redis.call(
359
- 'smembers', QlessJob.ns .. self.jid .. '-dependencies')
360
- }
361
-
362
- if #arg > 0 then
363
- local response = {}
364
- for index, key in ipairs(arg) do
365
- table.insert(response, data[key])
366
- end
367
- return response
368
- else
369
- return data
370
- end
371
- end
372
-
373
- function QlessJob:complete(now, worker, queue, data, ...)
374
- assert(worker, 'Complete(): Arg "worker" missing')
375
- assert(queue , 'Complete(): Arg "queue" missing')
376
- data = assert(cjson.decode(data),
377
- 'Complete(): Arg "data" missing or not JSON: ' .. tostring(data))
378
-
379
- local options = {}
380
- for i = 1, #arg, 2 do options[arg[i]] = arg[i + 1] end
381
-
382
- local nextq = options['next']
383
- local delay = assert(tonumber(options['delay'] or 0))
384
- local depends = assert(cjson.decode(options['depends'] or '[]'),
385
- 'Complete(): Arg "depends" not JSON: ' .. tostring(options['depends']))
386
-
387
- if delay > 0 and #depends > 0 then
388
- error('Complete(): "delay" and "depends" are not allowed together')
389
- end
390
-
391
- if options['delay'] and nextq == nil then
392
- error('Complete(): "delay" cannot be used without a "next".')
393
- end
394
-
395
- if options['depends'] and nextq == nil then
396
- error('Complete(): "depends" cannot be used without a "next".')
397
- end
398
-
399
- local bin = now - (now % 86400)
400
-
401
- local lastworker, state, priority, retries = unpack(
402
- redis.call('hmget', QlessJob.ns .. self.jid, 'worker', 'state',
403
- 'priority', 'retries', 'dependents'))
404
-
405
- if lastworker == false then
406
- error('Complete(): Job does not exist')
407
- elseif (state ~= 'running') then
408
- error('Complete(): Job is not currently running: ' .. state)
409
- elseif lastworker ~= worker then
410
- error('Complete(): Job has been handed out to another worker: ' ..
411
- tostring(lastworker))
412
- end
413
-
414
- self:history(now, 'done')
415
-
416
- if data then
417
- redis.call('hset', QlessJob.ns .. self.jid, 'data', cjson.encode(data))
418
- end
419
-
420
- local queue_obj = Qless.queue(queue)
421
- queue_obj.work.remove(self.jid)
422
- queue_obj.locks.remove(self.jid)
423
- queue_obj.scheduled.remove(self.jid)
424
-
425
- local waiting = 0
426
- Qless.queue(queue):stat(now, 'run', waiting)
427
-
428
- redis.call('zrem', 'ql:w:' .. worker .. ':jobs', self.jid)
429
-
430
- if redis.call('zscore', 'ql:tracked', self.jid) ~= false then
431
- Qless.publish('completed', self.jid)
432
- end
433
-
434
- if nextq then
435
- queue_obj = Qless.queue(nextq)
436
- Qless.publish('log', cjson.encode({
437
- jid = self.jid,
438
- event = 'advanced',
439
- queue = queue,
440
- to = nextq
441
- }))
442
-
443
- self:history(now, 'put', {q = nextq})
444
-
445
- if redis.call('zscore', 'ql:queues', nextq) == false then
446
- redis.call('zadd', 'ql:queues', now, nextq)
447
- end
448
-
449
- redis.call('hmset', QlessJob.ns .. self.jid,
450
- 'state', 'waiting',
451
- 'worker', '',
452
- 'failure', '{}',
453
- 'queue', nextq,
454
- 'expires', 0,
455
- 'remaining', tonumber(retries))
456
-
457
- if delay > 0 then
458
- queue_obj.scheduled.add(now + delay, self.jid)
459
- return 'scheduled'
460
- else
461
- local count = 0
462
- for i, j in ipairs(depends) do
463
- local state = redis.call('hget', QlessJob.ns .. j, 'state')
464
- if (state and state ~= 'complete') then
465
- count = count + 1
466
- redis.call(
467
- 'sadd', QlessJob.ns .. j .. '-dependents',self.jid)
468
- redis.call(
469
- 'sadd', QlessJob.ns .. self.jid .. '-dependencies', j)
470
- end
471
- end
472
- if count > 0 then
473
- queue_obj.depends.add(now, self.jid)
474
- redis.call('hset', QlessJob.ns .. self.jid, 'state', 'depends')
475
- return 'depends'
476
- else
477
- queue_obj.work.add(now, priority, self.jid)
478
- return 'waiting'
479
- end
480
- end
481
- else
482
- Qless.publish('log', cjson.encode({
483
- jid = self.jid,
484
- event = 'completed',
485
- queue = queue
486
- }))
487
-
488
- redis.call('hmset', QlessJob.ns .. self.jid,
489
- 'state', 'complete',
490
- 'worker', '',
491
- 'failure', '{}',
492
- 'queue', '',
493
- 'expires', 0,
494
- 'remaining', tonumber(retries))
495
-
496
- local count = Qless.config.get('jobs-history-count')
497
- local time = Qless.config.get('jobs-history')
498
-
499
- count = tonumber(count or 50000)
500
- time = tonumber(time or 7 * 24 * 60 * 60)
501
-
502
- redis.call('zadd', 'ql:completed', now, self.jid)
503
-
504
- local jids = redis.call('zrangebyscore', 'ql:completed', 0, now - time)
505
- for index, jid in ipairs(jids) do
506
- local tags = cjson.decode(redis.call('hget', QlessJob.ns .. jid, 'tags') or '{}')
507
- for i, tag in ipairs(tags) do
508
- redis.call('zrem', 'ql:t:' .. tag, jid)
509
- redis.call('zincrby', 'ql:tags', -1, tag)
510
- end
511
- redis.call('del', QlessJob.ns .. jid)
512
- redis.call('del', QlessJob.ns .. jid .. '-history')
513
- end
514
- redis.call('zremrangebyscore', 'ql:completed', 0, now - time)
515
-
516
- jids = redis.call('zrange', 'ql:completed', 0, (-1-count))
517
- for index, jid in ipairs(jids) do
518
- local tags = cjson.decode(redis.call('hget', QlessJob.ns .. jid, 'tags') or '{}')
519
- for i, tag in ipairs(tags) do
520
- redis.call('zrem', 'ql:t:' .. tag, jid)
521
- redis.call('zincrby', 'ql:tags', -1, tag)
522
- end
523
- redis.call('del', QlessJob.ns .. jid)
524
- redis.call('del', QlessJob.ns .. jid .. '-history')
525
- end
526
- redis.call('zremrangebyrank', 'ql:completed', 0, (-1-count))
527
-
528
- for i, j in ipairs(redis.call('smembers', QlessJob.ns .. self.jid .. '-dependents')) do
529
- redis.call('srem', QlessJob.ns .. j .. '-dependencies', self.jid)
530
- if redis.call('scard', QlessJob.ns .. j .. '-dependencies') == 0 then
531
- local q, p = unpack(redis.call('hmget', QlessJob.ns .. j, 'queue', 'priority'))
532
- if q then
533
- local queue = Qless.queue(q)
534
- queue.depends.remove(j)
535
- queue.work.add(now, p, j)
536
- redis.call('hset', QlessJob.ns .. j, 'state', 'waiting')
537
- end
538
- end
539
- end
540
-
541
- redis.call('del', QlessJob.ns .. self.jid .. '-dependents')
542
-
543
- return 'complete'
544
- end
545
- end
546
-
547
- function QlessJob:fail(now, worker, group, message, data)
548
- local worker = assert(worker , 'Fail(): Arg "worker" missing')
549
- local group = assert(group , 'Fail(): Arg "group" missing')
550
- local message = assert(message , 'Fail(): Arg "message" missing')
551
-
552
- local bin = now - (now % 86400)
553
-
554
- if data then
555
- data = cjson.decode(data)
556
- end
557
-
558
- local queue, state = unpack(redis.call('hmget', QlessJob.ns .. self.jid,
559
- 'queue', 'state'))
560
-
561
- if state ~= 'running' then
562
- error('Fail(): Job not currently running: ' .. state)
563
- end
564
-
565
- Qless.publish('log', cjson.encode({
566
- jid = self.jid,
567
- event = 'failed',
568
- worker = worker,
569
- group = group,
570
- message = message
571
- }))
572
-
573
- if redis.call('zscore', 'ql:tracked', self.jid) ~= false then
574
- Qless.publish('failed', self.jid)
575
- end
576
-
577
- redis.call('zrem', 'ql:w:' .. worker .. ':jobs', self.jid)
578
-
579
- self:history(now, 'failed', {worker = worker, group = group})
580
-
581
- redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. queue, 'failures', 1)
582
- redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. queue, 'failed' , 1)
583
-
584
- local queue_obj = Qless.queue(queue)
585
- queue_obj.work.remove(self.jid)
586
- queue_obj.locks.remove(self.jid)
587
- queue_obj.scheduled.remove(self.jid)
588
-
589
- if data then
590
- redis.call('hset', QlessJob.ns .. self.jid, 'data', cjson.encode(data))
591
- end
592
-
593
- redis.call('hmset', QlessJob.ns .. self.jid,
594
- 'state', 'failed',
595
- 'worker', '',
596
- 'expires', '',
597
- 'failure', cjson.encode({
598
- ['group'] = group,
599
- ['message'] = message,
600
- ['when'] = math.floor(now),
601
- ['worker'] = worker
602
- }))
603
-
604
- redis.call('sadd', 'ql:failures', group)
605
- redis.call('lpush', 'ql:f:' .. group, self.jid)
606
-
607
-
608
- return self.jid
609
- end
610
-
611
- function QlessJob:retry(now, queue, worker, delay, group, message)
612
- assert(queue , 'Retry(): Arg "queue" missing')
613
- assert(worker, 'Retry(): Arg "worker" missing')
614
- delay = assert(tonumber(delay or 0),
615
- 'Retry(): Arg "delay" not a number: ' .. tostring(delay))
616
-
617
- local oldqueue, state, retries, oldworker, priority, failure = unpack(redis.call('hmget', QlessJob.ns .. self.jid, 'queue', 'state', 'retries', 'worker', 'priority', 'failure'))
618
-
619
- if oldworker == false then
620
- error('Retry(): Job does not exist')
621
- elseif state ~= 'running' then
622
- error('Retry(): Job is not currently running: ' .. state)
623
- elseif oldworker ~= worker then
624
- error('Retry(): Job has been handed out to another worker: ' .. oldworker)
625
- end
626
-
627
- local remaining = tonumber(redis.call(
628
- 'hincrbyfloat', QlessJob.ns .. self.jid, 'remaining', -0.5))
629
- if (remaining * 2) % 2 == 1 then
630
- local remaining = tonumber(redis.call(
631
- 'hincrbyfloat', QlessJob.ns .. self.jid, 'remaining', -0.5))
632
- end
633
-
634
- Qless.queue(oldqueue).locks.remove(self.jid)
635
-
636
- redis.call('zrem', 'ql:w:' .. worker .. ':jobs', self.jid)
637
-
638
- if remaining < 0 then
639
- local group = 'failed-retries-' .. queue
640
- self:history(now, 'failed', {group = group})
641
-
642
- redis.call('hmset', QlessJob.ns .. self.jid, 'state', 'failed',
643
- 'worker', '',
644
- 'expires', '')
645
- if failure == {} then
646
- redis.call('hset', QlessJob.ns .. self.jid,
647
- 'failure', cjson.encode({
648
- ['group'] = group,
649
- ['message'] =
650
- 'Job exhausted retries in queue "' .. self.name .. '"',
651
- ['when'] = now,
652
- ['worker'] = unpack(job:data('worker'))
653
- }))
654
- end
655
-
656
- redis.call('sadd', 'ql:failures', group)
657
- redis.call('lpush', 'ql:f:' .. group, self.jid)
658
- else
659
- local queue_obj = Qless.queue(queue)
660
- if delay > 0 then
661
- queue_obj.scheduled.add(now + delay, self.jid)
662
- redis.call('hset', QlessJob.ns .. self.jid, 'state', 'scheduled')
663
- else
664
- queue_obj.work.add(now, priority, self.jid)
665
- redis.call('hset', QlessJob.ns .. self.jid, 'state', 'waiting')
666
- end
667
-
668
- if group ~= nil and message ~= nil then
669
- redis.call('hset', QlessJob.ns .. self.jid,
670
- 'failure', cjson.encode({
671
- ['group'] = group,
672
- ['message'] = message,
673
- ['when'] = math.floor(now),
674
- ['worker'] = worker
675
- })
676
- )
677
- end
678
- end
679
-
680
- return math.floor(remaining)
681
- end
682
-
683
- function QlessJob:depends(now, command, ...)
684
- assert(command, 'Depends(): Arg "command" missing')
685
- if redis.call('hget', QlessJob.ns .. self.jid, 'state') ~= 'depends' then
686
- return false
687
- end
688
-
689
- if command == 'on' then
690
- for i, j in ipairs(arg) do
691
- local state = redis.call('hget', QlessJob.ns .. j, 'state')
692
- if (state and state ~= 'complete') then
693
- redis.call('sadd', QlessJob.ns .. j .. '-dependents' , self.jid)
694
- redis.call('sadd', QlessJob.ns .. self.jid .. '-dependencies', j)
695
- end
696
- end
697
- return true
698
- elseif command == 'off' then
699
- if arg[1] == 'all' then
700
- for i, j in ipairs(redis.call('smembers', QlessJob.ns .. self.jid .. '-dependencies')) do
701
- redis.call('srem', QlessJob.ns .. j .. '-dependents', self.jid)
702
- end
703
- redis.call('del', QlessJob.ns .. self.jid .. '-dependencies')
704
- local q, p = unpack(redis.call('hmget', QlessJob.ns .. self.jid, 'queue', 'priority'))
705
- if q then
706
- local queue_obj = Qless.queue(q)
707
- queue_obj.depends.remove(self.jid)
708
- queue_obj.work.add(now, p, self.jid)
709
- redis.call('hset', QlessJob.ns .. self.jid, 'state', 'waiting')
710
- end
711
- else
712
- for i, j in ipairs(arg) do
713
- redis.call('srem', QlessJob.ns .. j .. '-dependents', self.jid)
714
- redis.call('srem', QlessJob.ns .. self.jid .. '-dependencies', j)
715
- if redis.call('scard', QlessJob.ns .. self.jid .. '-dependencies') == 0 then
716
- local q, p = unpack(redis.call('hmget', QlessJob.ns .. self.jid, 'queue', 'priority'))
717
- if q then
718
- local queue_obj = Qless.queue(q)
719
- queue_obj.depends.remove(self.jid)
720
- queue_obj.work.add(now, p, self.jid)
721
- redis.call('hset', QlessJob.ns .. self.jid, 'state', 'waiting')
722
- end
723
- end
724
- end
725
- end
726
- return true
727
- else
728
- error('Depends(): Argument "command" must be "on" or "off"')
729
- end
730
- end
731
-
732
- function QlessJob:heartbeat(now, worker, data)
733
- assert(worker, 'Heatbeat(): Arg "worker" missing')
734
-
735
- local queue = redis.call('hget', QlessJob.ns .. self.jid, 'queue') or ''
736
- local expires = now + tonumber(
737
- Qless.config.get(queue .. '-heartbeat') or
738
- Qless.config.get('heartbeat', 60))
739
-
740
- if data then
741
- data = cjson.decode(data)
742
- end
743
-
744
- local job_worker, state = unpack(redis.call('hmget', QlessJob.ns .. self.jid, 'worker', 'state'))
745
- if job_worker == false then
746
- error('Heartbeat(): Job does not exist')
747
- elseif state ~= 'running' then
748
- error('Heartbeat(): Job not currently running: ' .. state)
749
- elseif job_worker ~= worker or #job_worker == 0 then
750
- error('Heartbeat(): Job has been handed out to another worker: ' .. job_worker)
751
- else
752
- if data then
753
- redis.call('hmset', QlessJob.ns .. self.jid, 'expires', expires, 'worker', worker, 'data', cjson.encode(data))
754
- else
755
- redis.call('hmset', QlessJob.ns .. self.jid, 'expires', expires, 'worker', worker)
756
- end
757
-
758
- redis.call('zadd', 'ql:w:' .. worker .. ':jobs', expires, self.jid)
759
-
760
- local queue = Qless.queue(redis.call('hget', QlessJob.ns .. self.jid, 'queue'))
761
- queue.locks.add(expires, self.jid)
762
- return expires
763
- end
764
- end
765
-
766
- function QlessJob:priority(priority)
767
- priority = assert(tonumber(priority),
768
- 'Priority(): Arg "priority" missing or not a number: ' .. tostring(priority))
769
-
770
- local queue = redis.call('hget', QlessJob.ns .. self.jid, 'queue')
771
-
772
- if queue == nil then
773
- return false
774
- elseif queue == '' then
775
- redis.call('hset', QlessJob.ns .. self.jid, 'priority', priority)
776
- return priority
777
- else
778
- local queue_obj = Qless.queue(queue)
779
- if queue_obj.work.score(self.jid) then
780
- queue_obj.work.add(0, priority, self.jid)
781
- end
782
- redis.call('hset', QlessJob.ns .. self.jid, 'priority', priority)
783
- return priority
784
- end
785
- end
786
-
787
- function QlessJob:update(data)
788
- local tmp = {}
789
- for k, v in pairs(data) do
790
- table.insert(tmp, k)
791
- table.insert(tmp, v)
792
- end
793
- redis.call('hmset', QlessJob.ns .. self.jid, unpack(tmp))
794
- end
795
-
796
- function QlessJob:timeout(now)
797
- local queue_name, state, worker = unpack(redis.call('hmget',
798
- QlessJob.ns .. self.jid, 'queue', 'state', 'worker'))
799
- if queue_name == nil then
800
- error('Timeout(): Job does not exist')
801
- elseif state ~= 'running' then
802
- error('Timeout(): Job not running')
803
- else
804
- self:history(now, 'timed-out')
805
- local queue = Qless.queue(queue_name)
806
- queue.locks.remove(self.jid)
807
- queue.work.add(now, math.huge, self.jid)
808
- redis.call('hmset', QlessJob.ns .. self.jid,
809
- 'state', 'stalled', 'expires', 0)
810
- local encoded = cjson.encode({
811
- jid = self.jid,
812
- event = 'lock_lost',
813
- worker = worker
814
- })
815
- Qless.publish('w:' .. worker, encoded)
816
- Qless.publish('log', encoded)
817
- return queue
818
- end
819
- end
820
-
821
- function QlessJob:history(now, what, item)
822
- local history = redis.call('hget', QlessJob.ns .. self.jid, 'history')
823
- if history then
824
- history = cjson.decode(history)
825
- for i, value in ipairs(history) do
826
- redis.call('rpush', QlessJob.ns .. self.jid .. '-history',
827
- cjson.encode({math.floor(value.put), 'put', {q = value.q}}))
828
-
829
- if value.popped then
830
- redis.call('rpush', QlessJob.ns .. self.jid .. '-history',
831
- cjson.encode({math.floor(value.popped), 'popped',
832
- {worker = value.worker}}))
833
- end
834
-
835
- if value.failed then
836
- redis.call('rpush', QlessJob.ns .. self.jid .. '-history',
837
- cjson.encode(
838
- {math.floor(value.failed), 'failed', nil}))
839
- end
840
-
841
- if value.done then
842
- redis.call('rpush', QlessJob.ns .. self.jid .. '-history',
843
- cjson.encode(
844
- {math.floor(value.done), 'done', nil}))
845
- end
846
- end
847
- redis.call('hdel', QlessJob.ns .. self.jid, 'history')
848
- end
849
-
850
- if what == nil then
851
- local response = {}
852
- for i, value in ipairs(redis.call('lrange',
853
- QlessJob.ns .. self.jid .. '-history', 0, -1)) do
854
- value = cjson.decode(value)
855
- local dict = value[3] or {}
856
- dict['when'] = value[1]
857
- dict['what'] = value[2]
858
- table.insert(response, dict)
859
- end
860
- return response
861
- else
862
- return redis.call('rpush', QlessJob.ns .. self.jid .. '-history',
863
- cjson.encode({math.floor(now), what, item}))
864
- end
865
- end
866
- function Qless.queue(name)
867
- assert(name, 'Queue(): no queue name provided')
868
- local queue = {}
869
- setmetatable(queue, QlessQueue)
870
- queue.name = name
871
-
872
- queue.work = {
873
- peek = function(count)
874
- if count == 0 then
875
- return {}
876
- end
877
- local jids = {}
878
- for index, jid in ipairs(redis.call(
879
- 'zrevrange', queue:prefix('work'), 0, count - 1)) do
880
- table.insert(jids, jid)
881
- end
882
- return jids
883
- end, remove = function(...)
884
- if #arg > 0 then
885
- return redis.call('zrem', queue:prefix('work'), unpack(arg))
886
- end
887
- end, add = function(now, priority, jid)
888
- return redis.call('zadd',
889
- queue:prefix('work'), priority - (now / 10000000000), jid)
890
- end, score = function(jid)
891
- return redis.call('zscore', queue:prefix('work'), jid)
892
- end, length = function()
893
- return redis.call('zcard', queue:prefix('work'))
894
- end
895
- }
896
-
897
- queue.locks = {
898
- expired = function(now, offset, count)
899
- return redis.call('zrangebyscore',
900
- queue:prefix('locks'), -math.huge, now, 'LIMIT', offset, count)
901
- end, peek = function(now, offset, count)
902
- return redis.call('zrangebyscore', queue:prefix('locks'),
903
- now, math.huge, 'LIMIT', offset, count)
904
- end, add = function(expires, jid)
905
- redis.call('zadd', queue:prefix('locks'), expires, jid)
906
- end, remove = function(...)
907
- if #arg > 0 then
908
- return redis.call('zrem', queue:prefix('locks'), unpack(arg))
909
- end
910
- end, running = function(now)
911
- return redis.call('zcount', queue:prefix('locks'), now, math.huge)
912
- end, length = function(now)
913
- if now then
914
- return redis.call('zcount', queue:prefix('locks'), 0, now)
915
- else
916
- return redis.call('zcard', queue:prefix('locks'))
917
- end
918
- end
919
- }
920
-
921
- queue.depends = {
922
- peek = function(now, offset, count)
923
- return redis.call('zrange',
924
- queue:prefix('depends'), offset, offset + count - 1)
925
- end, add = function(now, jid)
926
- redis.call('zadd', queue:prefix('depends'), now, jid)
927
- end, remove = function(...)
928
- if #arg > 0 then
929
- return redis.call('zrem', queue:prefix('depends'), unpack(arg))
930
- end
931
- end, length = function()
932
- return redis.call('zcard', queue:prefix('depends'))
933
- end
934
- }
935
-
936
- queue.scheduled = {
937
- peek = function(now, offset, count)
938
- return redis.call('zrange',
939
- queue:prefix('scheduled'), offset, offset + count - 1)
940
- end, ready = function(now, offset, count)
941
- return redis.call('zrangebyscore',
942
- queue:prefix('scheduled'), 0, now, 'LIMIT', offset, count)
943
- end, add = function(when, jid)
944
- redis.call('zadd', queue:prefix('scheduled'), when, jid)
945
- end, remove = function(...)
946
- if #arg > 0 then
947
- return redis.call('zrem', queue:prefix('scheduled'), unpack(arg))
948
- end
949
- end, length = function()
950
- return redis.call('zcard', queue:prefix('scheduled'))
951
- end
952
- }
953
-
954
- queue.recurring = {
955
- peek = function(now, offset, count)
956
- return redis.call('zrangebyscore', queue:prefix('recur'),
957
- 0, now, 'LIMIT', offset, count)
958
- end, ready = function(now, offset, count)
959
- end, add = function(when, jid)
960
- redis.call('zadd', queue:prefix('recur'), when, jid)
961
- end, remove = function(...)
962
- if #arg > 0 then
963
- return redis.call('zrem', queue:prefix('recur'), unpack(arg))
964
- end
965
- end, update = function(increment, jid)
966
- redis.call('zincrby', queue:prefix('recur'), increment, jid)
967
- end, score = function(jid)
968
- return redis.call('zscore', queue:prefix('recur'), jid)
969
- end, length = function()
970
- return redis.call('zcard', queue:prefix('recur'))
971
- end
972
- }
973
- return queue
974
- end
975
-
976
- function QlessQueue:prefix(group)
977
- if group then
978
- return QlessQueue.ns..self.name..'-'..group
979
- else
980
- return QlessQueue.ns..self.name
981
- end
982
- end
983
-
984
- function QlessQueue:stats(now, date)
985
- date = assert(tonumber(date),
986
- 'Stats(): Arg "date" missing or not a number: '.. (date or 'nil'))
987
-
988
- local bin = date - (date % 86400)
989
-
990
- local histokeys = {
991
- 's0','s1','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','s12','s13','s14','s15','s16','s17','s18','s19','s20','s21','s22','s23','s24','s25','s26','s27','s28','s29','s30','s31','s32','s33','s34','s35','s36','s37','s38','s39','s40','s41','s42','s43','s44','s45','s46','s47','s48','s49','s50','s51','s52','s53','s54','s55','s56','s57','s58','s59',
992
- 'm1','m2','m3','m4','m5','m6','m7','m8','m9','m10','m11','m12','m13','m14','m15','m16','m17','m18','m19','m20','m21','m22','m23','m24','m25','m26','m27','m28','m29','m30','m31','m32','m33','m34','m35','m36','m37','m38','m39','m40','m41','m42','m43','m44','m45','m46','m47','m48','m49','m50','m51','m52','m53','m54','m55','m56','m57','m58','m59',
993
- 'h1','h2','h3','h4','h5','h6','h7','h8','h9','h10','h11','h12','h13','h14','h15','h16','h17','h18','h19','h20','h21','h22','h23',
994
- 'd1','d2','d3','d4','d5','d6'
995
- }
996
-
997
- local mkstats = function(name, bin, queue)
998
- local results = {}
999
-
1000
- local key = 'ql:s:' .. name .. ':' .. bin .. ':' .. queue
1001
- local count, mean, vk = unpack(redis.call('hmget', key, 'total', 'mean', 'vk'))
1002
-
1003
- count = tonumber(count) or 0
1004
- mean = tonumber(mean) or 0
1005
- vk = tonumber(vk)
1006
-
1007
- results.count = count or 0
1008
- results.mean = mean or 0
1009
- results.histogram = {}
1010
-
1011
- if not count then
1012
- results.std = 0
1013
- else
1014
- if count > 1 then
1015
- results.std = math.sqrt(vk / (count - 1))
1016
- else
1017
- results.std = 0
1018
- end
1019
- end
1020
-
1021
- local histogram = redis.call('hmget', key, unpack(histokeys))
1022
- for i=1,#histokeys do
1023
- table.insert(results.histogram, tonumber(histogram[i]) or 0)
1024
- end
1025
- return results
1026
- end
1027
-
1028
- local retries, failed, failures = unpack(redis.call('hmget', 'ql:s:stats:' .. bin .. ':' .. self.name, 'retries', 'failed', 'failures'))
1029
- return {
1030
- retries = tonumber(retries or 0),
1031
- failed = tonumber(failed or 0),
1032
- failures = tonumber(failures or 0),
1033
- wait = mkstats('wait', bin, self.name),
1034
- run = mkstats('run' , bin, self.name)
1035
- }
1036
- end
1037
-
1038
- function QlessQueue:peek(now, count)
1039
- count = assert(tonumber(count),
1040
- 'Peek(): Arg "count" missing or not a number: ' .. tostring(count))
1041
-
1042
- local jids = self.locks.expired(now, 0, count)
1043
-
1044
- self:check_recurring(now, count - #jids)
1045
-
1046
- self:check_scheduled(now, count - #jids)
1047
-
1048
- table.extend(jids, self.work.peek(count - #jids))
1049
-
1050
- return jids
1051
- end
1052
-
1053
- function QlessQueue:paused()
1054
- return redis.call('sismember', 'ql:paused_queues', self.name) == 1
1055
- end
1056
-
1057
- function QlessQueue.pause(...)
1058
- redis.call('sadd', 'ql:paused_queues', unpack(arg))
1059
- end
1060
-
1061
- function QlessQueue.unpause(...)
1062
- redis.call('srem', 'ql:paused_queues', unpack(arg))
1063
- end
1064
-
1065
- function QlessQueue:pop(now, worker, count)
1066
- assert(worker, 'Pop(): Arg "worker" missing')
1067
- count = assert(tonumber(count),
1068
- 'Pop(): Arg "count" missing or not a number: ' .. tostring(count))
1069
-
1070
- local expires = now + tonumber(
1071
- Qless.config.get(self.name .. '-heartbeat') or
1072
- Qless.config.get('heartbeat', 60))
1073
-
1074
- if self:paused() then
1075
- return {}
1076
- end
1077
-
1078
- redis.call('zadd', 'ql:workers', now, worker)
1079
-
1080
- local max_concurrency = tonumber(
1081
- Qless.config.get(self.name .. '-max-concurrency', 0))
1082
-
1083
- if max_concurrency > 0 then
1084
- local allowed = math.max(0, max_concurrency - self.locks.running(now))
1085
- count = math.min(allowed, count)
1086
- if count == 0 then
1087
- return {}
1088
- end
1089
- end
1090
-
1091
- local jids = self:invalidate_locks(now, count)
1092
-
1093
- self:check_recurring(now, count - #jids)
1094
-
1095
- self:check_scheduled(now, count - #jids)
1096
-
1097
- table.extend(jids, self.work.peek(count - #jids))
1098
-
1099
- local state
1100
- for index, jid in ipairs(jids) do
1101
- local job = Qless.job(jid)
1102
- state = unpack(job:data('state'))
1103
- job:history(now, 'popped', {worker = worker})
1104
-
1105
- local waiting = 0
1106
- self:stat(now, 'wait', waiting)
1107
-
1108
- redis.call('zadd', 'ql:w:' .. worker .. ':jobs', expires, jid)
1109
-
1110
- job:update({
1111
- worker = worker,
1112
- expires = expires,
1113
- state = 'running'
1114
- })
1115
-
1116
- self.locks.add(expires, jid)
1117
-
1118
- local tracked = redis.call('zscore', 'ql:tracked', jid) ~= false
1119
- if tracked then
1120
- Qless.publish('popped', jid)
1121
- end
1122
- end
1123
-
1124
- self.work.remove(unpack(jids))
1125
-
1126
- return jids
1127
- end
1128
-
1129
- function QlessQueue:stat(now, stat, val)
1130
- local bin = now - (now % 86400)
1131
- local key = 'ql:s:' .. stat .. ':' .. bin .. ':' .. self.name
1132
-
1133
- local count, mean, vk = unpack(
1134
- redis.call('hmget', key, 'total', 'mean', 'vk'))
1135
-
1136
- count = count or 0
1137
- if count == 0 then
1138
- mean = val
1139
- vk = 0
1140
- count = 1
1141
- else
1142
- count = count + 1
1143
- local oldmean = mean
1144
- mean = mean + (val - mean) / count
1145
- vk = vk + (val - mean) * (val - oldmean)
1146
- end
1147
-
1148
- val = math.floor(val)
1149
- if val < 60 then -- seconds
1150
- redis.call('hincrby', key, 's' .. val, 1)
1151
- elseif val < 3600 then -- minutes
1152
- redis.call('hincrby', key, 'm' .. math.floor(val / 60), 1)
1153
- elseif val < 86400 then -- hours
1154
- redis.call('hincrby', key, 'h' .. math.floor(val / 3600), 1)
1155
- else -- days
1156
- redis.call('hincrby', key, 'd' .. math.floor(val / 86400), 1)
1157
- end
1158
- redis.call('hmset', key, 'total', count, 'mean', mean, 'vk', vk)
1159
- end
1160
-
1161
- function QlessQueue:put(now, jid, klass, data, delay, ...)
1162
- assert(jid , 'Put(): Arg "jid" missing')
1163
- assert(klass, 'Put(): Arg "klass" missing')
1164
- data = assert(cjson.decode(data),
1165
- 'Put(): Arg "data" missing or not JSON: ' .. tostring(data))
1166
- delay = assert(tonumber(delay),
1167
- 'Put(): Arg "delay" not a number: ' .. tostring(delay))
1168
-
1169
- local options = {}
1170
- for i = 1, #arg, 2 do options[arg[i]] = arg[i + 1] end
1171
-
1172
- local job = Qless.job(jid)
1173
- local priority, tags, oldqueue, state, failure, retries, worker = unpack(redis.call('hmget', QlessJob.ns .. jid, 'priority', 'tags', 'queue', 'state', 'failure', 'retries', 'worker'))
1174
-
1175
- retries = assert(tonumber(options['retries'] or retries or 5) , 'Put(): Arg "retries" not a number: ' .. tostring(options['retries']))
1176
- tags = assert(cjson.decode(options['tags'] or tags or '[]' ), 'Put(): Arg "tags" not JSON' .. tostring(options['tags']))
1177
- priority = assert(tonumber(options['priority'] or priority or 0), 'Put(): Arg "priority" not a number' .. tostring(options['priority']))
1178
- local depends = assert(cjson.decode(options['depends'] or '[]') , 'Put(): Arg "depends" not JSON: ' .. tostring(options['depends']))
1179
-
1180
- if delay > 0 and #depends > 0 then
1181
- error('Put(): "delay" and "depends" are not allowed to be used together')
1182
- end
1183
-
1184
- Qless.publish('log', cjson.encode({
1185
- jid = jid,
1186
- event = 'put',
1187
- queue = self.name
1188
- }))
1189
-
1190
- job:history(now, 'put', {q = self.name})
1191
-
1192
- if oldqueue then
1193
- local queue_obj = Qless.queue(oldqueue)
1194
- queue_obj.work.remove(jid)
1195
- queue_obj.locks.remove(jid)
1196
- queue_obj.depends.remove(jid)
1197
- queue_obj.scheduled.remove(jid)
1198
- end
1199
-
1200
- if worker then
1201
- redis.call('zrem', 'ql:w:' .. worker .. ':jobs', jid)
1202
- Qless.publish('w:' .. worker, cjson.encode({
1203
- jid = jid,
1204
- event = 'put',
1205
- queue = self.name
1206
- }))
1207
- end
1208
-
1209
- if state == 'complete' then
1210
- redis.call('zrem', 'ql:completed', jid)
1211
- end
1212
-
1213
- for i, tag in ipairs(tags) do
1214
- redis.call('zadd', 'ql:t:' .. tag, now, jid)
1215
- redis.call('zincrby', 'ql:tags', 1, tag)
1216
- end
1217
-
1218
- if state == 'failed' then
1219
- failure = cjson.decode(failure)
1220
- redis.call('lrem', 'ql:f:' .. failure.group, 0, jid)
1221
- if redis.call('llen', 'ql:f:' .. failure.group) == 0 then
1222
- redis.call('srem', 'ql:failures', failure.group)
1223
- end
1224
- local bin = failure.when - (failure.when % 86400)
1225
- redis.call('hincrby', 'ql:s:stats:' .. bin .. ':' .. self.name, 'failed' , -1)
1226
- end
1227
-
1228
- redis.call('hmset', QlessJob.ns .. jid,
1229
- 'jid' , jid,
1230
- 'klass' , klass,
1231
- 'data' , cjson.encode(data),
1232
- 'priority' , priority,
1233
- 'tags' , cjson.encode(tags),
1234
- 'state' , ((delay > 0) and 'scheduled') or 'waiting',
1235
- 'worker' , '',
1236
- 'expires' , 0,
1237
- 'queue' , self.name,
1238
- 'retries' , retries,
1239
- 'remaining', retries)
1240
-
1241
- for i, j in ipairs(depends) do
1242
- local state = redis.call('hget', QlessJob.ns .. j, 'state')
1243
- if (state and state ~= 'complete') then
1244
- redis.call('sadd', QlessJob.ns .. j .. '-dependents' , jid)
1245
- redis.call('sadd', QlessJob.ns .. jid .. '-dependencies', j)
1246
- end
1247
- end
1248
-
1249
- if delay > 0 then
1250
- self.scheduled.add(now + delay, jid)
1251
- else
1252
- if redis.call('scard', QlessJob.ns .. jid .. '-dependencies') > 0 then
1253
- self.depends.add(now, jid)
1254
- redis.call('hset', QlessJob.ns .. jid, 'state', 'depends')
1255
- else
1256
- self.work.add(now, priority, jid)
1257
- end
1258
- end
1259
-
1260
- if redis.call('zscore', 'ql:queues', self.name) == false then
1261
- redis.call('zadd', 'ql:queues', now, self.name)
1262
- end
1263
-
1264
- if redis.call('zscore', 'ql:tracked', jid) ~= false then
1265
- Qless.publish('put', jid)
1266
- end
1267
-
1268
- return jid
1269
- end
1270
-
1271
- function QlessQueue:unfail(now, group, count)
1272
- assert(group, 'Unfail(): Arg "group" missing')
1273
- count = assert(tonumber(count or 25),
1274
- 'Unfail(): Arg "count" not a number: ' .. tostring(count))
1275
-
1276
- local jids = redis.call('lrange', 'ql:f:' .. group, -count, -1)
1277
-
1278
- local toinsert = {}
1279
- for index, jid in ipairs(jids) do
1280
- local job = Qless.job(job)
1281
- local data = job:data()
1282
- job:history(now, 'put', {q = self.name})
1283
- redis.call('hmset', QlessJob.ns .. data.jid,
1284
- 'state' , 'waiting',
1285
- 'worker' , '',
1286
- 'expires' , 0,
1287
- 'queue' , self.name,
1288
- 'remaining', data.retries or 5)
1289
- self.work.add(now, data.priority, data.jid)
1290
- end
1291
-
1292
- redis.call('ltrim', 'ql:f:' .. group, 0, -count - 1)
1293
- if (redis.call('llen', 'ql:f:' .. group) == 0) then
1294
- redis.call('srem', 'ql:failures', group)
1295
- end
1296
-
1297
- return #jids
1298
- end
1299
-
1300
- function QlessQueue:recur(now, jid, klass, data, spec, ...)
1301
- assert(jid , 'RecurringJob On(): Arg "jid" missing')
1302
- assert(klass, 'RecurringJob On(): Arg "klass" missing')
1303
- assert(spec , 'RecurringJob On(): Arg "spec" missing')
1304
- data = assert(cjson.decode(data),
1305
- 'RecurringJob On(): Arg "data" not JSON: ' .. tostring(data))
1306
-
1307
- if spec == 'interval' then
1308
- local interval = assert(tonumber(arg[1]),
1309
- 'Recur(): Arg "interval" not a number: ' .. tostring(arg[1]))
1310
- local offset = assert(tonumber(arg[2]),
1311
- 'Recur(): Arg "offset" not a number: ' .. tostring(arg[2]))
1312
- if interval <= 0 then
1313
- error('Recur(): Arg "interval" must be greater than or equal to 0')
1314
- end
1315
-
1316
- local options = {}
1317
- for i = 3, #arg, 2 do options[arg[i]] = arg[i + 1] end
1318
- options.tags = assert(cjson.decode(options.tags or {}),
1319
- 'Recur(): Arg "tags" must be JSON string array: ' .. tostring(
1320
- options.tags))
1321
- options.priority = assert(tonumber(options.priority or 0),
1322
- 'Recur(): Arg "priority" not a number: ' .. tostring(
1323
- options.priority))
1324
- options.retries = assert(tonumber(options.retries or 0),
1325
- 'Recur(): Arg "retries" not a number: ' .. tostring(
1326
- options.retries))
1327
- options.backlog = assert(tonumber(options.backlog or 0),
1328
- 'Recur(): Arg "backlog" not a number: ' .. tostring(
1329
- options.backlog))
1330
-
1331
- local count, old_queue = unpack(redis.call('hmget', 'ql:r:' .. jid, 'count', 'queue'))
1332
- count = count or 0
1333
-
1334
- if old_queue then
1335
- Qless.queue(old_queue).recurring.remove(jid)
1336
- end
1337
-
1338
- redis.call('hmset', 'ql:r:' .. jid,
1339
- 'jid' , jid,
1340
- 'klass' , klass,
1341
- 'data' , cjson.encode(data),
1342
- 'priority', options.priority,
1343
- 'tags' , cjson.encode(options.tags or {}),
1344
- 'state' , 'recur',
1345
- 'queue' , self.name,
1346
- 'type' , 'interval',
1347
- 'count' , count,
1348
- 'interval', interval,
1349
- 'retries' , options.retries,
1350
- 'backlog' , options.backlog)
1351
- self.recurring.add(now + offset, jid)
1352
-
1353
- if redis.call('zscore', 'ql:queues', self.name) == false then
1354
- redis.call('zadd', 'ql:queues', now, self.name)
1355
- end
1356
-
1357
- return jid
1358
- else
1359
- error('Recur(): schedule type "' .. tostring(spec) .. '" unknown')
1360
- end
1361
- end
1362
-
1363
- function QlessQueue:length()
1364
- return self.locks.length() + self.work.length() + self.scheduled.length()
1365
- end
1366
-
1367
- function QlessQueue:check_recurring(now, count)
1368
- local moved = 0
1369
- local r = self.recurring.peek(now, 0, count)
1370
- for index, jid in ipairs(r) do
1371
- local klass, data, priority, tags, retries, interval, backlog = unpack(
1372
- redis.call('hmget', 'ql:r:' .. jid, 'klass', 'data', 'priority',
1373
- 'tags', 'retries', 'interval', 'backlog'))
1374
- local _tags = cjson.decode(tags)
1375
- local score = math.floor(tonumber(self.recurring.score(jid)))
1376
- interval = tonumber(interval)
1377
-
1378
- backlog = tonumber(backlog or 0)
1379
- if backlog ~= 0 then
1380
- local num = ((now - score) / interval)
1381
- if num > backlog then
1382
- score = score + (
1383
- math.ceil(num - backlog) * interval
1384
- )
1385
- end
1386
- end
1387
-
1388
- while (score <= now) and (moved < count) do
1389
- local count = redis.call('hincrby', 'ql:r:' .. jid, 'count', 1)
1390
- moved = moved + 1
1391
-
1392
- for i, tag in ipairs(_tags) do
1393
- redis.call('zadd', 'ql:t:' .. tag, now, jid .. '-' .. count)
1394
- redis.call('zincrby', 'ql:tags', 1, tag)
1395
- end
1396
-
1397
- local child_jid = jid .. '-' .. count
1398
- redis.call('hmset', QlessJob.ns .. child_jid,
1399
- 'jid' , jid .. '-' .. count,
1400
- 'klass' , klass,
1401
- 'data' , data,
1402
- 'priority' , priority,
1403
- 'tags' , tags,
1404
- 'state' , 'waiting',
1405
- 'worker' , '',
1406
- 'expires' , 0,
1407
- 'queue' , self.name,
1408
- 'retries' , retries,
1409
- 'remaining', retries)
1410
- Qless.job(child_jid):history(score, 'put', {q = self.name})
1411
-
1412
- self.work.add(score, priority, jid .. '-' .. count)
1413
-
1414
- score = score + interval
1415
- self.recurring.add(score, jid)
1416
- end
1417
- end
1418
- end
1419
-
1420
- function QlessQueue:check_scheduled(now, count, execute)
1421
- local zadd = {}
1422
- local scheduled = self.scheduled.ready(now, 0, count)
1423
- for index, jid in ipairs(scheduled) do
1424
- local priority = tonumber(
1425
- redis.call('hget', QlessJob.ns .. jid, 'priority') or 0)
1426
- self.work.add(now, priority, jid)
1427
-
1428
- redis.call('hset', QlessJob.ns .. jid, 'state', 'waiting')
1429
- end
1430
-
1431
- if #zadd > 0 then
1432
- self.scheduled.remove(unpack(scheduled))
1433
- end
1434
- end
1435
-
1436
- function QlessQueue:invalidate_locks(now, count)
1437
- local jids = {}
1438
- for index, jid in ipairs(self.locks.expired(now, 0, count)) do
1439
- local worker, failure = unpack(
1440
- redis.call('hmget', QlessJob.ns .. jid, 'worker', 'failure'))
1441
- redis.call('zrem', 'ql:w:' .. worker .. ':jobs', jid)
1442
-
1443
- local grace_period = tonumber(Qless.config.get('grace-period'))
1444
-
1445
- local remaining = tonumber(redis.call(
1446
- 'hincrbyfloat', QlessJob.ns .. jid, 'remaining', -0.5))
1447
-
1448
- local send_message = ((remaining * 2) % 2 == 1)
1449
- local invalidate = not send_message
1450
-
1451
- if grace_period <= 0 then
1452
- remaining = tonumber(redis.call(
1453
- 'hincrbyfloat', QlessJob.ns .. jid, 'remaining', -0.5))
1454
- send_message = true
1455
- invalidate = true
1456
- end
1457
-
1458
- if send_message then
1459
- if redis.call('zscore', 'ql:tracked', jid) ~= false then
1460
- Qless.publish('stalled', jid)
1461
- end
1462
- Qless.job(jid):history(now, 'timed-out')
1463
-
1464
- local encoded = cjson.encode({
1465
- jid = jid,
1466
- event = 'lock_lost',
1467
- worker = worker
1468
- })
1469
- Qless.publish('w:' .. worker, encoded)
1470
- Qless.publish('log', encoded)
1471
- self.locks.add(now + grace_period, jid)
1472
-
1473
- local bin = now - (now % 86400)
1474
- redis.call('hincrby',
1475
- 'ql:s:stats:' .. bin .. ':' .. self.name, 'retries', 1)
1476
- end
1477
-
1478
- if invalidate then
1479
- if remaining < 0 then
1480
- self.work.remove(jid)
1481
- self.locks.remove(jid)
1482
- self.scheduled.remove(jid)
1483
-
1484
- local group = 'failed-retries-' .. Qless.job(jid):data()['queue']
1485
- local job = Qless.job(jid)
1486
- job:history(now, 'failed', {group = group})
1487
- redis.call('hmset', QlessJob.ns .. jid, 'state', 'failed',
1488
- 'worker', '',
1489
- 'expires', '')
1490
- if failure == {} then
1491
- redis.call('hset', QlessJob.ns .. jid,
1492
- 'failure', cjson.encode({
1493
- ['group'] = group,
1494
- ['message'] =
1495
- 'Job exhausted retries in queue "' .. self.name .. '"',
1496
- ['when'] = now,
1497
- ['worker'] = unpack(job:data('worker'))
1498
- }))
1499
- end
1500
-
1501
- redis.call('sadd', 'ql:failures', group)
1502
- redis.call('lpush', 'ql:f:' .. group, jid)
1503
-
1504
- if redis.call('zscore', 'ql:tracked', jid) ~= false then
1505
- Qless.publish('failed', jid)
1506
- end
1507
- else
1508
- table.insert(jids, jid)
1509
- end
1510
- end
1511
- end
1512
-
1513
- return jids
1514
- end
1515
-
1516
- function QlessQueue.deregister(...)
1517
- redis.call('zrem', Qless.ns .. 'queues', unpack(arg))
1518
- end
1519
-
1520
- function QlessQueue.counts(now, name)
1521
- if name then
1522
- local queue = Qless.queue(name)
1523
- local stalled = queue.locks.length(now)
1524
- return {
1525
- name = name,
1526
- waiting = queue.work.length(),
1527
- stalled = stalled,
1528
- running = queue.locks.length() - stalled,
1529
- scheduled = queue.scheduled.length(),
1530
- depends = queue.depends.length(),
1531
- recurring = queue.recurring.length(),
1532
- paused = queue:paused()
1533
- }
1534
- else
1535
- local queues = redis.call('zrange', 'ql:queues', 0, -1)
1536
- local response = {}
1537
- for index, qname in ipairs(queues) do
1538
- table.insert(response, QlessQueue.counts(now, qname))
1539
- end
1540
- return response
1541
- end
1542
- end
1543
- function QlessRecurringJob:data()
1544
- local job = redis.call(
1545
- 'hmget', 'ql:r:' .. self.jid, 'jid', 'klass', 'state', 'queue',
1546
- 'priority', 'interval', 'retries', 'count', 'data', 'tags', 'backlog')
1547
-
1548
- if not job[1] then
1549
- return nil
1550
- end
1551
-
1552
- return {
1553
- jid = job[1],
1554
- klass = job[2],
1555
- state = job[3],
1556
- queue = job[4],
1557
- priority = tonumber(job[5]),
1558
- interval = tonumber(job[6]),
1559
- retries = tonumber(job[7]),
1560
- count = tonumber(job[8]),
1561
- data = cjson.decode(job[9]),
1562
- tags = cjson.decode(job[10]),
1563
- backlog = tonumber(job[11] or 0)
1564
- }
1565
- end
1566
-
1567
- function QlessRecurringJob:update(...)
1568
- local options = {}
1569
- if redis.call('exists', 'ql:r:' .. self.jid) ~= 0 then
1570
- for i = 1, #arg, 2 do
1571
- local key = arg[i]
1572
- local value = arg[i+1]
1573
- if key == 'priority' or key == 'interval' or key == 'retries' then
1574
- value = assert(tonumber(value), 'Recur(): Arg "' .. key .. '" must be a number: ' .. tostring(value))
1575
- if key == 'interval' then
1576
- local queue, interval = unpack(redis.call('hmget', 'ql:r:' .. self.jid, 'queue', 'interval'))
1577
- Qless.queue(queue).recurring.update(
1578
- value - tonumber(interval), self.jid)
1579
- end
1580
- redis.call('hset', 'ql:r:' .. self.jid, key, value)
1581
- elseif key == 'data' then
1582
- value = assert(cjson.decode(value), 'Recur(): Arg "data" is not JSON-encoded: ' .. tostring(value))
1583
- redis.call('hset', 'ql:r:' .. self.jid, 'data', cjson.encode(value))
1584
- elseif key == 'klass' then
1585
- redis.call('hset', 'ql:r:' .. self.jid, 'klass', value)
1586
- elseif key == 'queue' then
1587
- local queue_obj = Qless.queue(
1588
- redis.call('hget', 'ql:r:' .. self.jid, 'queue'))
1589
- local score = queue_obj.recurring.score(self.jid)
1590
- queue_obj.recurring.remove(self.jid)
1591
- Qless.queue(value).recurring.add(score, self.jid)
1592
- redis.call('hset', 'ql:r:' .. self.jid, 'queue', value)
1593
- elseif key == 'backlog' then
1594
- value = assert(tonumber(value),
1595
- 'Recur(): Arg "backlog" not a number: ' .. tostring(value))
1596
- redis.call('hset', 'ql:r:' .. self.jid, 'backlog', value)
1597
- else
1598
- error('Recur(): Unrecognized option "' .. key .. '"')
1599
- end
1600
- end
1601
- return true
1602
- else
1603
- error('Recur(): No recurring job ' .. self.jid)
1604
- end
1605
- end
1606
-
1607
- function QlessRecurringJob:tag(...)
1608
- local tags = redis.call('hget', 'ql:r:' .. self.jid, 'tags')
1609
- if tags then
1610
- tags = cjson.decode(tags)
1611
- local _tags = {}
1612
- for i,v in ipairs(tags) do _tags[v] = true end
1613
-
1614
- for i=1,#arg do if _tags[arg[i]] == nil then table.insert(tags, arg[i]) end end
1615
-
1616
- tags = cjson.encode(tags)
1617
- redis.call('hset', 'ql:r:' .. self.jid, 'tags', tags)
1618
- return tags
1619
- else
1620
- return false
1621
- end
1622
- end
1623
-
1624
- function QlessRecurringJob:untag(...)
1625
- local tags = redis.call('hget', 'ql:r:' .. self.jid, 'tags')
1626
- if tags then
1627
- tags = cjson.decode(tags)
1628
- local _tags = {}
1629
- for i,v in ipairs(tags) do _tags[v] = true end
1630
- for i = 1,#arg do _tags[arg[i]] = nil end
1631
- local results = {}
1632
- for i, tag in ipairs(tags) do if _tags[tag] then table.insert(results, tag) end end
1633
- tags = cjson.encode(results)
1634
- redis.call('hset', 'ql:r:' .. self.jid, 'tags', tags)
1635
- return tags
1636
- else
1637
- return false
1638
- end
1639
- end
1640
-
1641
- function QlessRecurringJob:unrecur()
1642
- local queue = redis.call('hget', 'ql:r:' .. self.jid, 'queue')
1643
- if queue then
1644
- Qless.queue(queue).recurring.remove(self.jid)
1645
- redis.call('del', 'ql:r:' .. self.jid)
1646
- return true
1647
- else
1648
- return true
1649
- end
1650
- end
1651
- function QlessWorker.deregister(...)
1652
- redis.call('zrem', 'ql:workers', unpack(arg))
1653
- end
1654
-
1655
- function QlessWorker.counts(now, worker)
1656
- local interval = tonumber(Qless.config.get('max-worker-age', 86400))
1657
-
1658
- local workers = redis.call('zrangebyscore', 'ql:workers', 0, now - interval)
1659
- for index, worker in ipairs(workers) do
1660
- redis.call('del', 'ql:w:' .. worker .. ':jobs')
1661
- end
1662
-
1663
- redis.call('zremrangebyscore', 'ql:workers', 0, now - interval)
1664
-
1665
- if worker then
1666
- return {
1667
- jobs = redis.call('zrevrangebyscore', 'ql:w:' .. worker .. ':jobs', now + 8640000, now),
1668
- stalled = redis.call('zrevrangebyscore', 'ql:w:' .. worker .. ':jobs', now, 0)
1669
- }
1670
- else
1671
- local response = {}
1672
- local workers = redis.call('zrevrange', 'ql:workers', 0, -1)
1673
- for index, worker in ipairs(workers) do
1674
- table.insert(response, {
1675
- name = worker,
1676
- jobs = redis.call('zcount', 'ql:w:' .. worker .. ':jobs', now, now + 8640000),
1677
- stalled = redis.call('zcount', 'ql:w:' .. worker .. ':jobs', 0, now)
1678
- })
1679
- end
1680
- return response
1681
- end
1682
- end
1683
- local QlessAPI = {}
1684
-
1685
- function QlessAPI.get(now, jid)
1686
- local data = Qless.job(jid):data()
1687
- if not data then
1688
- return nil
1689
- end
1690
- return cjson.encode(data)
1691
- end
1692
-
1693
- function QlessAPI.multiget(now, ...)
1694
- local results = {}
1695
- for i, jid in ipairs(arg) do
1696
- table.insert(results, Qless.job(jid):data())
1697
- end
1698
- return cjson.encode(results)
1699
- end
1700
-
1701
- QlessAPI['config.get'] = function(now, key)
1702
- if not key then
1703
- return cjson.encode(Qless.config.get(key))
1704
- else
1705
- return Qless.config.get(key)
1706
- end
1707
- end
1708
-
1709
- QlessAPI['config.set'] = function(now, key, value)
1710
- return Qless.config.set(key, value)
1711
- end
1712
-
1713
- QlessAPI['config.unset'] = function(now, key)
1714
- return Qless.config.unset(key)
1715
- end
1716
-
1717
- QlessAPI.queues = function(now, queue)
1718
- return cjson.encode(QlessQueue.counts(now, queue))
1719
- end
1720
-
1721
- QlessAPI.complete = function(now, jid, worker, queue, data, ...)
1722
- return Qless.job(jid):complete(now, worker, queue, data, unpack(arg))
1723
- end
1724
-
1725
- QlessAPI.failed = function(now, group, start, limit)
1726
- return cjson.encode(Qless.failed(group, start, limit))
1727
- end
1728
-
1729
- QlessAPI.fail = function(now, jid, worker, group, message, data)
1730
- return Qless.job(jid):fail(now, worker, group, message, data)
1731
- end
1732
-
1733
- QlessAPI.jobs = function(now, state, ...)
1734
- return Qless.jobs(now, state, unpack(arg))
1735
- end
1736
-
1737
- QlessAPI.retry = function(now, jid, queue, worker, delay, group, message)
1738
- return Qless.job(jid):retry(now, queue, worker, delay, group, message)
1739
- end
1740
-
1741
- QlessAPI.depends = function(now, jid, command, ...)
1742
- return Qless.job(jid):depends(now, command, unpack(arg))
1743
- end
1744
-
1745
- QlessAPI.heartbeat = function(now, jid, worker, data)
1746
- return Qless.job(jid):heartbeat(now, worker, data)
1747
- end
1748
-
1749
- QlessAPI.workers = function(now, worker)
1750
- return cjson.encode(QlessWorker.counts(now, worker))
1751
- end
1752
-
1753
- QlessAPI.track = function(now, command, jid)
1754
- return cjson.encode(Qless.track(now, command, jid))
1755
- end
1756
-
1757
- QlessAPI.tag = function(now, command, ...)
1758
- return cjson.encode(Qless.tag(now, command, unpack(arg)))
1759
- end
1760
-
1761
- QlessAPI.stats = function(now, queue, date)
1762
- return cjson.encode(Qless.queue(queue):stats(now, date))
1763
- end
1764
-
1765
- QlessAPI.priority = function(now, jid, priority)
1766
- return Qless.job(jid):priority(priority)
1767
- end
1768
-
1769
- QlessAPI.peek = function(now, queue, count)
1770
- local jids = Qless.queue(queue):peek(now, count)
1771
- local response = {}
1772
- for i, jid in ipairs(jids) do
1773
- table.insert(response, Qless.job(jid):data())
1774
- end
1775
- return cjson.encode(response)
1776
- end
1777
-
1778
- QlessAPI.pop = function(now, queue, worker, count)
1779
- local jids = Qless.queue(queue):pop(now, worker, count)
1780
- local response = {}
1781
- for i, jid in ipairs(jids) do
1782
- table.insert(response, Qless.job(jid):data())
1783
- end
1784
- return cjson.encode(response)
1785
- end
1786
-
1787
- QlessAPI.pause = function(now, ...)
1788
- return QlessQueue.pause(unpack(arg))
1789
- end
1790
-
1791
- QlessAPI.unpause = function(now, ...)
1792
- return QlessQueue.unpause(unpack(arg))
1793
- end
1794
-
1795
- QlessAPI.cancel = function(now, ...)
1796
- return Qless.cancel(unpack(arg))
1797
- end
1798
-
1799
- QlessAPI.timeout = function(now, jid)
1800
- return Qless.job(jid):timeout(now)
1801
- end
1802
-
1803
- QlessAPI.put = function(now, queue, jid, klass, data, delay, ...)
1804
- return Qless.queue(queue):put(now, jid, klass, data, delay, unpack(arg))
1805
- end
1806
-
1807
- QlessAPI.unfail = function(now, queue, group, count)
1808
- return Qless.queue(queue):unfail(now, group, count)
1809
- end
1810
-
1811
- QlessAPI.recur = function(now, queue, jid, klass, data, spec, ...)
1812
- return Qless.queue(queue):recur(now, jid, klass, data, spec, unpack(arg))
1813
- end
1814
-
1815
- QlessAPI.unrecur = function(now, jid)
1816
- return Qless.recurring(jid):unrecur()
1817
- end
1818
-
1819
- QlessAPI['recur.get'] = function(now, jid)
1820
- local data = Qless.recurring(jid):data()
1821
- if not data then
1822
- return nil
1823
- end
1824
- return cjson.encode(data)
1825
- end
1826
-
1827
- QlessAPI['recur.update'] = function(now, jid, ...)
1828
- return Qless.recurring(jid):update(unpack(arg))
1829
- end
1830
-
1831
- QlessAPI['recur.tag'] = function(now, jid, ...)
1832
- return Qless.recurring(jid):tag(unpack(arg))
1833
- end
1834
-
1835
- QlessAPI['recur.untag'] = function(now, jid, ...)
1836
- return Qless.recurring(jid):untag(unpack(arg))
1837
- end
1838
-
1839
- QlessAPI.length = function(now, queue)
1840
- return Qless.queue(queue):length()
1841
- end
1842
-
1843
- QlessAPI['worker.deregister'] = function(now, ...)
1844
- return QlessWorker.deregister(unpack(arg))
1845
- end
1846
-
1847
- QlessAPI['queue.forget'] = function(now, ...)
1848
- QlessQueue.deregister(unpack(arg))
1849
- end
1850
-
1851
-
1852
- if #KEYS > 0 then erorr('No Keys should be provided') end
1853
-
1854
- local command_name = assert(table.remove(ARGV, 1), 'Must provide a command')
1855
- local command = assert(
1856
- QlessAPI[command_name], 'Unknown command ' .. command_name)
1857
-
1858
- local now = tonumber(table.remove(ARGV, 1))
1859
- local now = assert(
1860
- now, 'Arg "now" missing or not a number: ' .. (now or 'nil'))
1861
-
1862
- return command(now, unpack(ARGV))