rj 0.0.4.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/.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
|