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