leveret 0.1.0
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.
- 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: []
|