toiler 0.3.0 → 0.3.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require 'bundler/gem_tasks'
1
+ require 'bundler/gem_tasks'
data/bin/toiler CHANGED
@@ -1,12 +1,12 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'toiler'
4
-
5
- begin
6
- Toiler::CLI.instance.run(ARGV)
7
- rescue => e
8
- raise e if $DEBUG
9
- STDERR.puts e.message
10
- STDERR.puts e.backtrace.join("\n")
11
- exit 1
12
- end
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'toiler'
4
+
5
+ begin
6
+ Toiler::CLI.instance.run(ARGV)
7
+ rescue => e
8
+ raise e if $DEBUG
9
+ STDERR.puts e.message
10
+ STDERR.puts e.backtrace.join("\n")
11
+ exit 1
12
+ end
data/lib/toiler.rb CHANGED
@@ -1,55 +1,55 @@
1
- require 'aws-sdk'
2
- require 'toiler/utils/environment_loader'
3
- require 'toiler/utils/logging'
4
- require 'toiler/utils/argument_parser'
5
- require 'toiler/worker'
6
- require 'toiler/cli'
7
- require 'toiler/version'
8
-
9
- # Main module
10
- module Toiler
11
- @worker_class_registry = {}
12
- @options = {
13
- aws: {}
14
- }
15
- @fetchers = {}
16
- @processor_pools = {}
17
-
18
- attr_reader :worker_class_registry, :options, :fetchers, :processor_pools
19
- module_function :worker_class_registry, :options, :fetchers, :processor_pools
20
-
21
- module_function
22
-
23
- def logger
24
- Toiler::Utils::Logging.logger
25
- end
26
-
27
- def queues
28
- worker_class_registry.keys
29
- end
30
-
31
- def fetcher(queue)
32
- fetchers["fetcher_#{queue}".to_sym]
33
- end
34
-
35
- def set_fetcher(queue, val)
36
- fetchers["fetcher_#{queue}".to_sym] = val
37
- end
38
-
39
- def processor_pool(queue)
40
- processor_pools["processor_pool_#{queue}".to_sym]
41
- end
42
-
43
- def set_processor_pool(queue, val)
44
- processor_pools["processor_pool_#{queue}".to_sym] = val
45
- end
46
-
47
- def default_options
48
- {
49
- auto_visibility_timeout: false,
50
- concurrency: 1,
51
- auto_delete: false,
52
- batch: false
53
- }
54
- end
55
- end
1
+ require 'aws-sdk'
2
+ require 'toiler/utils/environment_loader'
3
+ require 'toiler/utils/logging'
4
+ require 'toiler/utils/argument_parser'
5
+ require 'toiler/worker'
6
+ require 'toiler/cli'
7
+ require 'toiler/version'
8
+
9
+ # Main module
10
+ module Toiler
11
+ @worker_class_registry = {}
12
+ @options = {
13
+ aws: {}
14
+ }
15
+ @fetchers = {}
16
+ @processor_pools = {}
17
+
18
+ attr_reader :worker_class_registry, :options, :fetchers, :processor_pools
19
+ module_function :worker_class_registry, :options, :fetchers, :processor_pools
20
+
21
+ module_function
22
+
23
+ def logger
24
+ Toiler::Utils::Logging.logger
25
+ end
26
+
27
+ def queues
28
+ worker_class_registry.keys
29
+ end
30
+
31
+ def fetcher(queue)
32
+ fetchers["fetcher_#{queue}".to_sym]
33
+ end
34
+
35
+ def set_fetcher(queue, val)
36
+ fetchers["fetcher_#{queue}".to_sym] = val
37
+ end
38
+
39
+ def processor_pool(queue)
40
+ processor_pools["processor_pool_#{queue}".to_sym]
41
+ end
42
+
43
+ def set_processor_pool(queue, val)
44
+ processor_pools["processor_pool_#{queue}".to_sym] = val
45
+ end
46
+
47
+ def default_options
48
+ {
49
+ auto_visibility_timeout: false,
50
+ concurrency: 1,
51
+ auto_delete: false,
52
+ batch: false
53
+ }
54
+ end
55
+ end
@@ -1,89 +1,104 @@
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
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_io_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|
63
- tell [:assign_messages, msgs] unless msgs.nil? || msgs.empty?
64
- scheduled.make_false
65
- tell :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
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
11
+
12
+ attr_accessor :queue, :wait, :visibility_timeout, :free_processors,
13
+ :scheduled, :executing
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
+ @executing = Concurrent::AtomicBoolean.new
24
+ debug "Finished initializing Fetcher for queue #{queue}"
25
+ end
26
+
27
+ def default_executor
28
+ Concurrent.global_io_executor
29
+ end
30
+
31
+ def on_message(msg)
32
+ method, *args = msg
33
+ send(method, *args)
34
+ rescue StandardError => e
35
+ error "Fetcher #{queue.name} raised exception #{e.class}"
36
+ end
37
+
38
+ def executing?
39
+ executing.value
40
+ end
41
+
42
+ def scheduled?
43
+ scheduled.value
44
+ end
45
+
46
+ def get_free_processors
47
+ free_processors.value
48
+ end
49
+
50
+ private
51
+
52
+ def batch?
53
+ @batch
54
+ end
55
+
56
+ def processor_finished
57
+ debug "Fetcher #{queue.name} received processor finished signal..."
58
+ free_processors.increment
59
+ schedule_poll
60
+ end
61
+
62
+ def max_messages
63
+ batch? ? FETCH_LIMIT : [FETCH_LIMIT, free_processors.value].min
64
+ end
65
+
66
+ def poll_future
67
+ Concurrent.future do
68
+ @executing.make_true
69
+ queue.receive_messages message_attribute_names: %w(All),
70
+ wait_time_seconds: wait,
71
+ max_number_of_messages: max_messages
72
+ @executing.make_false
73
+ end
74
+ end
75
+
76
+ def poll_messages
77
+ poll_future.on_completion! do |_success, msgs|
78
+ tell [:assign_messages, msgs] unless msgs.nil? || msgs.empty?
79
+ scheduled.make_false
80
+ tell :schedule_poll
81
+ end
82
+ end
83
+
84
+ def schedule_poll
85
+ return unless free_processors.value > 0 && scheduled.make_true
86
+ debug "Fetcher #{queue.name} scheduling polling..."
87
+ tell :poll_messages
88
+ end
89
+
90
+ def processor_pool
91
+ @processor_pool ||= Toiler.processor_pool queue.name
92
+ end
93
+
94
+ def assign_messages(messages)
95
+ messages = [messages] if batch?
96
+ messages.each do |m|
97
+ processor_pool.tell [:process, visibility_timeout, m]
98
+ free_processors.decrement
99
+ end
100
+ debug "Fetcher #{queue.name} assigned #{messages.count} messages"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,110 +1,119 @@
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}\n#{e.backtrace.join("\n")}"
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 = [1,queue_visibility/3].max
79
- Concurrent::TimerTask.execute execution_interval: interval,
80
- timeout_interval: interval do
81
- begin
82
- sqs_msg.visibility_timeout = queue_visibility
83
- yield sqs_msg, body if block_given?
84
- rescue StandardError => e
85
- error "Processor #{queue} failed to extend visibility of message: #{e.message}\n#{e.backtrace.join("\n")}"
86
- end
87
- end
88
- end
89
-
90
- def get_body(sqs_msg)
91
- if sqs_msg.is_a? Array
92
- sqs_msg.map { |m| parse_body m }
93
- else
94
- parse_body sqs_msg
95
- end
96
- end
97
-
98
- def parse_body(sqs_msg)
99
- case body_parser
100
- when :json then JSON.parse sqs_msg.body
101
- when Proc then body_parser.call sqs_msg
102
- when :text, nil then sqs_msg.body
103
- else body_parser.load sqs_msg.body
104
- end
105
- rescue => e
106
- raise "Error parsing the message body: #{e.message}"
107
- end
108
- end
109
- end
110
- end
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, :executing, :thread
12
+
13
+ def initialize(queue)
14
+ @queue = queue
15
+ @worker = Toiler.worker_class_registry[queue].new
16
+ @fetcher = Toiler.fetcher queue
17
+ @executing = Concurrent::AtomicBoolean.new
18
+ @thread = nil
19
+ init_options
20
+ processor_finished
21
+ end
22
+
23
+ def default_executor
24
+ Concurrent.global_io_executor
25
+ end
26
+
27
+ def on_message(msg)
28
+ method, *args = msg
29
+ send(method, *args)
30
+ rescue StandardError => e
31
+ error "Processor #{queue} failed processing, reason: #{e.class}\n#{e.backtrace.join("\n")}"
32
+ end
33
+
34
+ def executing?
35
+ executing.value
36
+ end
37
+
38
+ private
39
+
40
+ def init_options
41
+ @auto_visibility_timeout = @worker.class.auto_visibility_timeout?
42
+ @auto_delete = @worker.class.auto_delete?
43
+ toiler_options = @worker.class.toiler_options
44
+ @body_parser = toiler_options[:parser]
45
+ @extend_callback = toiler_options[:on_visibility_extend]
46
+ end
47
+
48
+ def auto_visibility_timeout?
49
+ @auto_visibility_timeout
50
+ end
51
+
52
+ def auto_delete?
53
+ @auto_delete
54
+ end
55
+
56
+ def process(visibility, sqs_msg)
57
+ executing.make_true
58
+ @thread = Thread.current
59
+ debug "Processor #{queue} begins processing..."
60
+ body = get_body(sqs_msg)
61
+ timer = visibility_extender visibility, sqs_msg, body, &extend_callback
62
+
63
+ debug "Worker #{queue} starts performing..."
64
+ worker.perform sqs_msg, body
65
+ debug "Worker #{queue} finishes performing..."
66
+ sqs_msg.delete if auto_delete?
67
+ ensure
68
+ process_cleanup timer
69
+ executing.make_false
70
+ @thread = nil
71
+ end
72
+
73
+ def process_cleanup(timer)
74
+ debug "Processor #{queue} starts cleanup after perform..."
75
+ timer.shutdown if timer
76
+ ::ActiveRecord::Base.clear_active_connections! if defined? ActiveRecord
77
+ processor_finished
78
+ debug "Processor #{queue} finished cleanup after perform..."
79
+ end
80
+
81
+ def processor_finished
82
+ fetcher.tell :processor_finished
83
+ end
84
+
85
+ def visibility_extender(queue_visibility, sqs_msg, body)
86
+ return unless auto_visibility_timeout?
87
+ interval = [1,queue_visibility/3].max
88
+ Concurrent::TimerTask.execute execution_interval: interval,
89
+ timeout_interval: interval do
90
+ begin
91
+ sqs_msg.visibility_timeout = queue_visibility
92
+ yield sqs_msg, body if block_given?
93
+ rescue StandardError => e
94
+ error "Processor #{queue} failed to extend visibility of message: #{e.message}\n#{e.backtrace.join("\n")}"
95
+ end
96
+ end
97
+ end
98
+
99
+ def get_body(sqs_msg)
100
+ if sqs_msg.is_a? Array
101
+ sqs_msg.map { |m| parse_body m }
102
+ else
103
+ parse_body sqs_msg
104
+ end
105
+ end
106
+
107
+ def parse_body(sqs_msg)
108
+ case body_parser
109
+ when :json then JSON.parse sqs_msg.body
110
+ when Proc then body_parser.call sqs_msg
111
+ when :text, nil then sqs_msg.body
112
+ else body_parser.load sqs_msg.body
113
+ end
114
+ rescue => e
115
+ raise "Error parsing the message body: #{e.message}"
116
+ end
117
+ end
118
+ end
119
+ end