leveret 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +204 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/leveret_worker +16 -0
- data/leveret.gemspec +26 -0
- data/lib/leveret/configuration.rb +48 -0
- data/lib/leveret/job.rb +159 -0
- data/lib/leveret/log_formatter.rb +24 -0
- data/lib/leveret/parameters.rb +59 -0
- data/lib/leveret/queue.rb +105 -0
- data/lib/leveret/version.rb +3 -0
- data/lib/leveret/worker.rb +147 -0
- data/lib/leveret.rb +78 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 78c17ba2d29034d05262a5c3454ddf1e423b6cdd
|
4
|
+
data.tar.gz: a5346c1ba41a7647f997b2599730c682bc0e885a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 95b4f65d3fac2a06b6a2c06def7bd9408d35d1bf31aead3828030f8d001f5792fbe6c8c2c63e2d1116c3e983e323ec3a1e83bcc0677253805657696cfb9a9872
|
7
|
+
data.tar.gz: 6c46a540248cc82b981ec5e5ff869fa578e5cbefc6b27ae1c7daed891e6178c62031cd2fc374cfc3dc14901886fbcc8ab0d56f9222c9d8f02e933ba6e2303c5e
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Dan Wentworth
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# Leveret
|
2
|
+
|
3
|
+
Leveret is an easy to use RabbitMQ backed job runner.
|
4
|
+
|
5
|
+
It's designed specifically to execute long running jobs (multiple hours) while allowing the applicatioin to be
|
6
|
+
restarted with no adverse effects on the currently running jobs.
|
7
|
+
|
8
|
+
Leveret has been tested with Ruby 2.2.3+ and RabbitMQ 3.5.0+.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'leveret'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install leveret
|
25
|
+
|
26
|
+
To use Leveret you need a running RabbitMQ installation. If you don't have it installed yet, you can do so via
|
27
|
+
[Homebrew](https://www.rabbitmq.com/install-homebrew.html) (MacOS), [APT](https://www.rabbitmq.com/install-debian.html)
|
28
|
+
(Debian/Ubuntu) or [RPM](https://www.rabbitmq.com/install-rpm.html) (Redhat/Fedora).
|
29
|
+
|
30
|
+
RabbitMQ version **3.5.0** or higher is recommended as this is the first version to support message priorities.
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
To create a job include `Leveret::Job` in a new class, and define a `perform` method that will do the work in
|
35
|
+
your job. Call `enqueue` on your new class with any parameters you want passed to the job at execution time.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class MyJob
|
39
|
+
include Leveret::Job
|
40
|
+
|
41
|
+
def perform
|
42
|
+
File.open('/tmp/leveret-test-file.txt', 'a+') do |f|
|
43
|
+
f.puts params[:test_text]
|
44
|
+
end
|
45
|
+
|
46
|
+
sleep 5 # Job takes a long time
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
MyJob.enqueue(test_text: "Hi there! Please write me to the test file.")
|
51
|
+
```
|
52
|
+
|
53
|
+
Now start a worker to execute the job:
|
54
|
+
|
55
|
+
```bash
|
56
|
+
bundle exec leveret_worker
|
57
|
+
```
|
58
|
+
|
59
|
+
### Queues
|
60
|
+
|
61
|
+
By default all are defined on a single standard queue (see Configuration for details). However, it's possible to use
|
62
|
+
multiple queues for different jobs. To do this set the `queue_name` in your job class. You'll also need to tell the
|
63
|
+
worker about your new queue when starting that.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
class MyOtherQueueJob
|
67
|
+
include Leveret::Job
|
68
|
+
|
69
|
+
queue_name 'other'
|
70
|
+
|
71
|
+
def perform
|
72
|
+
# ...
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
MyOtherQueueJob.enqueue(test_text: "Hi there! Please write me to the test file.")
|
77
|
+
```
|
78
|
+
|
79
|
+
If you don't always want to place the job on your other queue, you can specify the queue name when enqueuing it. Pass
|
80
|
+
the `queue_name` option when enqueuing the job.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
MyJob.enqueue(test_text: "Hi there! Please write me to the test file.", queue_name: 'other')
|
84
|
+
```
|
85
|
+
|
86
|
+
### Priorities
|
87
|
+
|
88
|
+
Leveret supports 3 levels of job priority, `:low`, `:normal` and `:high`. To set the priority you can define it in your
|
89
|
+
job class, or specify it at enqueue time by passing the `priority` option.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class MyHighPriorityMyJob
|
93
|
+
include Leveret::Job
|
94
|
+
|
95
|
+
priority :high
|
96
|
+
|
97
|
+
def perform
|
98
|
+
# very important work...
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
MyHighPriorityJob.enqueue
|
103
|
+
```
|
104
|
+
|
105
|
+
To specify priority at enqueue time:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
MyJob.enqueue(test_text: "Hi there! Please write me to the test file.", priority: :high)
|
109
|
+
```
|
110
|
+
|
111
|
+
### Workers
|
112
|
+
|
113
|
+
To start a leveret worker, simply run the `leveret_worker` executable included in the gem. Started with no arguments it
|
114
|
+
will create a worker monitoring the default queue and process one job at a time.
|
115
|
+
|
116
|
+
Changing the queues that a worker monitors requires passing a comma separated list of queue names in the environment
|
117
|
+
variable `QUEUES`. The example below watches for jobs on the queues `standard` and `other`.
|
118
|
+
|
119
|
+
```bash
|
120
|
+
bundle exec leveret_worker QUEUES=standard,other
|
121
|
+
```
|
122
|
+
|
123
|
+
By default, workers will only process one job at a time. For each job that is executed, a child process is forked, and
|
124
|
+
the job run in the new process. When the job completes, the fork exits. We can process more jobs simultaniously simply
|
125
|
+
by allowing more forks to run. To increase this limit set the `PROCESSES` environment variable. There is no limit to
|
126
|
+
this variable in Leveret, but you should be aware of your own OS and resource limits.
|
127
|
+
|
128
|
+
```bash
|
129
|
+
bundle exec leveret_worker PROCESSES=5
|
130
|
+
```
|
131
|
+
|
132
|
+
## Configuration
|
133
|
+
|
134
|
+
Configuration in Leveret is done via a configure block. In a Rails application it is recommended you place your
|
135
|
+
configuration in `config/initializers/leveret.rb`. Leveret comes configured with sane defaults for development, but you
|
136
|
+
may wish to change some for production use.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
Leveret.configure do |config|
|
140
|
+
# Location of your RabbitMQ server
|
141
|
+
config.amqp = "amqp://guest:guest@localhost:5672/"
|
142
|
+
# Name of the exchange Levert will create on RabbitMQ
|
143
|
+
config.exchange_name = 'leveret_exch'
|
144
|
+
# Path to send log output to
|
145
|
+
config.log_file = STDOUT
|
146
|
+
# Verbosity of log output
|
147
|
+
config.log_level = Logger::DEBUG
|
148
|
+
# String that is prepended to all queues created in RabbitMQ
|
149
|
+
config.queue_name_prefix = 'leveret_queue'
|
150
|
+
# Name of the queue to use if none other is specified
|
151
|
+
config.default_queue_name = 'standard'
|
152
|
+
# A block that should be called every time a child fork is created to process a job
|
153
|
+
config.after_fork = proc {}
|
154
|
+
# A block that is called whenever an exception occurs in a job
|
155
|
+
config.error_handler = proc { |ex| ex }
|
156
|
+
# The default number of jobs to process simultaniously, this can be overridden by the PROCESSES
|
157
|
+
# environment variable when starting a worker
|
158
|
+
config.concurrent_fork_count = 1
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
Most of these are pretty self-explanatory and can be left to their default values, however `after_fork` and
|
163
|
+
`error_handler` could use a little more explaining.
|
164
|
+
|
165
|
+
`after_fork` Is called immediately after a child process is forked to run a job. Any connections that need to be
|
166
|
+
reinitialized on fork should be done so here. For example, if you're using Rails you'll probably want to reconnect
|
167
|
+
to ActiveRecord here:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
Leveret.configure do |config|
|
171
|
+
config.after_fork = proc do
|
172
|
+
ActiveRecord::Base.establish_connection
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
`error_handler` is called whenever an exception is raised in your job. These exceptions are caught and logged, but not
|
178
|
+
raised afterwards. `error_handler` is your chance to decide what to do with these exceptions. You may wish to log them
|
179
|
+
using a service such as [Airbrake](https://airbrake.io/) or [Sentry](https://getsentry.com/welcome/). To configure an
|
180
|
+
error handler to log to Sentry the following would be necessary:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
Leveret.configure do |config|
|
184
|
+
config.error_handler = proc do |exception|
|
185
|
+
Raven.capture_exception(exception, tags: {component: 'leveret'})
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
## Development
|
191
|
+
|
192
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
193
|
+
|
194
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
195
|
+
|
196
|
+
## Contributing
|
197
|
+
|
198
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/darkphnx/leveret.
|
199
|
+
|
200
|
+
|
201
|
+
## License
|
202
|
+
|
203
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
204
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "leveret"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/leveret_worker
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "leveret"
|
5
|
+
|
6
|
+
if File.exist?("./config/environment.rb")
|
7
|
+
require File.expand_path("./config/environment.rb")
|
8
|
+
end
|
9
|
+
|
10
|
+
queues = ENV["QUEUES"].to_s.split(',')
|
11
|
+
queues << Leveret.configuration.default_queue_name if queues.empty?
|
12
|
+
|
13
|
+
concurrent_fork_count = ENV["PROCESSES"] || Leveret.configuration.concurrent_fork_count
|
14
|
+
|
15
|
+
worker = Leveret::Worker.new(queues: queues, concurrent_fork_count: concurrent_fork_count)
|
16
|
+
worker.do_work
|
data/leveret.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'leveret/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "leveret"
|
8
|
+
spec.version = Leveret::VERSION
|
9
|
+
spec.authors = ["Dan Wentworth"]
|
10
|
+
spec.email = ["dan@atechmedia.com"]
|
11
|
+
|
12
|
+
spec.summary = "Simple RabbitMQ backed backround worker"
|
13
|
+
spec.homepage = "https://github.com/darkphnx/leveret"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "bunny", '~> 2.3'
|
22
|
+
spec.add_dependency "json", '~> 1.8'
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Contains everything needed to configure leveret for work. Sensible defaults are included
|
3
|
+
# and will be initialized with the class.
|
4
|
+
#
|
5
|
+
# @!attribute amqp
|
6
|
+
# @return [String] Location of your RabbitMQ server. Default: +"amqp://guest:guest@localhost:5672/"+
|
7
|
+
# @!attribute exchange_name
|
8
|
+
# @return [String] Name of the exchange for Leveret to publish messages to. Default: +"leveret_exch"+
|
9
|
+
# @!attribute queue_name_prefix
|
10
|
+
# @return [String] This value will be prefixed to all queues created on your RabbitMQ instance.
|
11
|
+
# Default: +"leveret_queue"+
|
12
|
+
# @!attribute log_file
|
13
|
+
# @return [String] The path where logfiles should be written to. Default: +STDOUT+
|
14
|
+
# @!attribute log_level
|
15
|
+
# @return [Integer] The log severity which should be output to the log. Default: +Logger::DEBUG+
|
16
|
+
# @!attribute default_queue_name
|
17
|
+
# @return [String] The name of the queue that will be use unless explicitly specified in a job. Default:
|
18
|
+
# +"standard"+
|
19
|
+
# @!attribute after_fork
|
20
|
+
# @return [Proc] A proc which will be executed in a child after forking to process a message. Default: +proc {}+
|
21
|
+
# @!attribute error_handler
|
22
|
+
# @return [Proc] A proc which will be called if a job raises an exception. Default: +proc {|ex| ex }+
|
23
|
+
# @!attribute concurrent_fork_count
|
24
|
+
# @return [Integer] The number of jobs that can be processes simultanously. Default: +1+
|
25
|
+
class Configuration
|
26
|
+
attr_accessor :amqp, :exchange_name, :queue_name_prefix, :log_file, :log_level, :default_queue_name, :after_fork,
|
27
|
+
:error_handler, :concurrent_fork_count
|
28
|
+
|
29
|
+
# Create a new instance of Configuration with a set of sane defaults.
|
30
|
+
def initialize
|
31
|
+
assign_defaults
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def assign_defaults
|
37
|
+
self.amqp = "amqp://guest:guest@localhost:5672/"
|
38
|
+
self.exchange_name = 'leveret_exch'
|
39
|
+
self.log_file = STDOUT
|
40
|
+
self.log_level = Logger::DEBUG
|
41
|
+
self.queue_name_prefix = 'leveret_queue'
|
42
|
+
self.default_queue_name = 'standard'
|
43
|
+
self.after_fork = proc {}
|
44
|
+
self.error_handler = proc { |ex| ex }
|
45
|
+
self.concurrent_fork_count = 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/leveret/job.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Include this module in your job to create a leveret compatible job.
|
3
|
+
# Once included, simply override #perform to do your jobs action.
|
4
|
+
#
|
5
|
+
# To set a different queue name call #queue_name in your class, to set
|
6
|
+
# the default priority call #priority in your class.
|
7
|
+
#
|
8
|
+
# To queue a job simply call #enqueue on the class with the parameters
|
9
|
+
# to be passed. These params will be serialized as JSON in the interim,
|
10
|
+
# so ensure that your params are json-safe.
|
11
|
+
#
|
12
|
+
# @example Job Class
|
13
|
+
# class MyJob
|
14
|
+
# include Leveret::Job
|
15
|
+
#
|
16
|
+
# queue_name 'my_custom_queue' # omit for default
|
17
|
+
# priority :high # omit for default
|
18
|
+
#
|
19
|
+
# def perform
|
20
|
+
# File.open('/tmp/leveret-test-file.txt', 'a+') do |f|
|
21
|
+
# f.puts params[:test_text]
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# sleep 5 # Job takes a long time
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @example Queueing a Job
|
29
|
+
# # With options defined in class
|
30
|
+
# MyJob.enqueue(test_text: "Hi there, please write this text to the file")
|
31
|
+
#
|
32
|
+
# # Set the job priority at queue time
|
33
|
+
# MyJob.enqueue(test_text: "Hi there, please write this important text to the file", priority: :high)
|
34
|
+
#
|
35
|
+
# # Place in a different queue to the one defined in the class
|
36
|
+
# MyJob.enqueue(test_text: "Hi there, please write this different text to the file", queue_name: 'other_queue')
|
37
|
+
#
|
38
|
+
module Job
|
39
|
+
# Raise this when your job has failed, but try again when a worker is
|
40
|
+
# available again.
|
41
|
+
class RequeueJob < StandardError; end
|
42
|
+
|
43
|
+
# Raise this when your job has failed, but you don't want to requeue it
|
44
|
+
# and try again.
|
45
|
+
class RejectJob < StandardError; end
|
46
|
+
|
47
|
+
# Instance methods to mixin with your job
|
48
|
+
module InstanceMethods
|
49
|
+
# @!attribute params
|
50
|
+
# @return [Paramters] The parameters required for task execution
|
51
|
+
attr_accessor :params
|
52
|
+
|
53
|
+
# Create a new job ready for execution
|
54
|
+
#
|
55
|
+
# @param [Parameters] params Parameters to be consumed by {#perform} when performing the job
|
56
|
+
def initialize(params = Parameters.new)
|
57
|
+
self.params = params
|
58
|
+
end
|
59
|
+
|
60
|
+
# Runs the job and captures any exceptions to turn them into symbols which represent the status of the job
|
61
|
+
#
|
62
|
+
# @return [Symbol] :success, :requeue, :reject depending on job success
|
63
|
+
def run
|
64
|
+
Leveret.log.info "Running #{self.class.name} with #{params}"
|
65
|
+
perform
|
66
|
+
:success
|
67
|
+
rescue Leveret::Job::RequeueJob
|
68
|
+
Leveret.log.warn "Requeueing job #{self.class.name} with #{params}"
|
69
|
+
:requeue
|
70
|
+
rescue Leveret::Job::RejectJob
|
71
|
+
Leveret.log.warn "Rejecting job #{self.class.name} with #{params}"
|
72
|
+
:reject
|
73
|
+
rescue StandardError => e
|
74
|
+
Leveret.log.error "#{e.message} when processing #{self.class.name} with #{params}"
|
75
|
+
Leveret.configuration.error_handler.call(e)
|
76
|
+
:reject
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the job with no error handling. Generally you should call {#run} to execute the job since that'll write
|
80
|
+
# and log output and call any error handlers if the job goes sideways.
|
81
|
+
#
|
82
|
+
# @note Your class should override this method to contain the work to be done in this job.
|
83
|
+
#
|
84
|
+
# @raise [RequeueJob] Reject this job and put it back on the queue for future execution
|
85
|
+
# @raise [RejectJob] Reject this job and do not requeue it.
|
86
|
+
def perform
|
87
|
+
raise NotImplementedError
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Class methods to mixin with your job
|
92
|
+
module ClassMethods
|
93
|
+
# Shorthand to intialize a new job and run it with error handling
|
94
|
+
#
|
95
|
+
# @param [Parameters] params Parameters to pass to the job for execution
|
96
|
+
#
|
97
|
+
# @return [Symbol] :success, :requeue or :reject depending on job execution
|
98
|
+
def perform(params = Parameters.new)
|
99
|
+
new(params).run
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set a custom queue for this job
|
103
|
+
#
|
104
|
+
# @param [String] name Name of the queue to assign this job to
|
105
|
+
def queue_name(name)
|
106
|
+
job_options[:queue_name] = name
|
107
|
+
end
|
108
|
+
|
109
|
+
# Set a custom priority for this job
|
110
|
+
#
|
111
|
+
# @param [Symbol] priority Priority for this job, see {Queue::PRIORITY_MAP} for details
|
112
|
+
def priority(priority)
|
113
|
+
job_options[:priority] = priority
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Hash] The current set of options for this job, the +queue_name+ and +priority+.
|
117
|
+
def job_options
|
118
|
+
@job_options ||= {
|
119
|
+
queue_name: Leveret.configuration.default_queue_name,
|
120
|
+
priority: :normal
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
# Place a job onto the queue for processing by a worker.
|
125
|
+
#
|
126
|
+
# @param [Hash] params The parameters to be included with the work request. These can be anything, however
|
127
|
+
# the keys +:priority+ and +:queue_name+ are reserved for customising those aspects of the job's execution
|
128
|
+
#
|
129
|
+
# @option params [Symbol] :priority (job_options[:priority]) Override the class-level priority for this job only
|
130
|
+
# @option params [String] :queue_name (job_options[:queue_name]) Override the class-level queue name for this
|
131
|
+
# job only.
|
132
|
+
def enqueue(params = {})
|
133
|
+
priority = params.delete(:priority) || job_options[:priority]
|
134
|
+
q_name = params.delete(:queue_name) || job_options[:queue_name]
|
135
|
+
|
136
|
+
Leveret.log.info "Queuing #{name} to #{q_name} (#{priority}) with #{params}"
|
137
|
+
|
138
|
+
payload = { job: self.name, params: params }
|
139
|
+
queue(q_name).publish(payload, priority: priority)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @private Cache the queue for this job
|
143
|
+
#
|
144
|
+
# @param [q_name] The name of the queue we want to cache. If nil it'll use the name defined in
|
145
|
+
# +job_options[:queue_name]+
|
146
|
+
# @return [Queue] Cached Queue object for publishing jobs
|
147
|
+
def queue(q_name = nil)
|
148
|
+
q_name ||= job_options[:queue_name]
|
149
|
+
@queue ||= {}
|
150
|
+
@queue[q_name] ||= Leveret::Queue.new(q_name)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.included(base)
|
155
|
+
base.extend ClassMethods
|
156
|
+
base.include InstanceMethods
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Prettier logging than the default
|
3
|
+
class LogFormatter < Logger::Formatter
|
4
|
+
# ANSI colour codes for different message types
|
5
|
+
SEVERITY_TO_COLOR_MAP = { 'DEBUG' => '0;37', 'INFO' => '32', 'WARN' => '33', 'ERROR' => '31', 'FATAL' => '31',
|
6
|
+
'UNKNOWN' => '37' }.freeze
|
7
|
+
|
8
|
+
# Build a pretty formatted log line
|
9
|
+
#
|
10
|
+
# @param [String] severity Log level, one of debug, info, warn, error, fatal or unknown
|
11
|
+
# @param [Time] datetime Timestamp of log event
|
12
|
+
# @param [String] _progname (Unused) the name of the progname set in the logger
|
13
|
+
# @param [String] msg Body of the log message
|
14
|
+
#
|
15
|
+
# @return [String] Formatted and coloured log message in the format:
|
16
|
+
# "YYYY-MM-DD HH:MM:SS:USEC [SEVERITY] MESSAGE (pid:Process ID)"
|
17
|
+
def call(severity, datetime, _progname, msg)
|
18
|
+
formatted_time = datetime.strftime("%Y-%m-%d %H:%M:%S") << datetime.usec.to_s[0..2].rjust(3)
|
19
|
+
color = SEVERITY_TO_COLOR_MAP[severity]
|
20
|
+
|
21
|
+
"\033[0;37m#{formatted_time}\033[0m [\033[#{color}m#{severity}\033[0m] #{msg2str(msg)} (pid:#{Process.pid})\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Provides basic indifferent hash access for jobs, allows strings to be used to access symbol keyed values,
|
3
|
+
# or symbols to be used to access string keyed values.
|
4
|
+
#
|
5
|
+
# Overrides the [] method of the hash, all other calls (except {#serialize}) are delegated to the {#params} object
|
6
|
+
#
|
7
|
+
# Beware of using both strings and symbols with the same value in the same hash e.g. +{:one => 1, 'one' => 1}+
|
8
|
+
# only one of these values will ever be returned.
|
9
|
+
class Parameters
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# The parameters hash wrapped up by this object
|
13
|
+
attr_accessor :params
|
14
|
+
|
15
|
+
def_delegators :params, :==, :inspect, :to_s
|
16
|
+
|
17
|
+
# @param [Hash] params Hash you wish to access indifferently
|
18
|
+
def initialize(params = {})
|
19
|
+
self.params = params || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Access {#params} indifferently. Tries the passed key directly first, then tries it as a symbol, then tries
|
23
|
+
# it as a string.
|
24
|
+
#
|
25
|
+
# @param [Object] key Key of the item we're trying to access in {#params}
|
26
|
+
#
|
27
|
+
# @return [Object, nil] Value related to key, or nil if object is not found
|
28
|
+
def [](key)
|
29
|
+
params[key] || (key.respond_to?(:to_sym) && params[key.to_sym]) || (key.respond_to?(:to_s) && params[key.to_s])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Delegate any unknown methods to the {#params} hash
|
33
|
+
def method_missing(method_name, *arguments, &block)
|
34
|
+
params.send(method_name, *arguments, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check the {#params} hash as well as this class for a method's existence
|
38
|
+
def respond_to?(method_name, include_private = false)
|
39
|
+
params.respond_to?(method_name, include_private) || super
|
40
|
+
end
|
41
|
+
|
42
|
+
# Serialize the current value of {#params}. Outputs JSON.
|
43
|
+
#
|
44
|
+
# @return [String] JSON encoded representation of the params
|
45
|
+
def serialize
|
46
|
+
JSON.dump(params)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create a new instance of this class from a a serialized JSON object.
|
50
|
+
#
|
51
|
+
# @param [String] json JSON representation of the parameters we want to access
|
52
|
+
#
|
53
|
+
# @return [Parameters] New instance based on the passed JSON
|
54
|
+
def self.from_json(json)
|
55
|
+
params = JSON.load(json)
|
56
|
+
new(params)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Facilitates the publishing or subscribing of messages to the message queue.
|
3
|
+
#
|
4
|
+
# @!attribute [r] name
|
5
|
+
# @return [String] Name of the queue. This will have Leveret.queue_name_prefix prepended to it when creating a
|
6
|
+
# corresponding queue in RabbitMQ.
|
7
|
+
# @!attribute [r] queue
|
8
|
+
# @return [Bunny::Queue] The backend RabbitMQ queue
|
9
|
+
# @see http://reference.rubybunny.info/Bunny/Queue.html Bunny::Queue Documentation
|
10
|
+
class Queue
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
# Map the symbol names for priorities to the integers that RabbitMQ requires.
|
14
|
+
PRIORITY_MAP = { low: 0, normal: 1, high: 2 }.freeze
|
15
|
+
attr_reader :name, :queue
|
16
|
+
|
17
|
+
def_delegators :Leveret, :exchange, :channel, :log
|
18
|
+
def_delegators :queue, :pop, :purge
|
19
|
+
|
20
|
+
# Create a new queue with the name given in the params, if no name is given it will default to
|
21
|
+
# {Configuration#default_queue_name}. On instantiation constructor will immedaitely connect to
|
22
|
+
# RabbitMQ backend and create a queue with the appropriate name, or join an existing one.
|
23
|
+
#
|
24
|
+
# @param [String] name Name of the queue to connect to.
|
25
|
+
def initialize(name = nil)
|
26
|
+
@name = name || Leveret.configuration.default_queue_name
|
27
|
+
@queue = connect_to_queue
|
28
|
+
end
|
29
|
+
|
30
|
+
# Publish a mesage onto the queue. Fire and forget, this method is non-blocking and will not wait until
|
31
|
+
# the message is definitely on the queue.
|
32
|
+
#
|
33
|
+
# @param [Hash] payload The data we wish to send onto the queue, this will be serialized and automatically
|
34
|
+
# deserialized when received by a {#subscribe} block.
|
35
|
+
# @option options [Symbol] :priority (:normal) The priority this message should be treated with on the queue
|
36
|
+
# see {PRIORITY_MAP} for available options.
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def publish(payload, options = {})
|
40
|
+
priority_id = PRIORITY_MAP[options[:priority]] || PRIORITY_MAP[:normal]
|
41
|
+
payload = serialize_payload(payload)
|
42
|
+
|
43
|
+
log.debug "Publishing #{payload.inspect} for queue #{name} (Priority: #{priority_id})"
|
44
|
+
queue.publish(payload, persistent: true, routing_key: name, priority: priority_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Subscribe to this queue and yield a block for every message received. This method does not block, receiving and
|
48
|
+
# dispatching of messages will be handled in a separate thread.
|
49
|
+
#
|
50
|
+
# The receiving block is responsible for acknowledging or rejecting the message. This must be done using the
|
51
|
+
# same channel the message was received # on, {#Leveret.channel}. {Worker#ack_message} provides an example
|
52
|
+
# implementation of this acknowledgement.
|
53
|
+
#
|
54
|
+
# @note The receiving block is responsible for acking/rejecting the message. Please see the note for more details.
|
55
|
+
#
|
56
|
+
# @yieldparam delivery_tag [String] The identifier for this message that must be used do ack/reject the message
|
57
|
+
# @yieldparam payload [Parameters] A deserialized version of the payload contained in the message
|
58
|
+
#
|
59
|
+
# @return [void]
|
60
|
+
def subscribe
|
61
|
+
log.info "Subscribing to #{name}"
|
62
|
+
queue.subscribe(manual_ack: true) do |delivery_info, _properties, msg|
|
63
|
+
log.debug "Received #{msg} from #{name}"
|
64
|
+
yield(delivery_info.delivery_tag, deserialize_payload(msg))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Convert a set of parameters passed into a serialized form suitable for transport on the message queue
|
71
|
+
#
|
72
|
+
# @param [Hash] Paramets to be serialized
|
73
|
+
#
|
74
|
+
# @return [String] Encoded params ready to be sent onto the queue
|
75
|
+
def serialize_payload(params)
|
76
|
+
Leveret::Parameters.new(params).serialize
|
77
|
+
end
|
78
|
+
|
79
|
+
# Convert a set of serialized parameters into a {Parameters} object
|
80
|
+
#
|
81
|
+
# @param [String] JSON representation of the parameters
|
82
|
+
#
|
83
|
+
# @return [Parameters] Useful object representation of the parameters
|
84
|
+
def deserialize_payload(json)
|
85
|
+
Leveret::Parameters.from_json(json)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Create or return a representation of the queue on the RabbitMQ backend
|
89
|
+
#
|
90
|
+
# @return [Bunny::Queue] RabbitMQ queue
|
91
|
+
def connect_to_queue
|
92
|
+
queue = channel.queue(mq_name, durable: true, auto_delete: false, arguments: { 'x-max-priority' => 2 })
|
93
|
+
queue.bind(exchange, routing_key: name)
|
94
|
+
log.debug "Connected to #{mq_name}, bound on #{name}"
|
95
|
+
queue
|
96
|
+
end
|
97
|
+
|
98
|
+
# Calculate the name of the queue that should be used on the RabbitMQ backend
|
99
|
+
#
|
100
|
+
# @return [String] Backend queue name
|
101
|
+
def mq_name
|
102
|
+
@mq_name ||= [Leveret.configuration.queue_name_prefix, name].join('_')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Leveret
|
2
|
+
# Subscribes to one or more queues and forks workers to perform jobs as they arrive
|
3
|
+
#
|
4
|
+
# Call {#do_work} to subscribe to all queues and block the main thread.
|
5
|
+
class Worker
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# @!attribute queues
|
9
|
+
# @return [Array<Queue>] All of the queues this worker is going to subscribe to
|
10
|
+
# @!attribute consumers
|
11
|
+
# @return [Array<Bunny::Consumer>] All of the actively subscribed queues
|
12
|
+
attr_accessor :queues, :consumers
|
13
|
+
|
14
|
+
def_delegators :Leveret, :log, :channel, :configuration
|
15
|
+
|
16
|
+
# Create a new worker to process jobs from the list of queue names passed
|
17
|
+
#
|
18
|
+
# @option options [Array<String>] queues ([Leveret.configuration.default_queue_name]) A list of queue names for
|
19
|
+
# this worker to subscribe to and process
|
20
|
+
# @option options [Integer] concurrent_fork_count (Leveret.configuration.concurrent_fork_count) How many messages
|
21
|
+
# at a time should this worker process?
|
22
|
+
def initialize(options = {})
|
23
|
+
options = {
|
24
|
+
queues: [configuration.default_queue_name],
|
25
|
+
concurrent_fork_count: [configuration.concurrent_fork_count]
|
26
|
+
}.merge(options)
|
27
|
+
|
28
|
+
Leveret.configuration.concurrent_fork_count = options[:concurrent_fork_count]
|
29
|
+
|
30
|
+
self.queues = options[:queues].map { |name| Leveret::Queue.new(name) }
|
31
|
+
self.consumers = []
|
32
|
+
@time_to_die = false
|
33
|
+
end
|
34
|
+
|
35
|
+
# Subscribe to all of the {#queues} and begin processing jobs from them. This will block the main
|
36
|
+
# thread until an interrupt is received.
|
37
|
+
def do_work
|
38
|
+
log.info "Starting master process for #{queues.map(&:name).join(', ')}"
|
39
|
+
prepare_for_work
|
40
|
+
|
41
|
+
loop do
|
42
|
+
if @time_to_die
|
43
|
+
cancel_subscriptions
|
44
|
+
break
|
45
|
+
end
|
46
|
+
sleep 1
|
47
|
+
end
|
48
|
+
log.info "Exiting master process"
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Steps that need to be prepared before we can begin processing jobs
|
54
|
+
def prepare_for_work
|
55
|
+
setup_traps
|
56
|
+
self.process_name = 'leveret-worker-parent'
|
57
|
+
start_subscriptions
|
58
|
+
end
|
59
|
+
|
60
|
+
# Catch INT and TERM signals and set an instance variable to signal the main loop to quit when possible
|
61
|
+
def setup_traps
|
62
|
+
trap('INT') do
|
63
|
+
@time_to_die = true
|
64
|
+
end
|
65
|
+
trap('TERM') do
|
66
|
+
@time_to_die = true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set the title of this process so it's easier on the eye in top
|
71
|
+
def process_name=(name)
|
72
|
+
Process.setproctitle(name)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Subscribe to each queue defined in {#queues} and add the returned consumer to {#consumers}. This will
|
76
|
+
# allow us to gracefully cancel these subscriptions when we need to quit.
|
77
|
+
def start_subscriptions
|
78
|
+
queues.map do |queue|
|
79
|
+
consumers << queue.subscribe do |delivery_tag, payload|
|
80
|
+
fork_and_run(delivery_tag, payload)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Send cancel to each consumer in the {#consumers} list. This will end the current subscription.
|
86
|
+
def cancel_subscriptions
|
87
|
+
log.info "Interrupt received, preparing to exit"
|
88
|
+
consumers.each do |consumer|
|
89
|
+
log.debug "Cancelling consumer on #{consumer.queue.name}"
|
90
|
+
consumer.cancel
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Fork the current process and run the job described by #payload in the newly created child process.
|
95
|
+
# Detach the main process from the child so we can return to the main loop without waiting for it to finish
|
96
|
+
# processing the job.
|
97
|
+
#
|
98
|
+
# @param [String] delivery_tag The identifier that RabbitMQ uses to track the message. This will be used to ack
|
99
|
+
# or reject the message after processing.
|
100
|
+
# @param [Parameters] payload The job name and parameters the job requires
|
101
|
+
def fork_and_run(delivery_tag, payload)
|
102
|
+
pid = fork do
|
103
|
+
self.process_name = 'leveret-worker-child'
|
104
|
+
log.info "[#{delivery_tag}] Forked to child process #{pid} to run #{payload[:job]}"
|
105
|
+
|
106
|
+
Leveret.configuration.after_fork.call
|
107
|
+
|
108
|
+
result = perform_job(payload)
|
109
|
+
log.info "[#{delivery_tag}] Job returned #{result}"
|
110
|
+
ack_message(delivery_tag, result)
|
111
|
+
|
112
|
+
log.info "[#{delivery_tag}] Exiting child process #{pid}"
|
113
|
+
exit!(0)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Master doesn't need to know how it all went down, the worker will report it's own status back to the queue
|
117
|
+
Process.detach(pid)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Constantize the class name in the payload and execute the job with parameters
|
121
|
+
#
|
122
|
+
# @param [Parameters] payload The job name and parameters the job requires
|
123
|
+
# @return [Symbol] :success, :reject or :requeue depending on how job execution went
|
124
|
+
def perform_job(payload)
|
125
|
+
job_klass = Object.const_get(payload[:job])
|
126
|
+
job_klass.perform(Leveret::Parameters.new(payload[:params]))
|
127
|
+
end
|
128
|
+
|
129
|
+
# Sends a message back to RabbitMQ confirming the completed execution of the message
|
130
|
+
#
|
131
|
+
# @param [String] delivery_tag The identifier that RabbitMQ uses to track the message. This will be used to ack
|
132
|
+
# or reject the message after processing.
|
133
|
+
# @param [Symbol] result :success, :reject or :requeue depending on how we want to acknowledge the message
|
134
|
+
def ack_message(delivery_tag, result)
|
135
|
+
if result == :reject
|
136
|
+
log.debug "[#{delivery_tag}] Rejecting message"
|
137
|
+
channel.reject(delivery_tag)
|
138
|
+
elsif result == :requeue
|
139
|
+
log.debug "[#{delivery_tag}] Requeueing message"
|
140
|
+
channel.reject(delivery_tag, true)
|
141
|
+
else
|
142
|
+
log.debug "[#{delivery_tag}] Acknowledging message"
|
143
|
+
channel.acknowledge(delivery_tag)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/leveret.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'json'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'leveret/configuration'
|
6
|
+
require 'leveret/job'
|
7
|
+
require 'leveret/log_formatter'
|
8
|
+
require 'leveret/parameters'
|
9
|
+
require 'leveret/queue'
|
10
|
+
require 'leveret/worker'
|
11
|
+
require "leveret/version"
|
12
|
+
|
13
|
+
# Top level module, contains things that are required globally by Leveret, such as configuration,
|
14
|
+
# the RabbitMQ channel and the logger.
|
15
|
+
module Leveret
|
16
|
+
class << self
|
17
|
+
# @!attribute [w] configuration
|
18
|
+
# @return [Configuration] Set a totally new configuration object
|
19
|
+
attr_writer :configuration
|
20
|
+
|
21
|
+
# @return [Configuration] The current configuration of Leveret
|
22
|
+
def configuration
|
23
|
+
@configuration ||= Configuration.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Allows leveret to be configured via a block
|
27
|
+
#
|
28
|
+
# @see Configuration Attributes that can be configured
|
29
|
+
# @yield [config] The current configuration object
|
30
|
+
def configure
|
31
|
+
yield(configuration) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Connect to the RabbitMQ exchange that Leveret uses, used by the {Queue} for publishing and subscribing, not
|
35
|
+
# recommended for general use.
|
36
|
+
#
|
37
|
+
# @see http://reference.rubybunny.info/Bunny/Exchange.html Bunny documentation
|
38
|
+
# @return [Bunny::Exchange] RabbitMQ exchange
|
39
|
+
def exchange
|
40
|
+
@exchange ||= channel.exchange(Leveret.configuration.exchange_name, type: :direct, durable: true,
|
41
|
+
auto_delete: false)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Connect to the RabbitMQ channel that {Queue} and {Worker} both use. This channel is not thread safe, so should
|
45
|
+
# be reinitialized if necessary. Not recommended for general use.
|
46
|
+
#
|
47
|
+
# @see http://reference.rubybunny.info/Bunny/Channel.html Bunny documentation
|
48
|
+
# @return [Bunny::Channel] RabbitMQ chanel
|
49
|
+
def channel
|
50
|
+
@channel ||= begin
|
51
|
+
chan = mq_connection.create_channel
|
52
|
+
chan.prefetch(configuration.concurrent_fork_count)
|
53
|
+
chan
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Logger used throughout Leveret, see {Configuration} for config options.
|
58
|
+
#
|
59
|
+
# @return [Logger] Standard ruby logger
|
60
|
+
def log
|
61
|
+
@log ||= Logger.new(configuration.log_file).tap do |log|
|
62
|
+
log.level = configuration.log_level
|
63
|
+
log.progname = 'Leveret'
|
64
|
+
log.formatter = Leveret::LogFormatter.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def mq_connection
|
71
|
+
@mq_connection ||= begin
|
72
|
+
conn = Bunny.new(amqp: configuration.amqp)
|
73
|
+
conn.start
|
74
|
+
conn
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: leveret
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Wentworth
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bunny
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- dan@atechmedia.com
|
86
|
+
executables:
|
87
|
+
- leveret_worker
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- exe/leveret_worker
|
100
|
+
- leveret.gemspec
|
101
|
+
- lib/leveret.rb
|
102
|
+
- lib/leveret/configuration.rb
|
103
|
+
- lib/leveret/job.rb
|
104
|
+
- lib/leveret/log_formatter.rb
|
105
|
+
- lib/leveret/parameters.rb
|
106
|
+
- lib/leveret/queue.rb
|
107
|
+
- lib/leveret/version.rb
|
108
|
+
- lib/leveret/worker.rb
|
109
|
+
homepage: https://github.com/darkphnx/leveret
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.5.1
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Simple RabbitMQ backed backround worker
|
133
|
+
test_files: []
|