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.
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