resqueue 1.0.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.
- checksums.yaml +7 -0
- data/HISTORY.md +488 -0
- data/LICENSE +20 -0
- data/README.markdown +920 -0
- data/Rakefile +57 -0
- data/bin/resque +81 -0
- data/bin/resque-web +31 -0
- data/lib/resque.rb +578 -0
- data/lib/resque/data_store.rb +326 -0
- data/lib/resque/errors.rb +21 -0
- data/lib/resque/failure.rb +119 -0
- data/lib/resque/failure/airbrake.rb +33 -0
- data/lib/resque/failure/base.rb +73 -0
- data/lib/resque/failure/multiple.rb +68 -0
- data/lib/resque/failure/redis.rb +128 -0
- data/lib/resque/failure/redis_multi_queue.rb +104 -0
- data/lib/resque/helpers.rb +48 -0
- data/lib/resque/job.rb +296 -0
- data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
- data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
- data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
- data/lib/resque/logging.rb +18 -0
- data/lib/resque/plugin.rb +78 -0
- data/lib/resque/server.rb +299 -0
- data/lib/resque/server/helpers.rb +64 -0
- data/lib/resque/server/public/favicon.ico +0 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/poll.png +0 -0
- data/lib/resque/server/public/ranger.js +78 -0
- data/lib/resque/server/public/reset.css +44 -0
- data/lib/resque/server/public/style.css +91 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/test_helper.rb +19 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +29 -0
- data/lib/resque/server/views/failed_job.erb +50 -0
- data/lib/resque/server/views/failed_queues_overview.erb +24 -0
- data/lib/resque/server/views/key_sets.erb +17 -0
- data/lib/resque/server/views/key_string.erb +11 -0
- data/lib/resque/server/views/layout.erb +44 -0
- data/lib/resque/server/views/next_more.erb +22 -0
- data/lib/resque/server/views/overview.erb +4 -0
- data/lib/resque/server/views/queues.erb +58 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +111 -0
- data/lib/resque/server/views/working.erb +72 -0
- data/lib/resque/stat.rb +58 -0
- data/lib/resque/tasks.rb +72 -0
- data/lib/resque/thread_signal.rb +45 -0
- data/lib/resque/vendor/utf8_util.rb +26 -0
- data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
- data/lib/resque/vendor/utf8_util/utf8_util_19.rb +6 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +892 -0
- data/lib/resqueue.rb +4 -0
- data/lib/tasks/redis.rake +161 -0
- data/lib/tasks/resque.rake +2 -0
- metadata +197 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
module Resque
|
2
|
+
# An interface between Resque's persistence and the actual
|
3
|
+
# implementation.
|
4
|
+
class DataStore
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
HEARTBEAT_KEY = "workers:heartbeat"
|
8
|
+
|
9
|
+
def initialize(redis)
|
10
|
+
@redis = redis
|
11
|
+
@queue_access = QueueAccess.new(@redis)
|
12
|
+
@failed_queue_access = FailedQueueAccess.new(@redis)
|
13
|
+
@workers = Workers.new(@redis)
|
14
|
+
@stats_access = StatsAccess.new(@redis)
|
15
|
+
end
|
16
|
+
|
17
|
+
def_delegators :@queue_access, :push_to_queue,
|
18
|
+
:pop_from_queue,
|
19
|
+
:queue_size,
|
20
|
+
:peek_in_queue,
|
21
|
+
:queue_names,
|
22
|
+
:remove_queue,
|
23
|
+
:everything_in_queue,
|
24
|
+
:remove_from_queue,
|
25
|
+
:watch_queue,
|
26
|
+
:list_range
|
27
|
+
|
28
|
+
def_delegators :@failed_queue_access, :add_failed_queue,
|
29
|
+
:remove_failed_queue,
|
30
|
+
:num_failed,
|
31
|
+
:failed_queue_names,
|
32
|
+
:push_to_failed_queue,
|
33
|
+
:clear_failed_queue,
|
34
|
+
:update_item_in_failed_queue,
|
35
|
+
:remove_from_failed_queue
|
36
|
+
def_delegators :@workers, :worker_ids,
|
37
|
+
:workers_map,
|
38
|
+
:get_worker_payload,
|
39
|
+
:worker_exists?,
|
40
|
+
:register_worker,
|
41
|
+
:worker_started,
|
42
|
+
:unregister_worker,
|
43
|
+
:heartbeat,
|
44
|
+
:heartbeat!,
|
45
|
+
:remove_heartbeat,
|
46
|
+
:all_heartbeats,
|
47
|
+
:set_worker_payload,
|
48
|
+
:worker_start_time,
|
49
|
+
:worker_done_working
|
50
|
+
|
51
|
+
def_delegators :@stats_access, :clear_stat,
|
52
|
+
:decremet_stat,
|
53
|
+
:increment_stat,
|
54
|
+
:stat
|
55
|
+
|
56
|
+
# Compatibility with any non-Resque classes that were using Resque.redis as a way to access Redis
|
57
|
+
def method_missing(sym,*args,&block)
|
58
|
+
# TODO: deprecation warning?
|
59
|
+
@redis.send(sym,*args,&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
# make use respond like redis
|
63
|
+
def respond_to?(method,include_all=false)
|
64
|
+
@redis.respond_to?(method,include_all)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get a string identifying the underlying server.
|
68
|
+
# Probably should be private, but was public so must stay public
|
69
|
+
def identifier
|
70
|
+
# support 1.x versions of redis-rb
|
71
|
+
if @redis.respond_to?(:server)
|
72
|
+
@redis.server
|
73
|
+
elsif @redis.respond_to?(:nodes) # distributed
|
74
|
+
@redis.nodes.map { |n| n.id }.join(', ')
|
75
|
+
else
|
76
|
+
@redis.client.id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Force a reconnect to Redis.
|
81
|
+
def reconnect
|
82
|
+
@redis.client.reconnect
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an array of all known Resque keys in Redis. Redis' KEYS operation
|
86
|
+
# is O(N) for the keyspace, so be careful - this can be slow for big databases.
|
87
|
+
def all_resque_keys
|
88
|
+
@redis.keys("*").map do |key|
|
89
|
+
key.sub("#{@redis.namespace}:", '')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def server_time
|
94
|
+
time, _ = @redis.time
|
95
|
+
Time.at(time)
|
96
|
+
end
|
97
|
+
|
98
|
+
class QueueAccess
|
99
|
+
def initialize(redis)
|
100
|
+
@redis = redis
|
101
|
+
end
|
102
|
+
def push_to_queue(queue,encoded_item)
|
103
|
+
@redis.pipelined do
|
104
|
+
watch_queue(queue)
|
105
|
+
@redis.rpush redis_key_for_queue(queue), encoded_item
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Pop whatever is on queue
|
110
|
+
def pop_from_queue(queue)
|
111
|
+
@redis.lpop(redis_key_for_queue(queue))
|
112
|
+
end
|
113
|
+
|
114
|
+
# Get the number of items in the queue
|
115
|
+
def queue_size(queue)
|
116
|
+
@redis.llen(redis_key_for_queue(queue)).to_i
|
117
|
+
end
|
118
|
+
|
119
|
+
# Examine items in the queue.
|
120
|
+
#
|
121
|
+
# NOTE: if count is 1, you will get back an object, otherwise you will
|
122
|
+
# get an Array. I'm not making this up.
|
123
|
+
def peek_in_queue(queue, start = 0, count = 1)
|
124
|
+
list_range(redis_key_for_queue(queue), start, count)
|
125
|
+
end
|
126
|
+
|
127
|
+
def queue_names
|
128
|
+
Array(@redis.smembers(:queues))
|
129
|
+
end
|
130
|
+
|
131
|
+
def remove_queue(queue)
|
132
|
+
@redis.pipelined do
|
133
|
+
@redis.srem(:queues, queue.to_s)
|
134
|
+
@redis.del(redis_key_for_queue(queue))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def everything_in_queue(queue)
|
139
|
+
@redis.lrange(redis_key_for_queue(queue), 0, -1)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Remove data from the queue, if it's there, returning the number of removed elements
|
143
|
+
def remove_from_queue(queue,data)
|
144
|
+
@redis.lrem(redis_key_for_queue(queue), 0, data)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Private: do not call
|
148
|
+
def watch_queue(queue)
|
149
|
+
@redis.sadd(:queues, queue.to_s)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Private: do not call
|
153
|
+
def list_range(key, start = 0, count = 1)
|
154
|
+
if count == 1
|
155
|
+
@redis.lindex(key, start)
|
156
|
+
else
|
157
|
+
Array(@redis.lrange(key, start, start+count-1))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def redis_key_for_queue(queue)
|
164
|
+
"queue:#{queue}"
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
class FailedQueueAccess
|
170
|
+
def initialize(redis)
|
171
|
+
@redis = redis
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_failed_queue(failed_queue_name)
|
175
|
+
@redis.sadd(:failed_queues, failed_queue_name)
|
176
|
+
end
|
177
|
+
|
178
|
+
def remove_failed_queue(failed_queue_name=:failed)
|
179
|
+
@redis.del(failed_queue_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
def num_failed(failed_queue_name=:failed)
|
183
|
+
@redis.llen(failed_queue_name).to_i
|
184
|
+
end
|
185
|
+
|
186
|
+
def failed_queue_names(find_queue_names_in_key=nil)
|
187
|
+
if find_queue_names_in_key.nil?
|
188
|
+
[:failed]
|
189
|
+
else
|
190
|
+
Array(@redis.smembers(find_queue_names_in_key))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def push_to_failed_queue(data,failed_queue_name=:failed)
|
195
|
+
@redis.rpush(failed_queue_name,data)
|
196
|
+
end
|
197
|
+
|
198
|
+
def clear_failed_queue(failed_queue_name=:failed)
|
199
|
+
@redis.del(failed_queue_name)
|
200
|
+
end
|
201
|
+
|
202
|
+
def update_item_in_failed_queue(index_in_failed_queue,new_item_data,failed_queue_name=:failed)
|
203
|
+
@redis.lset(failed_queue_name, index_in_failed_queue, new_item_data)
|
204
|
+
end
|
205
|
+
|
206
|
+
def remove_from_failed_queue(index_in_failed_queue,failed_queue_name=nil)
|
207
|
+
failed_queue_name ||= :failed
|
208
|
+
hopefully_unique_value_we_can_use_to_delete_job = ""
|
209
|
+
@redis.lset(failed_queue_name, index_in_failed_queue, hopefully_unique_value_we_can_use_to_delete_job)
|
210
|
+
@redis.lrem(failed_queue_name, 1, hopefully_unique_value_we_can_use_to_delete_job)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class Workers
|
215
|
+
def initialize(redis)
|
216
|
+
@redis = redis
|
217
|
+
end
|
218
|
+
|
219
|
+
def worker_ids
|
220
|
+
Array(@redis.smembers(:workers))
|
221
|
+
end
|
222
|
+
|
223
|
+
# Given a list of worker ids, returns a map of those ids to the worker's value
|
224
|
+
# in redis, even if that value maps to nil
|
225
|
+
def workers_map(worker_ids)
|
226
|
+
redis_keys = worker_ids.map { |id| "worker:#{id}" }
|
227
|
+
@redis.mapped_mget(*redis_keys)
|
228
|
+
end
|
229
|
+
|
230
|
+
# return the worker's payload i.e. job
|
231
|
+
def get_worker_payload(worker_id)
|
232
|
+
@redis.get("worker:#{worker_id}")
|
233
|
+
end
|
234
|
+
|
235
|
+
def worker_exists?(worker_id)
|
236
|
+
@redis.sismember(:workers, worker_id)
|
237
|
+
end
|
238
|
+
|
239
|
+
def register_worker(worker)
|
240
|
+
@redis.pipelined do
|
241
|
+
@redis.sadd(:workers, worker)
|
242
|
+
worker_started(worker)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def worker_started(worker)
|
247
|
+
@redis.set(redis_key_for_worker_start_time(worker), Time.now.to_s)
|
248
|
+
end
|
249
|
+
|
250
|
+
def unregister_worker(worker, &block)
|
251
|
+
@redis.pipelined do
|
252
|
+
@redis.srem(:workers, worker)
|
253
|
+
@redis.del(redis_key_for_worker(worker))
|
254
|
+
@redis.del(redis_key_for_worker_start_time(worker))
|
255
|
+
@redis.hdel(HEARTBEAT_KEY, worker.to_s)
|
256
|
+
|
257
|
+
block.call
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def remove_heartbeat(worker)
|
262
|
+
@redis.hdel(HEARTBEAT_KEY, worker.to_s)
|
263
|
+
end
|
264
|
+
|
265
|
+
def heartbeat(worker)
|
266
|
+
heartbeat = @redis.hget(HEARTBEAT_KEY, worker.to_s)
|
267
|
+
heartbeat && Time.parse(heartbeat)
|
268
|
+
end
|
269
|
+
|
270
|
+
def heartbeat!(worker, time)
|
271
|
+
@redis.hset(HEARTBEAT_KEY, worker.to_s, time.iso8601)
|
272
|
+
end
|
273
|
+
|
274
|
+
def all_heartbeats
|
275
|
+
@redis.hgetall(HEARTBEAT_KEY)
|
276
|
+
end
|
277
|
+
|
278
|
+
def set_worker_payload(worker, data)
|
279
|
+
@redis.set(redis_key_for_worker(worker), data)
|
280
|
+
end
|
281
|
+
|
282
|
+
def worker_start_time(worker)
|
283
|
+
@redis.get(redis_key_for_worker_start_time(worker))
|
284
|
+
end
|
285
|
+
|
286
|
+
def worker_done_working(worker, &block)
|
287
|
+
@redis.pipelined do
|
288
|
+
@redis.del(redis_key_for_worker(worker))
|
289
|
+
block.call
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
def redis_key_for_worker(worker)
|
296
|
+
"worker:#{worker}"
|
297
|
+
end
|
298
|
+
|
299
|
+
def redis_key_for_worker_start_time(worker)
|
300
|
+
"#{redis_key_for_worker(worker)}:started"
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
class StatsAccess
|
306
|
+
def initialize(redis)
|
307
|
+
@redis = redis
|
308
|
+
end
|
309
|
+
def stat(stat)
|
310
|
+
@redis.get("stat:#{stat}").to_i
|
311
|
+
end
|
312
|
+
|
313
|
+
def increment_stat(stat, by = 1)
|
314
|
+
@redis.incrby("stat:#{stat}", by)
|
315
|
+
end
|
316
|
+
|
317
|
+
def decremet_stat(stat, by = 1)
|
318
|
+
@redis.decrby("stat:#{stat}", by)
|
319
|
+
end
|
320
|
+
|
321
|
+
def clear_stat(stat)
|
322
|
+
@redis.del("stat:#{stat}")
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Resque
|
2
|
+
# Raised whenever we need a queue but none is provided.
|
3
|
+
class NoQueueError < RuntimeError; end
|
4
|
+
|
5
|
+
# Raised when trying to create a job without a class
|
6
|
+
class NoClassError < RuntimeError; end
|
7
|
+
|
8
|
+
# Raised when a worker was killed while processing a job.
|
9
|
+
class DirtyExit < RuntimeError
|
10
|
+
attr_reader :process_status
|
11
|
+
|
12
|
+
def initialize(message=nil, process_status=nil)
|
13
|
+
@process_status = process_status
|
14
|
+
super message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
class PruneDeadWorkerDirtyExit < DirtyExit; end
|
18
|
+
|
19
|
+
# Raised when child process is TERM'd so job can rescue this to do shutdown work.
|
20
|
+
class TermException < SignalException; end
|
21
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Resque
|
2
|
+
# The Failure module provides an interface for working with different
|
3
|
+
# failure backends.
|
4
|
+
#
|
5
|
+
# You can use it to query the failure backend without knowing which specific
|
6
|
+
# backend is being used. For instance, the Resque web app uses it to display
|
7
|
+
# stats and other information.
|
8
|
+
module Failure
|
9
|
+
# Creates a new failure, which is delegated to the appropriate backend.
|
10
|
+
#
|
11
|
+
# Expects a hash with the following keys:
|
12
|
+
# :exception - The Exception object
|
13
|
+
# :worker - The Worker object who is reporting the failure
|
14
|
+
# :queue - The string name of the queue from which the job was pulled
|
15
|
+
# :payload - The job's payload
|
16
|
+
def self.create(options = {})
|
17
|
+
backend.new(*options.values_at(:exception, :worker, :queue, :payload)).save
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Sets the current backend. Expects a class descendent of
|
22
|
+
# `Resque::Failure::Base`.
|
23
|
+
#
|
24
|
+
# Example use:
|
25
|
+
# require 'resque/failure/airbrake'
|
26
|
+
# Resque::Failure.backend = Resque::Failure::Airbrake
|
27
|
+
def self.backend=(backend)
|
28
|
+
@backend = backend
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the current backend class. If none has been set, falls
|
32
|
+
# back to `Resque::Failure::Redis`
|
33
|
+
def self.backend
|
34
|
+
return @backend if @backend
|
35
|
+
|
36
|
+
case ENV['FAILURE_BACKEND']
|
37
|
+
when 'redis_multi_queue'
|
38
|
+
require 'resque/failure/redis_multi_queue'
|
39
|
+
@backend = Failure::RedisMultiQueue
|
40
|
+
when 'redis', nil
|
41
|
+
require 'resque/failure/redis'
|
42
|
+
@backend = Failure::Redis
|
43
|
+
else
|
44
|
+
raise ArgumentError, "invalid failure backend: #{FAILURE_BACKEND}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Obtain the failure queue name for a given job queue
|
49
|
+
def self.failure_queue_name(job_queue_name)
|
50
|
+
name = "#{job_queue_name}_failed"
|
51
|
+
Resque.data_store.add_failed_queue(name)
|
52
|
+
name
|
53
|
+
end
|
54
|
+
|
55
|
+
# Obtain the job queue name for a given failure queue
|
56
|
+
def self.job_queue_name(failure_queue_name)
|
57
|
+
failure_queue_name.sub(/_failed$/, '')
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an array of all the failed queues in the system
|
61
|
+
def self.queues
|
62
|
+
backend.queues
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the int count of how many failures we have seen.
|
66
|
+
def self.count(queue = nil, class_name = nil)
|
67
|
+
backend.count(queue, class_name)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns an array of all the failures, paginated.
|
71
|
+
#
|
72
|
+
# `offset` is the int of the first item in the page, `limit` is the
|
73
|
+
# number of items to return.
|
74
|
+
def self.all(offset = 0, limit = 1, queue = nil)
|
75
|
+
backend.all(offset, limit, queue)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Iterate across all failures with the given options
|
79
|
+
def self.each(offset = 0, limit = self.count, queue = nil, class_name = nil, order = 'desc', &block)
|
80
|
+
backend.each(offset, limit, queue, class_name, order, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
# The string url of the backend's web interface, if any.
|
84
|
+
def self.url
|
85
|
+
backend.url
|
86
|
+
end
|
87
|
+
|
88
|
+
# Clear all failure jobs
|
89
|
+
def self.clear(queue = nil)
|
90
|
+
backend.clear(queue)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.requeue(id, queue = nil)
|
94
|
+
backend.requeue(id, queue)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.remove(id, queue = nil)
|
98
|
+
backend.remove(id, queue)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Requeues all failed jobs in a specific queue.
|
102
|
+
# Queue name should be a string.
|
103
|
+
def self.requeue_queue(queue)
|
104
|
+
backend.requeue_queue(queue)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Requeues all failed jobs
|
108
|
+
def self.requeue_all
|
109
|
+
backend.requeue_all
|
110
|
+
end
|
111
|
+
|
112
|
+
# Removes all failed jobs in a specific queue.
|
113
|
+
# Queue name should be a string.
|
114
|
+
def self.remove_queue(queue)
|
115
|
+
backend.remove_queue(queue)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|