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,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