toiler 0.6.1 → 0.7.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/.rubocop.yml +29 -0
- data/.ruby-version +1 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +130 -42
- data/README.md +13 -13
- data/Rakefile +2 -0
- data/lib/toiler/actor/fetcher.rb +83 -48
- data/lib/toiler/actor/processor.rb +42 -44
- data/lib/toiler/actor/supervisor.rb +7 -5
- data/lib/toiler/actor/utils/actor_logging.rb +5 -3
- data/lib/toiler/aws/message.rb +3 -1
- data/lib/toiler/aws/queue.rb +19 -9
- data/lib/toiler/cli.rb +38 -32
- data/lib/toiler/gcp/message.rb +55 -0
- data/lib/toiler/gcp/queue.rb +37 -0
- data/lib/toiler/utils/argument_parser.rb +2 -0
- data/lib/toiler/utils/environment_loader.rb +16 -18
- data/lib/toiler/utils/logging.rb +5 -3
- data/lib/toiler/version.rb +3 -1
- data/lib/toiler/worker.rb +12 -4
- data/lib/toiler.rb +14 -4
- data/spec/models/fetcher_spec.rb +31 -5
- data/spec/models/supervisor_spec.rb +14 -3
- data/toiler.gemspec +8 -3
- metadata +41 -16
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'toiler/actor/fetcher'
|
|
2
4
|
require 'toiler/actor/processor'
|
|
3
5
|
|
|
@@ -7,10 +9,9 @@ module Toiler
|
|
|
7
9
|
class Supervisor < Concurrent::Actor::RestartingContext
|
|
8
10
|
include Utils::ActorLogging
|
|
9
11
|
|
|
10
|
-
attr_accessor :client
|
|
11
|
-
|
|
12
12
|
def initialize
|
|
13
|
-
|
|
13
|
+
super
|
|
14
|
+
|
|
14
15
|
spawn_processors
|
|
15
16
|
spawn_fetchers
|
|
16
17
|
end
|
|
@@ -21,11 +22,12 @@ module Toiler
|
|
|
21
22
|
|
|
22
23
|
def spawn_fetchers
|
|
23
24
|
Toiler.active_worker_class_registry.each do |queue, klass|
|
|
24
|
-
count
|
|
25
|
+
count = klass.concurrency
|
|
26
|
+
provider = klass.provider
|
|
25
27
|
begin
|
|
26
28
|
fetcher = Actor::Fetcher.spawn! name: "fetcher_#{queue}".to_sym,
|
|
27
29
|
supervise: true,
|
|
28
|
-
args: [queue,
|
|
30
|
+
args: [queue, count, provider]
|
|
29
31
|
Toiler.set_fetcher queue, fetcher
|
|
30
32
|
rescue StandardError => e
|
|
31
33
|
error "Failed to start Fetcher for queue #{queue}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Toiler
|
|
2
4
|
module Actor
|
|
3
5
|
module Utils
|
|
4
6
|
# Provides helper methods for logging
|
|
5
7
|
module ActorLogging
|
|
6
8
|
def error(msg)
|
|
7
|
-
log Logger::Severity::ERROR,
|
|
9
|
+
log Logger::Severity::ERROR, msg
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def info(msg)
|
|
@@ -12,7 +14,7 @@ module Toiler
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def debug(msg)
|
|
15
|
-
log Logger::Severity::DEBUG,
|
|
17
|
+
log Logger::Severity::DEBUG, msg
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def warn(msg)
|
|
@@ -20,7 +22,7 @@ module Toiler
|
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def fatal(msg)
|
|
23
|
-
log Logger::Severity::FATAL,
|
|
25
|
+
log Logger::Severity::FATAL, msg
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
end
|
data/lib/toiler/aws/message.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Toiler
|
|
2
4
|
module Aws
|
|
3
5
|
# SQS Message abstraction
|
|
@@ -24,7 +26,7 @@ module Toiler
|
|
|
24
26
|
)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
def
|
|
29
|
+
def modify_ack_deadline!(timeout)
|
|
28
30
|
client.change_message_visibility(
|
|
29
31
|
queue_url: queue_url,
|
|
30
32
|
receipt_handle: data.receipt_handle,
|
data/lib/toiler/aws/queue.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'toiler/aws/message'
|
|
2
4
|
|
|
3
5
|
module Toiler
|
|
@@ -7,13 +9,13 @@ module Toiler
|
|
|
7
9
|
class Queue
|
|
8
10
|
attr_accessor :name, :client, :url
|
|
9
11
|
|
|
10
|
-
def initialize(name, client
|
|
12
|
+
def initialize(name, client)
|
|
11
13
|
@name = name
|
|
12
|
-
@client = client
|
|
14
|
+
@client = client
|
|
13
15
|
@url = client.get_queue_url(queue_name: name).queue_url
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
def
|
|
18
|
+
def ack_deadline
|
|
17
19
|
client.get_queue_attributes(
|
|
18
20
|
queue_url: url,
|
|
19
21
|
attribute_names: ['VisibilityTimeout']
|
|
@@ -30,14 +32,22 @@ module Toiler
|
|
|
30
32
|
|
|
31
33
|
def send_messages(options)
|
|
32
34
|
client.send_message_batch(
|
|
33
|
-
sanitize_message_body
|
|
35
|
+
sanitize_message_body(options.merge(queue_url: url))
|
|
34
36
|
)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
def receive_messages(
|
|
38
|
-
client.receive_message(
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
def receive_messages(wait: nil, max_messages: nil)
|
|
40
|
+
client.receive_message(attribute_names: %w[All],
|
|
41
|
+
message_attribute_names: %w[All],
|
|
42
|
+
wait_time_seconds: wait,
|
|
43
|
+
max_number_of_messages: max_messages,
|
|
44
|
+
queue_url: url)
|
|
45
|
+
.messages
|
|
46
|
+
.map { |m| Message.new(client, url, m) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def max_messages
|
|
50
|
+
10
|
|
41
51
|
end
|
|
42
52
|
|
|
43
53
|
private
|
|
@@ -50,7 +60,7 @@ module Toiler
|
|
|
50
60
|
if body.is_a?(Hash)
|
|
51
61
|
m[:message_body] = JSON.dump(body)
|
|
52
62
|
elsif !body.is_a? String
|
|
53
|
-
|
|
63
|
+
raise ArgumentError, "Body must be a String, found #{body.class}"
|
|
54
64
|
end
|
|
55
65
|
end
|
|
56
66
|
|
data/lib/toiler/cli.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'singleton'
|
|
2
4
|
require 'timeout'
|
|
3
5
|
require 'optparse'
|
|
@@ -7,6 +9,7 @@ module Toiler
|
|
|
7
9
|
# See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
|
|
8
10
|
class Shutdown < Interrupt; end
|
|
9
11
|
|
|
12
|
+
# WaitShutdown is used to handle graceful shutdowns
|
|
10
13
|
class WaitShutdown < Interrupt
|
|
11
14
|
attr_accessor :wait
|
|
12
15
|
|
|
@@ -39,12 +42,12 @@ module Toiler
|
|
|
39
42
|
private
|
|
40
43
|
|
|
41
44
|
def handle_stop
|
|
42
|
-
while (readable_io =
|
|
45
|
+
while (readable_io = @self_read.wait_readable)
|
|
43
46
|
handle_signal(readable_io.first[0].gets.strip)
|
|
44
47
|
end
|
|
45
|
-
rescue WaitShutdown =>
|
|
46
|
-
Toiler.logger.info "Received Interrupt, Waiting up to #{
|
|
47
|
-
success = supervisor.ask(:terminate!).wait(
|
|
48
|
+
rescue WaitShutdown => e
|
|
49
|
+
Toiler.logger.info "Received Interrupt, Waiting up to #{e.wait} seconds for actors to finish..."
|
|
50
|
+
success = supervisor.ask(:terminate!).wait(e.wait)
|
|
48
51
|
if success
|
|
49
52
|
Toiler.logger.info 'Supervisor successfully terminated'
|
|
50
53
|
else
|
|
@@ -58,6 +61,7 @@ module Toiler
|
|
|
58
61
|
Concurrent.global_fast_executor.shutdown
|
|
59
62
|
Concurrent.global_io_executor.shutdown
|
|
60
63
|
return if Concurrent.global_io_executor.wait_for_termination(60)
|
|
64
|
+
|
|
61
65
|
Concurrent.global_io_executor.kill
|
|
62
66
|
end
|
|
63
67
|
|
|
@@ -67,21 +71,20 @@ module Toiler
|
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
def trap_signals
|
|
70
|
-
%w
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@self_write.puts(sig)
|
|
74
|
-
end
|
|
75
|
-
rescue ArgumentError
|
|
76
|
-
puts "System does not support signal #{sig}"
|
|
74
|
+
%w[INT TERM QUIT USR1 USR2 TTIN ABRT].each do |sig|
|
|
75
|
+
trap sig do
|
|
76
|
+
@self_write.puts(sig)
|
|
77
77
|
end
|
|
78
|
+
rescue ArgumentError
|
|
79
|
+
puts "System does not support signal #{sig}"
|
|
78
80
|
end
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
def print_stacktraces
|
|
82
84
|
return unless Toiler.logger
|
|
83
|
-
|
|
84
|
-
Toiler.logger.info
|
|
85
|
+
|
|
86
|
+
Toiler.logger.info '-------------------'
|
|
87
|
+
Toiler.logger.info 'Received QUIT, dumping threads:'
|
|
85
88
|
Thread.list.each do |t|
|
|
86
89
|
id = t.object_id
|
|
87
90
|
Toiler.logger.info "[thread:#{id}] #{t.backtrace.join("\n[thread:#{id}] ")}"
|
|
@@ -91,21 +94,24 @@ module Toiler
|
|
|
91
94
|
|
|
92
95
|
def print_status
|
|
93
96
|
return unless Toiler.logger
|
|
94
|
-
|
|
95
|
-
Toiler.logger.info
|
|
97
|
+
|
|
98
|
+
Toiler.logger.info '-------------------'
|
|
99
|
+
Toiler.logger.info 'Received QUIT, dumping status:'
|
|
96
100
|
Toiler.queues.each do |queue|
|
|
97
101
|
fetcher = Toiler.fetcher(queue).send(:core).send(:context)
|
|
98
102
|
processor_pool = Toiler.processor_pool(queue).send(:core).send(:context)
|
|
99
|
-
processors = processor_pool.instance_variable_get(:@workers).collect{|w| w.send(:core).send(:context)}
|
|
100
|
-
busy_processors = processors.count
|
|
103
|
+
processors = processor_pool.instance_variable_get(:@workers).collect { |w| w.send(:core).send(:context) }
|
|
104
|
+
busy_processors = processors.count(&:executing?)
|
|
101
105
|
message = "Status for [queue:#{queue}]:"
|
|
102
|
-
message += "\n[fetcher:#{fetcher.name}] [executing:#{fetcher.executing?}]
|
|
106
|
+
message += "\n[fetcher:#{fetcher.name}] [executing:#{fetcher.executing?}] " \
|
|
107
|
+
"[waiting_messages:#{fetcher.waiting_messages}] [free_processors:#{fetcher.free_processors}] " \
|
|
108
|
+
"[scheduled_task:#{!fetcher.scheduled_task.nil?}]"
|
|
103
109
|
message += "\n[processor_pool:#{processor_pool.name}] [workers:#{processors.count}] [busy:#{busy_processors}]"
|
|
104
110
|
processors.each do |processor|
|
|
105
111
|
thread = processor.thread
|
|
106
|
-
thread_id = thread.nil? ?
|
|
112
|
+
thread_id = thread.nil? ? 'nil' : thread.object_id
|
|
107
113
|
message += "\n[processor:#{processor.name}] [executing:#{processor.executing?}] [thread:#{thread_id}]"
|
|
108
|
-
message += " Stack:\n
|
|
114
|
+
message += " Stack:\n#{thread.backtrace.join("\n\t")}" unless thread.nil?
|
|
109
115
|
end
|
|
110
116
|
Toiler.logger.info message
|
|
111
117
|
end
|
|
@@ -118,22 +124,24 @@ module Toiler
|
|
|
118
124
|
print_stacktraces
|
|
119
125
|
print_status
|
|
120
126
|
when 'INT', 'TERM'
|
|
121
|
-
|
|
127
|
+
raise WaitShutdown, 60
|
|
122
128
|
when 'ABRT'
|
|
123
|
-
|
|
129
|
+
raise WaitShutdown, Toiler.options[:shutdown_timeout] * 60
|
|
124
130
|
end
|
|
125
131
|
end
|
|
126
132
|
|
|
127
133
|
def load_concurrent
|
|
128
134
|
require 'concurrent-edge'
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
if Toiler.logger
|
|
136
|
+
Concurrent.global_logger = lambda do |level, progname, msg = nil, &block|
|
|
137
|
+
Toiler.logger.log(level, msg, progname, &block)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
132
140
|
end
|
|
133
141
|
|
|
134
142
|
def daemonize
|
|
135
143
|
return unless Toiler.options[:daemon]
|
|
136
|
-
|
|
144
|
+
raise 'Logfile required when daemonizing' unless Toiler.options[:logfile]
|
|
137
145
|
|
|
138
146
|
files_to_reopen = []
|
|
139
147
|
ObjectSpace.each_object(File) do |file|
|
|
@@ -148,12 +156,10 @@ module Toiler
|
|
|
148
156
|
|
|
149
157
|
def reopen_files(files_to_reopen)
|
|
150
158
|
files_to_reopen.each do |file|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
puts "Failed to reopen file #{file}"
|
|
156
|
-
end
|
|
159
|
+
file.reopen file.path, 'a+'
|
|
160
|
+
file.sync = true
|
|
161
|
+
rescue StandardError
|
|
162
|
+
puts "Failed to reopen file #{file}"
|
|
157
163
|
end
|
|
158
164
|
end
|
|
159
165
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Toiler
|
|
4
|
+
module Gcp
|
|
5
|
+
# PubSub Message abstraction
|
|
6
|
+
# Provides methods for querying and acting on a PubSub message
|
|
7
|
+
class Message
|
|
8
|
+
attr_accessor :message
|
|
9
|
+
|
|
10
|
+
def initialize(message)
|
|
11
|
+
@message = message
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def delete
|
|
15
|
+
message.acknowledge!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def modify_ack_deadline!(timeout)
|
|
19
|
+
message.modify_ack_deadline! timeout
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def message_id
|
|
23
|
+
message.message_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def body
|
|
27
|
+
message.data
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def attributes
|
|
31
|
+
message.attributes
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delivery_attempt
|
|
35
|
+
message.delivery_attempt
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ordering_key
|
|
39
|
+
message.ordering_key
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def reject!
|
|
43
|
+
message.reject!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def published_at
|
|
47
|
+
message.published_at
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def ack_id
|
|
51
|
+
message.ack_id
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'toiler/gcp/message'
|
|
4
|
+
|
|
5
|
+
module Toiler
|
|
6
|
+
module Gcp
|
|
7
|
+
# GCP PubSub Queue abstraction
|
|
8
|
+
# Provides methods for querying and acting on a PubSub queue
|
|
9
|
+
class Queue
|
|
10
|
+
attr_accessor :name, :subscription
|
|
11
|
+
|
|
12
|
+
def initialize(name, client)
|
|
13
|
+
@name = name
|
|
14
|
+
@subscription = client.subscription name, skip_lookup: true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ack_deadline
|
|
18
|
+
subscription.deadline
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete_messages(messages)
|
|
22
|
+
subscription.acknowledge(messages)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def max_messages
|
|
26
|
+
# pubsub limit is 1000, but it makes little sense to pull so many messages at once
|
|
27
|
+
100
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def receive_messages(wait: nil, max_messages: nil)
|
|
31
|
+
immediate = wait.nil? || wait.zero?
|
|
32
|
+
subscription.pull(immediate: immediate, max: max_messages)
|
|
33
|
+
.map { |m| Message.new(m) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'erb'
|
|
2
4
|
require 'yaml'
|
|
3
5
|
|
|
@@ -12,7 +14,7 @@ module Toiler
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def self.load_for_rails_console
|
|
15
|
-
load(config_file:
|
|
17
|
+
load(config_file: "#{Rails.root}configtoiler.yml")
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def initialize(options)
|
|
@@ -26,36 +28,32 @@ module Toiler
|
|
|
26
28
|
Toiler.options.merge!(config_file_options)
|
|
27
29
|
Toiler.options.merge!(options)
|
|
28
30
|
initialize_aws
|
|
31
|
+
initialize_gcp
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
private
|
|
32
35
|
|
|
33
36
|
def config_file_options
|
|
34
|
-
if (path = options[:config_file])
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
path = nil
|
|
38
|
-
end
|
|
37
|
+
if (path = options[:config_file]) && !File.exist?(path)
|
|
38
|
+
Toiler.logger.warn "Config file #{path} does not exist"
|
|
39
|
+
path = nil
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
return {} unless path
|
|
42
43
|
|
|
43
|
-
deep_symbolize_keys YAML.
|
|
44
|
+
deep_symbolize_keys YAML.safe_load(ERB.new(File.read(path)).result)
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
def initialize_aws
|
|
47
48
|
return if Toiler.options[:aws].empty?
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
set_aws_credentials
|
|
49
|
+
|
|
50
|
+
Toiler.aws_client = ::Aws::SQS::Client.new Toiler.options[:aws]
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Toiler.options[:aws][:secret_access_key]
|
|
58
|
-
)
|
|
53
|
+
def initialize_gcp
|
|
54
|
+
return if Toiler.options[:gcp].empty?
|
|
55
|
+
|
|
56
|
+
Toiler.gcp_client = ::Google::Cloud::PubSub.new Toiler.options[:gcp]
|
|
59
57
|
end
|
|
60
58
|
|
|
61
59
|
def initialize_logger
|
|
@@ -90,8 +88,8 @@ module Toiler
|
|
|
90
88
|
require options[:require]
|
|
91
89
|
end
|
|
92
90
|
|
|
93
|
-
def deep_symbolize_keys(
|
|
94
|
-
|
|
91
|
+
def deep_symbolize_keys(hash)
|
|
92
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
95
93
|
k = key.respond_to?(:to_sym) ? key.to_sym : key
|
|
96
94
|
result[k] = if value.is_a? Hash
|
|
97
95
|
deep_symbolize_keys value
|
data/lib/toiler/utils/logging.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'time'
|
|
2
4
|
require 'logger'
|
|
3
5
|
|
|
@@ -21,8 +23,8 @@ module Toiler
|
|
|
21
23
|
|
|
22
24
|
module_function
|
|
23
25
|
|
|
24
|
-
def initialize_logger(log_target =
|
|
25
|
-
log_target =
|
|
26
|
+
def initialize_logger(log_target = $stdout)
|
|
27
|
+
log_target = $stdout if log_target.nil?
|
|
26
28
|
@logger = Logger.new(log_target)
|
|
27
29
|
@logger.level = Logger::INFO
|
|
28
30
|
@logger.formatter = Pretty.new
|
|
@@ -34,7 +36,7 @@ module Toiler
|
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def logger=(log)
|
|
37
|
-
@logger = (log
|
|
39
|
+
@logger = (log || Logger.new('/dev/null'))
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
end
|
data/lib/toiler/version.rb
CHANGED
data/lib/toiler/worker.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Toiler
|
|
2
4
|
# Toiler's Worker behaviour
|
|
3
5
|
module Worker
|
|
@@ -34,26 +36,32 @@ module Toiler
|
|
|
34
36
|
module ClassMethods
|
|
35
37
|
def toiler_options(options = {})
|
|
36
38
|
return class_variable_get(:@@toiler_options) if options.empty?
|
|
39
|
+
|
|
37
40
|
Toiler.register_worker(options[:queue], self) if options[:queue]
|
|
38
41
|
class_variable_get(:@@toiler_options).merge! options
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
def batch?
|
|
42
|
-
class_variable_get(:@@toiler_options)[:batch]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
44
|
def concurrency
|
|
46
45
|
class_variable_get(:@@toiler_options)[:concurrency]
|
|
47
46
|
end
|
|
48
47
|
|
|
48
|
+
def provider
|
|
49
|
+
class_variable_get(:@@toiler_options)[:provider]
|
|
50
|
+
end
|
|
51
|
+
|
|
49
52
|
def queue
|
|
50
53
|
class_variable_get(:@@toiler_options)[:queue]
|
|
51
54
|
end
|
|
52
55
|
|
|
56
|
+
# kept for compatibility reasons
|
|
53
57
|
def auto_visibility_timeout?
|
|
54
58
|
class_variable_get(:@@toiler_options)[:auto_visibility_timeout]
|
|
55
59
|
end
|
|
56
60
|
|
|
61
|
+
def deadline_extension?
|
|
62
|
+
class_variable_get(:@@toiler_options)[:deadline_extension]
|
|
63
|
+
end
|
|
64
|
+
|
|
57
65
|
def auto_delete?
|
|
58
66
|
class_variable_get(:@@toiler_options)[:auto_delete]
|
|
59
67
|
end
|
data/lib/toiler.rb
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'aws-sdk-sqs'
|
|
4
|
+
require 'google/cloud/pubsub'
|
|
5
|
+
require 'grpc'
|
|
2
6
|
require 'toiler/utils/environment_loader'
|
|
3
7
|
require 'toiler/utils/logging'
|
|
4
8
|
require 'toiler/utils/argument_parser'
|
|
@@ -10,13 +14,19 @@ require 'toiler/version'
|
|
|
10
14
|
module Toiler
|
|
11
15
|
@worker_class_registry = {}
|
|
12
16
|
@options = {
|
|
13
|
-
aws: {}
|
|
17
|
+
aws: {},
|
|
18
|
+
gcp: {}
|
|
14
19
|
}
|
|
15
20
|
@fetchers = {}
|
|
16
21
|
@processor_pools = {}
|
|
22
|
+
@aws_client = nil
|
|
23
|
+
@gcp_client = nil
|
|
17
24
|
|
|
18
25
|
attr_reader :worker_class_registry, :options, :fetchers, :processor_pools
|
|
19
|
-
|
|
26
|
+
attr_accessor :aws_client, :gcp_client
|
|
27
|
+
|
|
28
|
+
module_function :worker_class_registry, :options, :fetchers, :processor_pools,
|
|
29
|
+
:aws_client, :gcp_client, :aws_client=, :gcp_client=
|
|
20
30
|
|
|
21
31
|
module_function
|
|
22
32
|
|
|
@@ -63,10 +73,10 @@ module Toiler
|
|
|
63
73
|
def default_options
|
|
64
74
|
{
|
|
65
75
|
auto_visibility_timeout: false,
|
|
76
|
+
deadline_extension: false,
|
|
66
77
|
concurrency: 1,
|
|
67
78
|
auto_delete: false,
|
|
68
|
-
shutdown_timeout: 5
|
|
69
|
-
batch: false
|
|
79
|
+
shutdown_timeout: 5
|
|
70
80
|
}
|
|
71
81
|
end
|
|
72
82
|
|
data/spec/models/fetcher_spec.rb
CHANGED
|
@@ -4,19 +4,45 @@ require 'toiler/actor/fetcher'
|
|
|
4
4
|
RSpec.describe Toiler::Actor::Fetcher, type: :model do
|
|
5
5
|
let(:queue) { 'default' }
|
|
6
6
|
let(:client) { double(:aws_sqs_client) }
|
|
7
|
+
let(:aws_queue) { double(:aws_queue) }
|
|
8
|
+
let(:gcp_queue) { double(:gcp_queue) }
|
|
7
9
|
|
|
8
10
|
before do
|
|
9
11
|
allow_any_instance_of(Toiler::Actor::Fetcher).to receive(:log).and_return(true)
|
|
10
12
|
allow_any_instance_of(Toiler::Actor::Fetcher).to receive(:tell)
|
|
11
|
-
|
|
13
|
+
allow(Toiler::Aws::Queue).to receive(:new).and_return(aws_queue)
|
|
14
|
+
allow(Toiler::Gcp::Queue).to receive(:new).and_return(gcp_queue)
|
|
15
|
+
allow(aws_queue).to receive(:ack_deadline).and_return(100)
|
|
16
|
+
allow(gcp_queue).to receive(:ack_deadline).and_return(100)
|
|
12
17
|
allow(client).to receive(:get_queue_url).with(queue_name: 'default').and_return double(:queue, queue_url: 'http://aws.fake/queue')
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
describe "#new" do
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
context 'default' do
|
|
22
|
+
it 'completes sucessfully' do
|
|
23
|
+
fetcher = described_class.new(queue, 1, nil)
|
|
24
|
+
expect(aws_queue).to have_received(:ack_deadline)
|
|
25
|
+
expect(fetcher).to have_received(:tell).with(:pull_messages)
|
|
26
|
+
expect(fetcher.executing?).to eq(false)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'aws' do
|
|
31
|
+
it 'completes sucessfully' do
|
|
32
|
+
fetcher = described_class.new(queue, 1, :aws)
|
|
33
|
+
expect(aws_queue).to have_received(:ack_deadline)
|
|
34
|
+
expect(fetcher).to have_received(:tell).with(:pull_messages)
|
|
35
|
+
expect(fetcher.executing?).to eq(false)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'gcp' do
|
|
40
|
+
it 'completes sucessfully' do
|
|
41
|
+
fetcher = described_class.new(queue, 1, :gcp)
|
|
42
|
+
expect(gcp_queue).to have_received(:ack_deadline)
|
|
43
|
+
expect(fetcher).to have_received(:tell).with(:pull_messages)
|
|
44
|
+
expect(fetcher.executing?).to eq(false)
|
|
45
|
+
end
|
|
20
46
|
end
|
|
21
47
|
end
|
|
22
48
|
end
|
|
@@ -2,7 +2,6 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
require 'toiler/actor/supervisor'
|
|
4
4
|
RSpec.describe Toiler::Actor::Supervisor, type: :model do
|
|
5
|
-
let(:sqs_client) { double(:client) }
|
|
6
5
|
describe "#new" do
|
|
7
6
|
it 'only spawns fetchers for active workers' do
|
|
8
7
|
class InactiveWorker
|
|
@@ -12,8 +11,7 @@ RSpec.describe Toiler::Actor::Supervisor, type: :model do
|
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
Toiler.options.merge!(active_queues: ['default'])
|
|
15
|
-
expect(::
|
|
16
|
-
expect(Toiler::Actor::Fetcher).to receive(:spawn!).with(name: :fetcher_default, supervise: true, args: ['default', sqs_client, 1])
|
|
14
|
+
expect(Toiler::Actor::Fetcher).to receive(:spawn!).with(name: :fetcher_default, supervise: true, args: ['default', 1, nil])
|
|
17
15
|
expect(Concurrent::Actor::Utils::Pool).to receive(:spawn!).with(:processor_pool_default, 1)
|
|
18
16
|
supervisor = described_class.new
|
|
19
17
|
end
|
|
@@ -23,5 +21,18 @@ RSpec.describe Toiler::Actor::Supervisor, type: :model do
|
|
|
23
21
|
expect(Toiler::Utils::Logging.logger).to receive(:warn).with("No worker assigned to queue: missing")
|
|
24
22
|
Toiler.active_worker_class_registry
|
|
25
23
|
end
|
|
24
|
+
|
|
25
|
+
it 'sepcifies provider' do
|
|
26
|
+
class InactiveWorker
|
|
27
|
+
include Toiler::Worker
|
|
28
|
+
toiler_options queue: 'gcp_queue', provider: :gcp
|
|
29
|
+
def perform(sqs_message, body); end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Toiler.options.merge!(active_queues: ['gcp_queue'])
|
|
33
|
+
expect(Toiler::Actor::Fetcher).to receive(:spawn!).with(name: :fetcher_gcp_queue, supervise: true, args: ['gcp_queue', 1, :gcp])
|
|
34
|
+
expect(Concurrent::Actor::Utils::Pool).to receive(:spawn!).with(:processor_pool_gcp_queue, 1)
|
|
35
|
+
supervisor = described_class.new
|
|
36
|
+
end
|
|
26
37
|
end
|
|
27
38
|
end
|