opengotham_resque 1.8.2

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 (70) hide show
  1. data/.gitignore +2 -0
  2. data/.kick +26 -0
  3. data/HISTORY.md +142 -0
  4. data/LICENSE +20 -0
  5. data/README.markdown +794 -0
  6. data/Rakefile +112 -0
  7. data/bin/resque +57 -0
  8. data/bin/resque-web +23 -0
  9. data/config.ru +14 -0
  10. data/deps.rip +7 -0
  11. data/docs/HOOKS.md +121 -0
  12. data/docs/PLUGINS.md +93 -0
  13. data/examples/async_helper.rb +31 -0
  14. data/examples/demo/README.markdown +71 -0
  15. data/examples/demo/Rakefile +8 -0
  16. data/examples/demo/app.rb +38 -0
  17. data/examples/demo/config.ru +19 -0
  18. data/examples/demo/job.rb +22 -0
  19. data/examples/god/resque.god +53 -0
  20. data/examples/god/stale.god +26 -0
  21. data/examples/instance.rb +11 -0
  22. data/examples/monit/resque.monit +6 -0
  23. data/examples/simple.rb +30 -0
  24. data/init.rb +1 -0
  25. data/lib/resque.rb +287 -0
  26. data/lib/resque/errors.rb +10 -0
  27. data/lib/resque/failure.rb +66 -0
  28. data/lib/resque/failure/base.rb +61 -0
  29. data/lib/resque/failure/hoptoad.rb +132 -0
  30. data/lib/resque/failure/multiple.rb +48 -0
  31. data/lib/resque/failure/redis.rb +40 -0
  32. data/lib/resque/helpers.rb +63 -0
  33. data/lib/resque/job.rb +207 -0
  34. data/lib/resque/plugin.rb +46 -0
  35. data/lib/resque/server.rb +201 -0
  36. data/lib/resque/server/public/idle.png +0 -0
  37. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  38. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  39. data/lib/resque/server/public/poll.png +0 -0
  40. data/lib/resque/server/public/ranger.js +67 -0
  41. data/lib/resque/server/public/reset.css +48 -0
  42. data/lib/resque/server/public/style.css +81 -0
  43. data/lib/resque/server/public/working.png +0 -0
  44. data/lib/resque/server/test_helper.rb +19 -0
  45. data/lib/resque/server/views/error.erb +1 -0
  46. data/lib/resque/server/views/failed.erb +53 -0
  47. data/lib/resque/server/views/key_sets.erb +20 -0
  48. data/lib/resque/server/views/key_string.erb +11 -0
  49. data/lib/resque/server/views/layout.erb +44 -0
  50. data/lib/resque/server/views/next_more.erb +10 -0
  51. data/lib/resque/server/views/overview.erb +4 -0
  52. data/lib/resque/server/views/queues.erb +49 -0
  53. data/lib/resque/server/views/stats.erb +62 -0
  54. data/lib/resque/server/views/workers.erb +78 -0
  55. data/lib/resque/server/views/working.erb +69 -0
  56. data/lib/resque/stat.rb +53 -0
  57. data/lib/resque/tasks.rb +39 -0
  58. data/lib/resque/version.rb +3 -0
  59. data/lib/resque/worker.rb +478 -0
  60. data/tasks/redis.rake +159 -0
  61. data/tasks/resque.rake +2 -0
  62. data/test/job_hooks_test.rb +302 -0
  63. data/test/job_plugins_test.rb +209 -0
  64. data/test/plugin_test.rb +116 -0
  65. data/test/redis-test.conf +132 -0
  66. data/test/resque-web_test.rb +54 -0
  67. data/test/resque_test.rb +225 -0
  68. data/test/test_helper.rb +111 -0
  69. data/test/worker_test.rb +302 -0
  70. metadata +199 -0
@@ -0,0 +1,71 @@
1
+ Resque Demo
2
+ -----------
3
+
4
+ This is a dirt simple Resque setup for you to play with.
5
+
6
+
7
+ ### Starting the Demo App
8
+
9
+ Here's how to run the Sinatra app:
10
+
11
+ $ git clone git://github.com/defunkt/resque.git
12
+ $ cd resque/examples/demo
13
+ $ rackup config.ru
14
+ $ open http://localhost:9292/
15
+
16
+ Click 'Create New Job' a few times. You should see the number of
17
+ pending jobs rising.
18
+
19
+
20
+ ### Starting the Demo Worker
21
+
22
+ Now in another shell terminal start the worker:
23
+
24
+ $ cd resque/examples/demo
25
+ $ VERBOSE=true QUEUE=default rake resque:work
26
+
27
+ You should see the following output:
28
+
29
+ *** Starting worker hostname:90185:default
30
+ *** got: (Job{default} | Demo::Job | [{}])
31
+ Processed a job!
32
+ *** done: (Job{default} | Demo::Job | [{}])
33
+
34
+ You can also use `VVERBOSE` (very verbose) if you want to see more:
35
+
36
+ $ VERBOSE=true QUEUE=default rake resque:work
37
+ *** Starting worker hostname:90399:default
38
+ ** [05:55:09 2009-09-16] 90399: Registered signals
39
+ ** [05:55:09 2009-09-16] 90399: Checking default
40
+ ** [05:55:09 2009-09-16] 90399: Found job on default
41
+ ** [05:55:09 2009-09-16] 90399: got: (Job{default} | Demo::Job | [{}])
42
+ ** [05:55:09 2009-09-16] 90399: resque: Forked 90401 at 1253141709
43
+ ** [05:55:09 2009-09-16] 90401: resque: Processing default since 1253141709
44
+ Processed a job!
45
+ ** [05:55:10 2009-09-16] 90401: done: (Job{default} | Demo::Job | [{}])
46
+
47
+ Notice that our workers `require 'job'` in our `Rakefile`. This
48
+ ensures they have our app loaded and can access the job classes.
49
+
50
+
51
+ ### Starting the Resque frontend
52
+
53
+ Great, now let's check out the Resque frontend. Either click on 'View
54
+ Resque' in your web browser or run:
55
+
56
+ $ open http://localhost:9292/resque/
57
+
58
+ You should see the Resque web frontend. 404 page? Don't forget the
59
+ trailing slash!
60
+
61
+
62
+ ### config.ru
63
+
64
+ The `config.ru` shows you how to mount multiple Rack apps. Resque
65
+ should work fine on a subpath - feel free to load it up in your
66
+ Passenger app and protect it with some basic auth.
67
+
68
+
69
+ ### That's it!
70
+
71
+ Click around, add some more queues, add more jobs, do whatever, have fun.
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
2
+ require 'resque/tasks'
3
+ require 'job'
4
+
5
+ desc "Start the demo using `rackup`"
6
+ task :start do
7
+ exec "rackup config.ru"
8
+ end
@@ -0,0 +1,38 @@
1
+ require 'sinatra/base'
2
+ require 'resque'
3
+ require 'job'
4
+
5
+ module Demo
6
+ class App < Sinatra::Base
7
+ get '/' do
8
+ info = Resque.info
9
+ out = "<html><head><title>Resque Demo</title></head><body>"
10
+ out << "<p>"
11
+ out << "There are #{info[:pending]} pending and "
12
+ out << "#{info[:processed]} processed jobs across #{info[:queues]} queues."
13
+ out << "</p>"
14
+ out << '<form method="POST">'
15
+ out << '<input type="submit" value="Create New Job"/>'
16
+ out << '&nbsp;&nbsp;<a href="/resque/">View Resque</a>'
17
+ out << '</form>'
18
+
19
+ out << "<form action='/failing' method='POST''>"
20
+ out << '<input type="submit" value="Create Failing New Job"/>'
21
+ out << '&nbsp;&nbsp;<a href="/resque/">View Resque</a>'
22
+ out << '</form>'
23
+
24
+ out << "</body></html>"
25
+ out
26
+ end
27
+
28
+ post '/' do
29
+ Resque.enqueue(Job, params)
30
+ redirect "/"
31
+ end
32
+
33
+ post '/failing' do
34
+ Resque.enqueue(FailingJob, params)
35
+ redirect "/"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ require 'logger'
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
4
+ require 'app'
5
+ require 'resque/server'
6
+
7
+ use Rack::ShowExceptions
8
+
9
+ # Set the AUTH env variable to your basic auth password to protect Resque.
10
+ AUTH_PASSWORD = ENV['AUTH']
11
+ if AUTH_PASSWORD
12
+ Resque::Server.use Rack::Auth::Basic do |username, password|
13
+ password == AUTH_PASSWORD
14
+ end
15
+ end
16
+
17
+ run Rack::URLMap.new \
18
+ "/" => Demo::App.new,
19
+ "/resque" => Resque::Server.new
@@ -0,0 +1,22 @@
1
+ require 'resque'
2
+
3
+ module Demo
4
+ module Job
5
+ @queue = :default
6
+
7
+ def self.perform(params)
8
+ sleep 1
9
+ puts "Processed a job!"
10
+ end
11
+ end
12
+
13
+ module FailingJob
14
+ @queue = :failing
15
+
16
+ def self.perform(params)
17
+ sleep 1
18
+ raise 'not processable!'
19
+ puts "Processed a job!"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ rails_env = ENV['RAILS_ENV'] || "production"
2
+ rails_root = ENV['RAILS_ROOT'] || "/data/github/current"
3
+ num_workers = rails_env == 'production' ? 5 : 2
4
+
5
+ num_workers.times do |num|
6
+ God.watch do |w|
7
+ w.name = "resque-#{num}"
8
+ w.group = 'resque'
9
+ w.interval = 30.seconds
10
+ w.env = {"QUEUE"=>"critical,high,low", "RAILS_ENV"=>rails_env}
11
+ w.start = "/usr/bin/rake -f #{rails_root}/Rakefile environment resque:work"
12
+
13
+ w.uid = 'git'
14
+ w.gid = 'git'
15
+
16
+ # retart if memory gets too high
17
+ w.transition(:up, :restart) do |on|
18
+ on.condition(:memory_usage) do |c|
19
+ c.above = 350.megabytes
20
+ c.times = 2
21
+ end
22
+ end
23
+
24
+ # determine the state on startup
25
+ w.transition(:init, { true => :up, false => :start }) do |on|
26
+ on.condition(:process_running) do |c|
27
+ c.running = true
28
+ end
29
+ end
30
+
31
+ # determine when process has finished starting
32
+ w.transition([:start, :restart], :up) do |on|
33
+ on.condition(:process_running) do |c|
34
+ c.running = true
35
+ c.interval = 5.seconds
36
+ end
37
+
38
+ # failsafe
39
+ on.condition(:tries) do |c|
40
+ c.times = 5
41
+ c.transition = :start
42
+ c.interval = 5.seconds
43
+ end
44
+ end
45
+
46
+ # start if process is not running
47
+ w.transition(:up, :start) do |on|
48
+ on.condition(:process_running) do |c|
49
+ c.running = false
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,26 @@
1
+ # This will ride alongside god and kill any rogue stale worker
2
+ # processes. Their sacrifice is for the greater good.
3
+
4
+ WORKER_TIMEOUT = 60 * 10 # 10 minutes
5
+
6
+ Thread.new do
7
+ loop do
8
+ begin
9
+ `ps -e -o pid,command | grep [r]esque`.split("\n").each do |line|
10
+ parts = line.split(' ')
11
+ next if parts[-2] != "at"
12
+ started = parts[-1].to_i
13
+ elapsed = Time.now - Time.at(started)
14
+
15
+ if elapsed >= WORKER_TIMEOUT
16
+ ::Process.kill('USR1', parts[0].to_i)
17
+ end
18
+ end
19
+ rescue
20
+ # don't die because of stupid exceptions
21
+ nil
22
+ end
23
+
24
+ sleep 30
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # DelayedJob wants you to create instances. No problem.
2
+
3
+ class Archive < Struct.new(:repo_id, :branch)
4
+ def self.perform(*args)
5
+ new(*args).perform
6
+ end
7
+
8
+ def perform
9
+ # do work!
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ check process resque_worker_QUEUE
2
+ with pidfile /data/APP_NAME/current/tmp/pids/resque_worker_QUEUE.pid
3
+ start program = "/bin/sh -c 'cd /data/APP_NAME/current; RAILS_ENV=production QUEUE=queue_name VERBOSE=1 nohup rake resque:work& &> log/resque_worker_QUEUE.log && echo $! > tmp/pids/resque_worker_QUEUE.pid'" as uid deploy and gid deploy
4
+ stop program = "/bin/sh -c 'cd /data/APP_NAME/current && kill -s QUIT `cat tmp/pids/resque_worker_QUEUE.pid` && rm -f tmp/pids/resque_worker_QUEUE.pid; exit 0;'"
5
+ if totalmem is greater than 300 MB for 10 cycles then restart # eating up memory?
6
+ group resque_workers
@@ -0,0 +1,30 @@
1
+ # This is a simple Resque job.
2
+ class Archive
3
+ @queue = :file_serve
4
+
5
+ def self.perform(repo_id, branch = 'master')
6
+ repo = Repository.find(repo_id)
7
+ repo.create_archive(branch)
8
+ end
9
+ end
10
+
11
+ # This is in our app code
12
+ class Repository < Model
13
+ # ... stuff ...
14
+
15
+ def async_create_archive(branch)
16
+ Resque.enqueue(Archive, self.id, branch)
17
+ end
18
+
19
+ # ... more stuff ...
20
+ end
21
+
22
+ # Calling this code:
23
+ repo = Repository.find(22)
24
+ repo.async_create_archive('homebrew')
25
+
26
+ # Will return immediately and create a Resque job which is later
27
+ # processed.
28
+
29
+ # Essentially, this code is run by the worker when processing:
30
+ Archive.perform(22, 'homebrew')
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'resque'
@@ -0,0 +1,287 @@
1
+ require 'redis/namespace'
2
+
3
+ begin
4
+ require 'yajl'
5
+ rescue LoadError
6
+ require 'json'
7
+ end
8
+
9
+ require 'resque/version'
10
+
11
+ require 'resque/errors'
12
+
13
+ require 'resque/failure'
14
+ require 'resque/failure/base'
15
+
16
+ require 'resque/helpers'
17
+ require 'resque/stat'
18
+ require 'resque/job'
19
+ require 'resque/worker'
20
+ require 'resque/plugin'
21
+
22
+ module Resque
23
+ include Helpers
24
+ extend self
25
+
26
+ # Accepts:
27
+ # 1. A 'hostname:port' string
28
+ # 2. A 'hostname:port:db' string (to select the Redis db)
29
+ # 3. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
30
+ # or `Redis::Namespace`.
31
+ def redis=(server)
32
+ case server
33
+ when String
34
+ host, port, db = server.split(':')
35
+ redis = Redis.new(:host => host, :port => port,
36
+ :thread_safe => true, :db => db)
37
+ @redis = Redis::Namespace.new(:resque, :redis => redis)
38
+ when Redis, Redis::Client, Redis::DistRedis
39
+ @redis = Redis::Namespace.new(:resque, :redis => server)
40
+ when Redis::Namespace
41
+ @redis = server
42
+ else
43
+ raise "I don't know what to do with #{server.inspect}"
44
+ end
45
+ end
46
+
47
+ # Returns the current Redis connection. If none has been created, will
48
+ # create a new one.
49
+ def redis
50
+ return @redis if @redis
51
+ self.redis = 'localhost:6379'
52
+ self.redis
53
+ end
54
+
55
+ # The `before_first_fork` hook will be run in the **parent** process
56
+ # only once, before forking to run the first job. Be careful- any
57
+ # changes you make will be permanent for the lifespan of the
58
+ # worker.
59
+ #
60
+ # Call with a block to set the hook.
61
+ # Call with no arguments to return the hook.
62
+ def before_first_fork(&block)
63
+ block ? (@before_first_fork = block) : @before_first_fork
64
+ end
65
+
66
+ # Set a proc that will be called in the parent process before the
67
+ # worker forks for the first time.
68
+ def before_first_fork=(before_first_fork)
69
+ @before_first_fork = before_first_fork
70
+ end
71
+
72
+ # The `before_fork` hook will be run in the **parent** process
73
+ # before every job, so be careful- any changes you make will be
74
+ # permanent for the lifespan of the worker.
75
+ #
76
+ # Call with a block to set the hook.
77
+ # Call with no arguments to return the hook.
78
+ def before_fork(&block)
79
+ block ? (@before_fork = block) : @before_fork
80
+ end
81
+
82
+ # Set the before_fork proc.
83
+ def before_fork=(before_fork)
84
+ @before_fork = before_fork
85
+ end
86
+
87
+ # The `after_fork` hook will be run in the child process and is passed
88
+ # the current job. Any changes you make, therefor, will only live as
89
+ # long as the job currently being processes.
90
+ #
91
+ # Call with a block to set the hook.
92
+ # Call with no arguments to return the hook.
93
+ def after_fork(&block)
94
+ block ? (@after_fork = block) : @after_fork
95
+ end
96
+
97
+ # Set the after_fork proc.
98
+ def after_fork=(after_fork)
99
+ @after_fork = after_fork
100
+ end
101
+
102
+ def to_s
103
+ "Resque Client connected to #{redis.server}"
104
+ end
105
+
106
+
107
+ #
108
+ # queue manipulation
109
+ #
110
+
111
+ # Pushes a job onto a queue. Queue name should be a string and the
112
+ # item should be any JSON-able Ruby object.
113
+ def push(queue, item)
114
+ watch_queue(queue)
115
+ redis.rpush "queue:#{queue}", encode(item)
116
+ end
117
+
118
+ # Pops a job off a queue. Queue name should be a string.
119
+ #
120
+ # Returns a Ruby object.
121
+ def pop(queue)
122
+ decode redis.lpop("queue:#{queue}")
123
+ end
124
+
125
+ # Returns an int representing the size of a queue.
126
+ # Queue name should be a string.
127
+ def size(queue)
128
+ redis.llen("queue:#{queue}").to_i
129
+ end
130
+
131
+ # Returns an array of items currently queued. Queue name should be
132
+ # a string.
133
+ #
134
+ # start and count should be integer and can be used for pagination.
135
+ # start is the item to begin, count is how many items to return.
136
+ #
137
+ # To get the 3rd page of a 30 item, paginatied list one would use:
138
+ # Resque.peek('my_list', 59, 30)
139
+ def peek(queue, start = 0, count = 1)
140
+ list_range("queue:#{queue}", start, count)
141
+ end
142
+
143
+ # Does the dirty work of fetching a range of items from a Redis list
144
+ # and converting them into Ruby objects.
145
+ def list_range(key, start = 0, count = 1)
146
+ if count == 1
147
+ decode redis.lindex(key, start)
148
+ else
149
+ Array(redis.lrange(key, start, start+count-1)).map do |item|
150
+ decode item
151
+ end
152
+ end
153
+ end
154
+
155
+ # Returns an array of all known Resque queues as strings.
156
+ def queues
157
+ redis.smembers(:queues)
158
+ end
159
+
160
+ # Given a queue name, completely deletes the queue.
161
+ def remove_queue(queue)
162
+ redis.srem(:queues, queue.to_s)
163
+ redis.del("queue:#{queue}")
164
+ end
165
+
166
+ # Used internally to keep track of which queues we've created.
167
+ # Don't call this directly.
168
+ def watch_queue(queue)
169
+ redis.sadd(:queues, queue.to_s)
170
+ end
171
+
172
+
173
+ #
174
+ # job shortcuts
175
+ #
176
+
177
+ # This method can be used to conveniently add a job to a queue.
178
+ # It assumes the class you're passing it is a real Ruby class (not
179
+ # a string or reference) which either:
180
+ #
181
+ # a) has a @queue ivar set
182
+ # b) responds to `queue`
183
+ #
184
+ # If either of those conditions are met, it will use the value obtained
185
+ # from performing one of the above operations to determine the queue.
186
+ #
187
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
188
+ #
189
+ # This method is considered part of the `stable` API.
190
+ def enqueue(klass, *args)
191
+ Job.create(queue_from_class(klass), klass, *args)
192
+ end
193
+
194
+ # This method can be used to conveniently remove a job from a queue.
195
+ # It assumes the class you're passing it is a real Ruby class (not
196
+ # a string or reference) which either:
197
+ #
198
+ # a) has a @queue ivar set
199
+ # b) responds to `queue`
200
+ #
201
+ # If either of those conditions are met, it will use the value obtained
202
+ # from performing one of the above operations to determine the queue.
203
+ #
204
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
205
+ #
206
+ # If no args are given, this method will dequeue *all* jobs matching
207
+ # the provided class. See `Resque::Job.destroy` for more
208
+ # information.
209
+ #
210
+ # Returns the number of jobs destroyed.
211
+ #
212
+ # Example:
213
+ #
214
+ # # Removes all jobs of class `UpdateNetworkGraph`
215
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
216
+ #
217
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
218
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
219
+ #
220
+ # This method is considered part of the `stable` API.
221
+ def dequeue(klass, *args)
222
+ Job.destroy(queue_from_class(klass), klass, *args)
223
+ end
224
+
225
+ # Given a class, try to extrapolate an appropriate queue based on a
226
+ # class instance variable or `queue` method.
227
+ def queue_from_class(klass)
228
+ klass.instance_variable_get(:@queue) ||
229
+ (klass.respond_to?(:queue) and klass.queue)
230
+ end
231
+
232
+ # This method will return a `Resque::Job` object or a non-true value
233
+ # depending on whether a job can be obtained. You should pass it the
234
+ # precise name of a queue: case matters.
235
+ #
236
+ # This method is considered part of the `stable` API.
237
+ def reserve(queue)
238
+ Job.reserve(queue)
239
+ end
240
+
241
+
242
+ #
243
+ # worker shortcuts
244
+ #
245
+
246
+ # A shortcut to Worker.all
247
+ def workers
248
+ Worker.all
249
+ end
250
+
251
+ # A shortcut to Worker.working
252
+ def working
253
+ Worker.working
254
+ end
255
+
256
+ # A shortcut to unregister_worker
257
+ # useful for command line tool
258
+ def remove_worker(worker_id)
259
+ worker = Resque::Worker.find(worker_id)
260
+ worker.unregister_worker
261
+ end
262
+
263
+ #
264
+ # stats
265
+ #
266
+
267
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
268
+ def info
269
+ return {
270
+ :pending => queues.inject(0) { |m,k| m + size(k) },
271
+ :processed => Stat[:processed],
272
+ :queues => queues.size,
273
+ :workers => workers.size.to_i,
274
+ :working => working.size,
275
+ :failed => Stat[:failed],
276
+ :servers => [redis.server]
277
+ }
278
+ end
279
+
280
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
281
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
282
+ def keys
283
+ redis.keys("*").map do |key|
284
+ key.sub("#{redis.namespace}:", '')
285
+ end
286
+ end
287
+ end