qless 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -3
- data/README.md +70 -25
- data/Rakefile +125 -9
- data/exe/install_phantomjs +21 -0
- data/lib/qless.rb +115 -76
- data/lib/qless/config.rb +11 -9
- data/lib/qless/failure_formatter.rb +43 -0
- data/lib/qless/job.rb +201 -102
- data/lib/qless/job_reservers/ordered.rb +7 -1
- data/lib/qless/job_reservers/round_robin.rb +16 -6
- data/lib/qless/job_reservers/shuffled_round_robin.rb +9 -2
- data/lib/qless/lua/qless-lib.lua +2463 -0
- data/lib/qless/lua/qless.lua +2012 -0
- data/lib/qless/lua_script.rb +63 -12
- data/lib/qless/middleware/memory_usage_monitor.rb +62 -0
- data/lib/qless/middleware/metriks.rb +45 -0
- data/lib/qless/middleware/redis_reconnect.rb +6 -3
- data/lib/qless/middleware/requeue_exceptions.rb +94 -0
- data/lib/qless/middleware/retry_exceptions.rb +38 -9
- data/lib/qless/middleware/sentry.rb +3 -7
- data/lib/qless/middleware/timeout.rb +64 -0
- data/lib/qless/queue.rb +90 -55
- data/lib/qless/server.rb +177 -130
- data/lib/qless/server/views/_job.erb +33 -15
- data/lib/qless/server/views/completed.erb +11 -0
- data/lib/qless/server/views/layout.erb +70 -11
- data/lib/qless/server/views/overview.erb +93 -53
- data/lib/qless/server/views/queue.erb +9 -8
- data/lib/qless/server/views/queues.erb +18 -1
- data/lib/qless/subscriber.rb +37 -22
- data/lib/qless/tasks.rb +5 -10
- data/lib/qless/test_helpers/worker_helpers.rb +55 -0
- data/lib/qless/version.rb +3 -1
- data/lib/qless/worker.rb +4 -413
- data/lib/qless/worker/base.rb +247 -0
- data/lib/qless/worker/forking.rb +245 -0
- data/lib/qless/worker/serial.rb +41 -0
- metadata +135 -52
- data/lib/qless/qless-core/cancel.lua +0 -101
- data/lib/qless/qless-core/complete.lua +0 -233
- data/lib/qless/qless-core/config.lua +0 -56
- data/lib/qless/qless-core/depends.lua +0 -65
- data/lib/qless/qless-core/deregister_workers.lua +0 -12
- data/lib/qless/qless-core/fail.lua +0 -117
- data/lib/qless/qless-core/failed.lua +0 -83
- data/lib/qless/qless-core/get.lua +0 -37
- data/lib/qless/qless-core/heartbeat.lua +0 -51
- data/lib/qless/qless-core/jobs.lua +0 -41
- data/lib/qless/qless-core/pause.lua +0 -18
- data/lib/qless/qless-core/peek.lua +0 -165
- data/lib/qless/qless-core/pop.lua +0 -314
- data/lib/qless/qless-core/priority.lua +0 -32
- data/lib/qless/qless-core/put.lua +0 -169
- data/lib/qless/qless-core/qless-lib.lua +0 -2354
- data/lib/qless/qless-core/qless.lua +0 -1862
- data/lib/qless/qless-core/queues.lua +0 -58
- data/lib/qless/qless-core/recur.lua +0 -190
- data/lib/qless/qless-core/retry.lua +0 -73
- data/lib/qless/qless-core/stats.lua +0 -92
- data/lib/qless/qless-core/tag.lua +0 -100
- data/lib/qless/qless-core/track.lua +0 -79
- data/lib/qless/qless-core/unfail.lua +0 -54
- data/lib/qless/qless-core/unpause.lua +0 -12
- data/lib/qless/qless-core/workers.lua +0 -69
- 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
|
data/lib/qless/wait_until.rb
DELETED
@@ -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
|