adamwiggins-minion 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +83 -0
- data/VERSION +1 -0
- data/examples/math.rb +33 -0
- data/examples/sandwich.rb +25 -0
- data/lib/minion.rb +114 -0
- metadata +88 -0
data/README.rdoc
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
= Minion: super simple job queue over amqp
|
3
|
+
|
4
|
+
Minion makes processing jobs over AMQP simple and easy.
|
5
|
+
|
6
|
+
== Setup
|
7
|
+
|
8
|
+
Minion pulls the AMQP credentials out the environment via AMQP_URI.
|
9
|
+
|
10
|
+
$ export AMQP_URI="amqp://johndoe:abc123@localhost/my_vhost"
|
11
|
+
|
12
|
+
If no URI is supplied, Minion defaults to "amqp://guest:guest@localhost/" which
|
13
|
+
is the default credentials for Rabbitmq running locally.
|
14
|
+
|
15
|
+
== Principles
|
16
|
+
|
17
|
+
Minion treats your jobs with respect. The queues are durable and not
|
18
|
+
autodelete. When popping jobs off the queue, they will not receive an ack
|
19
|
+
until the job is done. You can rest assured that once queued, the job will not
|
20
|
+
be lost.
|
21
|
+
|
22
|
+
Sends are done synchronously and receives are done asynchronously. This allows
|
23
|
+
you to Minion.enqueue() from the console, or in a mongrel and you don't need to
|
24
|
+
worry about eventmachine. It also means that when enqueue returns, the AMQP
|
25
|
+
server has received your message. Daemons set to receive messages however use
|
26
|
+
eventmachine.
|
27
|
+
|
28
|
+
Message processing is done one at a time (prefetch 1). If you want tasks done
|
29
|
+
in parallel, run two minions.
|
30
|
+
|
31
|
+
== Push a job onto the queue
|
32
|
+
|
33
|
+
Its easy to push a job onto the queue.
|
34
|
+
|
35
|
+
Minion.enqueue("make.sandwich", { "for" => "me", "with" => "bread" })
|
36
|
+
|
37
|
+
Minion expects a queue name (and will create it if needed). The second argument
|
38
|
+
needs to be a hash.
|
39
|
+
|
40
|
+
== Processing a job
|
41
|
+
|
42
|
+
require 'minion'
|
43
|
+
|
44
|
+
include Minion
|
45
|
+
|
46
|
+
job "make.sandwich" do |args|
|
47
|
+
Sandwich.make(args["for"],args["with"])
|
48
|
+
end
|
49
|
+
|
50
|
+
== Chaining multiple steps
|
51
|
+
|
52
|
+
If you have a task that requires more than one step just pass an array of
|
53
|
+
queues when you enqueue.
|
54
|
+
|
55
|
+
Minion.enqueue([ "make.sandwich", "eat.sandwich" ], "for" => "me")
|
56
|
+
|
57
|
+
job "make.sandwich" do
|
58
|
+
## this return value is merged with for => me and sent to the next queue
|
59
|
+
{ "type" => "ham on rye" }
|
60
|
+
end
|
61
|
+
|
62
|
+
job "eat.sandwich" do |args|
|
63
|
+
puts "I have #{args["type"]} sandwich for #{args["me"]}"
|
64
|
+
end
|
65
|
+
|
66
|
+
== Error handling
|
67
|
+
|
68
|
+
When an error is thrown in a job handler, the job is requeued to be done later
|
69
|
+
and the minion process exits. If you define an error handler, however, the
|
70
|
+
error handler is run and the job is removed from the queue.
|
71
|
+
|
72
|
+
on_error do |e|
|
73
|
+
puts "got an error! #{e}"
|
74
|
+
end
|
75
|
+
|
76
|
+
== Logging
|
77
|
+
|
78
|
+
Minion logs to stdout via "puts". You can specify a custom logger like this:
|
79
|
+
|
80
|
+
logger do |msg|
|
81
|
+
puts msg
|
82
|
+
end
|
83
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
data/examples/math.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'minion'
|
5
|
+
|
6
|
+
include Minion
|
7
|
+
|
8
|
+
on_error do |e|
|
9
|
+
puts "got an error!"
|
10
|
+
end
|
11
|
+
|
12
|
+
logger do |msg|
|
13
|
+
puts "--> #{msg}"
|
14
|
+
end
|
15
|
+
|
16
|
+
job "math.incr" do |args|
|
17
|
+
{ "number" => (1 + args["number"].to_i) }
|
18
|
+
end
|
19
|
+
|
20
|
+
job "math.double" do |args|
|
21
|
+
{ "number" => (2 * args["number"].to_i) }
|
22
|
+
end
|
23
|
+
|
24
|
+
job "math.square" do |args|
|
25
|
+
{ "number" => (args["number"].to_i * args["number"].to_i) }
|
26
|
+
end
|
27
|
+
|
28
|
+
job "math.print" do |args|
|
29
|
+
puts "NUMBER -----> #{args["number"]}"
|
30
|
+
end
|
31
|
+
|
32
|
+
enqueue([ "math.incr", "math.double", "math.square", "math.incr", "math.double", "math.print" ], { :number => 3 })
|
33
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'minion'
|
5
|
+
|
6
|
+
include Minion
|
7
|
+
|
8
|
+
job "add.bread" do |args|
|
9
|
+
{ "bread" => "sourdough" }
|
10
|
+
end
|
11
|
+
|
12
|
+
job "add.meat" do |args|
|
13
|
+
{ "meat" => "turkey" }
|
14
|
+
end
|
15
|
+
|
16
|
+
job "add.condiments" do |args|
|
17
|
+
{ "condiments" => "mayo" }
|
18
|
+
end
|
19
|
+
|
20
|
+
job "eat.sandwich" do |args|
|
21
|
+
puts "YUM! A #{args['meat']} on #{args['bread']} sandwich with #{args['condiments']}"
|
22
|
+
end
|
23
|
+
|
24
|
+
enqueue(["add.bread", "add.meat", "add.condiments", "eat.sandwich" ])
|
25
|
+
|
data/lib/minion.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
require 'mq'
|
4
|
+
require 'bunny'
|
5
|
+
|
6
|
+
module Minion
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def enqueue(jobs, data = {})
|
10
|
+
## jobs can be one or more jobs
|
11
|
+
if jobs.respond_to? :shift
|
12
|
+
queue = jobs.shift
|
13
|
+
data["next_job"] = jobs unless jobs.empty?
|
14
|
+
else
|
15
|
+
queue = jobs
|
16
|
+
end
|
17
|
+
|
18
|
+
log "send: #{queue}:#{data.to_json}"
|
19
|
+
bunny.queue(queue, :durable => true, :auto_delete => false).publish(data.to_json)
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_error(&blk)
|
23
|
+
@@error_handler = blk
|
24
|
+
end
|
25
|
+
|
26
|
+
def logger(&blk)
|
27
|
+
@@logger = blk
|
28
|
+
end
|
29
|
+
|
30
|
+
def job(queue, &blk)
|
31
|
+
handler do
|
32
|
+
MQ.queue(queue).subscribe(:ack => true) do |h,m|
|
33
|
+
return if AMQP.closing?
|
34
|
+
begin
|
35
|
+
log "recv: #{queue}:#{m}"
|
36
|
+
|
37
|
+
args = JSON.load(m)
|
38
|
+
|
39
|
+
result = yield(args)
|
40
|
+
|
41
|
+
next_job(args, result)
|
42
|
+
rescue Object => e
|
43
|
+
raise unless error_handler
|
44
|
+
error_handler.call(e)
|
45
|
+
end
|
46
|
+
h.ack
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def run
|
52
|
+
log "Starting minion"
|
53
|
+
|
54
|
+
Signal.trap('INT') { AMQP.stop{ EM.stop } }
|
55
|
+
Signal.trap('TERM'){ AMQP.stop{ EM.stop } }
|
56
|
+
|
57
|
+
EM.run do
|
58
|
+
AMQP.start(amqp_config) do
|
59
|
+
MQ.prefetch(1)
|
60
|
+
@@handlers.each { |h| h.call }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def amqp_url
|
68
|
+
ENV["AMQP_URL"] || "amqp://guest:guest@localhost/"
|
69
|
+
end
|
70
|
+
|
71
|
+
def amqp_config
|
72
|
+
uri = URI.parse(amqp_url)
|
73
|
+
{
|
74
|
+
:vhost => uri.path,
|
75
|
+
:host => uri.host,
|
76
|
+
:user => uri.user,
|
77
|
+
:port => (uri.port || 5672),
|
78
|
+
:pass => uri.password
|
79
|
+
}
|
80
|
+
rescue
|
81
|
+
raise "invalid AMQP_URL: #{uri.inspect} (#{e})"
|
82
|
+
end
|
83
|
+
|
84
|
+
def new_bunny
|
85
|
+
b = Bunny.new(amqp_config)
|
86
|
+
b.start
|
87
|
+
b
|
88
|
+
end
|
89
|
+
|
90
|
+
def bunny
|
91
|
+
@@bunny ||= new_bunny
|
92
|
+
end
|
93
|
+
|
94
|
+
def log(msg)
|
95
|
+
@@logger ||= proc { |m| puts "#{Time.now} :minion: #{m}" }
|
96
|
+
@@logger.call(msg)
|
97
|
+
end
|
98
|
+
|
99
|
+
def handler(&blk)
|
100
|
+
@@handlers ||= []
|
101
|
+
at_exit { Minion.run } if @@handlers.size == 0
|
102
|
+
@@handlers << blk
|
103
|
+
end
|
104
|
+
|
105
|
+
def next_job(args, response)
|
106
|
+
queue = args.delete("next_job")
|
107
|
+
enqueue(queue,args.merge(response)) if queue
|
108
|
+
end
|
109
|
+
|
110
|
+
def error_handler
|
111
|
+
@@error_handler ||= nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adamwiggins-minion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Orion Henry
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-19 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: tmm1-amqp
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.6.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.7
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bunny
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.5.2
|
44
|
+
version:
|
45
|
+
description: Super simple job queue over AMQP
|
46
|
+
email: orion@heroku.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.rdoc
|
53
|
+
files:
|
54
|
+
- README.rdoc
|
55
|
+
- VERSION
|
56
|
+
- examples/math.rb
|
57
|
+
- examples/sandwich.rb
|
58
|
+
- lib/minion.rb
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://github.com/orionz/minion
|
61
|
+
licenses:
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options:
|
64
|
+
- --charset=UTF-8
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: "0"
|
78
|
+
version:
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project: minion
|
82
|
+
rubygems_version: 1.3.5
|
83
|
+
signing_key:
|
84
|
+
specification_version: 2
|
85
|
+
summary: Super simple job queue over AMQP
|
86
|
+
test_files:
|
87
|
+
- examples/math.rb
|
88
|
+
- examples/sandwich.rb
|