toiler 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/.gitignore +35 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +6 -0
- data/README.md +110 -0
- data/Rakefile +1 -0
- data/bin/toiler +12 -0
- data/lib/toiler.rb +80 -0
- data/lib/toiler/cli.rb +141 -0
- data/lib/toiler/core_ext.rb +47 -0
- data/lib/toiler/environment_loader.rb +82 -0
- data/lib/toiler/fetcher.rb +39 -0
- data/lib/toiler/logging.rb +42 -0
- data/lib/toiler/manager.rb +109 -0
- data/lib/toiler/message.rb +60 -0
- data/lib/toiler/processor.rb +88 -0
- data/lib/toiler/queue.rb +53 -0
- data/lib/toiler/scheduler.rb +16 -0
- data/lib/toiler/supervisor.rb +23 -0
- data/lib/toiler/version.rb +3 -0
- data/lib/toiler/worker.rb +42 -0
- data/toiler.gemspec +29 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1411df909332ded188599a79d720d618e46bf5bd
|
4
|
+
data.tar.gz: 1e665ab6d968eaa0a85955202e5d78e8fedd99a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b2fa690e79c8a88c53ef0e4df2445f57f1eb5212db96b3bb61904d035eed5bcefef868fdf37c949fe650e70875b210acfbcfe836bd7c2fdf2d1a3accf14bc96f
|
7
|
+
data.tar.gz: bf9f7db7c915ed650d81597ac0c0eb74e0a65215fdad13e0c82d7a843ff9d5d76ab4d7978882b61a01362237c2b358c8cbc7643534f7c1e0a1c719997d3141fc
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/vendor/bundle
|
26
|
+
/lib/bundler/man/
|
27
|
+
|
28
|
+
# for a library or gem, you might want to ignore these files since the code is
|
29
|
+
# intended to run in multiple environments; otherwise, check them in:
|
30
|
+
# Gemfile.lock
|
31
|
+
# .ruby-version
|
32
|
+
# .ruby-gemset
|
33
|
+
|
34
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
35
|
+
.rvmrc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
aws-sdk (2.0.41)
|
5
|
+
aws-sdk-resources (= 2.0.41)
|
6
|
+
aws-sdk-core (2.0.41)
|
7
|
+
builder (~> 3.0)
|
8
|
+
jmespath (~> 1.0)
|
9
|
+
multi_json (~> 1.0)
|
10
|
+
aws-sdk-resources (2.0.41)
|
11
|
+
aws-sdk-core (= 2.0.41)
|
12
|
+
builder (3.2.2)
|
13
|
+
celluloid (0.16.0)
|
14
|
+
timers (~> 4.0.0)
|
15
|
+
celluloid-io (0.16.2)
|
16
|
+
celluloid (>= 0.16.0)
|
17
|
+
nio4r (>= 1.1.0)
|
18
|
+
hitimes (1.2.2)
|
19
|
+
jmespath (1.0.2)
|
20
|
+
multi_json (~> 1.0)
|
21
|
+
multi_json (1.11.0)
|
22
|
+
nio4r (1.1.0)
|
23
|
+
timers (4.0.1)
|
24
|
+
hitimes
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
aws-sdk
|
31
|
+
celluloid
|
32
|
+
celluloid-io
|
data/LICENSE
ADDED
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
##Toiler
|
2
|
+
Toiler is a AWS SQS long-polling thread-based message processor.
|
3
|
+
It's based on [shoryuken](https://github.com/phstc/shoryuken) but takes
|
4
|
+
a different approach at loadbalancing and uses long-polling.
|
5
|
+
|
6
|
+
##Features
|
7
|
+
###Concurrency
|
8
|
+
Toiler allows to specify the amount of processors (threads) that should be spawned for each queue.
|
9
|
+
Instead of [shoryuken's](https://github.com/phstc/shoryuken) loadbalancing approach, Toiler delegates this work to the kernel scheduling threads.
|
10
|
+
|
11
|
+
###Long-Polling
|
12
|
+
A Fetcher thread is spawned for each queue.
|
13
|
+
Fetchers are resposible for polling SQS and retreiving messages.
|
14
|
+
They are optimised to not bring more messages than the amount of processors avaiable for such queue.
|
15
|
+
By long-polling fetchers wait for a configurable amount of time for messages to become available on a single request, this prevents unneccesarilly requesting messages when there are none.
|
16
|
+
|
17
|
+
###Message Parsing
|
18
|
+
Workers can configure a parser Class or Proc to parse an SQS message body before being processed.
|
19
|
+
|
20
|
+
###Batches
|
21
|
+
Toiler allows a Worker to be able to receive a batch of messages instead of a single one.
|
22
|
+
|
23
|
+
##Instalation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'toiler'
|
29
|
+
```
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
|
33
|
+
$ bundle
|
34
|
+
|
35
|
+
Or install it yourself as:
|
36
|
+
|
37
|
+
$ gem install toiler
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
### Worker class
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class MyWorker
|
45
|
+
include Toiler::Worker
|
46
|
+
|
47
|
+
toiler_options queue: 'default', concurrency: 5, auto_delete: true
|
48
|
+
toiler_options parser: :json
|
49
|
+
|
50
|
+
# toiler_options parser: ->(sqs_msg){ REXML::Document.new(sqs_msg.body) }
|
51
|
+
# toiler_options parser: MultiJson
|
52
|
+
# toiler_options auto_visibility_timeout: true
|
53
|
+
# toiler_options batch: true
|
54
|
+
|
55
|
+
def perform(sqs_msg, body)
|
56
|
+
puts body
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### Configuration
|
62
|
+
|
63
|
+
```yaml
|
64
|
+
aws:
|
65
|
+
access_key_id: ... # or <%= ENV['AWS_ACCESS_KEY_ID'] %>
|
66
|
+
secret_access_key: ... # or <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
|
67
|
+
region: us-east-1 # or <%= ENV['AWS_REGION'] %>
|
68
|
+
wait: 20 # The time in seconds to wait for messages during long-polling
|
69
|
+
```
|
70
|
+
|
71
|
+
### Rails Integration
|
72
|
+
|
73
|
+
You can tell Toiler to load your Rails application by passing the `-R` or `--rails` flag to the "toiler" command.
|
74
|
+
|
75
|
+
If you load Rails, and assuming your workers are located in the `app/workers` directory, they will be auto-loaded. This means you don't need to require them explicitly with `-r`.
|
76
|
+
|
77
|
+
|
78
|
+
### Start Toiler
|
79
|
+
|
80
|
+
```shell
|
81
|
+
bundle exec toiler -r worker.rb -C toiler.yml
|
82
|
+
```
|
83
|
+
|
84
|
+
Other options:
|
85
|
+
|
86
|
+
```bash
|
87
|
+
toiler --help
|
88
|
+
|
89
|
+
-d, --daemon Daemonize process
|
90
|
+
-r, --require [PATH|DIR] Location of the worker
|
91
|
+
-C, --config PATH Path to YAML config file
|
92
|
+
-R, --rails Load Rails
|
93
|
+
-L, --logfile PATH Path to writable logfile
|
94
|
+
-P, --pidfile PATH Path to pidfile
|
95
|
+
-v, --verbose Print more verbose output
|
96
|
+
-h, --help Show help
|
97
|
+
```
|
98
|
+
|
99
|
+
|
100
|
+
## Credits
|
101
|
+
|
102
|
+
Much of the credit goes to [Pablo Cantero](https://github.com/phstc), creator of [Shoryuken](https://github.com/phstc/shoryuken), and [everybody who contributed to it](https://github.com/phstc/shoryuken/graphs/contributors).
|
103
|
+
|
104
|
+
## Contributing
|
105
|
+
|
106
|
+
1. Fork it ( https://github.com/sschepens/toiler/fork )
|
107
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
108
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
109
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
110
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/toiler
ADDED
data/lib/toiler.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'toiler/core_ext'
|
3
|
+
require 'toiler/message'
|
4
|
+
require 'toiler/queue'
|
5
|
+
require 'toiler/worker'
|
6
|
+
require 'toiler/environment_loader'
|
7
|
+
require 'toiler/logging'
|
8
|
+
require 'toiler/cli'
|
9
|
+
require 'toiler/version'
|
10
|
+
|
11
|
+
module Toiler
|
12
|
+
@worker_registry = {}
|
13
|
+
@worker_class_registry = {}
|
14
|
+
@options = {
|
15
|
+
aws: {}
|
16
|
+
}
|
17
|
+
|
18
|
+
module_function
|
19
|
+
|
20
|
+
def options
|
21
|
+
@options
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
Toiler::Logging.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def worker_class_registry
|
29
|
+
@worker_class_registry
|
30
|
+
end
|
31
|
+
|
32
|
+
def worker_registry
|
33
|
+
@worker_registry
|
34
|
+
end
|
35
|
+
|
36
|
+
def queues
|
37
|
+
@worker_registry.keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetcher(queue)
|
41
|
+
Celluloid::Actor["fetcher_#{queue}".to_sym]
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_fetcher(queue, val)
|
45
|
+
Celluloid::Actor["fetcher_#{queue}".to_sym] = val
|
46
|
+
end
|
47
|
+
|
48
|
+
def processor_pool(queue)
|
49
|
+
Celluloid::Actor["processor_pool_#{queue}".to_sym]
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_processor_pool(queue, val)
|
53
|
+
Celluloid::Actor["processor_pool_#{queue}".to_sym] = val
|
54
|
+
end
|
55
|
+
|
56
|
+
def manager
|
57
|
+
Celluloid::Actor[:manager]
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_manager(val)
|
61
|
+
Celluloid::Actor[:manager] = val
|
62
|
+
end
|
63
|
+
|
64
|
+
def timer
|
65
|
+
Celluloid::Actor[:timer]
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_timer(val)
|
69
|
+
Celluloid::Actor[:timer] = val
|
70
|
+
end
|
71
|
+
|
72
|
+
def default_options
|
73
|
+
{
|
74
|
+
auto_visibility_timeout: false,
|
75
|
+
concurrency: 1,
|
76
|
+
auto_delete: false,
|
77
|
+
batch: false
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
data/lib/toiler/cli.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'optparse'
|
3
|
+
require 'toiler'
|
4
|
+
|
5
|
+
module Toiler
|
6
|
+
# See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
|
7
|
+
class Shutdown < Interrupt; end
|
8
|
+
|
9
|
+
class CLI
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
def run(args)
|
13
|
+
self_read, self_write = IO.pipe
|
14
|
+
|
15
|
+
%w(INT TERM USR1 USR2 TTIN).each do |sig|
|
16
|
+
begin
|
17
|
+
trap sig do
|
18
|
+
self_write.puts(sig)
|
19
|
+
end
|
20
|
+
rescue ArgumentError
|
21
|
+
puts "Signal #{sig} not supported"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
options = parse_cli_args(args)
|
26
|
+
|
27
|
+
EnvironmentLoader.load(options)
|
28
|
+
daemonize
|
29
|
+
write_pid
|
30
|
+
load_celluloid
|
31
|
+
|
32
|
+
begin
|
33
|
+
@supervisor = Supervisor.new
|
34
|
+
|
35
|
+
while (readable_io = IO.select([self_read]))
|
36
|
+
signal = readable_io.first[0].gets.strip
|
37
|
+
handle_signal(signal)
|
38
|
+
end
|
39
|
+
rescue Interrupt
|
40
|
+
@supervisor.stop
|
41
|
+
exit 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def handle_signal(_signal)
|
48
|
+
fail Interrupt
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_celluloid
|
52
|
+
fail "Celluloid cannot be required until here, or it will break Toiler's daemonization" if defined?(::Celluloid) && Toiler.options[:daemon]
|
53
|
+
|
54
|
+
# Celluloid can't be loaded until after we've daemonized
|
55
|
+
# because it spins up threads and creates locks which get
|
56
|
+
# into a very bad state if forked.
|
57
|
+
require 'celluloid/autostart'
|
58
|
+
Celluloid.logger = (Toiler.options[:verbose] ? Toiler.logger : nil)
|
59
|
+
require 'toiler/supervisor'
|
60
|
+
end
|
61
|
+
|
62
|
+
def daemonize
|
63
|
+
return unless Toiler.options[:daemon]
|
64
|
+
|
65
|
+
fail ArgumentError, "You really should set a logfile if you're going to daemonize" unless Toiler.options[:logfile]
|
66
|
+
|
67
|
+
files_to_reopen = []
|
68
|
+
ObjectSpace.each_object(File) do |file|
|
69
|
+
files_to_reopen << file unless file.closed?
|
70
|
+
end
|
71
|
+
|
72
|
+
Process.daemon(true, true)
|
73
|
+
|
74
|
+
files_to_reopen.each do |file|
|
75
|
+
begin
|
76
|
+
file.reopen file.path, 'a+'
|
77
|
+
#file.sync = true
|
78
|
+
rescue ::Exception
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
[$stdout, $stderr].each do |io|
|
83
|
+
File.open(Toiler.options[:logfile], 'ab') do |f|
|
84
|
+
io.reopen(f)
|
85
|
+
end
|
86
|
+
#io.sync = true
|
87
|
+
end
|
88
|
+
$stdin.reopen('/dev/null')
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_pid
|
92
|
+
if (path = Toiler.options[:pidfile])
|
93
|
+
File.open(path, 'w') do |f|
|
94
|
+
f.puts Process.pid
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_cli_args(argv)
|
100
|
+
opts = { queues: [] }
|
101
|
+
|
102
|
+
@parser = OptionParser.new do |o|
|
103
|
+
o.on '-d', '--daemon', 'Daemonize process' do |arg|
|
104
|
+
opts[:daemon] = arg
|
105
|
+
end
|
106
|
+
|
107
|
+
o.on '-r', '--require [PATH|DIR]', 'Location of the worker' do |arg|
|
108
|
+
opts[:require] = arg
|
109
|
+
end
|
110
|
+
|
111
|
+
o.on '-C', '--config PATH', 'Path to YAML config file' do |arg|
|
112
|
+
opts[:config_file] = arg
|
113
|
+
end
|
114
|
+
|
115
|
+
o.on '-R', '--rails', 'Load Rails' do |arg|
|
116
|
+
opts[:rails] = arg
|
117
|
+
end
|
118
|
+
|
119
|
+
o.on '-L', '--logfile PATH', 'Path to writable logfile' do |arg|
|
120
|
+
opts[:logfile] = arg
|
121
|
+
end
|
122
|
+
|
123
|
+
o.on '-P', '--pidfile PATH', 'Path to pidfile' do |arg|
|
124
|
+
opts[:pidfile] = arg
|
125
|
+
end
|
126
|
+
|
127
|
+
o.on '-v', '--verbose', 'Print more verbose output' do |arg|
|
128
|
+
opts[:verbose] = arg
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
@parser.banner = 'toiler [options]'
|
133
|
+
@parser.on_tail '-h', '--help', 'Show help' do
|
134
|
+
Toiler.logger.info @parser
|
135
|
+
exit 1
|
136
|
+
end
|
137
|
+
@parser.parse!(argv)
|
138
|
+
opts
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
require 'active_support/core_ext/hash/deep_merge'
|
4
|
+
rescue LoadError
|
5
|
+
class Hash
|
6
|
+
def stringify_keys
|
7
|
+
each_key do |key|
|
8
|
+
self[key.to_s] = delete(key)
|
9
|
+
end
|
10
|
+
self
|
11
|
+
end unless {}.respond_to?(:stringify_keys)
|
12
|
+
|
13
|
+
def symbolize_keys
|
14
|
+
each_key do |key|
|
15
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end unless {}.respond_to?(:symbolize_keys)
|
19
|
+
|
20
|
+
def deep_symbolize_keys
|
21
|
+
each_key do |key|
|
22
|
+
value = delete(key)
|
23
|
+
self[(key.to_sym rescue key) || key] = value
|
24
|
+
|
25
|
+
value.deep_symbolize_keys if value.is_a? Hash
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end unless {}.respond_to?(:deep_symbolize_keys)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'active_support/core_ext/string/inflections'
|
34
|
+
rescue LoadError
|
35
|
+
class String
|
36
|
+
def constantize
|
37
|
+
names = split('::')
|
38
|
+
names.shift if names.empty? || names.first.empty?
|
39
|
+
|
40
|
+
constant = Object
|
41
|
+
names.each do |name|
|
42
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
43
|
+
end
|
44
|
+
constant
|
45
|
+
end
|
46
|
+
end unless ''.respond_to?(:constantize)
|
47
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Toiler
|
5
|
+
class EnvironmentLoader
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def self.load(options)
|
9
|
+
new(options).load
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.load_for_rails_console
|
13
|
+
load(config_file: (Rails.root + 'config' + 'toiler.yml'))
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def load
|
21
|
+
initialize_logger
|
22
|
+
load_rails if options[:rails]
|
23
|
+
require_workers if options[:require]
|
24
|
+
Toiler.options.merge!(config_file_options)
|
25
|
+
Toiler.options.merge!(options)
|
26
|
+
initialize_aws
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def config_file_options
|
32
|
+
if (path = options[:config_file])
|
33
|
+
unless File.exist?(path)
|
34
|
+
Toiler.logger.warn "Config file #{path} does not exist"
|
35
|
+
path = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return {} unless path
|
40
|
+
|
41
|
+
YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize_aws
|
45
|
+
# aws-sdk tries to load the credentials from the ENV variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
|
46
|
+
# when not explicit supplied
|
47
|
+
fail 'AWS Credentials needed!' if Toiler.options[:aws].empty? && (ENV['AWS_ACCESS_KEY_ID'].nil? || ENV['AWS_SECRET_ACCESS_KEY'].nil?)
|
48
|
+
return if Toiler.options[:aws].empty?
|
49
|
+
|
50
|
+
::Aws.config[:region] = Toiler.options[:aws][:region]
|
51
|
+
::Aws.config[:credentials] = ::Aws::Credentials.new Toiler.options[:aws][:access_key_id], Toiler.options[:aws][:secret_access_key]
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_logger
|
55
|
+
Toiler::Logging.initialize_logger(options[:logfile]) if options[:logfile]
|
56
|
+
Toiler.logger.level = Logger::DEBUG if options[:verbose]
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_rails
|
60
|
+
# Adapted from: https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
|
61
|
+
|
62
|
+
require 'rails'
|
63
|
+
if ::Rails::VERSION::MAJOR < 4
|
64
|
+
require File.expand_path('config/environment.rb')
|
65
|
+
::Rails.application.eager_load!
|
66
|
+
else
|
67
|
+
# Painful contortions, see 1791 for discussion
|
68
|
+
require File.expand_path('config/application.rb')
|
69
|
+
::Rails::Application.initializer 'toiler.eager_load' do
|
70
|
+
::Rails.application.config.eager_load = true
|
71
|
+
end
|
72
|
+
require File.expand_path('config/environment.rb')
|
73
|
+
end
|
74
|
+
|
75
|
+
Toiler.logger.info 'Rails environment loaded'
|
76
|
+
end
|
77
|
+
|
78
|
+
def require_workers
|
79
|
+
require options[:require]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Toiler
|
2
|
+
class Fetcher
|
3
|
+
include Celluloid
|
4
|
+
include Celluloid::Logger
|
5
|
+
|
6
|
+
FETCH_LIMIT = 10.freeze
|
7
|
+
|
8
|
+
attr_accessor :queue, :wait, :batch
|
9
|
+
|
10
|
+
finalizer :shutdown
|
11
|
+
|
12
|
+
def initialize(queue, client = nil)
|
13
|
+
@queue = Queue.new queue, client
|
14
|
+
@wait = Toiler.options[:wait] || 20
|
15
|
+
@batch = Toiler.worker_class_registry[queue].batch?
|
16
|
+
async.poll_messages
|
17
|
+
end
|
18
|
+
|
19
|
+
def shutdown
|
20
|
+
instance_variables.each { |iv| remove_instance_variable iv }
|
21
|
+
end
|
22
|
+
|
23
|
+
def poll_messages
|
24
|
+
# AWS limits the batch size by 10
|
25
|
+
options = {
|
26
|
+
message_attribute_names: %w(All),
|
27
|
+
wait_time_seconds: wait
|
28
|
+
}
|
29
|
+
|
30
|
+
loop do
|
31
|
+
count = Toiler.manager.free_processors queue.name
|
32
|
+
options[:max_number_of_messages] = (batch || count > FETCH_LIMIT) ? FETCH_LIMIT : count
|
33
|
+
msgs = queue.receive_messages options
|
34
|
+
Toiler.manager.assign_messages queue.name, msgs unless msgs.empty?
|
35
|
+
Toiler.manager.wait_for_available_processors queue.name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Toiler
|
5
|
+
module Logging
|
6
|
+
class Pretty < Logger::Formatter
|
7
|
+
# Provide a call() method that returns the formatted message.
|
8
|
+
def call(severity, time, _program_name, message)
|
9
|
+
"#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
def context
|
13
|
+
c = Thread.current[:toiler_context]
|
14
|
+
c ? " #{c}" : ''
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module_function
|
19
|
+
|
20
|
+
def with_context(msg)
|
21
|
+
Thread.current[:toiler_context] = msg
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
Thread.current[:toiler_context] = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_logger(log_target = STDOUT)
|
28
|
+
@logger = Logger.new(log_target)
|
29
|
+
@logger.level = Logger::INFO
|
30
|
+
@logger.formatter = Pretty.new
|
31
|
+
@logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def logger
|
35
|
+
@logger || initialize_logger
|
36
|
+
end
|
37
|
+
|
38
|
+
def logger=(log)
|
39
|
+
@logger = (log ? log : Logger.new('/dev/null'))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'toiler/fetcher'
|
2
|
+
require 'toiler/processor'
|
3
|
+
|
4
|
+
module Toiler
|
5
|
+
class Manager
|
6
|
+
include Celluloid
|
7
|
+
include Celluloid::Logger
|
8
|
+
|
9
|
+
attr_accessor :queues, :client
|
10
|
+
|
11
|
+
finalizer :shutdown
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
Toiler.set_manager current_actor
|
15
|
+
async.init
|
16
|
+
end
|
17
|
+
|
18
|
+
def init
|
19
|
+
@queues = Toiler.worker_class_registry
|
20
|
+
@client = ::Aws::SQS::Client.new
|
21
|
+
init_workers
|
22
|
+
init_conditions
|
23
|
+
pool_processors
|
24
|
+
supervise_fetchers
|
25
|
+
end
|
26
|
+
|
27
|
+
def shutdown
|
28
|
+
instance_variables.each { |iv| remove_instance_variable iv }
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
terminate_fetchers
|
33
|
+
terminate_processors
|
34
|
+
end
|
35
|
+
|
36
|
+
def processor_finished(queue)
|
37
|
+
@conditions[queue].broadcast
|
38
|
+
end
|
39
|
+
|
40
|
+
def init_workers
|
41
|
+
Toiler.worker_class_registry.each do |q, klass|
|
42
|
+
Toiler.worker_registry[q] = klass.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def supervise_fetchers
|
47
|
+
queues.each do |queue, _klass|
|
48
|
+
Toiler.set_fetcher queue, Fetcher.supervise(queue, client).actors.first
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def pool_processors
|
53
|
+
queues.each do |q, klass|
|
54
|
+
count = klass.concurrency
|
55
|
+
processor = if count > 1
|
56
|
+
Processor.pool args: [q], size: count
|
57
|
+
else
|
58
|
+
Processor.supervise(q).actors.first
|
59
|
+
end
|
60
|
+
Toiler.set_processor_pool q, processor
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def terminate_fetchers
|
65
|
+
queues.each do |queue, _klass|
|
66
|
+
fetcher = Toiler.fetcher(queue)
|
67
|
+
fetcher.terminate if fetcher && fetcher.alive?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def terminate_processors
|
72
|
+
queues.each do |queue, _klass|
|
73
|
+
processor_pool = Toiler.processor_pool(queue)
|
74
|
+
processor_pool.terminate if processor_pool && processor_pool.alive?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def init_conditions
|
79
|
+
@conditions = {}
|
80
|
+
queues.each do |queue, _klass|
|
81
|
+
@conditions[queue] = Celluloid::Condition.new
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def free_processors(queue)
|
86
|
+
return 1 unless Toiler.processor_pool(queue).respond_to? :idle_size
|
87
|
+
Toiler.processor_pool(queue).idle_size
|
88
|
+
end
|
89
|
+
|
90
|
+
def assign_messages(queue, messages)
|
91
|
+
processor_pool = Toiler.processor_pool(queue)
|
92
|
+
if batch? queue
|
93
|
+
processor_pool.async.process(queue, messages)
|
94
|
+
else
|
95
|
+
messages.each do |m|
|
96
|
+
processor_pool.async.process(queue, m)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def wait_for_available_processors(queue)
|
102
|
+
@conditions[queue].wait if free_processors(queue) == 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def batch?(queue)
|
106
|
+
Toiler.worker_class_registry[queue].batch?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Toiler
|
2
|
+
class Message
|
3
|
+
attr_accessor :client, :queue_url, :data
|
4
|
+
|
5
|
+
def initialize(client, queue_url, data)
|
6
|
+
@client = client
|
7
|
+
@queue_url = queue_url
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete
|
12
|
+
client.delete_message(
|
13
|
+
queue_url: queue_url,
|
14
|
+
receipt_handle: data.receipt_handle
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def change_visibility(options)
|
19
|
+
client.change_message_visibility(
|
20
|
+
options.merge(queue_url: queue_url, receipt_handle: data.receipt_handle)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def visibility_timeout=(timeout)
|
25
|
+
client.change_message_visibility(
|
26
|
+
queue_url: queue_url,
|
27
|
+
receipt_handle: data.receipt_handle,
|
28
|
+
visibility_timeout: timeout
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def message_id
|
33
|
+
data.message_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def receipt_handle
|
37
|
+
data.receipt_handle
|
38
|
+
end
|
39
|
+
|
40
|
+
def md5_of_body
|
41
|
+
data.md5_of_body
|
42
|
+
end
|
43
|
+
|
44
|
+
def body
|
45
|
+
data.body
|
46
|
+
end
|
47
|
+
|
48
|
+
def attributes
|
49
|
+
data.attributes
|
50
|
+
end
|
51
|
+
|
52
|
+
def md5_of_message_attributes
|
53
|
+
data.md5_of_message_attributes
|
54
|
+
end
|
55
|
+
|
56
|
+
def message_attributes
|
57
|
+
data.message_attributes
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'toiler/scheduler'
|
3
|
+
|
4
|
+
module Toiler
|
5
|
+
class Processor
|
6
|
+
include Celluloid
|
7
|
+
include Celluloid::Logger
|
8
|
+
|
9
|
+
attr_accessor :queue, :scheduler
|
10
|
+
|
11
|
+
finalizer :shutdown
|
12
|
+
|
13
|
+
def initialize(queue)
|
14
|
+
@queue = queue
|
15
|
+
async.init
|
16
|
+
end
|
17
|
+
|
18
|
+
def init
|
19
|
+
@scheduler = Scheduler.supervise.actors.first
|
20
|
+
processor_finished
|
21
|
+
end
|
22
|
+
|
23
|
+
def shutdown
|
24
|
+
scheduler.terminate if scheduler && scheduler.alive?
|
25
|
+
instance_variables.each { |iv| remove_instance_variable iv }
|
26
|
+
end
|
27
|
+
|
28
|
+
def process(queue, sqs_msg)
|
29
|
+
exclusive do
|
30
|
+
worker = Toiler.worker_registry[queue]
|
31
|
+
timer = auto_visibility_timeout(queue, sqs_msg, worker.class)
|
32
|
+
|
33
|
+
begin
|
34
|
+
body = get_body(worker.class, sqs_msg)
|
35
|
+
worker.perform(sqs_msg, body)
|
36
|
+
sqs_msg.delete if worker.class.auto_delete?
|
37
|
+
ensure
|
38
|
+
timer.cancel if timer
|
39
|
+
::ActiveRecord::Base.clear_active_connections!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
ensure
|
43
|
+
processor_finished
|
44
|
+
end
|
45
|
+
|
46
|
+
def processor_finished
|
47
|
+
Toiler.manager.processor_finished queue
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def auto_visibility_timeout(queue, sqs_msg, worker_class)
|
53
|
+
return unless worker_class.auto_visibility_timeout?
|
54
|
+
queue_visibility_timeout = Toiler.fetcher(queue).queue.visibility_timeout
|
55
|
+
block = lambda do |msg, visibility_timeout|
|
56
|
+
msg.visibility_timeout = visibility_timeout
|
57
|
+
end
|
58
|
+
|
59
|
+
scheduler.custom_every(queue_visibility_timeout - 5, sqs_msg, queue_visibility_timeout, block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_body(worker_class, sqs_msg)
|
63
|
+
if sqs_msg.is_a? Array
|
64
|
+
sqs_msg.map { |m| parse_body(worker_class, m) }
|
65
|
+
else
|
66
|
+
parse_body(worker_class, sqs_msg)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_body(worker_class, sqs_msg)
|
71
|
+
body_parser = worker_class.get_toiler_options[:parser]
|
72
|
+
|
73
|
+
case body_parser
|
74
|
+
when :json
|
75
|
+
JSON.parse(sqs_msg.body)
|
76
|
+
when Proc
|
77
|
+
body_parser.call(sqs_msg)
|
78
|
+
when :text, nil
|
79
|
+
sqs_msg.body
|
80
|
+
else
|
81
|
+
body_parser.load(sqs_msg.body) if body_parser.respond_to?(:load) # i.e. Oj.load(...) or MultiJson.load(...)
|
82
|
+
end
|
83
|
+
rescue => e
|
84
|
+
logger.error "Error parsing the message body: #{e.message}\nbody_parser: #{body_parser}\nsqs_msg.body: #{sqs_msg.body}"
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/toiler/queue.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Toiler
|
2
|
+
class Queue
|
3
|
+
attr_accessor :name, :client, :url
|
4
|
+
|
5
|
+
def initialize(name, client = nil)
|
6
|
+
@name = name
|
7
|
+
@client = client || ::Aws::SQS::Client.new
|
8
|
+
@url = client.get_queue_url(queue_name: name).queue_url
|
9
|
+
end
|
10
|
+
|
11
|
+
def visibility_timeout
|
12
|
+
client.get_queue_attributes(
|
13
|
+
queue_url: url,
|
14
|
+
attribute_names: ['VisibilityTimeout']
|
15
|
+
).attributes['VisibilityTimeout'].to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete_messages(options)
|
19
|
+
client.delete_message_batch(options.merge(queue_url: url))
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_message(options)
|
23
|
+
client.send_message(sanitize_message_body(options.merge(queue_url: url)))
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_messages(options)
|
27
|
+
client.send_message_batch(sanitize_message_body(options.merge(queue_url: url)))
|
28
|
+
end
|
29
|
+
|
30
|
+
def receive_messages(options)
|
31
|
+
client.receive_message(options.merge(queue_url: url))
|
32
|
+
.messages
|
33
|
+
.map { |m| Message.new(client, url, m) }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def sanitize_message_body(options)
|
39
|
+
messages = options[:entries] || [options]
|
40
|
+
|
41
|
+
messages.each do |m|
|
42
|
+
body = m[:message_body]
|
43
|
+
if body.is_a?(Hash)
|
44
|
+
m[:message_body] = JSON.dump(body)
|
45
|
+
elsif !body.is_a? String
|
46
|
+
fail ArgumentError, "The message body must be a String and you passed a #{body.class}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Toiler
|
2
|
+
class Scheduler
|
3
|
+
include Celluloid
|
4
|
+
include Celluloid::Logger
|
5
|
+
|
6
|
+
execute_block_on_receiver :custom_every
|
7
|
+
|
8
|
+
def custom_every(*args, block)
|
9
|
+
period = args[0]
|
10
|
+
block_args = args[1..-1]
|
11
|
+
every(period) do
|
12
|
+
block.call(*block_args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'toiler/manager'
|
2
|
+
|
3
|
+
module Toiler
|
4
|
+
class Supervisor < Celluloid::SupervisionGroup
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
finalizer :shutdown
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@manager = Manager.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def stop
|
14
|
+
@manager.stop
|
15
|
+
@manager.terminate if @manager.alive?
|
16
|
+
end
|
17
|
+
|
18
|
+
def shutdown
|
19
|
+
@manager.terminate if @manager.alive?
|
20
|
+
instance_variables.each { |iv| remove_instance_variable iv }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Toiler
|
2
|
+
module Worker
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def toiler_options(options)
|
9
|
+
if @toiler_options
|
10
|
+
@toiler_options = @toiler_options.merge options
|
11
|
+
else
|
12
|
+
@toiler_options = Toiler.default_options.merge options
|
13
|
+
end
|
14
|
+
Toiler.worker_class_registry[options[:queue]] = self if options[:queue]
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_toiler_options
|
18
|
+
@toiler_options
|
19
|
+
end
|
20
|
+
|
21
|
+
def batch?
|
22
|
+
@toiler_options[:batch]
|
23
|
+
end
|
24
|
+
|
25
|
+
def concurrency
|
26
|
+
@toiler_options[:concurrency]
|
27
|
+
end
|
28
|
+
|
29
|
+
def queue
|
30
|
+
@toiler_options[:queue]
|
31
|
+
end
|
32
|
+
|
33
|
+
def auto_visibility_timeout?
|
34
|
+
@toiler_options[:auto_visibility_timeout]
|
35
|
+
end
|
36
|
+
|
37
|
+
def auto_delete?
|
38
|
+
@toiler_options[:auto_delete]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/toiler.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'toiler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'toiler'
|
8
|
+
spec.version = Toiler::VERSION
|
9
|
+
spec.authors = ['Sebastian Schepens']
|
10
|
+
spec.email = ['sebas.schep@hotmail.com']
|
11
|
+
spec.description = spec.summary = 'Toiler is a super efficient AWS SQS thread based message processor'
|
12
|
+
spec.homepage = 'https://github.com/sschepens/toiler'
|
13
|
+
spec.license = 'LGPLv3'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables << 'toiler'
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
21
|
+
spec.add_development_dependency 'rake'
|
22
|
+
spec.add_development_dependency 'rspec'
|
23
|
+
spec.add_development_dependency 'pry-byebug'
|
24
|
+
spec.add_development_dependency 'nokogiri'
|
25
|
+
spec.add_development_dependency 'dotenv'
|
26
|
+
|
27
|
+
spec.add_dependency 'aws-sdk', '~> 2.0.21'
|
28
|
+
spec.add_dependency 'celluloid', '~> 0.16.0'
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: toiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sebastian Schepens
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nokogiri
|
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
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dotenv
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: aws-sdk
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.0.21
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.0.21
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: celluloid
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.16.0
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.16.0
|
125
|
+
description: Toiler is a super efficient AWS SQS thread based message processor
|
126
|
+
email:
|
127
|
+
- sebas.schep@hotmail.com
|
128
|
+
executables:
|
129
|
+
- toiler
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- Gemfile
|
135
|
+
- Gemfile.lock
|
136
|
+
- LICENSE
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- bin/toiler
|
140
|
+
- lib/toiler.rb
|
141
|
+
- lib/toiler/cli.rb
|
142
|
+
- lib/toiler/core_ext.rb
|
143
|
+
- lib/toiler/environment_loader.rb
|
144
|
+
- lib/toiler/fetcher.rb
|
145
|
+
- lib/toiler/logging.rb
|
146
|
+
- lib/toiler/manager.rb
|
147
|
+
- lib/toiler/message.rb
|
148
|
+
- lib/toiler/processor.rb
|
149
|
+
- lib/toiler/queue.rb
|
150
|
+
- lib/toiler/scheduler.rb
|
151
|
+
- lib/toiler/supervisor.rb
|
152
|
+
- lib/toiler/version.rb
|
153
|
+
- lib/toiler/worker.rb
|
154
|
+
- toiler.gemspec
|
155
|
+
homepage: https://github.com/sschepens/toiler
|
156
|
+
licenses:
|
157
|
+
- LGPLv3
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.4.5
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Toiler is a super efficient AWS SQS thread based message processor
|
179
|
+
test_files: []
|