steini-resque 1.18.5

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