steini-resque 1.18.5

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 (54) hide show
  1. data/HISTORY.md +322 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +881 -0
  4. data/Rakefile +78 -0
  5. data/bin/resque +81 -0
  6. data/bin/resque-web +23 -0
  7. data/lib/resque.rb +352 -0
  8. data/lib/resque/errors.rb +10 -0
  9. data/lib/resque/failure.rb +70 -0
  10. data/lib/resque/failure/base.rb +64 -0
  11. data/lib/resque/failure/hoptoad.rb +48 -0
  12. data/lib/resque/failure/multiple.rb +54 -0
  13. data/lib/resque/failure/redis.rb +51 -0
  14. data/lib/resque/helpers.rb +63 -0
  15. data/lib/resque/job.rb +205 -0
  16. data/lib/resque/plugin.rb +56 -0
  17. data/lib/resque/server.rb +231 -0
  18. data/lib/resque/server/public/favicon.ico +0 -0
  19. data/lib/resque/server/public/idle.png +0 -0
  20. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  21. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  22. data/lib/resque/server/public/poll.png +0 -0
  23. data/lib/resque/server/public/ranger.js +73 -0
  24. data/lib/resque/server/public/reset.css +48 -0
  25. data/lib/resque/server/public/style.css +85 -0
  26. data/lib/resque/server/public/working.png +0 -0
  27. data/lib/resque/server/test_helper.rb +19 -0
  28. data/lib/resque/server/views/error.erb +1 -0
  29. data/lib/resque/server/views/failed.erb +64 -0
  30. data/lib/resque/server/views/key_sets.erb +19 -0
  31. data/lib/resque/server/views/key_string.erb +11 -0
  32. data/lib/resque/server/views/layout.erb +44 -0
  33. data/lib/resque/server/views/next_more.erb +10 -0
  34. data/lib/resque/server/views/overview.erb +4 -0
  35. data/lib/resque/server/views/queues.erb +49 -0
  36. data/lib/resque/server/views/stats.erb +62 -0
  37. data/lib/resque/server/views/workers.erb +109 -0
  38. data/lib/resque/server/views/working.erb +72 -0
  39. data/lib/resque/stat.rb +53 -0
  40. data/lib/resque/tasks.rb +51 -0
  41. data/lib/resque/version.rb +3 -0
  42. data/lib/resque/worker.rb +533 -0
  43. data/lib/tasks/redis.rake +161 -0
  44. data/lib/tasks/resque.rake +2 -0
  45. data/test/hoptoad_test.rb +25 -0
  46. data/test/job_hooks_test.rb +363 -0
  47. data/test/job_plugins_test.rb +230 -0
  48. data/test/plugin_test.rb +116 -0
  49. data/test/redis-test.conf +115 -0
  50. data/test/resque-web_test.rb +53 -0
  51. data/test/resque_test.rb +259 -0
  52. data/test/test_helper.rb +148 -0
  53. data/test/worker_test.rb +332 -0
  54. metadata +183 -0
@@ -0,0 +1,78 @@
1
+ #
2
+ # Setup
3
+ #
4
+
5
+ load 'lib/tasks/redis.rake'
6
+
7
+ $LOAD_PATH.unshift 'lib'
8
+ require 'resque/tasks'
9
+
10
+ def command?(command)
11
+ system("type #{command} > /dev/null 2>&1")
12
+ end
13
+
14
+
15
+ #
16
+ # Tests
17
+ #
18
+
19
+ require 'rake/testtask'
20
+
21
+ task :default => :test
22
+
23
+ if command?(:rg)
24
+ desc "Run the test suite with rg"
25
+ task :test do
26
+ Dir['test/**/*_test.rb'].each do |f|
27
+ sh("rg #{f}")
28
+ end
29
+ end
30
+ else
31
+ Rake::TestTask.new do |test|
32
+ test.libs << "test"
33
+ test.test_files = FileList['test/**/*_test.rb']
34
+ end
35
+ end
36
+
37
+ if command? :kicker
38
+ desc "Launch Kicker (like autotest)"
39
+ task :kicker do
40
+ puts "Kicking... (ctrl+c to cancel)"
41
+ exec "kicker -e rake test lib examples"
42
+ end
43
+ end
44
+
45
+
46
+ #
47
+ # Install
48
+ #
49
+
50
+ task :install => [ 'redis:install', 'dtach:install' ]
51
+
52
+
53
+ #
54
+ # Documentation
55
+ #
56
+
57
+ begin
58
+ require 'sdoc_helpers'
59
+ rescue LoadError
60
+ end
61
+
62
+
63
+ #
64
+ # Publishing
65
+ #
66
+
67
+ desc "Push a new version to Gemcutter"
68
+ task :publish do
69
+ require 'resque/version'
70
+
71
+ sh "gem build resque.gemspec"
72
+ sh "gem push resque-#{Resque::Version}.gem"
73
+ sh "git tag v#{Resque::Version}"
74
+ sh "git push origin v#{Resque::Version}"
75
+ sh "git push origin master"
76
+ sh "git clean -fd"
77
+ exec "rake pages"
78
+ end
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ begin
5
+ require 'redis-namespace'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'redis-namespace'
9
+ end
10
+ require 'resque'
11
+ require 'optparse'
12
+
13
+ parser = OptionParser.new do |opts|
14
+ opts.banner = "Usage: resque [options] COMMAND"
15
+
16
+ opts.separator ""
17
+ opts.separator "Options:"
18
+
19
+ opts.on("-r", "--redis [HOST:PORT]", "Redis connection string") do |host|
20
+ Resque.redis = host
21
+ end
22
+
23
+ opts.on("-N", "--namespace [NAMESPACE]", "Redis namespace") do |namespace|
24
+ Resque.redis.namespace = namespace
25
+ end
26
+
27
+ opts.on("-h", "--help", "Show this message") do
28
+ puts opts
29
+ exit
30
+ end
31
+
32
+ opts.separator ""
33
+ opts.separator "Commands:"
34
+ opts.separator " remove WORKER Removes a worker"
35
+ opts.separator " kill WORKER Kills a worker"
36
+ opts.separator " list Lists known workers"
37
+ end
38
+
39
+ def kill(worker)
40
+ abort "** resque kill WORKER_ID" if worker.nil?
41
+ pid = worker.split(':')[1].to_i
42
+
43
+ begin
44
+ Process.kill("KILL", pid)
45
+ puts "** killed #{worker}"
46
+ rescue Errno::ESRCH
47
+ puts "** worker #{worker} not running"
48
+ end
49
+
50
+ remove worker
51
+ end
52
+
53
+ def remove(worker)
54
+ abort "** resque remove WORKER_ID" if worker.nil?
55
+
56
+ Resque.remove_worker(worker)
57
+ puts "** removed #{worker}"
58
+ end
59
+
60
+ def list
61
+ if Resque.workers.any?
62
+ Resque.workers.each do |worker|
63
+ puts "#{worker} (#{worker.state})"
64
+ end
65
+ else
66
+ puts "None"
67
+ end
68
+ end
69
+
70
+ parser.parse!
71
+
72
+ case ARGV[0]
73
+ when 'kill'
74
+ kill ARGV[1]
75
+ when 'remove'
76
+ remove ARGV[1]
77
+ when 'list'
78
+ list
79
+ else
80
+ puts parser.help
81
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ begin
5
+ require 'vegas'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'vegas'
9
+ end
10
+ require 'resque/server'
11
+
12
+
13
+ Vegas::Runner.new(Resque::Server, 'resque-web', {
14
+ :before_run => lambda {|v|
15
+ path = (ENV['RESQUECONFIG'] || v.args.first)
16
+ load path.to_s.strip if path
17
+ }
18
+ }) do |runner, opts, app|
19
+ opts.on('-N NAMESPACE', "--namespace NAMESPACE", "set the Redis namespace") {|namespace|
20
+ runner.logger.info "Using Redis namespace '#{namespace}'"
21
+ Resque.redis.namespace = namespace
22
+ }
23
+ end
@@ -0,0 +1,352 @@
1
+ require 'redis/namespace'
2
+
3
+ require 'resque/version'
4
+
5
+ require 'resque/errors'
6
+
7
+ require 'resque/failure'
8
+ require 'resque/failure/base'
9
+
10
+ require 'resque/helpers'
11
+ require 'resque/stat'
12
+ require 'resque/job'
13
+ require 'resque/worker'
14
+ require 'resque/plugin'
15
+
16
+ module Resque
17
+ include Helpers
18
+ extend self
19
+
20
+ # Accepts:
21
+ # 1. A 'hostname:port' String
22
+ # 2. A 'hostname:port:db' String (to select the Redis db)
23
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
24
+ # 4. A Redis URL String 'redis://host:port'
25
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
26
+ # or `Redis::Namespace`.
27
+ def redis=(server)
28
+ case server
29
+ when String
30
+ if server =~ /redis\:\/\//
31
+ redis = Redis.connect(:url => server, :thread_safe => true)
32
+ else
33
+ server, namespace = server.split('/', 2)
34
+ host, port, db = server.split(':')
35
+ redis = Redis.new(:host => host, :port => port,
36
+ :thread_safe => true, :db => db)
37
+ end
38
+ namespace ||= :resque
39
+
40
+ @redis = Redis::Namespace.new(namespace, :redis => redis)
41
+ when Redis::Namespace
42
+ @redis = server
43
+ else
44
+ @redis = Redis::Namespace.new(:resque, :redis => server)
45
+ end
46
+ end
47
+
48
+ # Returns the current Redis connection. If none has been created, will
49
+ # create a new one.
50
+ def redis
51
+ return @redis if @redis
52
+ self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379"
53
+ self.redis
54
+ end
55
+
56
+ def redis_id
57
+ # support 1.x versions of redis-rb
58
+ if redis.respond_to?(:server)
59
+ redis.server
60
+ elsif redis.respond_to?(:nodes) # distributed
61
+ redis.nodes.map { |n| n.id }.join(', ')
62
+ else
63
+ redis.client.id
64
+ end
65
+ end
66
+
67
+ # The `before_first_fork` hook will be run in the **parent** process
68
+ # only once, before forking to run the first job. Be careful- any
69
+ # changes you make will be permanent for the lifespan of the
70
+ # worker.
71
+ #
72
+ # Call with a block to set the hook.
73
+ # Call with no arguments to return the hook.
74
+ def before_first_fork(&block)
75
+ block ? (@before_first_fork = block) : @before_first_fork
76
+ end
77
+
78
+ # Set a proc that will be called in the parent process before the
79
+ # worker forks for the first time.
80
+ def before_first_fork=(before_first_fork)
81
+ @before_first_fork = before_first_fork
82
+ end
83
+
84
+ # The `before_fork` hook will be run in the **parent** process
85
+ # before every job, so be careful- any changes you make will be
86
+ # permanent for the lifespan of the worker.
87
+ #
88
+ # Call with a block to set the hook.
89
+ # Call with no arguments to return the hook.
90
+ def before_fork(&block)
91
+ block ? (@before_fork = block) : @before_fork
92
+ end
93
+
94
+ # Set the before_fork proc.
95
+ def before_fork=(before_fork)
96
+ @before_fork = before_fork
97
+ end
98
+
99
+ # The `after_fork` hook will be run in the child process and is passed
100
+ # the current job. Any changes you make, therefore, will only live as
101
+ # long as the job currently being processed.
102
+ #
103
+ # Call with a block to set the hook.
104
+ # Call with no arguments to return the hook.
105
+ def after_fork(&block)
106
+ block ? (@after_fork = block) : @after_fork
107
+ end
108
+
109
+ # Set the after_fork proc.
110
+ def after_fork=(after_fork)
111
+ @after_fork = after_fork
112
+ end
113
+
114
+ def to_s
115
+ "Resque Client connected to #{redis_id}"
116
+ end
117
+
118
+ # If 'inline' is true Resque will call #perform method inline
119
+ # without queuing it into Redis and without any Resque callbacks.
120
+ # The 'inline' is false Resque jobs will be put in queue regularly.
121
+ def inline?
122
+ @inline
123
+ end
124
+ alias_method :inline, :inline?
125
+
126
+ def inline=(inline)
127
+ @inline = inline
128
+ end
129
+
130
+ #
131
+ # queue manipulation
132
+ #
133
+
134
+ # Pushes a job onto a queue. Queue name should be a string and the
135
+ # item should be any JSON-able Ruby object.
136
+ #
137
+ # Resque works generally expect the `item` to be a hash with the following
138
+ # keys:
139
+ #
140
+ # class - The String name of the job to run.
141
+ # args - An Array of arguments to pass the job. Usually passed
142
+ # via `class.to_class.perform(*args)`.
143
+ #
144
+ # Example
145
+ #
146
+ # Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ])
147
+ #
148
+ # Returns nothing
149
+ def push(queue, item)
150
+ watch_queue(queue)
151
+ redis.rpush "queue:#{queue}", encode(item)
152
+ end
153
+
154
+ # Pops a job off a queue. Queue name should be a string.
155
+ #
156
+ # Returns a Ruby object.
157
+ def pop(queue)
158
+ decode redis.lpop("queue:#{queue}")
159
+ end
160
+
161
+ # Returns an integer representing the size of a queue.
162
+ # Queue name should be a string.
163
+ def size(queue)
164
+ redis.llen("queue:#{queue}").to_i
165
+ end
166
+
167
+ # Returns an array of items currently queued. Queue name should be
168
+ # a string.
169
+ #
170
+ # start and count should be integer and can be used for pagination.
171
+ # start is the item to begin, count is how many items to return.
172
+ #
173
+ # To get the 3rd page of a 30 item, paginatied list one would use:
174
+ # Resque.peek('my_list', 59, 30)
175
+ def peek(queue, start = 0, count = 1)
176
+ list_range("queue:#{queue}", start, count)
177
+ end
178
+
179
+ # Does the dirty work of fetching a range of items from a Redis list
180
+ # and converting them into Ruby objects.
181
+ def list_range(key, start = 0, count = 1)
182
+ if count == 1
183
+ decode redis.lindex(key, start)
184
+ else
185
+ Array(redis.lrange(key, start, start+count-1)).map do |item|
186
+ decode item
187
+ end
188
+ end
189
+ end
190
+
191
+ # Returns an array of all known Resque queues as strings.
192
+ def queues
193
+ Array(redis.smembers(:queues))
194
+ end
195
+
196
+ # Given a queue name, completely deletes the queue.
197
+ def remove_queue(queue)
198
+ redis.srem(:queues, queue.to_s)
199
+ redis.del("queue:#{queue}")
200
+ end
201
+
202
+ # Used internally to keep track of which queues we've created.
203
+ # Don't call this directly.
204
+ def watch_queue(queue)
205
+ redis.sadd(:queues, queue.to_s)
206
+ end
207
+
208
+
209
+ #
210
+ # job shortcuts
211
+ #
212
+
213
+ # This method can be used to conveniently add a job to a queue.
214
+ # It assumes the class you're passing it is a real Ruby class (not
215
+ # a string or reference) which either:
216
+ #
217
+ # a) has a @queue ivar set
218
+ # b) responds to `queue`
219
+ #
220
+ # If either of those conditions are met, it will use the value obtained
221
+ # from performing one of the above operations to determine the queue.
222
+ #
223
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
224
+ #
225
+ # This method is considered part of the `stable` API.
226
+ def enqueue(klass, *args)
227
+ # Perform before_enqueue hooks. Don't perform enqueue if any hook returns false
228
+ before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook|
229
+ klass.send(hook, *args)
230
+ end
231
+ return if before_hooks.any? { |result| result == false }
232
+
233
+ Job.create(queue_from_class(klass), klass, *args)
234
+
235
+ Plugin.after_enqueue_hooks(klass).each do |hook|
236
+ klass.send(hook, *args)
237
+ end
238
+ end
239
+
240
+ # This method can be used to conveniently remove a job from a queue.
241
+ # It assumes the class you're passing it is a real Ruby class (not
242
+ # a string or reference) which either:
243
+ #
244
+ # a) has a @queue ivar set
245
+ # b) responds to `queue`
246
+ #
247
+ # If either of those conditions are met, it will use the value obtained
248
+ # from performing one of the above operations to determine the queue.
249
+ #
250
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
251
+ #
252
+ # If no args are given, this method will dequeue *all* jobs matching
253
+ # the provided class. See `Resque::Job.destroy` for more
254
+ # information.
255
+ #
256
+ # Returns the number of jobs destroyed.
257
+ #
258
+ # Example:
259
+ #
260
+ # # Removes all jobs of class `UpdateNetworkGraph`
261
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
262
+ #
263
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
264
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
265
+ #
266
+ # This method is considered part of the `stable` API.
267
+ def dequeue(klass, *args)
268
+ Job.destroy(queue_from_class(klass), klass, *args)
269
+ end
270
+
271
+ # Given a class, try to extrapolate an appropriate queue based on a
272
+ # class instance variable or `queue` method.
273
+ def queue_from_class(klass)
274
+ klass.instance_variable_get(:@queue) ||
275
+ (klass.respond_to?(:queue) and klass.queue)
276
+ end
277
+
278
+ # This method will return a `Resque::Job` object or a non-true value
279
+ # depending on whether a job can be obtained. You should pass it the
280
+ # precise name of a queue: case matters.
281
+ #
282
+ # This method is considered part of the `stable` API.
283
+ def reserve(queue)
284
+ Job.reserve(queue)
285
+ end
286
+
287
+ # Validates if the given klass could be a valid Resque job
288
+ #
289
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
290
+ #
291
+ # If given klass is nil this method will raise a `Resque::NoClassError`
292
+ def validate(klass, queue = nil)
293
+ queue ||= queue_from_class(klass)
294
+
295
+ if !queue
296
+ raise NoQueueError.new("Jobs must be placed onto a queue.")
297
+ end
298
+
299
+ if klass.to_s.empty?
300
+ raise NoClassError.new("Jobs must be given a class.")
301
+ end
302
+ end
303
+
304
+
305
+ #
306
+ # worker shortcuts
307
+ #
308
+
309
+ # A shortcut to Worker.all
310
+ def workers
311
+ Worker.all
312
+ end
313
+
314
+ # A shortcut to Worker.working
315
+ def working
316
+ Worker.working
317
+ end
318
+
319
+ # A shortcut to unregister_worker
320
+ # useful for command line tool
321
+ def remove_worker(worker_id)
322
+ worker = Resque::Worker.find(worker_id)
323
+ worker.unregister_worker
324
+ end
325
+
326
+ #
327
+ # stats
328
+ #
329
+
330
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
331
+ def info
332
+ return {
333
+ :pending => queues.inject(0) { |m,k| m + size(k) },
334
+ :processed => Stat[:processed],
335
+ :queues => queues.size,
336
+ :workers => workers.size.to_i,
337
+ :working => working.size,
338
+ :failed => Stat[:failed],
339
+ :servers => [redis_id],
340
+ :environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
341
+ }
342
+ end
343
+
344
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
345
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
346
+ def keys
347
+ redis.keys("*").map do |key|
348
+ key.sub("#{redis.namespace}:", '')
349
+ end
350
+ end
351
+ end
352
+