resque 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of resque might be problematic. Click here for more details.
- data/.kick +26 -0
- data/HISTORY.md +3 -0
- data/LICENSE +20 -0
- data/README.markdown +638 -0
- data/Rakefile +61 -0
- data/TODO.md +60 -0
- data/bin/resque +57 -0
- data/bin/resque-web +47 -0
- data/config.ru +8 -0
- data/deps.rip +5 -0
- data/examples/async_helper.rb +31 -0
- data/examples/demo/README.markdown +71 -0
- data/examples/demo/Rakefile +3 -0
- data/examples/demo/app.rb +27 -0
- data/examples/demo/config.ru +19 -0
- data/examples/demo/job.rb +12 -0
- data/examples/existing_classes_as_jobs.rb +3 -0
- data/examples/instance.rb +11 -0
- data/examples/simple.rb +30 -0
- data/init.rb +1 -0
- data/lib/resque.rb +184 -0
- data/lib/resque/errors.rb +7 -0
- data/lib/resque/failure.rb +57 -0
- data/lib/resque/failure/base.rb +54 -0
- data/lib/resque/failure/hoptoad.rb +88 -0
- data/lib/resque/failure/redis.rb +28 -0
- data/lib/resque/helpers.rb +57 -0
- data/lib/resque/job.rb +91 -0
- data/lib/resque/server.rb +154 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/nav-bg.png +0 -0
- data/lib/resque/server/public/ranger.js +7 -0
- data/lib/resque/server/public/reset.css +51 -0
- data/lib/resque/server/public/style.css +67 -0
- data/lib/resque/server/public/tab_b.gif +0 -0
- data/lib/resque/server/public/tab_r.gif +0 -0
- data/lib/resque/server/public/tabs.css +189 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +29 -0
- data/lib/resque/server/views/key.erb +17 -0
- data/lib/resque/server/views/layout.erb +43 -0
- data/lib/resque/server/views/next_more.erb +12 -0
- data/lib/resque/server/views/overview.erb +2 -0
- data/lib/resque/server/views/queues.erb +40 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +72 -0
- data/lib/resque/server/views/working.erb +66 -0
- data/lib/resque/stat.rb +53 -0
- data/lib/resque/tasks.rb +24 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +406 -0
- data/tasks/redis.rake +125 -0
- data/tasks/resque.rake +2 -0
- data/test/redis-test.conf +132 -0
- data/test/resque_test.rb +160 -0
- data/test/test_helper.rb +90 -0
- data/test/worker_test.rb +212 -0
- metadata +124 -0
data/Rakefile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
eval File.read('tasks/redis.rake')
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
|
4
|
+
require 'resque/tasks'
|
5
|
+
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc "Run tests"
|
9
|
+
task :test do
|
10
|
+
# Don't use the rake/testtask because it loads a new
|
11
|
+
# Ruby interpreter - we want to run tests with the current
|
12
|
+
# `rake` so our library manager still works
|
13
|
+
Dir['test/*_test.rb'].each do |f|
|
14
|
+
require f
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Activate kicker - gem install kicker"
|
19
|
+
task :kick do
|
20
|
+
exec "kicker -e rake lib test"
|
21
|
+
end
|
22
|
+
|
23
|
+
task :install => [ 'redis:install', 'dtach:install' ]
|
24
|
+
|
25
|
+
desc "Build a gem"
|
26
|
+
task :gem => [ :test, :gemspec, :build ]
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'jeweler'
|
30
|
+
$LOAD_PATH.unshift 'lib'
|
31
|
+
require 'resque/version'
|
32
|
+
|
33
|
+
Jeweler::Tasks.new do |gemspec|
|
34
|
+
gemspec.name = "resque"
|
35
|
+
gemspec.summary = ""
|
36
|
+
gemspec.description = ""
|
37
|
+
gemspec.email = "chris@ozmm.org"
|
38
|
+
gemspec.homepage = "http://github.com/defunkt/resque"
|
39
|
+
gemspec.authors = ["Chris Wanstrath"]
|
40
|
+
gemspec.version = Resque::Version
|
41
|
+
end
|
42
|
+
rescue LoadError
|
43
|
+
puts "Jeweler not available. Install it with: "
|
44
|
+
puts "gem install jeweler"
|
45
|
+
end
|
46
|
+
|
47
|
+
begin
|
48
|
+
require 'sdoc_helpers'
|
49
|
+
rescue LoadError
|
50
|
+
puts "sdoc support not enabled. Please gem install sdoc-helpers."
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Push a new version to Gemcutter"
|
54
|
+
task :publish => [ :test, :gemspec, :build ] do
|
55
|
+
system "git tag v#{Resque::Version}"
|
56
|
+
system "git push origin v#{Resque::Version}"
|
57
|
+
system "git push origin master"
|
58
|
+
system "gem push pkg/resque-#{Resque::Version}.gem"
|
59
|
+
system "git clean -fd"
|
60
|
+
exec "rake pages"
|
61
|
+
end
|
data/TODO.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
Little Stuff
|
2
|
+
-----------
|
3
|
+
|
4
|
+
[ ] Show stale workers in red with a warning icon in the Sinatra app
|
5
|
+
|
6
|
+
|
7
|
+
Big Stuff
|
8
|
+
---------
|
9
|
+
|
10
|
+
### async
|
11
|
+
|
12
|
+
I want the `async` helper to be first class. Something like this:
|
13
|
+
|
14
|
+
class Repository < ActiveRecord::Base
|
15
|
+
include Resque::AsyncHelper
|
16
|
+
end
|
17
|
+
|
18
|
+
This adds an `async` instance method and a `perform` class method.
|
19
|
+
|
20
|
+
The `async` method looks like this:
|
21
|
+
|
22
|
+
def async(method, *args)
|
23
|
+
Resque.enqueue(self.class, id, method, *args)
|
24
|
+
end
|
25
|
+
|
26
|
+
The `perform` method looks like this:
|
27
|
+
|
28
|
+
def self.perform(id, method, *args)
|
29
|
+
find(id).send(method, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
Of course, you can define your own `self.perform` and have it still
|
33
|
+
work beautifully. We might override ours in GitHub to look like this:
|
34
|
+
|
35
|
+
def self.perform(id, method, *args)
|
36
|
+
return unless repo = cached_by_id(id)
|
37
|
+
repo.send(method, *args)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then: `@repo.async(:update_disk_usage)` issues a job equivalent to:
|
41
|
+
|
42
|
+
Resque.enqueue(Repository, 44, :update_disk_usage)
|
43
|
+
|
44
|
+
Booyah.
|
45
|
+
|
46
|
+
|
47
|
+
### gem install
|
48
|
+
|
49
|
+
`gem install resque` should pull in yajl, redis, sinatra, rake, and rack
|
50
|
+
|
51
|
+
Do it like Unicorn does it.
|
52
|
+
|
53
|
+
### Parent / Child => Master / Workers
|
54
|
+
|
55
|
+
On an ideal setup (REE + Linux) you'll have 2N Resque processes
|
56
|
+
running at any time: N parents and N children.
|
57
|
+
|
58
|
+
We can cut this number down to N+1 by moving from a parent / child
|
59
|
+
architecture to a master / workers architecture.
|
60
|
+
|
data/bin/resque
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
require 'resque'
|
5
|
+
|
6
|
+
def kill(worker)
|
7
|
+
abort "** resque kill WORKER_ID" if worker.nil?
|
8
|
+
pid = worker.split(':')[1].to_i
|
9
|
+
|
10
|
+
begin
|
11
|
+
Process.kill("KILL", pid)
|
12
|
+
puts "** killed #{worker}"
|
13
|
+
rescue Errno::ESRCH
|
14
|
+
puts "** worker #{worker} not running"
|
15
|
+
end
|
16
|
+
|
17
|
+
remove worker
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove(worker)
|
21
|
+
abort "** resque remove WORKER_ID" if worker.nil?
|
22
|
+
|
23
|
+
Resque.remove_worker(worker)
|
24
|
+
puts "** removed #{worker}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def list
|
28
|
+
if Resque.workers.any?
|
29
|
+
Resque.workers.each do |worker|
|
30
|
+
puts "#{worker} (#{worker.state})"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
puts "None"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if (i = ARGV.index('-r')) && ARGV[i+1]
|
38
|
+
Resque.redis = ARGV[i+1]
|
39
|
+
ARGV.delete_at(i)
|
40
|
+
ARGV.delete_at(i+1)
|
41
|
+
end
|
42
|
+
|
43
|
+
case ARGV[0]
|
44
|
+
when 'kill'
|
45
|
+
kill ARGV[1]
|
46
|
+
when 'remove'
|
47
|
+
remove ARGV[1]
|
48
|
+
when 'list'
|
49
|
+
list
|
50
|
+
else
|
51
|
+
puts "Usage: resque [-r redis_host:redis_port] COMMAND [option]"
|
52
|
+
puts
|
53
|
+
puts "Commands:"
|
54
|
+
puts " remove WORKER Removes a worker"
|
55
|
+
puts " kill WORKER Kills a worker"
|
56
|
+
puts " list Lists known workers"
|
57
|
+
end
|
data/bin/resque-web
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
__DIR__ = File.expand_path(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
if `which rackup`.empty?
|
6
|
+
abort "** Can't find `rackup` in PATH."
|
7
|
+
end
|
8
|
+
|
9
|
+
if ARGV.include?('-h')
|
10
|
+
puts <<-usage
|
11
|
+
Usage: resque-web [ruby options] [rack options] [resque config]
|
12
|
+
|
13
|
+
Starts a Resque front-end by way of `rackup`.
|
14
|
+
|
15
|
+
Ruby options:
|
16
|
+
-e, --eval LINE evaluate a LINE of code
|
17
|
+
-d, --debug set debugging flags (set $DEBUG to true)
|
18
|
+
-w, --warn turn warnings on for your script
|
19
|
+
-I, --include PATH specify $LOAD_PATH (may be used more than once)
|
20
|
+
-r, --require LIBRARY require the library, before executing your script
|
21
|
+
Rack options:
|
22
|
+
-s, --server SERVER serve using SERVER (webrick/mongrel)
|
23
|
+
-o, --host HOST listen on HOST (default: 0.0.0.0)
|
24
|
+
-p, --port PORT use PORT (default: 9292)
|
25
|
+
-E, --env ENVIRONMENT use ENVIRONMENT for defaults (default: development)
|
26
|
+
-D, --daemonize run daemonized in the background
|
27
|
+
-P, --pid FILE file to store PID (default: rack.pid)
|
28
|
+
Common options:
|
29
|
+
-h, --help Show this message
|
30
|
+
--version Show version
|
31
|
+
usage
|
32
|
+
else
|
33
|
+
if !ENV['CONFIG']&&ARGV[-1]&&ARGV[-1][0]!=?-&&(ARGV[-2]?ARGV[-2][0]!=?-:true)
|
34
|
+
if File.file?(file = File.expand_path(ARGV[-1]))
|
35
|
+
ARGV.delete_at(-1)
|
36
|
+
ENV['CONFIG'] = file
|
37
|
+
else
|
38
|
+
abort "** Can't find #{file}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
args = ARGV
|
43
|
+
ENV['RUBYLIB'] = ENV['RUBYLIB'].to_s + ':' + __DIR__ + '/../lib'
|
44
|
+
args.unshift '-e', 'require "resque";load ENV["CONFIG"] if ENV["CONFIG"]'
|
45
|
+
args.push File.expand_path(File.dirname(__FILE__) + "/../config.ru")
|
46
|
+
exec "rackup", *args
|
47
|
+
end
|
data/config.ru
ADDED
data/deps.rip
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# If you want to just call a method on an object in the background,
|
2
|
+
# we can easily add that functionality to Resque.
|
3
|
+
#
|
4
|
+
# This is similar to DelayedJob's `send_later`.
|
5
|
+
#
|
6
|
+
# Keep in mind that, unlike DelayedJob, only simple Ruby objects
|
7
|
+
# can be persisted.
|
8
|
+
#
|
9
|
+
# If it can be represented in JSON, it can be stored in a job.
|
10
|
+
|
11
|
+
# Here's our ActiveRecord class
|
12
|
+
class Repository < ActiveRecord::Base
|
13
|
+
# This will be called by a worker when a job needs to be processed
|
14
|
+
def self.perform(id, method, *args)
|
15
|
+
find(id).send(method, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
# We can pass this any Repository instance method that we want to
|
19
|
+
# run later.
|
20
|
+
def async(method, *args)
|
21
|
+
Resque.enqueue(Repository, id, method, *args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Now we can call any method and have it execute later:
|
26
|
+
|
27
|
+
@repo.async(:update_disk_usage)
|
28
|
+
|
29
|
+
# or
|
30
|
+
|
31
|
+
@repo.async(:update_network_source_id, 34)
|
@@ -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,27 @@
|
|
1
|
+
require 'sinatra'
|
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 << ' <a href="/resque/">View Resque</a>'
|
17
|
+
out << '</form>'
|
18
|
+
out << "</body></html>"
|
19
|
+
out
|
20
|
+
end
|
21
|
+
|
22
|
+
post '/' do
|
23
|
+
Resque.enqueue(Job, params)
|
24
|
+
redirect "/"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
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
|
data/examples/simple.rb
ADDED
@@ -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')
|