rj 0.0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/examples/configuration.rb +29 -0
- data/lib/rabbit_jobs.rb +24 -0
- data/lib/rabbit_jobs/amqp_helpers.rb +45 -0
- data/lib/rabbit_jobs/configuration.rb +124 -0
- data/lib/rabbit_jobs/helpers.rb +53 -0
- data/lib/rabbit_jobs/job.rb +88 -0
- data/lib/rabbit_jobs/logger.rb +18 -0
- data/lib/rabbit_jobs/publisher.rb +56 -0
- data/lib/rabbit_jobs/tasks.rb +51 -0
- data/lib/rabbit_jobs/version.rb +3 -0
- data/lib/rabbit_jobs/worker.rb +119 -0
- data/lib/tasks/rabbit_jobs.rake +3 -0
- data/rabbit_jobs.gemspec +22 -0
- data/spec/fixtures/config.yml +17 -0
- data/spec/fixtures/jobs.rb +29 -0
- data/spec/integration/publisher_spec.rb +19 -0
- data/spec/integration/worker_spec.rb +18 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/unit/configuration_spec.rb +89 -0
- data/spec/unit/job_spec.rb +29 -0
- data/spec/unit/logger_spec.rb +23 -0
- data/spec/unit/rabbit_jobs_spec.rb +11 -0
- data/spec/unit/worker_spec.rb +60 -0
- metadata +106 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Pavel Lazureykis
|
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,29 @@
|
|
1
|
+
# RabbitJobs
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'rabbit_jobs'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install rabbit_jobs
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rabbit_jobs'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
RabbitJobs.configure do |c|
|
7
|
+
c.host "127.0.0.1"
|
8
|
+
|
9
|
+
c.exchange 'test_exchange', durable: true, auto_delete: false
|
10
|
+
|
11
|
+
c.queue 'rabbit_jobs_test1', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
|
12
|
+
c.queue 'rabbit_jobs_test2', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
|
13
|
+
c.queue 'rabbit_jobs_test3', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
|
14
|
+
end
|
15
|
+
|
16
|
+
puts JSON.pretty_generate(RabbitJobs.config.to_hash)
|
17
|
+
|
18
|
+
puts JSON.pretty_generate(RabbitJobs.config.queues)
|
19
|
+
|
20
|
+
class MyJob < RabbitJobs::Job
|
21
|
+
|
22
|
+
expires_in 60 # dont perform this job after 60 seconds
|
23
|
+
|
24
|
+
def self.perform(time)
|
25
|
+
puts "This job was published at #{}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
RabbitJobs.publish_to('rabbit_jobs_test1', MyJob, Time.now)
|
data/lib/rabbit_jobs.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'rabbit_jobs/version'
|
4
|
+
|
5
|
+
require 'rabbit_jobs/helpers'
|
6
|
+
require 'rabbit_jobs/amqp_helpers'
|
7
|
+
require 'rabbit_jobs/configuration'
|
8
|
+
require 'rabbit_jobs/logger'
|
9
|
+
|
10
|
+
require 'rabbit_jobs/job'
|
11
|
+
require 'rabbit_jobs/publisher'
|
12
|
+
require 'rabbit_jobs/worker'
|
13
|
+
|
14
|
+
module RabbitJobs
|
15
|
+
extend self
|
16
|
+
|
17
|
+
def publish(klass, opts = {}, *params)
|
18
|
+
RabbitJobs::Publisher.publish(klass, opts, *params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def publish_to(routing_key, klass, opts = {}, *params)
|
22
|
+
RabbitJobs::Publisher.publish_to(routing_key, klass, opts, *params)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module RabbitJobs
|
4
|
+
module AmqpHelpers
|
5
|
+
|
6
|
+
# Calls given block with initialized amqp
|
7
|
+
|
8
|
+
def amqp_with_exchange(&block)
|
9
|
+
raise ArgumentError unless block
|
10
|
+
|
11
|
+
AMQP.start(host: RabbitJobs.config.host) do |connection|
|
12
|
+
channel = AMQP::Channel.new(connection)
|
13
|
+
|
14
|
+
channel.on_error do |ch, channel_close|
|
15
|
+
puts "Channel-level error: #{channel_close.reply_text}, shutting down..."
|
16
|
+
connection.close { EM.stop }
|
17
|
+
end
|
18
|
+
|
19
|
+
exchange = channel.direct(RabbitJobs.config[:exchange], RabbitJobs.config[:exchange_params])
|
20
|
+
|
21
|
+
# go work
|
22
|
+
block.call(connection, exchange)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def amqp_with_queue(routing_key, &block)
|
27
|
+
|
28
|
+
raise ArgumentError unless routing_key && block
|
29
|
+
|
30
|
+
amqp_with_exchange do |connection, exchange|
|
31
|
+
queue = exchange.channel.queue(RabbitJobs.config.queue_name(routing_key), RabbitJobs.config[:queues][routing_key])
|
32
|
+
queue.bind(exchange, :routing_key => routing_key)
|
33
|
+
|
34
|
+
# go work
|
35
|
+
block.call(connection, queue)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def make_queue(exchange, routing_key)
|
40
|
+
queue = exchange.channel.queue(RabbitJobs.config.queue_name(routing_key), RabbitJobs.config[:queues][routing_key])
|
41
|
+
queue.bind(exchange, :routing_key => routing_key)
|
42
|
+
queue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module RabbitJobs
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def configure(&block)
|
9
|
+
@@configuration ||= Configuration.new
|
10
|
+
block.call(@@configuration)
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
@@configuration ||= load_config
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_config
|
18
|
+
self.configure do |c|
|
19
|
+
c.host 'localhost'
|
20
|
+
c.exchange 'rabbit_jobs', auto_delete: false, durable: true
|
21
|
+
c.queue 'default', auto_delete: false, ack: true, durable: true
|
22
|
+
end
|
23
|
+
@@configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
class Configuration
|
27
|
+
include Helpers
|
28
|
+
|
29
|
+
DEFAULT_QUEUE_PARAMS = {
|
30
|
+
auto_delete: false,
|
31
|
+
durable: true,
|
32
|
+
ack: true
|
33
|
+
}
|
34
|
+
|
35
|
+
DEFAULT_EXCHANGE_PARAMS = {
|
36
|
+
auto_delete: false,
|
37
|
+
durable: true
|
38
|
+
}
|
39
|
+
|
40
|
+
DEFAULT_MESSAGE_PARAMS = {
|
41
|
+
persistent: true,
|
42
|
+
nowait: false,
|
43
|
+
immediate: false
|
44
|
+
}
|
45
|
+
|
46
|
+
def to_hash
|
47
|
+
@data.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@data = {
|
52
|
+
host: 'localhost',
|
53
|
+
exchange: 'rabbit_jobs',
|
54
|
+
exchange_params: DEFAULT_EXCHANGE_PARAMS,
|
55
|
+
queues: {}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def [](name)
|
60
|
+
@data[name]
|
61
|
+
end
|
62
|
+
|
63
|
+
def host(value = nil)
|
64
|
+
if value
|
65
|
+
raise ArgumentError unless value.is_a?(String) && value != ""
|
66
|
+
@data[:host] = value.to_s
|
67
|
+
else
|
68
|
+
@data[:host]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def exchange(value = nil, params = {})
|
73
|
+
if value
|
74
|
+
raise ArgumentError unless value.is_a?(String) && value != ""
|
75
|
+
@data[:exchange] = value.downcase
|
76
|
+
@data[:exchange_params] = DEFAULT_EXCHANGE_PARAMS.merge(params)
|
77
|
+
else
|
78
|
+
@data[:exchange]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def queue(name, params = {})
|
83
|
+
raise ArgumentError.new("name is #{name.inspect}") unless name && name.is_a?(String) && name != ""
|
84
|
+
raise ArgumentError.new("params is #{params.inspect}") unless params && params.is_a?(Hash)
|
85
|
+
|
86
|
+
name = name.downcase
|
87
|
+
|
88
|
+
if @data[:queues][name]
|
89
|
+
@data[:queues][name].merge!(params)
|
90
|
+
else
|
91
|
+
@data[:queues][name] = DEFAULT_QUEUE_PARAMS.merge(params)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def routing_keys
|
96
|
+
@data[:queues].keys
|
97
|
+
end
|
98
|
+
|
99
|
+
def queue_name(routing_key)
|
100
|
+
[@data[:exchange], routing_key].join('#')
|
101
|
+
end
|
102
|
+
|
103
|
+
def load_file(filename)
|
104
|
+
load_yaml(File.read(filename))
|
105
|
+
end
|
106
|
+
|
107
|
+
def load_yaml(text)
|
108
|
+
convert_yaml_config(YAML.load(text))
|
109
|
+
end
|
110
|
+
|
111
|
+
def convert_yaml_config(yaml)
|
112
|
+
if yaml['rabbit_jobs']
|
113
|
+
convert_yaml_config(yaml['rabbit_jobs'])
|
114
|
+
else
|
115
|
+
@data = {host: nil, exchange: nil, queues: {}}
|
116
|
+
host yaml['host']
|
117
|
+
exchange yaml['exchange'], symbolize_keys!(yaml['exchange_params'])
|
118
|
+
yaml['queues'].each do |name, params|
|
119
|
+
queue name, symbolize_keys!(params) || {}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module RabbitJobs
|
4
|
+
module Helpers
|
5
|
+
def symbolize_keys!(hash)
|
6
|
+
hash.inject({}) do |options, (key, value)|
|
7
|
+
options[(key.to_sym rescue key) || key] = value
|
8
|
+
options
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# Tries to find a constant with the name specified in the argument string:
|
14
|
+
#
|
15
|
+
# constantize("Module") # => Module
|
16
|
+
# constantize("Test::Unit") # => Test::Unit
|
17
|
+
#
|
18
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
19
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
20
|
+
# account:
|
21
|
+
#
|
22
|
+
# C = 'outside'
|
23
|
+
# module M
|
24
|
+
# C = 'inside'
|
25
|
+
# C # => 'inside'
|
26
|
+
# constantize("C") # => 'outside', same as ::C
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# NameError is raised when the constant is unknown.
|
30
|
+
def constantize(camel_cased_word)
|
31
|
+
camel_cased_word = camel_cased_word.to_s
|
32
|
+
|
33
|
+
if camel_cased_word.include?('-')
|
34
|
+
camel_cased_word = classify(camel_cased_word)
|
35
|
+
end
|
36
|
+
|
37
|
+
names = camel_cased_word.split('::')
|
38
|
+
names.shift if names.empty? || names.first.empty?
|
39
|
+
|
40
|
+
constant = Object
|
41
|
+
names.each do |name|
|
42
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
43
|
+
|
44
|
+
if constant.const_defined?(name, *args)
|
45
|
+
constant = constant.const_get(name)
|
46
|
+
else
|
47
|
+
constant = constant.const_missing(name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
constant
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'json'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module RabbitJobs::Job
|
6
|
+
extend RabbitJobs::Helpers
|
7
|
+
extend RabbitJobs::Logger
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
include RabbitJobs::Logger
|
12
|
+
base.extend (ClassMethods)
|
13
|
+
|
14
|
+
def initialize(*perform_params)
|
15
|
+
self.params = *perform_params
|
16
|
+
self.opts = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :params, :opts, :child_pid
|
20
|
+
|
21
|
+
def run_perform
|
22
|
+
if @child_pid = fork
|
23
|
+
srand # Reseeding
|
24
|
+
log "Forked #{@child_pid} at #{Time.now} to process #{self.class}.perform(#{ params.map(&:inspect).join(', ') })"
|
25
|
+
Process.wait(@child_pid)
|
26
|
+
yield if block_given?
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
# log 'before perform'
|
30
|
+
self.class.perform(*params)
|
31
|
+
# log 'after perform'
|
32
|
+
rescue
|
33
|
+
puts $!.inspect
|
34
|
+
end
|
35
|
+
exit!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def payload
|
40
|
+
{'class' => self.class.to_s, 'opts' => (self.opts || {}), 'params' => params}.to_json
|
41
|
+
# ([self.class.to_s] + params).to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
def expires_in
|
45
|
+
self.class.rj_expires_in
|
46
|
+
end
|
47
|
+
|
48
|
+
def expires?
|
49
|
+
!!self.expires_in
|
50
|
+
end
|
51
|
+
|
52
|
+
def expired?
|
53
|
+
if self.opts['expires_at']
|
54
|
+
Time.now > Time.new(opts['expires_at'])
|
55
|
+
elsif expires? && opts['created_at']
|
56
|
+
Time.now > (Time.new(opts['created_at']) + expires_in)
|
57
|
+
else
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods
|
64
|
+
attr_accessor :rj_expires_in
|
65
|
+
|
66
|
+
# DSL method for jobs
|
67
|
+
def expires_in(seconds)
|
68
|
+
@rj_expires_in = seconds
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.parse(payload)
|
73
|
+
begin
|
74
|
+
encoded = JSON.parse(payload)
|
75
|
+
job_klass = constantize(encoded['class'])
|
76
|
+
job = job_klass.new(*encoded['params'])
|
77
|
+
job.opts = encoded['opts']
|
78
|
+
job
|
79
|
+
rescue
|
80
|
+
log "JOB INIT ERROR at #{Time.now.to_s}:"
|
81
|
+
log $!.inspect
|
82
|
+
log $!.backtrace
|
83
|
+
log "message: #{payload.inspect}"
|
84
|
+
# Mailer.send(klass_name, params, $!)
|
85
|
+
# raise $!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RabbitJobs
|
2
|
+
module Logger
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def log(string)
|
6
|
+
puts string
|
7
|
+
end
|
8
|
+
|
9
|
+
def log!(string)
|
10
|
+
@@verbose ||= false
|
11
|
+
log(string) if RabbitJobs::Logger.verbose
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :verbose
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'amqp'
|
5
|
+
require 'eventmachine'
|
6
|
+
|
7
|
+
module RabbitJobs
|
8
|
+
module Publisher
|
9
|
+
extend self
|
10
|
+
extend AmqpHelpers
|
11
|
+
|
12
|
+
def publish(klass, opts = {}, *params)
|
13
|
+
key = RabbitJobs.config.routing_keys.first
|
14
|
+
publish_to(key, klass, opts, *params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def publish_to(routing_key, klass, opts = {}, *params)
|
18
|
+
raise ArgumentError unless klass && routing_key
|
19
|
+
opts ||= {}
|
20
|
+
|
21
|
+
job = klass.new(*params)
|
22
|
+
job.opts = opts
|
23
|
+
|
24
|
+
publish_job_to(routing_key, job)
|
25
|
+
end
|
26
|
+
|
27
|
+
def publish_job_to(routing_key, job)
|
28
|
+
amqp_with_exchange do |connection, exchange|
|
29
|
+
|
30
|
+
queue = make_queue(exchange, routing_key)
|
31
|
+
|
32
|
+
job.opts['created_at'] = Time.now.to_s
|
33
|
+
|
34
|
+
payload = job.payload
|
35
|
+
exchange.publish(job.payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({routing_key: routing_key})) {
|
36
|
+
connection.close { EM.stop }
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def purge_queue(routing_key)
|
42
|
+
raise ArgumentError unless routing_key
|
43
|
+
|
44
|
+
amqp_with_queue(routing_key) do |connection, queue|
|
45
|
+
queue.status do |number_of_messages, number_of_consumers|
|
46
|
+
queue.purge {
|
47
|
+
connection.close {
|
48
|
+
EM.stop
|
49
|
+
return number_of_messages
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# require 'resque/tasks'
|
2
|
+
# will give you the resque tasks
|
3
|
+
|
4
|
+
namespace :rj do
|
5
|
+
task :setup
|
6
|
+
|
7
|
+
desc "Start a Rabbit Jobs worker"
|
8
|
+
task :work => [ :preload, :setup ] do
|
9
|
+
require 'rabbit_jobs'
|
10
|
+
|
11
|
+
queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
|
12
|
+
|
13
|
+
begin
|
14
|
+
worker = RabbitJobs::Worker.new(*queues)
|
15
|
+
worker.pidfile = ENV['PIDFILE']
|
16
|
+
worker.background = %w(yes true).include? ENV['BACKGROUND']
|
17
|
+
RabbitJobs::Logger.verbose = true if ENV['VERBOSE']
|
18
|
+
# worker.very_verbose = ENV['VVERBOSE']
|
19
|
+
end
|
20
|
+
|
21
|
+
# worker.log "Starting worker #{worker.pid}"
|
22
|
+
# worker.verbose = true
|
23
|
+
worker.work 10
|
24
|
+
# worker.work(ENV['INTERVAL'] || 5) # interval, will block
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Start multiple Resque workers. Should only be used in dev mode."
|
28
|
+
task :workers do
|
29
|
+
threads = []
|
30
|
+
|
31
|
+
ENV['COUNT'].to_i.times do
|
32
|
+
threads << Thread.new do
|
33
|
+
system "rake resque:work"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
threads.each { |thread| thread.join }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Preload app files if this is Rails
|
41
|
+
task :preload => :setup do
|
42
|
+
if defined?(Rails) && Rails.respond_to?(:application)
|
43
|
+
# Rails 3
|
44
|
+
Rails.application.eager_load!
|
45
|
+
elsif defined?(Rails::Initializer)
|
46
|
+
# Rails 2.3
|
47
|
+
$rails_rake_task = false
|
48
|
+
Rails::Initializer.run :load_application_classes
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module RabbitJobs
|
4
|
+
class Worker
|
5
|
+
include AmqpHelpers
|
6
|
+
include Logger
|
7
|
+
|
8
|
+
attr_accessor :pidfile, :background
|
9
|
+
|
10
|
+
# Workers should be initialized with an array of string queue
|
11
|
+
# names. The order is important: a Worker will check the first
|
12
|
+
# queue given for a job. If none is found, it will check the
|
13
|
+
# second queue name given. If a job is found, it will be
|
14
|
+
# processed. Upon completion, the Worker will again check the
|
15
|
+
# first queue given, and so forth. In this way the queue list
|
16
|
+
# passed to a Worker on startup defines the priorities of queues.
|
17
|
+
#
|
18
|
+
# If passed a single "*", this Worker will operate on all queues
|
19
|
+
# in alphabetical order. Queues can be dynamically added or
|
20
|
+
# removed without needing to restart workers using this method.
|
21
|
+
def initialize(*queues)
|
22
|
+
@queues = queues.map { |queue| queue.to_s.strip }.flatten.uniq
|
23
|
+
if @queues == ['*'] || @queues.empty?
|
24
|
+
@queues = RabbitJobs.config.routing_keys
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def queues
|
29
|
+
@queues || ['default']
|
30
|
+
end
|
31
|
+
|
32
|
+
# Subscribes to channel and working on jobs
|
33
|
+
def work(time = 0)
|
34
|
+
startup
|
35
|
+
|
36
|
+
processed_count = 0
|
37
|
+
amqp_with_exchange do |connection, exchange|
|
38
|
+
exchange.channel.prefetch(1)
|
39
|
+
|
40
|
+
check_shutdown = Proc.new {
|
41
|
+
if @shutdown
|
42
|
+
log "Processed jobs: #{processed_count}"
|
43
|
+
log "Stopping worker..."
|
44
|
+
|
45
|
+
connection.close {
|
46
|
+
File.delete(self.pidfile) if self.pidfile
|
47
|
+
EM.stop { exit! }
|
48
|
+
}
|
49
|
+
end
|
50
|
+
}
|
51
|
+
|
52
|
+
queues.each do |routing_key|
|
53
|
+
queue = make_queue(exchange, routing_key)
|
54
|
+
|
55
|
+
log "Worker ##{Process.pid} <= #{exchange.name}##{routing_key}"
|
56
|
+
|
57
|
+
queue.subscribe(ack: true) do |metadata, payload|
|
58
|
+
@job = RabbitJobs::Job.parse(payload)
|
59
|
+
@job.run_perform unless @job.expired?
|
60
|
+
metadata.ack
|
61
|
+
processed_count += 1
|
62
|
+
check_shutdown.call
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if time > 0
|
67
|
+
# for debugging
|
68
|
+
EM.add_timer(time) do
|
69
|
+
self.shutdown
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
EM.add_periodic_timer(1) do
|
74
|
+
check_shutdown.call
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def shutdown
|
80
|
+
@shutdown = true
|
81
|
+
end
|
82
|
+
|
83
|
+
def startup
|
84
|
+
# prune_dead_workers
|
85
|
+
|
86
|
+
Process.daemon(true) if self.background
|
87
|
+
|
88
|
+
if self.pidfile
|
89
|
+
File.open(self.pidfile, 'w') { |f| f << Process.pid }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Fix buffering so we can `rake rj:work > resque.log` and
|
93
|
+
# get output from the child in there.
|
94
|
+
$stdout.sync = true
|
95
|
+
|
96
|
+
@shutdown = false
|
97
|
+
|
98
|
+
Signal.trap('TERM') { shutdown }
|
99
|
+
Signal.trap('INT') { shutdown! }
|
100
|
+
end
|
101
|
+
|
102
|
+
def shutdown!
|
103
|
+
shutdown
|
104
|
+
kill_child
|
105
|
+
end
|
106
|
+
|
107
|
+
def kill_child
|
108
|
+
if @job && @job.child_pid
|
109
|
+
# log! "Killing child at #{@child}"
|
110
|
+
if Kernel.system("ps -o pid,state -p #{@job.child_pid}")
|
111
|
+
Process.kill("KILL", @job.child_pid) rescue nil
|
112
|
+
else
|
113
|
+
# log! "Child #{@child} not found, restarting."
|
114
|
+
# shutdown
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/rabbit_jobs.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'rabbit_jobs/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Pavel Lazureykis"]
|
7
|
+
gem.email = ["lazureykis@gmail.com"]
|
8
|
+
gem.description = %q{Background jobs on RabbitMQ}
|
9
|
+
gem.summary = %q{Background jobs on RabbitMQ}
|
10
|
+
gem.homepage = ""
|
11
|
+
gem.date = Time.now.strftime('%Y-%m-%d')
|
12
|
+
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.name = "rj"
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.version = RabbitJobs::VERSION
|
19
|
+
|
20
|
+
gem.add_dependency "amqp", "~> 0.9"
|
21
|
+
gem.add_dependency "rake"
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
rabbit_jobs:
|
2
|
+
host: example.com
|
3
|
+
exchange: 'my_exchange'
|
4
|
+
exchange_params:
|
5
|
+
durable: true
|
6
|
+
auto_delete: false
|
7
|
+
queues:
|
8
|
+
durable_queue:
|
9
|
+
durable: true
|
10
|
+
auto_delete: false
|
11
|
+
ack: true
|
12
|
+
arguments:
|
13
|
+
x-ha-policy: all
|
14
|
+
fast_queue:
|
15
|
+
durable: false
|
16
|
+
auto_delete: true
|
17
|
+
ack: false
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class TestJob
|
4
|
+
include RabbitJobs::Job
|
5
|
+
end
|
6
|
+
|
7
|
+
class PrintTimeJob
|
8
|
+
include RabbitJobs::Job
|
9
|
+
|
10
|
+
def self.perform(time)
|
11
|
+
puts "Running job queued at #{time}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class JobWithExpire
|
16
|
+
include RabbitJobs::Job
|
17
|
+
expires_in 60*60 # expires in 1 hour
|
18
|
+
def self.perform
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ExpiredJob
|
24
|
+
include RabbitJobs::Job
|
25
|
+
|
26
|
+
def self.perform
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
describe RabbitJobs::Publisher do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
queue_name = 'test'
|
9
|
+
RabbitJobs.configure do |c|
|
10
|
+
c.exchange 'test'
|
11
|
+
c.queue 'rspec_queue'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should publish message to queue' do
|
16
|
+
RabbitJobs.publish(TestJob, nil, 'some', 'other', 'params')
|
17
|
+
RabbitJobs::Publisher.purge_queue('rspec_queue').should == 1
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
describe RabbitJobs::Worker do
|
6
|
+
it 'should listen for messages' do
|
7
|
+
RabbitJobs.configure do |c|
|
8
|
+
c.exchange 'test_durable', auto_delete: false, durable: true
|
9
|
+
c.queue 'rspec_durable_queue', auto_delete: false, durable: true, ack: true
|
10
|
+
end
|
11
|
+
|
12
|
+
5.times { RabbitJobs.publish(PrintTimeJob, nil, Time.now) }
|
13
|
+
5.times { RabbitJobs.publish(ExpiredJob, { :expires_at => Time.now - 10 }) }
|
14
|
+
worker = RabbitJobs::Worker.new
|
15
|
+
|
16
|
+
worker.work(1) # work for 1 second
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "spec" # ignore spec files
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rr'
|
8
|
+
|
9
|
+
require 'rabbit_jobs'
|
10
|
+
|
11
|
+
require 'fixtures/jobs'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.mock_with :rr
|
15
|
+
# or if that doesn't work due to a version incompatibility
|
16
|
+
# config.mock_with RR::Adapters::Rspec
|
17
|
+
|
18
|
+
config.before(:each) do
|
19
|
+
# clear config options
|
20
|
+
RabbitJobs.class_variable_set '@@configuration', nil
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RabbitJobs::Configuration do
|
5
|
+
it 'builds configuration from configure block' do
|
6
|
+
RabbitJobs.configure do |c|
|
7
|
+
c.host "somehost.lan"
|
8
|
+
|
9
|
+
c.exchange 'my_exchange', durable: true, auto_delete: false
|
10
|
+
|
11
|
+
c.queue 'durable_queue', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
|
12
|
+
c.queue 'fast_queue', durable: false, auto_delete: true, ack: false
|
13
|
+
end
|
14
|
+
|
15
|
+
RabbitJobs.config.to_hash.should == {
|
16
|
+
host: "somehost.lan",
|
17
|
+
exchange: "my_exchange",
|
18
|
+
exchange_params: {
|
19
|
+
durable: true,
|
20
|
+
auto_delete: false
|
21
|
+
},
|
22
|
+
queues: {
|
23
|
+
"durable_queue" => {
|
24
|
+
durable: true,
|
25
|
+
auto_delete: false,
|
26
|
+
ack: true,
|
27
|
+
arguments: {"x-ha-policy"=>"all"}
|
28
|
+
},
|
29
|
+
"fast_queue" => {
|
30
|
+
durable: false,
|
31
|
+
auto_delete: true,
|
32
|
+
ack: false
|
33
|
+
},
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'builds configuration from yaml' do
|
39
|
+
RabbitJobs.config.load_file(File.expand_path('../../fixtures/config.yml', __FILE__))
|
40
|
+
|
41
|
+
RabbitJobs.config.to_hash.should == {
|
42
|
+
host: "example.com",
|
43
|
+
exchange: "my_exchange",
|
44
|
+
exchange_params: {
|
45
|
+
durable: true,
|
46
|
+
auto_delete: false
|
47
|
+
},
|
48
|
+
queues: {
|
49
|
+
"durable_queue" => {
|
50
|
+
durable: true,
|
51
|
+
auto_delete: false,
|
52
|
+
ack: true,
|
53
|
+
arguments: {"x-ha-policy"=>"all"}
|
54
|
+
},
|
55
|
+
"fast_queue" => {
|
56
|
+
durable: false,
|
57
|
+
auto_delete: true,
|
58
|
+
ack: false
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'use default config' do
|
65
|
+
RabbitJobs.config.to_hash.should == {
|
66
|
+
host: "localhost",
|
67
|
+
exchange: "rabbit_jobs",
|
68
|
+
exchange_params: {
|
69
|
+
auto_delete: false,
|
70
|
+
durable: true
|
71
|
+
},
|
72
|
+
queues: {
|
73
|
+
"default" => {
|
74
|
+
auto_delete: false,
|
75
|
+
ack: true,
|
76
|
+
durable: true
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns settings on some methods' do
|
83
|
+
RabbitJobs.config.host.should == 'localhost'
|
84
|
+
RabbitJobs.config[:host].should == 'localhost'
|
85
|
+
RabbitJobs.config.routing_keys.should == ['default']
|
86
|
+
RabbitJobs.config.exchange.should == 'rabbit_jobs'
|
87
|
+
RabbitJobs.config.queue_name('default').should == 'rabbit_jobs#default'
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RabbitJobs::Job do
|
5
|
+
it 'should parse class and params' do
|
6
|
+
job = RabbitJobs::Job.parse({class: 'TestJob', params: [1,2,3]}.to_json)
|
7
|
+
job.params.should == [1, 2, 3]
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should understand expires_in' do
|
11
|
+
job = JobWithExpire.new(1, 2, 3)
|
12
|
+
job.expires_in.should == 60*60
|
13
|
+
job.expires?.should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'job expiration' do
|
17
|
+
it 'should expire job by expires_in option' do
|
18
|
+
job = TestJob.new
|
19
|
+
job.opts['expires_at'] = (Time.now - 10).to_s
|
20
|
+
job.expired?.should == true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should expire job by expires_in option in job class and current_time' do
|
24
|
+
job = JobWithExpire.new(1, 2, 3)
|
25
|
+
job.opts['created_at'] = (Time.now - job.expires_in - 10).to_s
|
26
|
+
job.expired?.should == true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RabbitJobs::Logger do
|
5
|
+
it '#log should write messages to stdout' do
|
6
|
+
mock($stdout).puts "hello"
|
7
|
+
RabbitJobs::Logger.log("hello")
|
8
|
+
end
|
9
|
+
|
10
|
+
it '#log! should not write messages to stdout in normal mode' do
|
11
|
+
RabbitJobs::Logger.verbose = false
|
12
|
+
|
13
|
+
dont_allow(RabbitJobs::Logger).log("hello")
|
14
|
+
RabbitJobs::Logger.log!("hello")
|
15
|
+
end
|
16
|
+
|
17
|
+
it '#log! should write messages to stdout in verbose mode' do
|
18
|
+
RabbitJobs::Logger.verbose = true
|
19
|
+
|
20
|
+
mock(RabbitJobs::Logger).log("hello")
|
21
|
+
RabbitJobs::Logger.log!("hello")
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
describe RabbitJobs do
|
4
|
+
it 'should pass publish methods to publisher' do
|
5
|
+
mock(RabbitJobs::Publisher).publish(TestJob, nil, 1, 2, "string")
|
6
|
+
RabbitJobs.publish(TestJob, nil, 1, 2, "string")
|
7
|
+
|
8
|
+
mock(RabbitJobs::Publisher).publish_to('default_queue', TestJob, nil, 1, 2, "string")
|
9
|
+
RabbitJobs.publish_to('default_queue', TestJob, nil, 1, 2, "string")
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RabbitJobs::Worker do
|
5
|
+
describe 'methods' do
|
6
|
+
before :each do
|
7
|
+
@worker = RabbitJobs::Worker.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it '#initialize with default options' do
|
11
|
+
@worker.queues.should == ['default']
|
12
|
+
end
|
13
|
+
|
14
|
+
it '#startup should set @shutdown to false' do
|
15
|
+
@worker.instance_variable_get('@shutdown').should_not == true
|
16
|
+
|
17
|
+
mock(Signal).trap('TERM')
|
18
|
+
mock(Signal).trap('INT')
|
19
|
+
|
20
|
+
@worker.startup
|
21
|
+
|
22
|
+
@worker.instance_variable_get('@shutdown').should_not == true
|
23
|
+
end
|
24
|
+
|
25
|
+
it '#startup should write process id to file' do
|
26
|
+
mock(Signal).trap('TERM')
|
27
|
+
mock(Signal).trap('INT')
|
28
|
+
|
29
|
+
filename = 'test_worker.pid'
|
30
|
+
mock(File).open(filename, 'w') {}
|
31
|
+
@worker.pidfile = filename
|
32
|
+
@worker.startup
|
33
|
+
@worker.pidfile.should == filename
|
34
|
+
end
|
35
|
+
|
36
|
+
it '#shutdown should set @shutdown to true' do
|
37
|
+
@worker.instance_variable_get('@shutdown').should_not == true
|
38
|
+
@worker.shutdown
|
39
|
+
@worker.instance_variable_get('@shutdown').should == true
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#shutdown! should kill child process' do
|
43
|
+
mock(@worker.kill_child)
|
44
|
+
mock(@worker.shutdown)
|
45
|
+
|
46
|
+
@worker.shutdown!
|
47
|
+
end
|
48
|
+
|
49
|
+
it '#kill_child' do
|
50
|
+
job = TestJob.new()
|
51
|
+
job.instance_variable_set '@child_pid', 123123
|
52
|
+
@worker.instance_variable_set('@job', job)
|
53
|
+
|
54
|
+
mock(Kernel).system("ps -o pid,state -p #{123123}") { true }
|
55
|
+
mock(Process).kill("KILL", 123123)
|
56
|
+
|
57
|
+
@worker.kill_child
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rj
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pavel Lazureykis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: amqp
|
16
|
+
requirement: &70272887573820 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.9'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70272887573820
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70272887573200 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70272887573200
|
36
|
+
description: Background jobs on RabbitMQ
|
37
|
+
email:
|
38
|
+
- lazureykis@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .rspec
|
45
|
+
- Gemfile
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- examples/configuration.rb
|
50
|
+
- lib/rabbit_jobs.rb
|
51
|
+
- lib/rabbit_jobs/amqp_helpers.rb
|
52
|
+
- lib/rabbit_jobs/configuration.rb
|
53
|
+
- lib/rabbit_jobs/helpers.rb
|
54
|
+
- lib/rabbit_jobs/job.rb
|
55
|
+
- lib/rabbit_jobs/logger.rb
|
56
|
+
- lib/rabbit_jobs/publisher.rb
|
57
|
+
- lib/rabbit_jobs/tasks.rb
|
58
|
+
- lib/rabbit_jobs/version.rb
|
59
|
+
- lib/rabbit_jobs/worker.rb
|
60
|
+
- lib/tasks/rabbit_jobs.rake
|
61
|
+
- rabbit_jobs.gemspec
|
62
|
+
- spec/fixtures/config.yml
|
63
|
+
- spec/fixtures/jobs.rb
|
64
|
+
- spec/integration/publisher_spec.rb
|
65
|
+
- spec/integration/worker_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
- spec/unit/configuration_spec.rb
|
68
|
+
- spec/unit/job_spec.rb
|
69
|
+
- spec/unit/logger_spec.rb
|
70
|
+
- spec/unit/rabbit_jobs_spec.rb
|
71
|
+
- spec/unit/worker_spec.rb
|
72
|
+
homepage: ''
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.15
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Background jobs on RabbitMQ
|
96
|
+
test_files:
|
97
|
+
- spec/fixtures/config.yml
|
98
|
+
- spec/fixtures/jobs.rb
|
99
|
+
- spec/integration/publisher_spec.rb
|
100
|
+
- spec/integration/worker_spec.rb
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/unit/configuration_spec.rb
|
103
|
+
- spec/unit/job_spec.rb
|
104
|
+
- spec/unit/logger_spec.rb
|
105
|
+
- spec/unit/rabbit_jobs_spec.rb
|
106
|
+
- spec/unit/worker_spec.rb
|