qless 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/README.md +42 -3
- data/Rakefile +26 -2
- data/{bin → exe}/qless-web +3 -2
- data/lib/qless.rb +55 -28
- data/lib/qless/config.rb +1 -3
- data/lib/qless/job.rb +127 -22
- data/lib/qless/job_reservers/round_robin.rb +3 -1
- data/lib/qless/job_reservers/shuffled_round_robin.rb +14 -0
- data/lib/qless/lua_script.rb +42 -0
- data/lib/qless/middleware/redis_reconnect.rb +24 -0
- data/lib/qless/middleware/retry_exceptions.rb +43 -0
- data/lib/qless/middleware/sentry.rb +70 -0
- data/lib/qless/qless-core/cancel.lua +89 -59
- data/lib/qless/qless-core/complete.lua +16 -1
- data/lib/qless/qless-core/config.lua +12 -0
- data/lib/qless/qless-core/deregister_workers.lua +12 -0
- data/lib/qless/qless-core/fail.lua +24 -14
- data/lib/qless/qless-core/heartbeat.lua +2 -1
- data/lib/qless/qless-core/pause.lua +18 -0
- data/lib/qless/qless-core/pop.lua +24 -3
- data/lib/qless/qless-core/put.lua +14 -1
- data/lib/qless/qless-core/qless-lib.lua +2354 -0
- data/lib/qless/qless-core/qless.lua +1862 -0
- data/lib/qless/qless-core/retry.lua +1 -1
- data/lib/qless/qless-core/unfail.lua +54 -0
- data/lib/qless/qless-core/unpause.lua +12 -0
- data/lib/qless/queue.rb +45 -21
- data/lib/qless/server.rb +38 -39
- data/lib/qless/server/static/css/docs.css +21 -1
- data/lib/qless/server/views/_job.erb +5 -5
- data/lib/qless/server/views/overview.erb +14 -9
- data/lib/qless/subscriber.rb +48 -0
- data/lib/qless/version.rb +1 -1
- data/lib/qless/wait_until.rb +19 -0
- data/lib/qless/worker.rb +243 -33
- metadata +49 -30
- data/bin/install_phantomjs +0 -7
- data/bin/qless-campfire +0 -106
- data/bin/qless-growl +0 -99
- data/lib/qless/lua.rb +0 -25
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,7 +20,7 @@ put in. So if a worker is working on a job, and you move it, the worker's reques
|
|
20
20
|
complete the job will be ignored.
|
21
21
|
|
22
22
|
A job can be `canceled`, which means it disappears into the ether, and we'll never
|
23
|
-
pay it any mind
|
23
|
+
pay it any mind ever again. A job can be `dropped`, which is when a worker fails
|
24
24
|
to heartbeat or complete the job in a timely fashion, or a job can be `failed`,
|
25
25
|
which is when a host recognizes some systematically problematic state about the
|
26
26
|
job. A worker should only fail a job if the error is likely not a transient one;
|
@@ -189,17 +189,20 @@ Then run the `qless:work` rake task:
|
|
189
189
|
rake qless:work
|
190
190
|
```
|
191
191
|
|
192
|
-
The following signals are supported:
|
192
|
+
The following signals are supported in the parent process:
|
193
193
|
|
194
194
|
* TERM: Shutdown immediately, stop processing jobs.
|
195
195
|
* INT: Shutdown immediately, stop processing jobs.
|
196
196
|
* QUIT: Shutdown after the current job has finished processing.
|
197
197
|
* USR1: Kill the forked child immediately, continue processing jobs.
|
198
|
-
* USR2: Don't process any new jobs
|
198
|
+
* USR2: Don't process any new jobs, and dump the current backtrace.
|
199
199
|
* CONT: Start processing jobs again after a USR2
|
200
200
|
|
201
201
|
You should send these to the master process, not the child.
|
202
202
|
|
203
|
+
The child process supports the `USR2` signal, whch causes it to
|
204
|
+
dump its current backtrace.
|
205
|
+
|
203
206
|
Workers also support middleware modules that can be used to inject
|
204
207
|
logic before, after or around the processing of a single job in
|
205
208
|
the child process. This can be useful, for example, when you need to
|
@@ -228,6 +231,42 @@ Qless::Worker.class_eval do
|
|
228
231
|
end
|
229
232
|
```
|
230
233
|
|
234
|
+
Per-Job Middlewares
|
235
|
+
===================
|
236
|
+
|
237
|
+
Qless also supports middleware on a per-job basis, when you have some
|
238
|
+
orthogonal logic to run in the context of some (but not all) jobs.
|
239
|
+
|
240
|
+
Per-job middlewares are defined the same as worker middlewares:
|
241
|
+
|
242
|
+
``` ruby
|
243
|
+
module ReEstablishDBConnection
|
244
|
+
def around_perform(job)
|
245
|
+
MyORM.establish_connection
|
246
|
+
super
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
To add them to a job class, you first have to make your job class
|
252
|
+
middleware-enabled by extending it with
|
253
|
+
`Qless::Job::SupportsMiddleware`, then extend your middleware
|
254
|
+
modules:
|
255
|
+
|
256
|
+
``` ruby
|
257
|
+
class MyJobClass
|
258
|
+
extend Qless::Job::SupportsMiddleware
|
259
|
+
extend ReEstablishDBConnection
|
260
|
+
extend MyOtherAwesomeMiddleware
|
261
|
+
|
262
|
+
def self.perform(job)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
Note that `Qless::Job::SupportsMiddleware` must be extended onto your
|
268
|
+
job class _before_ any other middleware modules.
|
269
|
+
|
231
270
|
Web Interface
|
232
271
|
=============
|
233
272
|
|
data/Rakefile
CHANGED
@@ -11,7 +11,8 @@ RSpec::Core::RakeTask.new(:spec) do |t|
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# TODO: bump this up as test coverage increases. It was 90.29 when I last updated it on 2012-05-21.
|
14
|
-
|
14
|
+
# On travis where we skip JS tests, it's at 83.9 on 2013-01-15
|
15
|
+
min_coverage_threshold = 83.5
|
15
16
|
desc "Checks the spec coverage and fails if it is less than #{min_coverage_threshold}%"
|
16
17
|
task :check_coverage do
|
17
18
|
percent = File.read("./coverage/coverage_percent.txt").to_f
|
@@ -24,5 +25,28 @@ end
|
|
24
25
|
|
25
26
|
task default: [:spec, :check_coverage]
|
26
27
|
|
27
|
-
|
28
28
|
require 'qless/tasks'
|
29
|
+
|
30
|
+
namespace :qless do
|
31
|
+
task :setup do
|
32
|
+
require 'qless'
|
33
|
+
queue = Qless::Client.new.queues["example"]
|
34
|
+
queue.client.redis.flushdb
|
35
|
+
|
36
|
+
ENV['QUEUES'] = queue.name
|
37
|
+
ENV['VVERBOSE'] = '1'
|
38
|
+
|
39
|
+
class ExampleJob
|
40
|
+
def self.perform(job)
|
41
|
+
sleep_time = job.data.fetch("sleep")
|
42
|
+
print "Sleeping for #{sleep_time}..."
|
43
|
+
sleep sleep_time
|
44
|
+
puts "done"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
20.times do |i|
|
49
|
+
queue.put(ExampleJob, sleep: i)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/{bin → exe}/qless-web
RENAMED
@@ -9,8 +9,9 @@ rescue LoadError
|
|
9
9
|
end
|
10
10
|
|
11
11
|
require 'qless/server'
|
12
|
+
client = Qless::Client.new
|
12
13
|
|
13
|
-
Vegas::Runner.new(Qless::Server, 'qless-web', {
|
14
|
+
Vegas::Runner.new(Qless::Server.new(client), 'qless-web', {
|
14
15
|
:before_run => lambda {|v|
|
15
16
|
path = (ENV['RESQUECONFIG'] || v.args.first)
|
16
17
|
load path.to_s.strip if path
|
@@ -20,4 +21,4 @@ Vegas::Runner.new(Qless::Server, 'qless-web', {
|
|
20
21
|
# runner.logger.info "Using Redis connection '#{redis_conf}'"
|
21
22
|
# Resque.redis = redis_conf
|
22
23
|
# }
|
23
|
-
end
|
24
|
+
end
|
data/lib/qless.rb
CHANGED
@@ -3,15 +3,28 @@ require "redis"
|
|
3
3
|
require "json"
|
4
4
|
require "securerandom"
|
5
5
|
|
6
|
+
module Qless
|
7
|
+
# Define our error base class before requiring the other
|
8
|
+
# files so they can define subclasses.
|
9
|
+
Error = Class.new(StandardError)
|
10
|
+
|
11
|
+
# to maintain backwards compatibility with v2.x of that gem we need this constant because:
|
12
|
+
# * (lua.rb) the #evalsha method signature changed between v2.x and v3.x of the redis ruby gem
|
13
|
+
# * (worker.rb) in v3.x you have to reconnect to the redis server after forking the process
|
14
|
+
USING_LEGACY_REDIS_VERSION = ::Redis::VERSION.to_f < 3.0
|
15
|
+
end
|
16
|
+
|
6
17
|
require "qless/version"
|
7
18
|
require "qless/config"
|
8
19
|
require "qless/queue"
|
9
20
|
require "qless/job"
|
10
|
-
require "qless/
|
21
|
+
require "qless/lua_script"
|
11
22
|
|
12
23
|
module Qless
|
13
24
|
extend self
|
14
25
|
|
26
|
+
UnsupportedRedisVersionError = Class.new(Error)
|
27
|
+
|
15
28
|
def generate_jid
|
16
29
|
SecureRandom.uuid.gsub('-', '')
|
17
30
|
end
|
@@ -27,27 +40,25 @@ module Qless
|
|
27
40
|
@worker_name ||= [Socket.gethostname, Process.pid.to_s].join('-')
|
28
41
|
end
|
29
42
|
|
30
|
-
class UnsupportedRedisVersionError < StandardError; end
|
31
|
-
|
32
43
|
class ClientJobs
|
33
44
|
def initialize(client)
|
34
45
|
@client = client
|
35
46
|
end
|
36
|
-
|
47
|
+
|
37
48
|
def complete(offset=0, count=25)
|
38
49
|
@client._jobs.call([], ['complete', offset, count])
|
39
50
|
end
|
40
|
-
|
51
|
+
|
41
52
|
def tracked
|
42
53
|
results = JSON.parse(@client._track.call([], []))
|
43
54
|
results['jobs'] = results['jobs'].map { |j| Job.new(@client, j) }
|
44
55
|
results
|
45
56
|
end
|
46
|
-
|
57
|
+
|
47
58
|
def tagged(tag, offset=0, count=25)
|
48
59
|
JSON.parse(@client._tag.call([], ['get', tag, offset, count]))
|
49
60
|
end
|
50
|
-
|
61
|
+
|
51
62
|
def failed(t=nil, start=0, limit=25)
|
52
63
|
if not t
|
53
64
|
JSON.parse(@client._failed.call([], []))
|
@@ -57,7 +68,7 @@ module Qless
|
|
57
68
|
results
|
58
69
|
end
|
59
70
|
end
|
60
|
-
|
71
|
+
|
61
72
|
def [](id)
|
62
73
|
results = @client._get.call([], [id])
|
63
74
|
if results.nil?
|
@@ -70,41 +81,41 @@ module Qless
|
|
70
81
|
Job.new(@client, JSON.parse(results))
|
71
82
|
end
|
72
83
|
end
|
73
|
-
|
84
|
+
|
74
85
|
class ClientWorkers
|
75
86
|
def initialize(client)
|
76
87
|
@client = client
|
77
88
|
end
|
78
|
-
|
89
|
+
|
79
90
|
def counts
|
80
91
|
JSON.parse(@client._workers.call([], [Time.now.to_i]))
|
81
92
|
end
|
82
|
-
|
93
|
+
|
83
94
|
def [](name)
|
84
95
|
JSON.parse(@client._workers.call([], [Time.now.to_i, name]))
|
85
96
|
end
|
86
97
|
end
|
87
|
-
|
98
|
+
|
88
99
|
class ClientQueues
|
89
100
|
def initialize(client)
|
90
101
|
@client = client
|
91
102
|
end
|
92
|
-
|
103
|
+
|
93
104
|
def counts
|
94
105
|
JSON.parse(@client._queues.call([], [Time.now.to_i]))
|
95
106
|
end
|
96
|
-
|
107
|
+
|
97
108
|
def [](name)
|
98
109
|
Queue.new(name, @client)
|
99
110
|
end
|
100
111
|
end
|
101
|
-
|
112
|
+
|
102
113
|
class ClientEvents
|
103
114
|
def initialize(redis)
|
104
115
|
@redis = redis
|
105
116
|
@actions = Hash.new()
|
106
117
|
end
|
107
|
-
|
118
|
+
|
108
119
|
def canceled(&block) ; @actions[:canceled ] = block; end
|
109
120
|
def completed(&block); @actions[:completed] = block; end
|
110
121
|
def failed(&block) ; @actions[:failed ] = block; end
|
@@ -113,7 +124,7 @@ module Qless
|
|
113
124
|
def put(&block) ; @actions[:put ] = block; end
|
114
125
|
def track(&block) ; @actions[:track ] = block; end
|
115
126
|
def untrack(&block) ; @actions[:untrack ] = block; end
|
116
|
-
|
127
|
+
|
117
128
|
def listen
|
118
129
|
yield(self) if block_given?
|
119
130
|
@redis.subscribe(:canceled, :completed, :failed, :popped, :stalled, :put, :track, :untrack) do |on|
|
@@ -125,19 +136,20 @@ module Qless
|
|
125
136
|
end
|
126
137
|
end
|
127
138
|
end
|
128
|
-
|
139
|
+
|
129
140
|
def stop
|
130
141
|
@redis.unsubscribe
|
131
142
|
end
|
132
143
|
end
|
133
|
-
|
144
|
+
|
134
145
|
class Client
|
135
146
|
# Lua scripts
|
136
147
|
attr_reader :_cancel, :_config, :_complete, :_fail, :_failed, :_get, :_heartbeat, :_jobs, :_peek, :_pop
|
137
148
|
attr_reader :_priority, :_put, :_queues, :_recur, :_retry, :_stats, :_tag, :_track, :_workers, :_depends
|
149
|
+
attr_reader :_pause, :_unpause, :_deregister_workers
|
138
150
|
# A real object
|
139
151
|
attr_reader :config, :redis, :jobs, :queues, :workers
|
140
|
-
|
152
|
+
|
141
153
|
def initialize(options = {})
|
142
154
|
# This is the redis instance we're connected to
|
143
155
|
@redis = options[:redis] || Redis.connect(options) # use connect so REDIS_URL will be honored
|
@@ -145,10 +157,11 @@ module Qless
|
|
145
157
|
assert_minimum_redis_version("2.5.5")
|
146
158
|
@config = Config.new(self)
|
147
159
|
['cancel', 'config', 'complete', 'depends', 'fail', 'failed', 'get', 'heartbeat', 'jobs', 'peek', 'pop',
|
148
|
-
'priority', 'put', 'queues', 'recur', 'retry', 'stats', 'tag', 'track', 'workers'
|
149
|
-
|
160
|
+
'priority', 'put', 'queues', 'recur', 'retry', 'stats', 'tag', 'track', 'workers', 'pause', 'unpause',
|
161
|
+
'deregister_workers'].each do |f|
|
162
|
+
self.instance_variable_set("@_#{f}", Qless::LuaScript.new(f, @redis))
|
150
163
|
end
|
151
|
-
|
164
|
+
|
152
165
|
@jobs = ClientJobs.new(self)
|
153
166
|
@queues = ClientQueues.new(self)
|
154
167
|
@workers = ClientWorkers.new(self)
|
@@ -157,29 +170,43 @@ module Qless
|
|
157
170
|
def inspect
|
158
171
|
"<Qless::Client #{@options} >"
|
159
172
|
end
|
160
|
-
|
173
|
+
|
161
174
|
def events
|
162
175
|
# Events needs its own redis instance of the same configuration, because
|
163
176
|
# once it's subscribed, we can only use pub-sub-like commands. This way,
|
164
177
|
# we still have access to the client in the normal case
|
165
178
|
@events ||= ClientEvents.new(Redis.connect(@options))
|
166
179
|
end
|
167
|
-
|
180
|
+
|
168
181
|
def track(jid)
|
169
182
|
@_track.call([], ['track', jid, Time.now.to_i])
|
170
183
|
end
|
171
|
-
|
184
|
+
|
172
185
|
def untrack(jid)
|
173
186
|
@_track.call([], ['untrack', jid, Time.now.to_i])
|
174
187
|
end
|
175
|
-
|
188
|
+
|
176
189
|
def tags(offset=0, count=100)
|
177
190
|
JSON.parse(@_tag.call([], ['top', offset, count]))
|
178
191
|
end
|
192
|
+
|
193
|
+
def deregister_workers(*worker_names)
|
194
|
+
_deregister_workers.call([], worker_names)
|
195
|
+
end
|
196
|
+
|
197
|
+
def bulk_cancel(jids)
|
198
|
+
@_cancel.call([], jids)
|
199
|
+
end
|
200
|
+
|
201
|
+
def new_redis_connection
|
202
|
+
::Redis.new(url: redis.id)
|
203
|
+
end
|
204
|
+
|
179
205
|
private
|
180
206
|
|
181
207
|
def assert_minimum_redis_version(version)
|
182
|
-
|
208
|
+
# remove the "-pre2" from "2.6.8-pre2"
|
209
|
+
redis_version = @redis.info.fetch("redis_version").split('-').first
|
183
210
|
return if Gem::Version.new(redis_version) >= Gem::Version.new(version)
|
184
211
|
|
185
212
|
raise UnsupportedRedisVersionError,
|
data/lib/qless/config.rb
CHANGED
data/lib/qless/job.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
require "qless"
|
2
2
|
require "qless/queue"
|
3
|
-
require "qless/lua"
|
4
3
|
require "redis"
|
5
4
|
require "json"
|
6
5
|
|
7
6
|
module Qless
|
8
7
|
class BaseJob
|
8
|
+
attr_reader :client
|
9
|
+
|
9
10
|
def initialize(client, jid)
|
10
11
|
@client = client
|
11
12
|
@jid = jid
|
12
13
|
end
|
13
14
|
|
14
15
|
def klass
|
15
|
-
@klass ||= @klass_name.split('::').inject(
|
16
|
+
@klass ||= @klass_name.split('::').inject(Object) { |context, name| context.const_get(name) }
|
16
17
|
end
|
17
18
|
|
18
19
|
def queue
|
@@ -21,12 +22,30 @@ module Qless
|
|
21
22
|
end
|
22
23
|
|
23
24
|
class Job < BaseJob
|
24
|
-
attr_reader :jid, :expires_at, :state, :queue_name, :
|
25
|
-
attr_reader :original_retries, :retries_left
|
25
|
+
attr_reader :jid, :expires_at, :state, :queue_name, :worker_name, :failure, :klass_name, :tracked, :dependencies, :dependents
|
26
|
+
attr_reader :original_retries, :retries_left, :raw_queue_history
|
26
27
|
attr_accessor :data, :priority, :tags
|
27
28
|
|
29
|
+
MiddlewareMisconfiguredError = Class.new(StandardError)
|
30
|
+
|
31
|
+
module SupportsMiddleware
|
32
|
+
def around_perform(job)
|
33
|
+
perform(job)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
28
37
|
def perform
|
29
|
-
|
38
|
+
middlewares = Job.middlewares_on(klass)
|
39
|
+
|
40
|
+
if middlewares.last == SupportsMiddleware
|
41
|
+
klass.around_perform(self)
|
42
|
+
elsif middlewares.any?
|
43
|
+
raise MiddlewareMisconfiguredError, "The middleware chain for #{klass} " +
|
44
|
+
"(#{middlewares.inspect}) is misconfigured. Qless::Job::SupportsMiddleware " +
|
45
|
+
"must be extended onto your job class first if you want to use any middleware."
|
46
|
+
else
|
47
|
+
klass.perform(self)
|
48
|
+
end
|
30
49
|
end
|
31
50
|
|
32
51
|
def self.build(client, klass, attributes = {})
|
@@ -49,29 +68,38 @@ module Qless
|
|
49
68
|
"dependents" => []
|
50
69
|
}
|
51
70
|
attributes = defaults.merge(Qless.stringify_hash_keys(attributes))
|
52
|
-
attributes["data"] = JSON.
|
71
|
+
attributes["data"] = JSON.parse(JSON.dump attributes["data"])
|
53
72
|
new(client, attributes)
|
54
73
|
end
|
55
74
|
|
75
|
+
def self.middlewares_on(job_klass)
|
76
|
+
job_klass.singleton_class.ancestors.select do |ancestor|
|
77
|
+
ancestor.method_defined?(:around_perform)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
56
81
|
def initialize(client, atts)
|
57
82
|
super(client, atts.fetch('jid'))
|
58
83
|
%w{jid data priority tags state tracked
|
59
|
-
failure
|
84
|
+
failure dependencies dependents}.each do |att|
|
60
85
|
self.instance_variable_set("@#{att}".to_sym, atts.fetch(att))
|
61
86
|
end
|
62
87
|
|
63
|
-
@expires_at
|
64
|
-
@klass_name
|
65
|
-
@queue_name
|
66
|
-
@worker_name
|
67
|
-
@original_retries
|
68
|
-
@retries_left
|
88
|
+
@expires_at = atts.fetch('expires')
|
89
|
+
@klass_name = atts.fetch('klass')
|
90
|
+
@queue_name = atts.fetch('queue')
|
91
|
+
@worker_name = atts.fetch('worker')
|
92
|
+
@original_retries = atts.fetch('retries')
|
93
|
+
@retries_left = atts.fetch('remaining')
|
94
|
+
@raw_queue_history = atts.fetch('history')
|
69
95
|
|
70
96
|
# This is a silly side-effect of Lua doing JSON parsing
|
71
97
|
@tags = [] if @tags == {}
|
72
98
|
@dependents = [] if @dependents == {}
|
73
99
|
@dependencies = [] if @dependencies == {}
|
74
100
|
@state_changed = false
|
101
|
+
@before_callbacks = Hash.new { |h, k| h[k] = [] }
|
102
|
+
@after_callbacks = Hash.new { |h, k| h[k] = [] }
|
75
103
|
end
|
76
104
|
|
77
105
|
def priority=(priority)
|
@@ -93,7 +121,7 @@ module Qless
|
|
93
121
|
end
|
94
122
|
|
95
123
|
def description
|
96
|
-
"#{@
|
124
|
+
"#{@klass_name} (#{@jid} / #{@queue_name} / #{@state})"
|
97
125
|
end
|
98
126
|
|
99
127
|
def inspect
|
@@ -104,9 +132,57 @@ module Qless
|
|
104
132
|
@expires_at - Time.now.to_f
|
105
133
|
end
|
106
134
|
|
135
|
+
def reconnect_to_redis
|
136
|
+
@client.redis.client.reconnect
|
137
|
+
end
|
138
|
+
|
139
|
+
def history
|
140
|
+
warn "WARNING: Qless::Job#history is deprecated; use Qless::Job#raw_queue_history instead" +
|
141
|
+
"; called from:\n#{caller.first}\n"
|
142
|
+
raw_queue_history
|
143
|
+
end
|
144
|
+
|
145
|
+
def queue_history
|
146
|
+
@queue_history ||= @raw_queue_history.map do |history_event|
|
147
|
+
history_event.each_with_object({}) do |(key, value), hash|
|
148
|
+
# The only Numeric (Integer or Float) values we get in the history are timestamps
|
149
|
+
hash[key] = if value.is_a?(Numeric)
|
150
|
+
Time.at(value).utc
|
151
|
+
else
|
152
|
+
value
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def initially_put_at
|
159
|
+
@initially_put_at ||= history_timestamp('put', :min)
|
160
|
+
end
|
161
|
+
|
162
|
+
def to_hash
|
163
|
+
{
|
164
|
+
jid: jid,
|
165
|
+
expires_at: expires_at,
|
166
|
+
state: state,
|
167
|
+
queue_name: queue_name,
|
168
|
+
history: raw_queue_history,
|
169
|
+
worker_name: worker_name,
|
170
|
+
failure: failure,
|
171
|
+
klass_name: klass_name,
|
172
|
+
tracked: tracked,
|
173
|
+
dependencies: dependencies,
|
174
|
+
dependents: dependents,
|
175
|
+
original_retries: original_retries,
|
176
|
+
retries_left: retries_left,
|
177
|
+
data: data,
|
178
|
+
priority: priority,
|
179
|
+
tags: tags
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
107
183
|
# Move this from it's current queue into another
|
108
184
|
def move(queue)
|
109
|
-
note_state_change do
|
185
|
+
note_state_change :move do
|
110
186
|
@client._put.call([queue], [
|
111
187
|
@jid, @klass_name, JSON.generate(@data), Time.now.to_f, 0
|
112
188
|
])
|
@@ -115,7 +191,7 @@ module Qless
|
|
115
191
|
|
116
192
|
# Fail a job
|
117
193
|
def fail(group, message)
|
118
|
-
note_state_change do
|
194
|
+
note_state_change :fail do
|
119
195
|
@client._fail.call([], [
|
120
196
|
@jid,
|
121
197
|
@worker_name,
|
@@ -134,13 +210,15 @@ module Qless
|
|
134
210
|
JSON.generate(@data)]) || false
|
135
211
|
end
|
136
212
|
|
213
|
+
CantCompleteError = Class.new(Qless::Error)
|
214
|
+
|
137
215
|
# Complete a job
|
138
216
|
# Options include
|
139
217
|
# => next (String) the next queue
|
140
218
|
# => delay (int) how long to delay it in the next queue
|
141
219
|
def complete(nxt=nil, options={})
|
142
|
-
|
143
|
-
if nxt.nil?
|
220
|
+
note_state_change :complete do
|
221
|
+
response = if nxt.nil?
|
144
222
|
@client._complete.call([], [
|
145
223
|
@jid, @worker_name, @queue_name, Time.now.to_f, JSON.generate(@data)])
|
146
224
|
else
|
@@ -148,8 +226,19 @@ module Qless
|
|
148
226
|
@jid, @worker_name, @queue_name, Time.now.to_f, JSON.generate(@data), 'next', nxt, 'delay',
|
149
227
|
options.fetch(:delay, 0), 'depends', JSON.generate(options.fetch(:depends, []))])
|
150
228
|
end
|
229
|
+
|
230
|
+
if response
|
231
|
+
response
|
232
|
+
else
|
233
|
+
description = if reloaded_instance = @client.jobs[@jid]
|
234
|
+
reloaded_instance.description
|
235
|
+
else
|
236
|
+
self.description + " -- can't be reloaded"
|
237
|
+
end
|
238
|
+
|
239
|
+
raise CantCompleteError, "Failed to complete #{description}"
|
240
|
+
end
|
151
241
|
end
|
152
|
-
response.nil? ? false : response
|
153
242
|
end
|
154
243
|
|
155
244
|
def state_changed?
|
@@ -157,7 +246,7 @@ module Qless
|
|
157
246
|
end
|
158
247
|
|
159
248
|
def cancel
|
160
|
-
note_state_change do
|
249
|
+
note_state_change :cancel do
|
161
250
|
@client._cancel.call([], [@jid])
|
162
251
|
end
|
163
252
|
end
|
@@ -179,7 +268,7 @@ module Qless
|
|
179
268
|
end
|
180
269
|
|
181
270
|
def retry(delay=0)
|
182
|
-
note_state_change do
|
271
|
+
note_state_change :retry do
|
183
272
|
results = @client._retry.call([], [@jid, @queue_name, @worker_name, Time.now.to_f, delay])
|
184
273
|
results.nil? ? false : results
|
185
274
|
end
|
@@ -193,13 +282,29 @@ module Qless
|
|
193
282
|
!!@client._depends.call([], [@jid, 'off'] + jids)
|
194
283
|
end
|
195
284
|
|
285
|
+
[:fail, :complete, :cancel, :move, :retry].each do |event|
|
286
|
+
define_method :"before_#{event}" do |&block|
|
287
|
+
@before_callbacks[event] << block
|
288
|
+
end
|
289
|
+
|
290
|
+
define_method :"after_#{event}" do |&block|
|
291
|
+
@after_callbacks[event].unshift block
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
196
295
|
private
|
197
296
|
|
198
|
-
def note_state_change
|
297
|
+
def note_state_change(event)
|
298
|
+
@before_callbacks[event].each { |blk| blk.call(self) }
|
199
299
|
result = yield
|
200
300
|
@state_changed = true
|
301
|
+
@after_callbacks[event].each { |blk| blk.call(self) }
|
201
302
|
result
|
202
303
|
end
|
304
|
+
|
305
|
+
def history_timestamp(name, selector)
|
306
|
+
queue_history.map { |q| q[name] }.compact.send(selector)
|
307
|
+
end
|
203
308
|
end
|
204
309
|
|
205
310
|
class RecurringJob < BaseJob
|