qusion 0.1.1
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.
- data/Gemfile +11 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +20 -0
- data/README.rdoc +132 -0
- data/Rakefile +71 -0
- data/VERSION +1 -0
- data/lib/qusion.rb +78 -0
- data/lib/qusion/amqp_config.rb +63 -0
- data/lib/qusion/channel_pool.rb +64 -0
- data/qusion.gemspec +79 -0
- data/spec/fixtures/framework-amqp.yml +28 -0
- data/spec/fixtures/hardcoded-amqp.yml +9 -0
- data/spec/mock_rails.rb +14 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/unit/amqp_config_spec.rb +48 -0
- data/spec/unit/channel_pool_spec.rb +48 -0
- data/spec/unit/qusion_spec.rb +81 -0
- metadata +177 -0
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "rake", ">= 0.8.7"
|
4
|
+
gem "bundler", "~> 1.0.0"
|
5
|
+
gem "amqp", "0.6.7", :require => 'mq'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem "jeweler", "~> 1.5.1" # adds Bundler support for gemspec generation
|
9
|
+
gem "cucumber", ">=0.9.4"
|
10
|
+
gem "rspec", "~>1.3.0"
|
11
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
amqp (0.6.7)
|
5
|
+
eventmachine (>= 0.12.4)
|
6
|
+
builder (3.0.0)
|
7
|
+
cucumber (0.10.0)
|
8
|
+
builder (>= 2.1.2)
|
9
|
+
diff-lcs (~> 1.1.2)
|
10
|
+
gherkin (~> 2.3.2)
|
11
|
+
json (~> 1.4.6)
|
12
|
+
term-ansicolor (~> 1.0.5)
|
13
|
+
diff-lcs (1.1.2)
|
14
|
+
eventmachine (0.12.10)
|
15
|
+
gherkin (2.3.2)
|
16
|
+
json (~> 1.4.6)
|
17
|
+
term-ansicolor (~> 1.0.5)
|
18
|
+
git (1.2.5)
|
19
|
+
jeweler (1.5.1)
|
20
|
+
bundler (~> 1.0.0)
|
21
|
+
git (>= 1.2.5)
|
22
|
+
rake
|
23
|
+
json (1.4.6)
|
24
|
+
rake (0.8.7)
|
25
|
+
rspec (1.3.1)
|
26
|
+
term-ansicolor (1.0.5)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
amqp (= 0.6.7)
|
33
|
+
bundler (~> 1.0.0)
|
34
|
+
cucumber (>= 0.9.4)
|
35
|
+
jeweler (~> 1.5.1)
|
36
|
+
rake (>= 0.8.7)
|
37
|
+
rspec (~> 1.3.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009-2011 Daniel DeLeo, CustomInk, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
= Qusion
|
2
|
+
|
3
|
+
Qusion makes AMQP[http://github.com/tmm1/amqp] work with your webserver with
|
4
|
+
no fuss. It offers three features:
|
5
|
+
* It sets up the required callbacks and/or worker threads so that AMQP will work with Passenger, Thin, or Mongrel. WEBrick, SCGI, and Evented Mongrel are experimentally supported, but not heavily tested.
|
6
|
+
* A Channel Pool. You can cause problems for yourself if you create new channels (with MQ.new) for every request. The pool sets up a few of these when your app starts and reuses them.
|
7
|
+
* YAML configuration files. In Rails, create config/amqp.yml then fill in the details for development, test and production. Use Qusion.start() in your environment.rb file (or an initializer) and you're good to go.
|
8
|
+
|
9
|
+
= This fork of Qusion
|
10
|
+
|
11
|
+
This is a fork of James Tucker's fork[https://github.com/raggi/qusion] of
|
12
|
+
Dan DeLeo's original version[https://github.com/danielsdeleo/qusion]. Tucker
|
13
|
+
did a fair amount of cleanup, mostly to the threading logic, so I went with his
|
14
|
+
fork. Improvements I've made include:
|
15
|
+
* Conversion from a Rails plugin to this gem.
|
16
|
+
* Support for Rails 3.
|
17
|
+
* Removed support for Merb (it complicated the code and tests; just use Rails 3).
|
18
|
+
|
19
|
+
= Before You Start
|
20
|
+
|
21
|
+
Qusion makes it easy to just install and start using AMQP in your application.
|
22
|
+
But there are many ways to use background jobs within a Rails app, so it's
|
23
|
+
worth taking some time to consider the tradeoffs of each approach.
|
24
|
+
|
25
|
+
* If your background job needs are simple and you're using a relational database, Delayed::Job[http://github.com/tobi/delayed_job/] lets you schedule background tasks through the database. You won't need to run another application (the AMQP Broker) to keep your app running.
|
26
|
+
* The 0.6.x version of the Ruby amqp library may drop messages when the AMQP broker goes down. Pivotal Labs has discussed this problem on their blog[http://pivots.pivotallabs.com/users/will/blog/articles/966-how-to-not-test-rabbitmq-part-1]. This issue will likely be addressed in the 0.7.0 release of amqp, but can be avoided entirely using a synchronous amqp library such as bunny[http://github.com/celldee/bunny]. For a ready-made background job solution using Bunny to publish jobs to the queue, see Minion[http://github.com/orionz/minion/].
|
27
|
+
* Qusion runs EventMachine in a separate thread on Phusion Passenger, Mongrel, and other non-evented servers. There are some inefficiencies in Ruby 1.8's threading model that make running EM in a thread quite slow. Joe Damato and Aman Gupta have created a patch[http://github.com/ice799/matzruby/tree/heap_stacks] for the problem which is included in an experimental branch of REE. You can learn more about the patch from Phusion's Blog[http://blog.phusion.nl/2009/12/15/google-tech-talk-on-ruby-enterprise-edition/].
|
28
|
+
|
29
|
+
= Getting Started
|
30
|
+
|
31
|
+
First you'll need the amqp library and a working RabbitMQ installation. This
|
32
|
+
entails:
|
33
|
+
|
34
|
+
* Install Erlang for your platform
|
35
|
+
* Install RabbitMQ for your platform
|
36
|
+
* On OSX, use Homebrew to install Erlang and RabbitMQ: brew install rabbitmq
|
37
|
+
* Install bundler: http://gembundler.com/
|
38
|
+
== Install Qusion
|
39
|
+
Include the qusion gem in your Gemfile:
|
40
|
+
|
41
|
+
gem "qusion"
|
42
|
+
|
43
|
+
Create an initializer (e.g. config/initializers/qusion.rb) and add:
|
44
|
+
Qusion.start
|
45
|
+
|
46
|
+
EM.next_tick do
|
47
|
+
# do some AMQP stuff
|
48
|
+
end
|
49
|
+
|
50
|
+
And that's it! This will set up AMQP for any ruby app server (tested on
|
51
|
+
mongrel, thin, and passenger). Now, you can use all of AMQP's functionality as
|
52
|
+
normal. In your controllers or models, you might have:
|
53
|
+
|
54
|
+
MQ.new.queue("my-work-queue").publish("do work, son!")
|
55
|
+
|
56
|
+
and it should just work.
|
57
|
+
|
58
|
+
= Channel Pools
|
59
|
+
|
60
|
+
It's considered bad practice to use MQ.new over and over, as it creates a new
|
61
|
+
AMQP channel, and that creates a new Erlang process in RabbitMQ. Erlang
|
62
|
+
processes are super light weight, but you'll be wasting them and causing the
|
63
|
+
Erlang VM GC headaches if you create them wantonly. So don't do that. Instead,
|
64
|
+
use the channel pool provided by Qusion. It's simple: wherever you'd normally
|
65
|
+
put MQ.new, just replace it with Qusion.channel. Examples:
|
66
|
+
|
67
|
+
# Create a queue:
|
68
|
+
Qusion.channel.queue("my-worker-queue")
|
69
|
+
# Topics:
|
70
|
+
Qusion.channel.topic("my-topic-exchange")
|
71
|
+
# etc.
|
72
|
+
|
73
|
+
This feature is a bit experimental, so the optimal pool size isn't known yet.
|
74
|
+
The default is 5. You can change it by adding something like the following to
|
75
|
+
your environment.rb:
|
76
|
+
|
77
|
+
Qusion.channel_pool_size(3)
|
78
|
+
|
79
|
+
= Configuration
|
80
|
+
|
81
|
+
You can put your AMQP server details in config/amqp.yml and Qusion will load
|
82
|
+
it when you call Qusion.start(). Example:
|
83
|
+
|
84
|
+
# Put this in config/amqp.yml
|
85
|
+
default: &default
|
86
|
+
host: localhost
|
87
|
+
port: 5672
|
88
|
+
user: guest
|
89
|
+
pass: guest
|
90
|
+
vhost: /
|
91
|
+
timeout: 3600 # seconds
|
92
|
+
logging: false
|
93
|
+
ssl: false
|
94
|
+
|
95
|
+
development:
|
96
|
+
<<: *default
|
97
|
+
|
98
|
+
test:
|
99
|
+
<<: *default
|
100
|
+
|
101
|
+
If you're too hardcore for Rails (maybe you're using Sinatra or Ramaze), you
|
102
|
+
can still use a YAML config file, but there's no support for different
|
103
|
+
environments. So do something like this:
|
104
|
+
|
105
|
+
# Tell Qusion where your config file is:
|
106
|
+
Qusion.start("/path/to/amqp.yml")
|
107
|
+
|
108
|
+
# Your configuration looks like this:
|
109
|
+
application:
|
110
|
+
host: localhost
|
111
|
+
port: 5672
|
112
|
+
...
|
113
|
+
|
114
|
+
If you just want to get started without configuring anything, Qusion.start()
|
115
|
+
will use the default options if it can't find a config file. And, finally, you
|
116
|
+
can give options directly to Qusion.start() like this:
|
117
|
+
|
118
|
+
Qusion.start(:host => "my-amqp-broker.mydomain.com", :user => "me", :pass => "am_I_really_putting_this_in_VCS?")
|
119
|
+
|
120
|
+
|
121
|
+
= Bugs? Hacking?
|
122
|
+
|
123
|
+
If you find any bugs, or feel the need to add a feature, fork away. You can
|
124
|
+
also contact me directly via the email address in my profile if you have any
|
125
|
+
questions.
|
126
|
+
|
127
|
+
= Shouts
|
128
|
+
* Qusion's code for Phusion Passenger's starting_worker_process event was originally posted by Aman Gupta (tmm1[http://github.com/tmm1]) on the AMQP list[http://groups.google.com/group/ruby-amqp]
|
129
|
+
* Brightbox's Warren[http://github.com/brightbox/warren] library provides some similar functionality. It doesn't support webserver-specific EventMachine setup, but it does have built-in encryption and support for the synchronous (non-EventMachine) Bunny[http://github.com/celldee/bunny] AMQP client.
|
130
|
+
|
131
|
+
Original author: dan@kallistec.com
|
132
|
+
Forked by: cmurphy@customink.com
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
require "spec/rake/spectask"
|
6
|
+
require "cucumber"
|
7
|
+
require "cucumber/rake/task"
|
8
|
+
|
9
|
+
desc 'Default: run specs.'
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
begin
|
13
|
+
Bundler.setup(:default, :development)
|
14
|
+
rescue Bundler::BundlerError => e
|
15
|
+
$stderr.puts e.message
|
16
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
17
|
+
exit e.status_code
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'jeweler'
|
22
|
+
Jeweler::Tasks.new do |gem|
|
23
|
+
gem.name = "qusion"
|
24
|
+
gem.summary = "Makes AMQP work with Ruby on Rails with no fuss."
|
25
|
+
gem.description = %Q{See the README for more details.}
|
26
|
+
gem.email = "cmurphy@customink.com"
|
27
|
+
gem.homepage = "http://github.com/customink/qusion"
|
28
|
+
gem.authors = ["Dan DeLeo", "Christopher R. Murphy"]
|
29
|
+
end
|
30
|
+
rescue LoadError
|
31
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Run Cucumber Features"
|
35
|
+
Cucumber::Rake::Task.new do |t|
|
36
|
+
t.cucumber_opts = "-c -n"
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Run all of the specs"
|
40
|
+
Spec::Rake::SpecTask.new do |t|
|
41
|
+
t.ruby_opts << '-rubygems'
|
42
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
43
|
+
t.fail_on_error = false
|
44
|
+
end
|
45
|
+
|
46
|
+
namespace :spec do
|
47
|
+
desc "Generate HTML report for failing examples"
|
48
|
+
Spec::Rake::SpecTask.new('report') do |t|
|
49
|
+
t.ruby_opts << '-rubygems'
|
50
|
+
t.spec_files = FileList['failing_examples/**/*.rb']
|
51
|
+
t.spec_opts = ["--format", "html:doc/tools/reports/failing_examples.html", "--diff", '--options', '"spec/spec.opts"']
|
52
|
+
t.fail_on_error = false
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Run all spec with RCov"
|
56
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
57
|
+
t.ruby_opts << '-rubygems'
|
58
|
+
t.rcov = true
|
59
|
+
t.rcov_dir = 'doc/tools/coverage/'
|
60
|
+
t.rcov_opts = ['--exclude', 'spec']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Rake::RDocTask.new do |rdoc|
|
65
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
66
|
+
|
67
|
+
rdoc.rdoc_dir = 'rdoc'
|
68
|
+
rdoc.title = "qusion #{version}"
|
69
|
+
rdoc.rdoc_files.include('README*')
|
70
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
71
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/lib/qusion.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "eventmachine"
|
3
|
+
require "mq"
|
4
|
+
|
5
|
+
require "qusion/channel_pool"
|
6
|
+
require "qusion/amqp_config"
|
7
|
+
|
8
|
+
module Qusion
|
9
|
+
class << self
|
10
|
+
attr_reader :thread
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.start(*opts)
|
14
|
+
amqp_opts = AmqpConfig.new(*opts).config_opts
|
15
|
+
start_amqp_dispatcher(amqp_opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.start_amqp_dispatcher(amqp_settings={})
|
19
|
+
AMQP.settings.merge!(amqp_settings)
|
20
|
+
|
21
|
+
if defined?(::PhusionPassenger) && ::PhusionPassenger.respond_to?(:on_event)
|
22
|
+
::PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
23
|
+
if forked
|
24
|
+
EM.stop if EM.reactor_running?
|
25
|
+
thread && thread.join
|
26
|
+
Thread.current[:mq] = nil
|
27
|
+
AMQP.instance_variable_set(:@conn, nil)
|
28
|
+
end
|
29
|
+
start_in_background
|
30
|
+
die_gracefully_on_signal
|
31
|
+
end
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
start_in_background
|
36
|
+
die_gracefully_on_signal
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.die_gracefully_on_signal
|
40
|
+
Signal.trap("INT") { graceful_stop }
|
41
|
+
Signal.trap("TERM") { graceful_stop }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.channel
|
45
|
+
ChannelPool.instance.channel
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.channel_pool_size(new_pool_size)
|
49
|
+
ChannelPool.pool_size = new_pool_size
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.start_in_background
|
53
|
+
if EM.reactor_running?
|
54
|
+
raise ArgumentError, 'AMQP already connected' if ready_to_dispatch?
|
55
|
+
AMQP.start
|
56
|
+
else
|
57
|
+
raise ArgumentError, 'Qusion already started' if thread && thread.alive?
|
58
|
+
@thread = Thread.new do
|
59
|
+
EM.run { AMQP.start }
|
60
|
+
raise "Premature AMQP shutdown" unless @graceful_stop
|
61
|
+
end
|
62
|
+
thread.abort_on_exception = true
|
63
|
+
thread.join(0.1) until ready_to_dispatch?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.graceful_stop
|
68
|
+
EM.schedule do
|
69
|
+
@graceful_stop = true
|
70
|
+
AMQP.stop { EM.stop }
|
71
|
+
end
|
72
|
+
thread && thread.join
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.ready_to_dispatch?
|
76
|
+
EM.reactor_running? && AMQP.conn && AMQP.conn.connected?
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Qusion
|
5
|
+
|
6
|
+
class AmqpConfig
|
7
|
+
attr_reader :config_path, :framework_env
|
8
|
+
|
9
|
+
def initialize(opts=nil)
|
10
|
+
if opts && opts.respond_to?(:keys)
|
11
|
+
@config_path = nil
|
12
|
+
@config_opts = opts
|
13
|
+
elsif opts
|
14
|
+
@config_path = opts
|
15
|
+
else
|
16
|
+
load_framework_config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_framework_config
|
21
|
+
@config_path = Rails.root + "/config/amqp.yml"
|
22
|
+
@framework_env = Rails.env
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_opts
|
26
|
+
@config_opts ||= load_config_opts
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_config_opts
|
30
|
+
if config_path && config_from_yaml = load_amqp_config_file
|
31
|
+
if framework_env
|
32
|
+
framework_amqp_opts(config_from_yaml)
|
33
|
+
else
|
34
|
+
amqp_opts(config_from_yaml)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def framework_amqp_opts(config_hash)
|
42
|
+
symbolize_keys(config_hash[framework_env.to_s])
|
43
|
+
end
|
44
|
+
|
45
|
+
def amqp_opts(config_hash)
|
46
|
+
symbolize_keys(config_hash.first.last)
|
47
|
+
end
|
48
|
+
|
49
|
+
def symbolize_keys(config_hash)
|
50
|
+
symbolized_hsh = {}
|
51
|
+
config_hash.each {|option, value| symbolized_hsh[option.to_sym] = value }
|
52
|
+
symbolized_hsh
|
53
|
+
end
|
54
|
+
|
55
|
+
def load_amqp_config_file
|
56
|
+
begin
|
57
|
+
YAML.load_file(config_path)
|
58
|
+
rescue Errno::ENOENT
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "singleton"
|
3
|
+
|
4
|
+
module Qusion
|
5
|
+
|
6
|
+
# ChannelPool maintains a pool of AMQP channel objects that can be reused.
|
7
|
+
# The motivation behind this is that if you were to use MQ.new to create a
|
8
|
+
# new channel for every request, your AMQP broker could be swamped trying to
|
9
|
+
# maintain a bunch of channels that you're only using once.
|
10
|
+
#
|
11
|
+
# To use the channel pool, just replace <tt>MQ.new</tt> in your code with <tt>Qusion.channel</tt>
|
12
|
+
#
|
13
|
+
# # Instead of this:
|
14
|
+
# MQ.new.queue("my-worker-queue")
|
15
|
+
# # Do this:
|
16
|
+
# Qusion.channel.queue("my-worker-queue")
|
17
|
+
#
|
18
|
+
# By default, ChannelPool maintains a pool of 5 channels. This can be adjusted with
|
19
|
+
# <tt>ChannelPool.pool_size=()</tt> or <tt>Qusion.channel_pool_size()</tt>
|
20
|
+
# The optimal pool size is not yet known, but I suspect you might need a
|
21
|
+
# larger value if using Thin in production, and a smaller value otherwise.
|
22
|
+
class ChannelPool
|
23
|
+
include Singleton
|
24
|
+
|
25
|
+
class << self
|
26
|
+
|
27
|
+
def pool_size=(new_pool_size)
|
28
|
+
reset
|
29
|
+
@pool_size = new_pool_size
|
30
|
+
end
|
31
|
+
|
32
|
+
def pool_size
|
33
|
+
@pool_size ||= 5
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset
|
37
|
+
@pool_size = nil
|
38
|
+
instance.reset
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :pool
|
44
|
+
|
45
|
+
def channel
|
46
|
+
@i ||= 1
|
47
|
+
@i = (@i + 1) % pool_size
|
48
|
+
pool[@i]
|
49
|
+
end
|
50
|
+
|
51
|
+
def pool
|
52
|
+
@pool ||= Array.new(pool_size) { MQ.new }
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset
|
56
|
+
@pool = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def pool_size
|
60
|
+
self.class.pool_size
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/qusion.gemspec
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{qusion}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Dan DeLeo", "Christopher R. Murphy"]
|
12
|
+
s.date = %q{2010-12-09}
|
13
|
+
s.description = %q{See the README for more details.}
|
14
|
+
s.email = %q{cmurphy@customink.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/qusion.rb",
|
27
|
+
"lib/qusion/amqp_config.rb",
|
28
|
+
"lib/qusion/channel_pool.rb",
|
29
|
+
"qusion.gemspec",
|
30
|
+
"spec/fixtures/framework-amqp.yml",
|
31
|
+
"spec/fixtures/hardcoded-amqp.yml",
|
32
|
+
"spec/mock_rails.rb",
|
33
|
+
"spec/spec.opts",
|
34
|
+
"spec/spec_helper.rb",
|
35
|
+
"spec/unit/amqp_config_spec.rb",
|
36
|
+
"spec/unit/channel_pool_spec.rb",
|
37
|
+
"spec/unit/qusion_spec.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/customink/qusion}
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubygems_version = %q{1.3.7}
|
42
|
+
s.summary = %q{Makes AMQP work with Ruby on Rails with no fuss.}
|
43
|
+
s.test_files = [
|
44
|
+
"spec/mock_rails.rb",
|
45
|
+
"spec/spec_helper.rb",
|
46
|
+
"spec/unit/amqp_config_spec.rb",
|
47
|
+
"spec/unit/channel_pool_spec.rb",
|
48
|
+
"spec/unit/qusion_spec.rb"
|
49
|
+
]
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
53
|
+
s.specification_version = 3
|
54
|
+
|
55
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
56
|
+
s.add_runtime_dependency(%q<rake>, [">= 0.8.7"])
|
57
|
+
s.add_runtime_dependency(%q<bundler>, ["~> 1.0.0"])
|
58
|
+
s.add_runtime_dependency(%q<amqp>, ["= 0.6.7"])
|
59
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
60
|
+
s.add_development_dependency(%q<cucumber>, [">= 0.9.4"])
|
61
|
+
s.add_development_dependency(%q<rspec>, ["~> 1.3.0"])
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<rake>, [">= 0.8.7"])
|
64
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
65
|
+
s.add_dependency(%q<amqp>, ["= 0.6.7"])
|
66
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
67
|
+
s.add_dependency(%q<cucumber>, [">= 0.9.4"])
|
68
|
+
s.add_dependency(%q<rspec>, ["~> 1.3.0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rake>, [">= 0.8.7"])
|
72
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
73
|
+
s.add_dependency(%q<amqp>, ["= 0.6.7"])
|
74
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
75
|
+
s.add_dependency(%q<cucumber>, [">= 0.9.4"])
|
76
|
+
s.add_dependency(%q<rspec>, ["~> 1.3.0"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
development:
|
2
|
+
host: localhost
|
3
|
+
port: 5672
|
4
|
+
user: guest
|
5
|
+
pass: guest
|
6
|
+
vhost: /
|
7
|
+
timeout: 600
|
8
|
+
logging: false
|
9
|
+
ssl: false
|
10
|
+
|
11
|
+
test:
|
12
|
+
host: localhost
|
13
|
+
port: 5672
|
14
|
+
user: guest
|
15
|
+
pass: guest
|
16
|
+
vhost: /
|
17
|
+
logging: false
|
18
|
+
ssl: false
|
19
|
+
|
20
|
+
production:
|
21
|
+
host: localhost
|
22
|
+
port: 5672
|
23
|
+
user: guest
|
24
|
+
pass: guest
|
25
|
+
vhost: /
|
26
|
+
timeout: 3600
|
27
|
+
logging: false
|
28
|
+
ssl: false
|
data/spec/mock_rails.rb
ADDED
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-f specdoc -c -t 2
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.dirname(__FILE__) + "/../../spec_helper"
|
3
|
+
require File.dirname(__FILE__) + "/../../mock_rails"
|
4
|
+
|
5
|
+
describe AmqpConfig do
|
6
|
+
|
7
|
+
it "should use #{Rails.root}/config/amqp.yml" do
|
8
|
+
AmqpConfig.new.config_path.should == "/path/to/rails/config/amqp.yml"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should use the provided path no matter what" do
|
12
|
+
path = AmqpConfig.new("/custom/path/to/amqp.yml").config_path
|
13
|
+
path.should == "/custom/path/to/amqp.yml"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should use a provided options hash if given" do
|
17
|
+
conf = AmqpConfig.new(:host => "my-broker.mydomain.com")
|
18
|
+
conf.config_path.should be_nil
|
19
|
+
conf.config_opts.should == {:host => "my-broker.mydomain.com"}
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should use the default amqp options in rails if amqp.yml doesn't exist" do
|
23
|
+
Rails.stub!(:root).and_return(File.dirname(__FILE__) + '/../')
|
24
|
+
AmqpConfig.new.config_opts.should == {}
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should load a YAML file when using a framework" do
|
28
|
+
conf = AmqpConfig.new
|
29
|
+
conf.stub!(:config_path).and_return(File.dirname(__FILE__) + "/../fixtures/framework-amqp.yml")
|
30
|
+
conf.stub!(:framework_env).and_return("production")
|
31
|
+
conf.config_opts.should == {:host => 'localhost',:port => 5672,
|
32
|
+
:user => 'guest', :pass => 'guest',
|
33
|
+
:vhost => '/', :timeout => 3600,
|
34
|
+
:logging => false, :ssl => false}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should use the first set of opts when given a explicit file path" do
|
38
|
+
conf = AmqpConfig.new
|
39
|
+
conf.stub!(:config_path).and_return(File.dirname(__FILE__) + "/../fixtures/hardcoded-amqp.yml")
|
40
|
+
conf.stub!(:framework_env).and_return(nil)
|
41
|
+
p conf.config_opts
|
42
|
+
conf.config_opts.should == {:host => 'localhost',:port => 5672,
|
43
|
+
:user => 'guest', :pass => 'guest',
|
44
|
+
:vhost => '/', :timeout => 600,
|
45
|
+
:logging => false, :ssl => false}
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.dirname(__FILE__) + "/../../spec_helper"
|
3
|
+
|
4
|
+
describe ChannelPool do
|
5
|
+
MQ = Object.new
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
ChannelPool.reset
|
9
|
+
@channel_pool = ChannelPool.instance
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be singleton" do
|
13
|
+
lambda { ChannelPool.new }.should raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should adjust the pool size" do
|
17
|
+
ChannelPool.pool_size = 5
|
18
|
+
ChannelPool.pool_size.should == 5
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should reset itself when the pool size is set" do
|
22
|
+
ChannelPool.should_receive(:reset)
|
23
|
+
ChannelPool.pool_size = 23
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should create a pool of AMQP channels" do
|
27
|
+
ChannelPool.pool_size = 3
|
28
|
+
::MQ.should_receive(:new).exactly(3).times
|
29
|
+
@channel_pool.pool
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should default to a pool size of 5" do
|
33
|
+
::MQ.should_receive(:new).exactly(5).times.and_return("swanky")
|
34
|
+
@channel_pool.pool
|
35
|
+
@channel_pool.instance_variable_get(:@pool).should == %w{ swanky swanky swanky swanky swanky}
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return a channel in a round-robin" do
|
39
|
+
@channel_pool.instance_variable_set(:@pool, [1,2,3,4,5])
|
40
|
+
@channel_pool.channel.should == 3
|
41
|
+
@channel_pool.channel.should == 4
|
42
|
+
@channel_pool.channel.should == 5
|
43
|
+
@channel_pool.channel.should == 1
|
44
|
+
@channel_pool.channel.should == 2
|
45
|
+
@channel_pool.channel.should == 3
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.dirname(__FILE__) + "/../../spec_helper"
|
3
|
+
|
4
|
+
describe "Qusion Convenience Methods" do
|
5
|
+
|
6
|
+
it "should get a channel from the pool" do
|
7
|
+
channel_pool = mock("channel pool")
|
8
|
+
ChannelPool.should_receive(:instance).and_return(channel_pool)
|
9
|
+
channel_pool.should_receive(:channel)
|
10
|
+
Qusion.channel
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set the channel pool size" do
|
14
|
+
ChannelPool.should_receive(:pool_size=).with(7)
|
15
|
+
Qusion.channel_pool_size(7)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should load the configuration and setup AMQP for the webserver" do
|
19
|
+
config = mock("config")
|
20
|
+
AmqpConfig.should_receive(:new).and_return(config)
|
21
|
+
config.should_receive(:config_opts).and_return(:config => :opts)
|
22
|
+
Qusion.should_receive(:start_amqp_dispatcher).with(:config => :opts)
|
23
|
+
Qusion.start
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Qusion, 'amqp startup' do
|
29
|
+
|
30
|
+
before do
|
31
|
+
AMQP.stub!(:settings).and_return({})
|
32
|
+
end
|
33
|
+
|
34
|
+
after(:each) do
|
35
|
+
Object.send(:remove_const, :PhusionPassenger) if defined? ::PhusionPassenger
|
36
|
+
Object.send(:remove_const, :Thin) if defined? ::Thin
|
37
|
+
Object.send(:remove_const, :Mongrel) if defined? ::Mongrel
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should kill the reactor and start a new AMQP connection when forked in Passenger" do
|
41
|
+
Qusion.should_receive(:die_gracefully_on_signal).once
|
42
|
+
::PhusionPassenger = Module.new
|
43
|
+
forked = mock("starting_worker_process_callback_obj")
|
44
|
+
::PhusionPassenger.should_receive(:on_event).with(:starting_worker_process).and_yield(forked)
|
45
|
+
EM.should_receive(:reactor_running?).exactly(3).times.and_return(true)
|
46
|
+
amqp_conn = mock('amqp_conn')
|
47
|
+
amqp_conn.should_receive(:connected?).and_return(false)
|
48
|
+
AMQP.should_receive(:conn).any_number_of_times.and_return(amqp_conn)
|
49
|
+
EM.should_receive(:stop)
|
50
|
+
AMQP.should_receive(:start).once
|
51
|
+
Qusion.start_amqp_dispatcher
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should set AMQP's connection settings when running under Thin" do
|
55
|
+
Qusion.should_receive(:die_gracefully_on_signal)
|
56
|
+
Qusion.should_receive(:start_in_background)
|
57
|
+
::Thin = Module.new
|
58
|
+
Qusion.start_amqp_dispatcher(:cookie => "yummy")
|
59
|
+
AMQP.settings[:cookie].should == "yummy"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should start a worker thread when running under Mongrel" do
|
63
|
+
Qusion.should_receive(:die_gracefully_on_signal)
|
64
|
+
mock_thread = mock('thread')
|
65
|
+
mock_thread.should_receive(:abort_on_exception=).with(true)
|
66
|
+
Qusion.should_receive(:ready_to_dispatch?).twice.and_return(false, true)
|
67
|
+
mock_thread.should_receive(:join)
|
68
|
+
Thread.should_receive(:new).and_return(mock_thread)
|
69
|
+
::Mongrel = Module.new
|
70
|
+
Qusion.start_amqp_dispatcher
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be ready to dispatch when the reactor is running and amqp is connected" do
|
74
|
+
EM.should_receive(:reactor_running?).and_return(true)
|
75
|
+
amqp_conn = mock('amqp_conn')
|
76
|
+
amqp_conn.should_receive(:connected?).and_return(true)
|
77
|
+
AMQP.should_receive(:conn).any_number_of_times.and_return(amqp_conn)
|
78
|
+
Qusion.ready_to_dispatch?.should == true
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: qusion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Dan DeLeo
|
13
|
+
- Christopher R. Murphy
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-09 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rake
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 8
|
31
|
+
- 7
|
32
|
+
version: 0.8.7
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 1
|
45
|
+
- 0
|
46
|
+
- 0
|
47
|
+
version: 1.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: amqp
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - "="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
- 6
|
61
|
+
- 7
|
62
|
+
version: 0.6.7
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: *id003
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: jeweler
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ~>
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 1
|
75
|
+
- 5
|
76
|
+
- 1
|
77
|
+
version: 1.5.1
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: cucumber
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
- 9
|
91
|
+
- 4
|
92
|
+
version: 0.9.4
|
93
|
+
type: :development
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: *id005
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rspec
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
segments:
|
104
|
+
- 1
|
105
|
+
- 3
|
106
|
+
- 0
|
107
|
+
version: 1.3.0
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: *id006
|
111
|
+
description: See the README for more details.
|
112
|
+
email: cmurphy@customink.com
|
113
|
+
executables: []
|
114
|
+
|
115
|
+
extensions: []
|
116
|
+
|
117
|
+
extra_rdoc_files:
|
118
|
+
- LICENSE
|
119
|
+
- README.rdoc
|
120
|
+
files:
|
121
|
+
- Gemfile
|
122
|
+
- Gemfile.lock
|
123
|
+
- LICENSE
|
124
|
+
- README.rdoc
|
125
|
+
- Rakefile
|
126
|
+
- VERSION
|
127
|
+
- lib/qusion.rb
|
128
|
+
- lib/qusion/amqp_config.rb
|
129
|
+
- lib/qusion/channel_pool.rb
|
130
|
+
- qusion.gemspec
|
131
|
+
- spec/fixtures/framework-amqp.yml
|
132
|
+
- spec/fixtures/hardcoded-amqp.yml
|
133
|
+
- spec/mock_rails.rb
|
134
|
+
- spec/spec.opts
|
135
|
+
- spec/spec_helper.rb
|
136
|
+
- spec/unit/amqp_config_spec.rb
|
137
|
+
- spec/unit/channel_pool_spec.rb
|
138
|
+
- spec/unit/qusion_spec.rb
|
139
|
+
has_rdoc: true
|
140
|
+
homepage: http://github.com/customink/qusion
|
141
|
+
licenses: []
|
142
|
+
|
143
|
+
post_install_message:
|
144
|
+
rdoc_options: []
|
145
|
+
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
hash: 2610415720212610617
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
version: "0"
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
segments:
|
163
|
+
- 0
|
164
|
+
version: "0"
|
165
|
+
requirements: []
|
166
|
+
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 1.3.7
|
169
|
+
signing_key:
|
170
|
+
specification_version: 3
|
171
|
+
summary: Makes AMQP work with Ruby on Rails with no fuss.
|
172
|
+
test_files:
|
173
|
+
- spec/mock_rails.rb
|
174
|
+
- spec/spec_helper.rb
|
175
|
+
- spec/unit/amqp_config_spec.rb
|
176
|
+
- spec/unit/channel_pool_spec.rb
|
177
|
+
- spec/unit/qusion_spec.rb
|