qusion 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ application:
2
+ host: localhost
3
+ port: 5672
4
+ user: guest
5
+ pass: guest
6
+ vhost: /
7
+ timeout: 600
8
+ logging: false
9
+ ssl: false
@@ -0,0 +1,14 @@
1
+ class Rails
2
+ module VERSION
3
+ MAJOR = 3
4
+ end
5
+
6
+ def self.env
7
+ # ActiveSupport::StringInquirer.new("test")
8
+ "test"
9
+ end
10
+
11
+ def self.root
12
+ "/path/to/rails"
13
+ end
14
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ -f specdoc -c -t 2
@@ -0,0 +1,4 @@
1
+ # encoding: UTF-8
2
+ require 'spec/autorun'
3
+ require 'qusion'
4
+ include Qusion
@@ -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