queueing_rabbit 0.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rvmrc +48 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/Rakefile +5 -0
- data/lib/queueing_rabbit/callbacks.rb +31 -0
- data/lib/queueing_rabbit/client/amqp.rb +148 -0
- data/lib/queueing_rabbit/client/bunny.rb +62 -0
- data/lib/queueing_rabbit/client/callbacks.rb +14 -0
- data/lib/queueing_rabbit/configuration.rb +24 -0
- data/lib/queueing_rabbit/job.rb +32 -0
- data/lib/queueing_rabbit/logging.rb +17 -0
- data/lib/queueing_rabbit/serializer.rb +19 -0
- data/lib/queueing_rabbit/tasks.rb +37 -0
- data/lib/queueing_rabbit/version.rb +3 -0
- data/lib/queueing_rabbit/worker.rb +96 -0
- data/lib/queueing_rabbit.rb +67 -0
- data/lib/tasks/queueing_rabbit.rake +2 -0
- data/queueing_rabbit.gemspec +49 -0
- data/spec/integration/asynchronous_publishing_and_consuming_spec.rb +62 -0
- data/spec/integration/jobs/print_line_job.rb +17 -0
- data/spec/integration/synchronous_publishing_and_asynchronous_consuming_spec.rb +39 -0
- data/spec/integration/synchronous_publishing_spec.rb +24 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/shared_contexts.rb +17 -0
- data/spec/support/shared_examples.rb +60 -0
- data/spec/unit/queueing_rabbit/callbacks_spec.rb +53 -0
- data/spec/unit/queueing_rabbit/client/amqp_spec.rb +193 -0
- data/spec/unit/queueing_rabbit/client/bunny_spec.rb +68 -0
- data/spec/unit/queueing_rabbit/client/callbacks_spec.rb +22 -0
- data/spec/unit/queueing_rabbit/configuration_spec.rb +19 -0
- data/spec/unit/queueing_rabbit/job_spec.rb +23 -0
- data/spec/unit/queueing_rabbit/logging_spec.rb +9 -0
- data/spec/unit/queueing_rabbit/serializer_spec.rb +26 -0
- data/spec/unit/queueing_rabbit/worker_spec.rb +133 -0
- data/spec/unit/queueing_rabbit_spec.rb +105 -0
- metadata +168 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
+
environment_id="ruby-1.9.3-p392@queueing_rabbit"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.18.18 (latest)" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
else
|
29
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
+
rvm --create "$environment_id" || {
|
31
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
+
return 1
|
33
|
+
}
|
34
|
+
fi
|
35
|
+
|
36
|
+
# If you use bundler, this might be useful to you:
|
37
|
+
# if [[ -s Gemfile ]] && {
|
38
|
+
# ! builtin command -v bundle >/dev/null ||
|
39
|
+
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
|
40
|
+
# }
|
41
|
+
# then
|
42
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
+
# gem install bundler
|
44
|
+
# fi
|
45
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
+
# then
|
47
|
+
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
|
48
|
+
# fi
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Artem Chistyakov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# QueueingRabbit
|
2
|
+
|
3
|
+
QueueingRabbit is a Ruby library that provides a convenient object-oriented
|
4
|
+
syntax for managing background jobs with AMQP. All jobs' argumets are
|
5
|
+
serialized to JSON and transfered as AMQP message payloads. The library
|
6
|
+
implements amqp and bunny gems as adapters, making it possible to use
|
7
|
+
synchronous publishing and asynchronous consuming, which might be useful for
|
8
|
+
Rails app running on non-EventMachine based application servers (i. e.
|
9
|
+
Passenger).
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'queueing_rabbit'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install queueing_rabbit
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
QueueingRabbit is currently in RC1 and is not recommended for production use.
|
28
|
+
|
29
|
+
The docs are coming soon, currently you can check out the examples in
|
30
|
+
`spec/integration` dir.
|
31
|
+
|
32
|
+
## Contributing
|
33
|
+
|
34
|
+
1. Fork it
|
35
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
36
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
37
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
38
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module QueueingRabbit
|
2
|
+
|
3
|
+
module Callbacks
|
4
|
+
|
5
|
+
def before_consuming(&block)
|
6
|
+
setup_callback(:consuming_started, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def after_consuming(&block)
|
10
|
+
setup_callback(:consuming_done, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_event_machine_start(&block)
|
14
|
+
setup_callback(:event_machine_started, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup_callback(event, &block)
|
18
|
+
@callbacks ||= {}
|
19
|
+
@callbacks[event] ||= []
|
20
|
+
@callbacks[event] << block
|
21
|
+
end
|
22
|
+
|
23
|
+
def trigger_event(event)
|
24
|
+
if @callbacks && @callbacks[event]
|
25
|
+
@callbacks[event].each { |c| c.call }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'amqp'
|
2
|
+
|
3
|
+
module QueueingRabbit
|
4
|
+
|
5
|
+
module Client
|
6
|
+
|
7
|
+
class AMQP
|
8
|
+
|
9
|
+
include QueueingRabbit::Serializer
|
10
|
+
include QueueingRabbit::Logging
|
11
|
+
extend QueueingRabbit::Logging
|
12
|
+
extend QueueingRabbit::Client::Callbacks
|
13
|
+
|
14
|
+
attr_reader :connection, :exchange_name, :exchange_options
|
15
|
+
|
16
|
+
define_callback :on_tcp_failure do |_|
|
17
|
+
fatal "unable to establish TCP connection to broker"
|
18
|
+
EM.stop
|
19
|
+
end
|
20
|
+
|
21
|
+
define_callback :on_tcp_loss do |c, _|
|
22
|
+
info "re-establishing TCP connection to broker"
|
23
|
+
c.reconnect(false, 1)
|
24
|
+
end
|
25
|
+
|
26
|
+
define_callback :on_tcp_recovery do
|
27
|
+
info "TCP connection to broker is back and running"
|
28
|
+
end
|
29
|
+
|
30
|
+
define_callback :on_channel_error do |ch, channel_close|
|
31
|
+
EM.stop
|
32
|
+
fatal "channel error occured: #{channel_close.reply_text}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.connection_options
|
36
|
+
{:timeout => QueueingRabbit.tcp_timeout,
|
37
|
+
:heartbeat => QueueingRabbit.heartbeat,
|
38
|
+
:on_tcp_connection_failure => self.callback(:on_tcp_failure)}
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.connect
|
42
|
+
self.run_event_machine
|
43
|
+
|
44
|
+
self.new(::AMQP.connect(QueueingRabbit.amqp_uri),
|
45
|
+
QueueingRabbit.amqp_exchange_name,
|
46
|
+
QueueingRabbit.amqp_exchange_options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.run_event_machine
|
50
|
+
return if EM.reactor_running?
|
51
|
+
|
52
|
+
@event_machine_thread = Thread.new do
|
53
|
+
EM.run do
|
54
|
+
QueueingRabbit.trigger_event(:event_machine_started)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.join_event_machine_thread
|
60
|
+
@event_machine_thread.join if @event_machine_thread
|
61
|
+
end
|
62
|
+
|
63
|
+
def disconnect
|
64
|
+
info "closing AMQP broker connection..."
|
65
|
+
|
66
|
+
connection.close do
|
67
|
+
yield if block_given?
|
68
|
+
|
69
|
+
EM.stop if EM.reactor_running?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def define_queue(channel, queue_name, options={})
|
74
|
+
routing_keys = [*options.delete(:routing_keys)] + [queue_name]
|
75
|
+
|
76
|
+
channel.queue(queue_name.to_s, options) do |queue|
|
77
|
+
routing_keys.each do |key|
|
78
|
+
queue.bind(exchange(channel), :routing_key => key.to_s)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def listen_queue(channel, queue_name, options={}, &block)
|
84
|
+
define_queue(channel, queue_name, options)
|
85
|
+
.subscribe(:ack => true) do |metadata, payload|
|
86
|
+
begin
|
87
|
+
process_message(deserialize(payload), &block)
|
88
|
+
metadata.ack
|
89
|
+
rescue JSON::JSONError => e
|
90
|
+
error "JSON parser error occured: #{e.message}"
|
91
|
+
debug e
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def process_message(arguments)
|
97
|
+
begin
|
98
|
+
yield arguments
|
99
|
+
rescue => e
|
100
|
+
error "unexpected error #{e.class} occured: #{e.message}"
|
101
|
+
debug e
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def open_channel(options={})
|
106
|
+
::AMQP::Channel.new(connection,
|
107
|
+
::AMQP::Channel.next_channel_id,
|
108
|
+
options) do |c, open_ok|
|
109
|
+
c.on_error(&self.class.callback(:on_channel_error))
|
110
|
+
yield c, open_ok
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def define_exchange(channel, options={})
|
115
|
+
channel.direct(exchange_name, exchange_options.merge(options))
|
116
|
+
end
|
117
|
+
alias_method :exchange, :define_exchange
|
118
|
+
|
119
|
+
def enqueue(channel, routing_key, payload)
|
120
|
+
exchange(channel).publish(serialize(payload), :key => routing_key.to_s,
|
121
|
+
:persistent => true)
|
122
|
+
end
|
123
|
+
alias_method :publish, :enqueue
|
124
|
+
|
125
|
+
def queue_size(queue)
|
126
|
+
raise NotImplementedError
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def setup_callbacks
|
132
|
+
connection.on_tcp_connection_loss(&self.class.callback(:on_tcp_loss))
|
133
|
+
connection.on_recovery(&self.class.callback(:on_tcp_recovery))
|
134
|
+
end
|
135
|
+
|
136
|
+
def initialize(connection, exchange_name, exchange_options = {})
|
137
|
+
@connection = connection
|
138
|
+
@exchange_name = exchange_name
|
139
|
+
@exchange_options = exchange_options
|
140
|
+
|
141
|
+
setup_callbacks
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
module QueueingRabbit
|
4
|
+
|
5
|
+
module Client
|
6
|
+
|
7
|
+
class Bunny
|
8
|
+
|
9
|
+
include QueueingRabbit::Serializer
|
10
|
+
|
11
|
+
attr_reader :connection, :exchange_name, :exchange_options
|
12
|
+
|
13
|
+
def self.connect
|
14
|
+
self.new(::Bunny.new(QueueingRabbit.amqp_uri),
|
15
|
+
QueueingRabbit.amqp_exchange_name,
|
16
|
+
QueueingRabbit.amqp_exchange_options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def open_channel(options = {})
|
20
|
+
ch = connection.create_channel
|
21
|
+
yield ch, nil
|
22
|
+
# ch.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def define_queue(channel, name, options = {})
|
26
|
+
routing_keys = [*options.delete(:routing_keys)] + [name]
|
27
|
+
|
28
|
+
channel.queue(name.to_s, options) do |q|
|
29
|
+
routing_keys.each { |key| q.bind(exchange, :routing_key => key.to_s) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def enqueue(channel, routing_key, payload)
|
34
|
+
exchange(channel).publish(serialize(payload), :key => routing_key.to_s,
|
35
|
+
:persistent => true)
|
36
|
+
end
|
37
|
+
alias_method :publish, :enqueue
|
38
|
+
|
39
|
+
def define_exchange(channel, options={})
|
40
|
+
channel.direct(exchange_name, exchange_options.merge(options))
|
41
|
+
end
|
42
|
+
alias_method :exchange, :define_exchange
|
43
|
+
|
44
|
+
def queue_size(queue)
|
45
|
+
queue.status[:message_count]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def initialize(connection, exchange_name, exchange_options)
|
51
|
+
@connection = connection
|
52
|
+
@exchange_name = exchange_name
|
53
|
+
@exchange_options = exchange_options
|
54
|
+
|
55
|
+
@connection.start
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module QueueingRabbit
|
2
|
+
|
3
|
+
module Configuration
|
4
|
+
attr_accessor :amqp_uri, :amqp_exchange_name, :amqp_exchange_options
|
5
|
+
attr_writer :tcp_timeout, :heartbeat
|
6
|
+
|
7
|
+
def configure
|
8
|
+
yield self
|
9
|
+
end
|
10
|
+
|
11
|
+
def tcp_timeout
|
12
|
+
@tcp_timeout ||= 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def heartbeat
|
16
|
+
@heartbeat ||= 10
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_client
|
20
|
+
QueueingRabbit::Client::Bunny
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module QueueingRabbit
|
2
|
+
module Job
|
3
|
+
def queue_name
|
4
|
+
@queue_name ||= self.name.split('::')[-1]
|
5
|
+
end
|
6
|
+
|
7
|
+
def queue_options
|
8
|
+
@queue_options ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def queue(name, options = {})
|
12
|
+
@queue_name = name
|
13
|
+
@queue_options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def queue_size
|
17
|
+
QueueingRabbit.queue_size(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def channel_options
|
21
|
+
@channel_options ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def channel(options={})
|
25
|
+
@channel_options = options
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class AbstractJob
|
30
|
+
extend Job
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module QueueingRabbit
|
4
|
+
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
# Logging levels are defined at:
|
8
|
+
# http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html
|
9
|
+
%w[fatal error warn info debug].each do |level|
|
10
|
+
define_method(level) do |message|
|
11
|
+
QueueingRabbit.logger.__send__(level, message) if QueueingRabbit.logger
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module QueueingRabbit
|
4
|
+
module Serializer
|
5
|
+
def serialize(args)
|
6
|
+
JSON.dump(args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def deserialize(msg)
|
10
|
+
symbolize_keys(JSON.parse(msg))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def symbolize_keys(hash)
|
16
|
+
hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# require 'queueing_rabbit/tasks'
|
2
|
+
# will give you the queueing_rabbit tasks
|
3
|
+
|
4
|
+
namespace :queueing_rabbit do
|
5
|
+
task :setup
|
6
|
+
|
7
|
+
desc "Start a queueing rabbit worker"
|
8
|
+
task :work => :setup do
|
9
|
+
require 'queueing_rabbit'
|
10
|
+
|
11
|
+
if ENV['PIDFILE'] && File.exists?(ENV['PIDFILE'])
|
12
|
+
abort "PID file already exists. Is the worker running?"
|
13
|
+
end
|
14
|
+
|
15
|
+
jobs = (ENV['JOBS'] || ENV['JOB']).to_s.split(',')
|
16
|
+
|
17
|
+
begin
|
18
|
+
worker = QueueingRabbit::Worker.new(*jobs)
|
19
|
+
rescue QueueingRabbit::NoJobError
|
20
|
+
abort "set JOB env var, e.g. $ JOB=ExportDataJob,CompressFileJob " \
|
21
|
+
"rake queueing_rabbit:work"
|
22
|
+
end
|
23
|
+
|
24
|
+
if ENV['BACKGROUND']
|
25
|
+
unless Process.respond_to?('daemon')
|
26
|
+
abort "env var BACKGROUND is set, which requires ruby >= 1.9"
|
27
|
+
end
|
28
|
+
Process.daemon(true)
|
29
|
+
end
|
30
|
+
|
31
|
+
worker.use_pidfile(ENV['PIDFILE']) if ENV['PIDFILE']
|
32
|
+
|
33
|
+
worker.info "starting a new queueing_rabbit worker #{worker}"
|
34
|
+
|
35
|
+
worker.work!
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module QueueingRabbit
|
2
|
+
class Worker
|
3
|
+
include QueueingRabbit::Logging
|
4
|
+
|
5
|
+
attr_accessor :jobs
|
6
|
+
|
7
|
+
def initialize(*jobs)
|
8
|
+
self.jobs = jobs.map { |job| job.to_s.strip }
|
9
|
+
|
10
|
+
sync_stdio
|
11
|
+
validate_jobs
|
12
|
+
constantize_jobs
|
13
|
+
use_asynchronous_client
|
14
|
+
end
|
15
|
+
|
16
|
+
def work
|
17
|
+
conn = QueueingRabbit.connection
|
18
|
+
trap_signals(conn)
|
19
|
+
|
20
|
+
jobs.each { |job| run_job(conn, job) }
|
21
|
+
|
22
|
+
QueueingRabbit.trigger_event(:consuming_started)
|
23
|
+
end
|
24
|
+
|
25
|
+
def work!
|
26
|
+
work
|
27
|
+
QueueingRabbit::Client::AMQP.join_event_machine_thread
|
28
|
+
end
|
29
|
+
|
30
|
+
def use_pidfile(filename)
|
31
|
+
File.open(@pidfile = filename, 'w') { |f| f << pid }
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_pidfile
|
35
|
+
File.delete(@pidfile) if @pidfile && File.exists?(@pidfile)
|
36
|
+
end
|
37
|
+
|
38
|
+
def pid
|
39
|
+
Process.pid
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"PID=#{pid}, JOBS=#{jobs.join(',')}"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def use_asynchronous_client
|
49
|
+
QueueingRabbit.client = QueueingRabbit::Client::AMQP
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_jobs
|
53
|
+
if jobs.nil? || jobs.empty?
|
54
|
+
fatal "no jobs specified to work on."
|
55
|
+
raise JobNotPresentError.new("No jobs specified to work on.")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def constantize_jobs
|
60
|
+
self.jobs = jobs.map do |job|
|
61
|
+
begin
|
62
|
+
Kernel.const_get(job)
|
63
|
+
rescue NameError
|
64
|
+
fatal "job #{job} doesn't exist."
|
65
|
+
raise JobNotFoundError.new("Job #{job} doesn't exist.")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_job(conn, job)
|
71
|
+
conn.open_channel(job.channel_options) do |channel, _|
|
72
|
+
conn.listen_queue(channel, job.queue_name, job.queue_options) do |args|
|
73
|
+
info "performing job #{job} with arguments #{args.inspect}"
|
74
|
+
job.perform(args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def sync_stdio
|
80
|
+
$stdout.sync = true
|
81
|
+
$stderr.sync = true
|
82
|
+
end
|
83
|
+
|
84
|
+
def trap_signals(connection)
|
85
|
+
handler = Proc.new do
|
86
|
+
connection.disconnect {
|
87
|
+
QueueingRabbit.trigger_event(:consuming_done)
|
88
|
+
remove_pidfile
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
Signal.trap("TERM", &handler)
|
93
|
+
Signal.trap("INT", &handler)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|