resqueue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +488 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +920 -0
  5. data/Rakefile +57 -0
  6. data/bin/resque +81 -0
  7. data/bin/resque-web +31 -0
  8. data/lib/resque.rb +578 -0
  9. data/lib/resque/data_store.rb +326 -0
  10. data/lib/resque/errors.rb +21 -0
  11. data/lib/resque/failure.rb +119 -0
  12. data/lib/resque/failure/airbrake.rb +33 -0
  13. data/lib/resque/failure/base.rb +73 -0
  14. data/lib/resque/failure/multiple.rb +68 -0
  15. data/lib/resque/failure/redis.rb +128 -0
  16. data/lib/resque/failure/redis_multi_queue.rb +104 -0
  17. data/lib/resque/helpers.rb +48 -0
  18. data/lib/resque/job.rb +296 -0
  19. data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
  20. data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
  21. data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
  22. data/lib/resque/logging.rb +18 -0
  23. data/lib/resque/plugin.rb +78 -0
  24. data/lib/resque/server.rb +299 -0
  25. data/lib/resque/server/helpers.rb +64 -0
  26. data/lib/resque/server/public/favicon.ico +0 -0
  27. data/lib/resque/server/public/idle.png +0 -0
  28. data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
  29. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  30. data/lib/resque/server/public/poll.png +0 -0
  31. data/lib/resque/server/public/ranger.js +78 -0
  32. data/lib/resque/server/public/reset.css +44 -0
  33. data/lib/resque/server/public/style.css +91 -0
  34. data/lib/resque/server/public/working.png +0 -0
  35. data/lib/resque/server/test_helper.rb +19 -0
  36. data/lib/resque/server/views/error.erb +1 -0
  37. data/lib/resque/server/views/failed.erb +29 -0
  38. data/lib/resque/server/views/failed_job.erb +50 -0
  39. data/lib/resque/server/views/failed_queues_overview.erb +24 -0
  40. data/lib/resque/server/views/key_sets.erb +17 -0
  41. data/lib/resque/server/views/key_string.erb +11 -0
  42. data/lib/resque/server/views/layout.erb +44 -0
  43. data/lib/resque/server/views/next_more.erb +22 -0
  44. data/lib/resque/server/views/overview.erb +4 -0
  45. data/lib/resque/server/views/queues.erb +58 -0
  46. data/lib/resque/server/views/stats.erb +62 -0
  47. data/lib/resque/server/views/workers.erb +111 -0
  48. data/lib/resque/server/views/working.erb +72 -0
  49. data/lib/resque/stat.rb +58 -0
  50. data/lib/resque/tasks.rb +72 -0
  51. data/lib/resque/thread_signal.rb +45 -0
  52. data/lib/resque/vendor/utf8_util.rb +26 -0
  53. data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
  54. data/lib/resque/vendor/utf8_util/utf8_util_19.rb +6 -0
  55. data/lib/resque/version.rb +3 -0
  56. data/lib/resque/worker.rb +892 -0
  57. data/lib/resqueue.rb +4 -0
  58. data/lib/tasks/redis.rake +161 -0
  59. data/lib/tasks/resque.rake +2 -0
  60. 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