resqueue 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|