toiler 0.1.5 → 0.2.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 +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +7 -4
- data/Gemfile.lock +26 -57
- data/README.md +15 -0
- data/lib/toiler.rb +15 -44
- data/lib/toiler/actor/fetcher.rb +89 -0
- data/lib/toiler/actor/processor.rb +106 -0
- data/lib/toiler/actor/supervisor.rb +45 -0
- data/lib/toiler/actor/utils/actor_logging.rb +28 -0
- data/lib/toiler/aws/message.rb +64 -0
- data/lib/toiler/aws/queue.rb +61 -0
- data/lib/toiler/cli.rb +67 -94
- data/lib/toiler/utils/argument_parser.rb +50 -0
- data/lib/toiler/utils/environment_loader.rb +104 -0
- data/lib/toiler/utils/logging.rb +37 -0
- data/lib/toiler/version.rb +2 -1
- data/lib/toiler/worker.rb +35 -15
- data/toiler.gemspec +4 -4
- metadata +32 -32
- data/celluloid-task-pooledfiber/.gitignore +0 -9
- data/celluloid-task-pooledfiber/.rspec +0 -2
- data/celluloid-task-pooledfiber/.travis.yml +0 -5
- data/celluloid-task-pooledfiber/Gemfile +0 -11
- data/celluloid-task-pooledfiber/LICENSE.txt +0 -21
- data/celluloid-task-pooledfiber/README.md +0 -37
- data/celluloid-task-pooledfiber/Rakefile +0 -8
- data/celluloid-task-pooledfiber/celluloid-task-pooled-fiber.gemspec +0 -18
- data/celluloid-task-pooledfiber/lib/celluloid/task/pooled_fiber.rb +0 -26
- data/celluloid-task-pooledfiber/lib/celluloid/util/fiber_pool.rb +0 -95
- data/celluloid-task-pooledfiber/spec/celluloid/tasks/pooled_fiber_spec.rb +0 -5
- data/celluloid-task-pooledfiber/spec/spec_helper.rb +0 -60
- data/celluloid-task-pooledfiber/spec/support/shared_examples_for_task.rb +0 -49
- data/lib/toiler/core_ext.rb +0 -47
- data/lib/toiler/environment_loader.rb +0 -82
- data/lib/toiler/fetcher.rb +0 -56
- data/lib/toiler/logging.rb +0 -42
- data/lib/toiler/manager.rb +0 -71
- data/lib/toiler/message.rb +0 -60
- data/lib/toiler/processor.rb +0 -86
- data/lib/toiler/queue.rb +0 -53
- data/lib/toiler/scheduler.rb +0 -16
- data/lib/toiler/supervisor.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ae93426e47a18dc09639561ed7587a92edf718d
|
4
|
+
data.tar.gz: 4642b9ecc2357810f894f816764df5f5ebe78680
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b047fdb8cce9cee315f7ef58646a0a64724591b6949d6f6a4201d41481a0b0f3a3f76ee8d61ffcaf5b32e5e1de4e93166eff56f474e1e5f4410fc1cf382538b
|
7
|
+
data.tar.gz: 22a1f742e648f17b0c7051f6372f90ca7a619756bcd2bf681344a453456fc11b36c5a06855ef6b06f5a499d2efbea342fd36be29e0c8ea6cd39b0bd72aec403a
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/Gemfile
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
gem '
|
5
|
-
# gem 'celluloid-task-pooledfiber', github: 'celluloid/celluloid-task-pooledfiber', branch: '0.2.0-prerelease'
|
6
|
-
# gem 'celluloid-io'
|
3
|
+
gem 'concurrent-ruby', '~> 0.9.0'
|
4
|
+
gem 'concurrent-ruby-edge', '~> 0.1.0'
|
7
5
|
gem 'aws-sdk'
|
6
|
+
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'rubocop'
|
10
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,69 +1,38 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
ast (2.0.0)
|
5
|
+
astrolabe (1.3.0)
|
6
|
+
parser (>= 2.2.0.pre.3, < 3.0)
|
7
|
+
aws-sdk (2.1.3)
|
8
|
+
aws-sdk-resources (= 2.1.3)
|
9
|
+
aws-sdk-core (2.1.3)
|
8
10
|
jmespath (~> 1.0)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
bundler
|
15
|
-
celluloid-essentials
|
16
|
-
celluloid-extras
|
17
|
-
celluloid-fsm
|
18
|
-
celluloid-pool
|
19
|
-
celluloid-supervision
|
20
|
-
dotenv
|
21
|
-
nenv
|
22
|
-
rspec-logsplit (>= 0.1.2)
|
23
|
-
timers (~> 4.0.0)
|
24
|
-
celluloid-essentials (0.20.0.pre17)
|
25
|
-
bundler
|
26
|
-
dotenv
|
27
|
-
nenv
|
28
|
-
rspec-logsplit (>= 0.1.2)
|
29
|
-
timers (~> 4.0.0)
|
30
|
-
celluloid-extras (0.1.4)
|
31
|
-
bundler
|
32
|
-
dotenv
|
33
|
-
nenv
|
34
|
-
rspec-logsplit (>= 0.1.2)
|
35
|
-
timers (~> 4.0.0)
|
36
|
-
celluloid-fsm (0.9.0.pre13)
|
37
|
-
bundler
|
38
|
-
dotenv
|
39
|
-
nenv
|
40
|
-
rspec-logsplit (>= 0.1.2)
|
41
|
-
timers (~> 4.0.0)
|
42
|
-
celluloid-pool (0.11.0.pre1)
|
43
|
-
bundler
|
44
|
-
dotenv
|
45
|
-
nenv
|
46
|
-
rspec-logsplit (>= 0.1.2)
|
47
|
-
timers (~> 4.0.0)
|
48
|
-
celluloid-supervision (0.20.0.pre6)
|
49
|
-
bundler
|
50
|
-
dotenv
|
51
|
-
nenv
|
52
|
-
rspec-logsplit (>= 0.1.2)
|
53
|
-
timers (~> 4.0.0)
|
54
|
-
dotenv (2.0.1)
|
55
|
-
hitimes (1.2.2)
|
11
|
+
aws-sdk-resources (2.1.3)
|
12
|
+
aws-sdk-core (= 2.1.3)
|
13
|
+
concurrent-ruby (0.9.0)
|
14
|
+
concurrent-ruby-edge (0.1.0)
|
15
|
+
concurrent-ruby (~> 0.9.0)
|
56
16
|
jmespath (1.0.2)
|
57
17
|
multi_json (~> 1.0)
|
58
|
-
multi_json (1.11.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
18
|
+
multi_json (1.11.2)
|
19
|
+
parser (2.3.0.pre.2)
|
20
|
+
ast (>= 1.1, < 3.0)
|
21
|
+
powerpack (0.1.1)
|
22
|
+
rainbow (2.0.0)
|
23
|
+
rubocop (0.32.1)
|
24
|
+
astrolabe (~> 1.3)
|
25
|
+
parser (>= 2.2.2.5, < 3.0)
|
26
|
+
powerpack (~> 0.1)
|
27
|
+
rainbow (>= 1.99.1, < 3.0)
|
28
|
+
ruby-progressbar (~> 1.4)
|
29
|
+
ruby-progressbar (1.7.5)
|
63
30
|
|
64
31
|
PLATFORMS
|
65
32
|
ruby
|
66
33
|
|
67
34
|
DEPENDENCIES
|
68
35
|
aws-sdk
|
69
|
-
|
36
|
+
concurrent-ruby (~> 0.9.0)
|
37
|
+
concurrent-ruby-edge (~> 0.1.0)
|
38
|
+
rubocop
|
data/README.md
CHANGED
@@ -8,6 +8,8 @@ a different approach at loadbalancing and uses long-polling.
|
|
8
8
|
Toiler allows to specify the amount of processors (threads) that should be spawned for each queue.
|
9
9
|
Instead of [shoryuken's](https://github.com/phstc/shoryuken) loadbalancing approach, Toiler delegates this work to the kernel scheduling threads.
|
10
10
|
|
11
|
+
Because Toiler uses threads to provide concurrency, **each thread instatiates a new worker**, as it should be expected, so please **use class variables to store shared variables like clients**.
|
12
|
+
|
11
13
|
###Long-Polling
|
12
14
|
A Fetcher thread is spawned for each queue.
|
13
15
|
Fetchers are resposible for polling SQS and retreiving messages.
|
@@ -20,6 +22,9 @@ Workers can configure a parser Class or Proc to parse an SQS message body before
|
|
20
22
|
###Batches
|
21
23
|
Toiler allows a Worker to be able to receive a batch of messages instead of a single one.
|
22
24
|
|
25
|
+
###Auto Visibility Extension
|
26
|
+
Toiler has the ability to automatically extend the visibility timeout of and SQS message to prevent the message from re-entering the queue if processing of such message is taking longer than the queue's visibility timeout.
|
27
|
+
|
23
28
|
##Instalation
|
24
29
|
|
25
30
|
Add this line to your application's Gemfile:
|
@@ -52,7 +57,17 @@ class MyWorker
|
|
52
57
|
# toiler_options auto_visibility_timeout: true
|
53
58
|
# toiler_options batch: true
|
54
59
|
|
60
|
+
#Example connection client that should be shared across all instances of MyWorker
|
61
|
+
@@client = ConnectionClient.new
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
@last_message = nil
|
65
|
+
end
|
66
|
+
|
55
67
|
def perform(sqs_msg, body)
|
68
|
+
#Workers are thread safe, yay!
|
69
|
+
#Each worker instance is assured to be processing only one message at a time
|
70
|
+
@last_message = sqs_msg
|
56
71
|
puts body
|
57
72
|
end
|
58
73
|
end
|
data/lib/toiler.rb
CHANGED
@@ -1,76 +1,47 @@
|
|
1
1
|
require 'aws-sdk'
|
2
|
-
require 'toiler/
|
3
|
-
require 'toiler/
|
4
|
-
require 'toiler/
|
2
|
+
require 'toiler/utils/environment_loader'
|
3
|
+
require 'toiler/utils/logging'
|
4
|
+
require 'toiler/utils/argument_parser'
|
5
5
|
require 'toiler/worker'
|
6
|
-
require 'toiler/environment_loader'
|
7
|
-
require 'toiler/logging'
|
8
6
|
require 'toiler/cli'
|
9
7
|
require 'toiler/version'
|
10
8
|
|
9
|
+
# Main module
|
11
10
|
module Toiler
|
12
|
-
@worker_registry = {}
|
13
11
|
@worker_class_registry = {}
|
14
12
|
@options = {
|
15
13
|
aws: {}
|
16
14
|
}
|
15
|
+
@fetchers = {}
|
16
|
+
@processor_pools = {}
|
17
17
|
|
18
|
-
|
18
|
+
attr_reader :worker_class_registry, :options, :fetchers, :processor_pools
|
19
|
+
module_function :worker_class_registry, :options, :fetchers, :processor_pools
|
19
20
|
|
20
|
-
|
21
|
-
@options
|
22
|
-
end
|
21
|
+
module_function
|
23
22
|
|
24
23
|
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
|
24
|
+
Toiler::Utils::Logging.logger
|
34
25
|
end
|
35
26
|
|
36
27
|
def queues
|
37
|
-
|
28
|
+
worker_class_registry.keys
|
38
29
|
end
|
39
30
|
|
40
31
|
def fetcher(queue)
|
41
|
-
|
32
|
+
fetchers["fetcher_#{queue}".to_sym]
|
42
33
|
end
|
43
34
|
|
44
35
|
def set_fetcher(queue, val)
|
45
|
-
|
36
|
+
fetchers["fetcher_#{queue}".to_sym] = val
|
46
37
|
end
|
47
38
|
|
48
39
|
def processor_pool(queue)
|
49
|
-
|
40
|
+
processor_pools["processor_pool_#{queue}".to_sym]
|
50
41
|
end
|
51
42
|
|
52
43
|
def set_processor_pool(queue, val)
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
def scheduler(queue)
|
57
|
-
Celluloid::Actor["scheduler_#{queue}".to_sym]
|
58
|
-
end
|
59
|
-
|
60
|
-
def manager
|
61
|
-
Celluloid::Actor[:manager]
|
62
|
-
end
|
63
|
-
|
64
|
-
def set_manager(val)
|
65
|
-
Celluloid::Actor[:manager] = val
|
66
|
-
end
|
67
|
-
|
68
|
-
def timer
|
69
|
-
Celluloid::Actor[:timer]
|
70
|
-
end
|
71
|
-
|
72
|
-
def set_timer(val)
|
73
|
-
Celluloid::Actor[:timer] = val
|
44
|
+
processor_pools["processor_pool_#{queue}".to_sym] = val
|
74
45
|
end
|
75
46
|
|
76
47
|
def default_options
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'toiler/actor/utils/actor_logging'
|
2
|
+
require 'toiler/aws/queue'
|
3
|
+
|
4
|
+
module Toiler
|
5
|
+
module Actor
|
6
|
+
# Actor polling for messages only when processors are ready, otherwise idle
|
7
|
+
class Fetcher < Concurrent::Actor::RestartingContext
|
8
|
+
include Utils::ActorLogging
|
9
|
+
|
10
|
+
FETCH_LIMIT = 10.freeze
|
11
|
+
|
12
|
+
attr_accessor :queue, :wait, :visibility_timeout, :free_processors,
|
13
|
+
:scheduled
|
14
|
+
|
15
|
+
def initialize(queue, client)
|
16
|
+
debug "Initializing Fetcher for queue #{queue}..."
|
17
|
+
@queue = Toiler::Aws::Queue.new queue, client
|
18
|
+
@wait = Toiler.options[:wait] || 20
|
19
|
+
@free_processors = Concurrent::AtomicFixnum.new(0)
|
20
|
+
@batch = Toiler.worker_class_registry[queue].batch?
|
21
|
+
@visibility_timeout = @queue.visibility_timeout
|
22
|
+
@scheduled = Concurrent::AtomicBoolean.new
|
23
|
+
debug "Finished initializing Fetcher for queue #{queue}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_executor
|
27
|
+
Concurrent.global_fast_executor
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_message(msg)
|
31
|
+
method, *args = msg
|
32
|
+
send(method, *args)
|
33
|
+
rescue StandardError => e
|
34
|
+
error "Fetcher #{queue.name} raised exception #{e.class}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def batch?
|
40
|
+
@batch
|
41
|
+
end
|
42
|
+
|
43
|
+
def processor_finished
|
44
|
+
debug "Fetcher #{queue.name} received processor finished signal..."
|
45
|
+
free_processors.increment
|
46
|
+
schedule_poll
|
47
|
+
end
|
48
|
+
|
49
|
+
def max_messages
|
50
|
+
batch? ? FETCH_LIMIT : [FETCH_LIMIT, free_processors.value].min
|
51
|
+
end
|
52
|
+
|
53
|
+
def poll_future
|
54
|
+
Concurrent.future do
|
55
|
+
queue.receive_messages message_attribute_names: %w(All),
|
56
|
+
wait_time_seconds: wait,
|
57
|
+
max_number_of_messages: max_messages
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def poll_messages
|
62
|
+
poll_future.on_completion! do |_success, msgs, _reason|
|
63
|
+
scheduled.make_false
|
64
|
+
tell [:assign_messages, msgs] unless msgs.nil? || msgs.empty?
|
65
|
+
schedule_poll
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def schedule_poll
|
70
|
+
return unless free_processors.value > 0 && scheduled.make_true
|
71
|
+
debug "Fetcher #{queue.name} scheduling polling..."
|
72
|
+
tell :poll_messages
|
73
|
+
end
|
74
|
+
|
75
|
+
def processor_pool
|
76
|
+
@processor_pool ||= Toiler.processor_pool queue.name
|
77
|
+
end
|
78
|
+
|
79
|
+
def assign_messages(messages)
|
80
|
+
messages = [messages] if batch?
|
81
|
+
messages.each do |m|
|
82
|
+
processor_pool.tell [:process, visibility_timeout, m]
|
83
|
+
free_processors.decrement
|
84
|
+
end
|
85
|
+
debug "Fetcher #{queue.name} assigned #{messages.count} messages"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'toiler/actor/utils/actor_logging'
|
3
|
+
|
4
|
+
module Toiler
|
5
|
+
module Actor
|
6
|
+
# Responsible for processing sqs messages and notifying Fetcher when done
|
7
|
+
class Processor < Concurrent::Actor::RestartingContext
|
8
|
+
include Utils::ActorLogging
|
9
|
+
|
10
|
+
attr_accessor :queue, :worker, :fetcher, :body_parser,
|
11
|
+
:extend_callback
|
12
|
+
|
13
|
+
def initialize(queue)
|
14
|
+
@queue = queue
|
15
|
+
@worker = Toiler.worker_class_registry[queue].new
|
16
|
+
@fetcher = Toiler.fetcher queue
|
17
|
+
init_options
|
18
|
+
processor_finished
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_executor
|
22
|
+
Concurrent.global_io_executor
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_message(msg)
|
26
|
+
method, *args = msg
|
27
|
+
send(method, *args)
|
28
|
+
rescue StandardError => e
|
29
|
+
error "Processor #{queue} failed processing, reason: #{e.class}"
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def init_options
|
36
|
+
@auto_visibility_timeout = @worker.class.auto_visibility_timeout?
|
37
|
+
@auto_delete = @worker.class.auto_delete?
|
38
|
+
toiler_options = @worker.class.toiler_options
|
39
|
+
@body_parser = toiler_options[:parser]
|
40
|
+
@extend_callback = toiler_options[:on_visibility_extend]
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_visibility_timeout?
|
44
|
+
@auto_visibility_timeout
|
45
|
+
end
|
46
|
+
|
47
|
+
def auto_delete?
|
48
|
+
@auto_delete
|
49
|
+
end
|
50
|
+
|
51
|
+
def process(visibility, sqs_msg)
|
52
|
+
debug "Processor #{queue} begins processing..."
|
53
|
+
body = get_body(sqs_msg)
|
54
|
+
timer = visibility_extender visibility, sqs_msg, body, &extend_callback
|
55
|
+
|
56
|
+
debug "Worker #{queue} starts performing..."
|
57
|
+
worker.perform sqs_msg, body
|
58
|
+
debug "Worker #{queue} finishes performing..."
|
59
|
+
sqs_msg.delete if auto_delete?
|
60
|
+
ensure
|
61
|
+
process_cleanup timer
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_cleanup(timer)
|
65
|
+
debug "Processor #{queue} starts cleanup after perform..."
|
66
|
+
timer.shutdown if timer
|
67
|
+
::ActiveRecord::Base.clear_active_connections! if defined? ActiveRecord
|
68
|
+
processor_finished
|
69
|
+
debug "Processor #{queue} finished cleanup after perform..."
|
70
|
+
end
|
71
|
+
|
72
|
+
def processor_finished
|
73
|
+
fetcher.tell :processor_finished
|
74
|
+
end
|
75
|
+
|
76
|
+
def visibility_extender(queue_visibility, sqs_msg, body)
|
77
|
+
return unless auto_visibility_timeout?
|
78
|
+
interval = queue_visibility - 5
|
79
|
+
Concurrent::TimerTask.execute execution_interval: interval,
|
80
|
+
timeout_interval: interval do
|
81
|
+
sqs_msg.visibility_timeout = queue_visibility
|
82
|
+
yield sqs_msg, body if block_given?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_body(sqs_msg)
|
87
|
+
if sqs_msg.is_a? Array
|
88
|
+
sqs_msg.map { |m| parse_body m }
|
89
|
+
else
|
90
|
+
parse_body sqs_msg
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_body(sqs_msg)
|
95
|
+
case body_parser
|
96
|
+
when :json then JSON.parse sqs_msg.body
|
97
|
+
when Proc then body_parser.call sqs_msg
|
98
|
+
when :text, nil then sqs_msg.body
|
99
|
+
else body_parser.load sqs_msg.body
|
100
|
+
end
|
101
|
+
rescue => e
|
102
|
+
raise "Error parsing the message body: #{e.message}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|