opengotham_resque 1.8.2

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