toiler 0.3.1.beta1 → 0.3.1.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,104 +1,120 @@
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
+ 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
+ @polling = Concurrent::AtomicBoolean.new
25
+ debug "Finished initializing Fetcher for queue #{queue}"
26
+ end
27
+
28
+ def default_executor
29
+ Concurrent.global_io_executor
30
+ end
31
+
32
+ def on_message(msg)
33
+ executing.make_true
34
+ method, *args = msg
35
+ send(method, *args)
36
+ rescue StandardError => e
37
+ error "Fetcher #{queue.name} raised exception #{e.class}"
38
+ ensure
39
+ executing.make_false
40
+ end
41
+
42
+ def executing?
43
+ executing.value
44
+ end
45
+
46
+ def polling?
47
+ polling.value
48
+ end
49
+
50
+ def scheduled?
51
+ scheduled.value
52
+ end
53
+
54
+ def get_free_processors
55
+ free_processors.value
56
+ end
57
+
58
+ private
59
+
60
+ def batch?
61
+ @batch
62
+ end
63
+
64
+ def processor_started
65
+ debug "Fetcher #{queue.name} received processor started signal..."
66
+ free_processors.decrement
67
+ end
68
+
69
+ def processor_finished
70
+ debug "Fetcher #{queue.name} received processor finished signal..."
71
+ free_processors.increment
72
+ schedule_poll
73
+ end
74
+
75
+ def max_messages
76
+ batch? ? FETCH_LIMIT : [FETCH_LIMIT, free_processors.value].min
77
+ end
78
+
79
+ def poll_future
80
+ Concurrent.future do
81
+ queue.receive_messages message_attribute_names: %w(All),
82
+ wait_time_seconds: wait,
83
+ max_number_of_messages: max_messages
84
+ end
85
+ end
86
+
87
+ def poll_messages
88
+ polling.make_true
89
+ poll_future.on_completion! do |success, msgs, error|
90
+ polling.make_false
91
+ scheduled.make_false
92
+ if success && !msgs.nil? && !msgs.empty?
93
+ tell [:assign_messages, msgs]
94
+ else
95
+ tell :schedule_poll
96
+ end
97
+ end
98
+ end
99
+
100
+ def schedule_poll
101
+ return unless free_processors.value > 0 && scheduled.make_true
102
+ debug "Fetcher #{queue.name} scheduling polling..."
103
+ tell :poll_messages
104
+ end
105
+
106
+ def processor_pool
107
+ @processor_pool ||= Toiler.processor_pool queue.name
108
+ end
109
+
110
+ def assign_messages(messages)
111
+ messages = [messages] if batch?
112
+ messages.each do |m|
113
+ processor_pool.tell [:process, visibility_timeout, m]
114
+ end
115
+ debug "Fetcher #{queue.name} assigned #{messages.count} messages"
116
+ tell :schedule_poll
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,119 +1,128 @@
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
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
+ process_init
58
+ body = get_body(sqs_msg)
59
+ timer = visibility_extender visibility, sqs_msg, body, &extend_callback
60
+
61
+ debug "Worker #{queue} starts performing..."
62
+ worker.perform sqs_msg, body
63
+ debug "Worker #{queue} finishes performing..."
64
+ sqs_msg.delete if auto_delete?
65
+ ensure
66
+ process_cleanup timer
67
+ end
68
+
69
+ def process_init
70
+ processor_started
71
+ @executing.make_true
72
+ @thread = Thread.current
73
+ debug "Processor #{queue} begins processing..."
74
+ end
75
+
76
+ def process_cleanup(timer)
77
+ debug "Processor #{queue} starts cleanup after perform..."
78
+ timer.shutdown if timer
79
+ ::ActiveRecord::Base.clear_active_connections! if defined? ActiveRecord
80
+ processor_finished
81
+ @executing.make_false
82
+ @thread = nil
83
+ debug "Processor #{queue} finished cleanup after perform..."
84
+ end
85
+
86
+ def processor_finished
87
+ fetcher.tell :processor_finished
88
+ end
89
+
90
+ def processor_started
91
+ fetcher.tell :processor_started
92
+ end
93
+
94
+ def visibility_extender(queue_visibility, sqs_msg, body)
95
+ return unless auto_visibility_timeout?
96
+ interval = [1,queue_visibility/3].max
97
+ Concurrent::TimerTask.execute execution_interval: interval,
98
+ timeout_interval: interval do
99
+ begin
100
+ sqs_msg.visibility_timeout = queue_visibility
101
+ yield sqs_msg, body if block_given?
102
+ rescue StandardError => e
103
+ error "Processor #{queue} failed to extend visibility of message: #{e.message}\n#{e.backtrace.join("\n")}"
104
+ end
105
+ end
106
+ end
107
+
108
+ def get_body(sqs_msg)
109
+ if sqs_msg.is_a? Array
110
+ sqs_msg.map { |m| parse_body m }
111
+ else
112
+ parse_body sqs_msg
113
+ end
114
+ end
115
+
116
+ def parse_body(sqs_msg)
117
+ case body_parser
118
+ when :json then JSON.parse sqs_msg.body
119
+ when Proc then body_parser.call sqs_msg
120
+ when :text, nil then sqs_msg.body
121
+ else body_parser.load sqs_msg.body
122
+ end
123
+ rescue => e
124
+ raise "Error parsing the message body: #{e.message}"
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,55 +1,55 @@
1
- require 'toiler/actor/fetcher'
2
- require 'toiler/actor/processor'
3
-
4
- module Toiler
5
- module Actor
6
- # Actor that starts and supervises Toiler's actors
7
- class Supervisor < Concurrent::Actor::RestartingContext
8
- include Utils::ActorLogging
9
-
10
- attr_accessor :client
11
-
12
- def initialize
13
- @client = ::Aws::SQS::Client.new
14
- spawn_fetchers
15
- spawn_processors
16
- end
17
-
18
- def on_message(_msg)
19
- pass
20
- end
21
-
22
- def queues
23
- Toiler.worker_class_registry
24
- end
25
-
26
- def spawn_fetchers
27
- queues.each do |queue, _klass|
28
- begin
29
- fetcher = Actor::Fetcher.spawn! name: "fetcher_#{queue}".to_sym,
30
- supervise: true, args: [queue, client]
31
- Toiler.set_fetcher queue, fetcher
32
- rescue StandardError => e
33
- error "Failed to start Fetcher for queue #{queue}: #{e.message}\n#{e.backtrace.join("\n")}"
34
- end
35
- end
36
- end
37
-
38
- def spawn_processors
39
- queues.each do |queue, klass|
40
- name = "processor_pool_#{queue}".to_sym
41
- count = klass.concurrency
42
- begin
43
- pool = Concurrent::Actor::Utils::Pool.spawn! name, count do |index|
44
- Actor::Processor.spawn name: "processor_#{queue}_#{index}".to_sym,
45
- supervise: true, args: [queue]
46
- end
47
- Toiler.set_processor_pool queue, pool
48
- rescue StandardError => e
49
- error "Failed to spawn Processor Pool for queue #{queue}: #{e.message}\n#{e.backtrace.join("\n")}"
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
1
+ require 'toiler/actor/fetcher'
2
+ require 'toiler/actor/processor'
3
+
4
+ module Toiler
5
+ module Actor
6
+ # Actor that starts and supervises Toiler's actors
7
+ class Supervisor < Concurrent::Actor::RestartingContext
8
+ include Utils::ActorLogging
9
+
10
+ attr_accessor :client
11
+
12
+ def initialize
13
+ @client = ::Aws::SQS::Client.new
14
+ spawn_fetchers
15
+ spawn_processors
16
+ end
17
+
18
+ def on_message(_msg)
19
+ pass
20
+ end
21
+
22
+ def queues
23
+ Toiler.worker_class_registry
24
+ end
25
+
26
+ def spawn_fetchers
27
+ queues.each do |queue, _klass|
28
+ begin
29
+ fetcher = Actor::Fetcher.spawn! name: "fetcher_#{queue}".to_sym,
30
+ supervise: true, args: [queue, client]
31
+ Toiler.set_fetcher queue, fetcher
32
+ rescue StandardError => e
33
+ error "Failed to start Fetcher for queue #{queue}: #{e.message}\n#{e.backtrace.join("\n")}"
34
+ end
35
+ end
36
+ end
37
+
38
+ def spawn_processors
39
+ queues.each do |queue, klass|
40
+ name = "processor_pool_#{queue}".to_sym
41
+ count = klass.concurrency
42
+ begin
43
+ pool = Concurrent::Actor::Utils::Pool.spawn! name, count do |index|
44
+ Actor::Processor.spawn name: "processor_#{queue}_#{index}".to_sym,
45
+ supervise: true, args: [queue]
46
+ end
47
+ Toiler.set_processor_pool queue, pool
48
+ rescue StandardError => e
49
+ error "Failed to spawn Processor Pool for queue #{queue}: #{e.message}\n#{e.backtrace.join("\n")}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,28 +1,28 @@
1
- module Toiler
2
- module Actor
3
- module Utils
4
- # Provides helper methods for logging
5
- module ActorLogging
6
- def error(msg)
7
- log Logger::Severity::ERROR, msg
8
- end
9
-
10
- def info(msg)
11
- log Logger::Severity::INFO, msg
12
- end
13
-
14
- def debug(msg)
15
- log Logger::Severity::DEBUG, msg
16
- end
17
-
18
- def warn(msg)
19
- log Logger::Severity::WARN, smsg
20
- end
21
-
22
- def fatal(msg)
23
- log Logger::Severity::FATAL, msg
24
- end
25
- end
26
- end
27
- end
28
- end
1
+ module Toiler
2
+ module Actor
3
+ module Utils
4
+ # Provides helper methods for logging
5
+ module ActorLogging
6
+ def error(msg)
7
+ log Logger::Severity::ERROR, msg
8
+ end
9
+
10
+ def info(msg)
11
+ log Logger::Severity::INFO, msg
12
+ end
13
+
14
+ def debug(msg)
15
+ log Logger::Severity::DEBUG, msg
16
+ end
17
+
18
+ def warn(msg)
19
+ log Logger::Severity::WARN, smsg
20
+ end
21
+
22
+ def fatal(msg)
23
+ log Logger::Severity::FATAL, msg
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end