qless 0.9.3 → 0.10.0

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 (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,79 +0,0 @@
1
- -- Track(0)
2
- -- Track(0, ('track' | 'untrack'), jid, now)
3
- -- ------------------------------------------
4
- -- If no arguments are provided, it returns details of all currently-tracked jobs.
5
- -- If the first argument is 'track', then it will start tracking the job associated
6
- -- with that id, and 'untrack' stops tracking it. In this context, tracking is
7
- -- nothing more than saving the job to a list of jobs that are considered special.
8
- -- __Returns__ JSON:
9
- --
10
- -- {
11
- -- 'jobs': [
12
- -- {
13
- -- 'jid': ...,
14
- -- # All the other details you'd get from 'get'
15
- -- }, {
16
- -- ...
17
- -- }
18
- -- ], 'expired': [
19
- -- # These are all the jids that are completed and whose data expired
20
- -- 'deadbeef',
21
- -- ...,
22
- -- ...,
23
- -- ]
24
- -- }
25
- --
26
-
27
- if #KEYS ~= 0 then
28
- error('Track(): No keys expected. Got ' .. #KEYS)
29
- end
30
-
31
- if ARGV[1] ~= nil then
32
- local jid = assert(ARGV[2] , 'Track(): Arg "jid" missing')
33
- local now = assert(tonumber(ARGV[3]), 'Track(): Arg "now" missing or not a number: ' .. (ARGV[3] or 'nil'))
34
- if string.lower(ARGV[1]) == 'track' then
35
- redis.call('publish', 'track', jid)
36
- return redis.call('zadd', 'ql:tracked', now, jid)
37
- elseif string.lower(ARGV[1]) == 'untrack' then
38
- redis.call('publish', 'untrack', jid)
39
- return redis.call('zrem', 'ql:tracked', jid)
40
- else
41
- error('Track(): Unknown action "' .. ARGV[1] .. '"')
42
- end
43
- else
44
- local response = {
45
- jobs = {},
46
- expired = {}
47
- }
48
- local jids = redis.call('zrange', 'ql:tracked', 0, -1)
49
- for index, jid in ipairs(jids) do
50
- local job = redis.call(
51
- 'hmget', 'ql:j:' .. jid, 'jid', 'klass', 'state', 'queue', 'worker', 'priority',
52
- 'expires', 'retries', 'remaining', 'data', 'tags', 'history', 'failure')
53
-
54
- if job[1] then
55
- table.insert(response.jobs, {
56
- jid = job[1],
57
- klass = job[2],
58
- state = job[3],
59
- queue = job[4],
60
- worker = job[5] or '',
61
- tracked = true,
62
- priority = tonumber(job[6]),
63
- expires = tonumber(job[7]) or 0,
64
- retries = tonumber(job[8]),
65
- remaining = tonumber(job[9]),
66
- data = cjson.decode(job[10]),
67
- tags = cjson.decode(job[11]),
68
- history = cjson.decode(job[12]),
69
- failure = cjson.decode(job[13] or '{}'),
70
- dependents = redis.call('smembers', 'ql:j:' .. jid .. '-dependents'),
71
- dependencies = redis.call('smembers', 'ql:j:' .. jid .. '-dependencies')
72
-
73
- })
74
- else
75
- table.insert(response.expired, jid)
76
- end
77
- end
78
- return cjson.encode(response)
79
- end
@@ -1,54 +0,0 @@
1
- -- Unfail(0, now, group, queue, [count])
2
- --
3
- -- Move `count` jobs out of the failed state and into the provided queue
4
-
5
- if #KEYS ~= 0 then
6
- error('Unfail(): Expected 0 KEYS arguments')
7
- end
8
-
9
- local now = assert(tonumber(ARGV[1]), 'Unfail(): Arg "now" missing' )
10
- local group = assert(ARGV[2] , 'Unfail(): Arg "group" missing')
11
- local queue = assert(ARGV[3] , 'Unfail(): Arg "queue" missing')
12
- local count = assert(tonumber(ARGV[4] or 25),
13
- 'Unfail(): Arg "count" not a number: ' .. tostring(ARGV[4]))
14
-
15
- -- Get up to that many jobs, and we'll put them in the appropriate queue
16
- local jids = redis.call('lrange', 'ql:f:' .. group, -count, -1)
17
-
18
- -- Get each job's original number of retries,
19
- local jobs = {}
20
- for index, jid in ipairs(jids) do
21
- local packed = redis.call('hgetall', 'ql:j:' .. jid)
22
- local unpacked = {}
23
- for i = 1, #packed, 2 do unpacked[packed[i]] = packed[i + 1] end
24
- table.insert(jobs, unpacked)
25
- end
26
-
27
- -- And now set each job's state, and put it into the appropriate queue
28
- local toinsert = {}
29
- for index, job in ipairs(jobs) do
30
- job.history = cjson.decode(job.history or '{}')
31
- table.insert(job.history, {
32
- q = queue,
33
- put = math.floor(now)
34
- })
35
- redis.call('hmset', 'ql:j:' .. job.jid,
36
- 'state' , 'waiting',
37
- 'worker' , '',
38
- 'expires' , 0,
39
- 'queue' , queue,
40
- 'remaining', job.retries or 5,
41
- 'history' , cjson.encode(job.history))
42
- table.insert(toinsert, job.priority - (now / 10000000000))
43
- table.insert(toinsert, job.jid)
44
- end
45
-
46
- redis.call('zadd', 'ql:q:' .. queue .. '-work', unpack(toinsert))
47
-
48
- -- Remove these jobs from the failed state
49
- redis.call('ltrim', 'ql:f:' .. group, 0, -count - 1)
50
- if (redis.call('llen', 'ql:f:' .. group) == 0) then
51
- redis.call('srem', 'ql:failures', group)
52
- end
53
-
54
- return #jids
@@ -1,12 +0,0 @@
1
- -- This script takes the name of the queue(s) and removes it
2
- -- from the ql:paused_queues set.
3
- --
4
- -- Args: The list of queues to pause.
5
-
6
- if #KEYS > 0 then error('Pause(): No Keys should be provided') end
7
- if #ARGV < 1 then error('Pause(): Must provide at least one queue to pause') end
8
-
9
- local key = 'ql:paused_queues'
10
-
11
- redis.call('srem', key, unpack(ARGV))
12
-
@@ -1,69 +0,0 @@
1
- -- Workers(0, now, [worker])
2
- -- -------------------------
3
- -- Provide data about all the workers, or if a specific worker is provided, then
4
- -- which jobs that worker is responsible for. If no worker is provided, expect a
5
- -- response of the form:
6
- --
7
- -- [
8
- -- # This is sorted by the recency of activity from that worker
9
- -- {
10
- -- 'name' : 'hostname1-pid1',
11
- -- 'jobs' : 20,
12
- -- 'stalled': 0
13
- -- }, {
14
- -- ...
15
- -- }
16
- -- ]
17
- --
18
- -- If a worker id is provided, then expect a response of the form:
19
- --
20
- -- {
21
- -- 'jobs': [
22
- -- jid1,
23
- -- jid2,
24
- -- ...
25
- -- ], 'stalled': [
26
- -- jid1,
27
- -- ...
28
- -- ]
29
- -- }
30
- --
31
- if #KEYS > 0 then
32
- error('Workers(): No key arguments expected')
33
- end
34
-
35
- local now = assert(tonumber(ARGV[1]), 'Workers(): Arg "now" missing or not a number: ' .. (ARGV[1] or 'nil'))
36
-
37
- -- Clean up all the workers' job lists if they're too old. This is determined
38
- -- by the `max-worker-age` configuration, defaulting to the last day. Seems
39
- -- like a 'reasonable' default
40
- local interval = tonumber(
41
- redis.call('hget', 'ql:config', 'max-worker-age')) or 86400
42
-
43
- local workers = redis.call('zrangebyscore', 'ql:workers', 0, now - interval)
44
- for index, worker in ipairs(workers) do
45
- redis.call('del', 'ql:w:' .. worker .. ':jobs')
46
- end
47
-
48
- -- And now remove them from the list of known workers
49
- redis.call('zremrangebyscore', 'ql:workers', 0, now - interval)
50
-
51
- if #ARGV == 1 then
52
- local response = {}
53
- local workers = redis.call('zrevrange', 'ql:workers', 0, -1)
54
- for index, worker in ipairs(workers) do
55
- table.insert(response, {
56
- name = worker,
57
- jobs = redis.call('zcount', 'ql:w:' .. worker .. ':jobs', now, now + 8640000),
58
- stalled = redis.call('zcount', 'ql:w:' .. worker .. ':jobs', 0, now)
59
- })
60
- end
61
- return cjson.encode(response)
62
- else
63
- local worker = assert(ARGV[2], 'Workers(): Arg "worker" missing.')
64
- local response = {
65
- jobs = redis.call('zrevrangebyscore', 'ql:w:' .. worker .. ':jobs', now + 8640000, now),
66
- stalled = redis.call('zrevrangebyscore', 'ql:w:' .. worker .. ':jobs', now, 0)
67
- }
68
- return cjson.encode(response)
69
- end
@@ -1,19 +0,0 @@
1
- module Qless
2
- module WaitUntil
3
- TimeoutError = Class.new(StandardError)
4
-
5
- def wait_until(timeout)
6
- timeout_at = Time.now + timeout
7
-
8
- loop do
9
- return if yield
10
- sleep 0.002
11
- if Time.now > timeout_at
12
- raise TimeoutError, "Timed out after #{timeout} seconds"
13
- end
14
- end
15
- end
16
-
17
- module_function :wait_until
18
- end
19
- end