qless 0.9.2 → 0.9.3
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 +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
|