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
@@ -0,0 +1,45 @@
|
|
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
|
+
attr_accessor :client
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@client = ::Aws::SQS::Client.new
|
12
|
+
spawn_fetchers
|
13
|
+
spawn_processors
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_message(_msg)
|
17
|
+
pass
|
18
|
+
end
|
19
|
+
|
20
|
+
def queues
|
21
|
+
Toiler.worker_class_registry
|
22
|
+
end
|
23
|
+
|
24
|
+
def spawn_fetchers
|
25
|
+
queues.each do |queue, _klass|
|
26
|
+
fetcher = Actor::Fetcher.spawn! name: "fetcher_#{queue}".to_sym,
|
27
|
+
supervise: true, args: [queue, client]
|
28
|
+
Toiler.set_fetcher queue, fetcher
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def spawn_processors
|
33
|
+
queues.each do |queue, klass|
|
34
|
+
name = "processor_pool_#{queue}".to_sym
|
35
|
+
count = klass.concurrency
|
36
|
+
pool = Concurrent::Actor::Utils::Pool.spawn! name, count do |index|
|
37
|
+
Actor::Processor.spawn name: "processor_#{queue}_#{index}".to_sym,
|
38
|
+
supervise: true, args: [queue]
|
39
|
+
end
|
40
|
+
Toiler.set_processor_pool queue, pool
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +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, self.class, msg
|
8
|
+
end
|
9
|
+
|
10
|
+
def info(msg)
|
11
|
+
log Logger::Severity::INFO, self.class, msg
|
12
|
+
end
|
13
|
+
|
14
|
+
def debug(msg)
|
15
|
+
log Logger::Severity::DEBUG, self.class, msg
|
16
|
+
end
|
17
|
+
|
18
|
+
def warn(msg)
|
19
|
+
log Logger::Severity::WARN, self.class, msg
|
20
|
+
end
|
21
|
+
|
22
|
+
def fatal(msg)
|
23
|
+
log Logger::Severity::FATAL, self.class, msg
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Toiler
|
2
|
+
module Aws
|
3
|
+
# SQS Message abstraction
|
4
|
+
# Provides methods for querying and acting on a SQS message
|
5
|
+
class Message
|
6
|
+
attr_accessor :client, :queue_url, :data
|
7
|
+
|
8
|
+
def initialize(client, queue_url, data)
|
9
|
+
@client = client
|
10
|
+
@queue_url = queue_url
|
11
|
+
@data = data
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete
|
15
|
+
client.delete_message(
|
16
|
+
queue_url: queue_url,
|
17
|
+
receipt_handle: data.receipt_handle
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def change_visibility(options)
|
22
|
+
client.change_message_visibility(
|
23
|
+
options.merge(queue_url: queue_url, receipt_handle: receipt_handle)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def visibility_timeout=(timeout)
|
28
|
+
client.change_message_visibility(
|
29
|
+
queue_url: queue_url,
|
30
|
+
receipt_handle: data.receipt_handle,
|
31
|
+
visibility_timeout: timeout
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def message_id
|
36
|
+
data.message_id
|
37
|
+
end
|
38
|
+
|
39
|
+
def receipt_handle
|
40
|
+
data.receipt_handle
|
41
|
+
end
|
42
|
+
|
43
|
+
def md5_of_body
|
44
|
+
data.md5_of_body
|
45
|
+
end
|
46
|
+
|
47
|
+
def body
|
48
|
+
data.body
|
49
|
+
end
|
50
|
+
|
51
|
+
def attributes
|
52
|
+
data.attributes
|
53
|
+
end
|
54
|
+
|
55
|
+
def md5_of_message_attributes
|
56
|
+
data.md5_of_message_attributes
|
57
|
+
end
|
58
|
+
|
59
|
+
def message_attributes
|
60
|
+
data.message_attributes
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'toiler/aws/message'
|
2
|
+
|
3
|
+
module Toiler
|
4
|
+
module Aws
|
5
|
+
# SQS Queue abstraction
|
6
|
+
# Provides methods for querying and acting on a SQS queue
|
7
|
+
class Queue
|
8
|
+
attr_accessor :name, :client, :url
|
9
|
+
|
10
|
+
def initialize(name, client = nil)
|
11
|
+
@name = name
|
12
|
+
@client = client || ::Aws::SQS::Client.new
|
13
|
+
@url = client.get_queue_url(queue_name: name).queue_url
|
14
|
+
end
|
15
|
+
|
16
|
+
def visibility_timeout
|
17
|
+
client.get_queue_attributes(
|
18
|
+
queue_url: url,
|
19
|
+
attribute_names: ['VisibilityTimeout']
|
20
|
+
).attributes['VisibilityTimeout'].to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete_messages(options)
|
24
|
+
client.delete_message_batch options.merge queue_url: url
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_message(options)
|
28
|
+
client.send_message sanitize_message_body options.merge queue_url: url
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_messages(options)
|
32
|
+
client.send_message_batch(
|
33
|
+
sanitize_message_body options.merge queue_url: url
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def receive_messages(options)
|
38
|
+
client.receive_message(options.merge(queue_url: url))
|
39
|
+
.messages
|
40
|
+
.map { |m| Message.new(client, url, m) }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def sanitize_message_body(options)
|
46
|
+
messages = options[:entries] || [options]
|
47
|
+
|
48
|
+
messages.each do |m|
|
49
|
+
body = m[:message_body]
|
50
|
+
if body.is_a?(Hash)
|
51
|
+
m[:message_body] = JSON.dump(body)
|
52
|
+
elsif !body.is_a? String
|
53
|
+
fail ArgumentError, "Body must be a String, found #{body.class}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/toiler/cli.rb
CHANGED
@@ -7,71 +7,81 @@ module Toiler
|
|
7
7
|
# See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
|
8
8
|
class Shutdown < Interrupt; end
|
9
9
|
|
10
|
+
# Command line client interface
|
10
11
|
class CLI
|
11
12
|
include Singleton
|
12
13
|
|
13
|
-
|
14
|
-
self_read, self_write = IO.pipe
|
15
|
-
|
16
|
-
%w(INT TERM USR1 USR2 TTIN).each do |sig|
|
17
|
-
begin
|
18
|
-
trap sig do
|
19
|
-
self_write.puts(sig)
|
20
|
-
end
|
21
|
-
rescue ArgumentError
|
22
|
-
puts "Signal #{sig} not supported"
|
23
|
-
end
|
24
|
-
end
|
14
|
+
attr_accessor :supervisor
|
25
15
|
|
26
|
-
|
16
|
+
def run(args)
|
17
|
+
@self_read, @self_write = IO.pipe
|
27
18
|
|
28
|
-
|
19
|
+
trap_signals
|
20
|
+
options = Utils::ArgumentParser.parse(args)
|
21
|
+
Utils::EnvironmentLoader.load(options)
|
29
22
|
daemonize
|
30
23
|
write_pid
|
31
|
-
|
24
|
+
load_concurrent
|
25
|
+
start_supervisor
|
32
26
|
|
33
|
-
|
34
|
-
|
35
|
-
@supervisor = Supervisor.new
|
27
|
+
handle_stop
|
28
|
+
end
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
30
|
+
private
|
31
|
+
|
32
|
+
def handle_stop
|
33
|
+
while (readable_io = IO.select([@self_read]))
|
34
|
+
handle_signal(readable_io.first[0].gets.strip)
|
35
|
+
end
|
36
|
+
rescue Interrupt
|
37
|
+
puts 'Waiting up to 60 seconds for actors to finish...'
|
38
|
+
supervisor.ask(:terminate!).wait(60)
|
39
|
+
ensure
|
40
|
+
exit 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def shutdown_pools
|
44
|
+
Concurrent.global_fast_executor.shutdown
|
45
|
+
Concurrent.global_io_executor.shutdown
|
46
|
+
return if Concurrent.global_io_executor.wait_for_termination(60)
|
47
|
+
Concurrent.global_io_executor.kill
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_supervisor
|
51
|
+
require 'toiler/actor/supervisor'
|
52
|
+
@supervisor = Actor::Supervisor.spawn! :supervisor
|
53
|
+
end
|
54
|
+
|
55
|
+
def trap_signals
|
56
|
+
%w(INT TERM USR1 USR2 TTIN).each do |sig|
|
43
57
|
begin
|
44
|
-
|
45
|
-
@
|
58
|
+
trap sig do
|
59
|
+
@self_write.puts(sig)
|
46
60
|
end
|
47
|
-
|
48
|
-
|
61
|
+
rescue ArgumentError
|
62
|
+
puts "System does not support signal #{sig}"
|
49
63
|
end
|
50
64
|
end
|
51
65
|
end
|
52
66
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
67
|
+
def handle_signal(signal)
|
68
|
+
case signal
|
69
|
+
when 'INT', 'TERM'
|
70
|
+
fail Interrupt
|
71
|
+
end
|
57
72
|
end
|
58
73
|
|
59
|
-
def
|
60
|
-
fail
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
require 'celluloid/current'
|
66
|
-
require 'celluloid/task/pooled_fiber'
|
67
|
-
Celluloid.task_class = Celluloid::Task::PooledFiber
|
68
|
-
Celluloid.logger = (Toiler.options[:verbose] ? Toiler.logger : nil)
|
74
|
+
def load_concurrent
|
75
|
+
fail 'Concurrent should not be required now' if defined?(::Concurrent)
|
76
|
+
require 'concurrent-edge'
|
77
|
+
Concurrent.global_logger = lambda do |level, progname, msg = nil, &block|
|
78
|
+
Toiler.logger.log(level, msg, progname, &block)
|
79
|
+
end if Toiler.logger
|
69
80
|
end
|
70
81
|
|
71
82
|
def daemonize
|
72
83
|
return unless Toiler.options[:daemon]
|
73
|
-
|
74
|
-
fail ArgumentError, "You really should set a logfile if you're going to daemonize" unless Toiler.options[:logfile]
|
84
|
+
fail 'Logfile required when daemonizing' unless Toiler.options[:logfile]
|
75
85
|
|
76
86
|
files_to_reopen = []
|
77
87
|
ObjectSpace.each_object(File) do |file|
|
@@ -80,71 +90,34 @@ module Toiler
|
|
80
90
|
|
81
91
|
Process.daemon(true, true)
|
82
92
|
|
93
|
+
reopen_files(files_to_reopen)
|
94
|
+
reopen_std
|
95
|
+
end
|
96
|
+
|
97
|
+
def reopen_files(files_to_reopen)
|
83
98
|
files_to_reopen.each do |file|
|
84
99
|
begin
|
85
100
|
file.reopen file.path, 'a+'
|
86
|
-
|
87
|
-
rescue
|
101
|
+
file.sync = true
|
102
|
+
rescue StandardError
|
103
|
+
puts "Failed to reopen file #{file}"
|
88
104
|
end
|
89
105
|
end
|
106
|
+
end
|
90
107
|
|
108
|
+
def reopen_std
|
91
109
|
[$stdout, $stderr].each do |io|
|
92
110
|
File.open(Toiler.options[:logfile], 'ab') do |f|
|
93
111
|
io.reopen(f)
|
94
112
|
end
|
95
|
-
|
113
|
+
io.sync = true
|
96
114
|
end
|
97
115
|
$stdin.reopen('/dev/null')
|
98
116
|
end
|
99
117
|
|
100
118
|
def write_pid
|
101
|
-
|
102
|
-
|
103
|
-
f.puts Process.pid
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def parse_cli_args(argv)
|
109
|
-
opts = { queues: [] }
|
110
|
-
|
111
|
-
@parser = OptionParser.new do |o|
|
112
|
-
o.on '-d', '--daemon', 'Daemonize process' do |arg|
|
113
|
-
opts[:daemon] = arg
|
114
|
-
end
|
115
|
-
|
116
|
-
o.on '-r', '--require [PATH|DIR]', 'Location of the worker' do |arg|
|
117
|
-
opts[:require] = arg
|
118
|
-
end
|
119
|
-
|
120
|
-
o.on '-C', '--config PATH', 'Path to YAML config file' do |arg|
|
121
|
-
opts[:config_file] = arg
|
122
|
-
end
|
123
|
-
|
124
|
-
o.on '-R', '--rails', 'Load Rails' do |arg|
|
125
|
-
opts[:rails] = arg
|
126
|
-
end
|
127
|
-
|
128
|
-
o.on '-L', '--logfile PATH', 'Path to writable logfile' do |arg|
|
129
|
-
opts[:logfile] = arg
|
130
|
-
end
|
131
|
-
|
132
|
-
o.on '-P', '--pidfile PATH', 'Path to pidfile' do |arg|
|
133
|
-
opts[:pidfile] = arg
|
134
|
-
end
|
135
|
-
|
136
|
-
o.on '-v', '--verbose', 'Print more verbose output' do |arg|
|
137
|
-
opts[:verbose] = arg
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
@parser.banner = 'toiler [options]'
|
142
|
-
@parser.on_tail '-h', '--help', 'Show help' do
|
143
|
-
Toiler.logger.info @parser
|
144
|
-
exit 1
|
145
|
-
end
|
146
|
-
@parser.parse!(argv)
|
147
|
-
opts
|
119
|
+
file = Toiler.options[:pidfile]
|
120
|
+
File.write file, Process.pid if file
|
148
121
|
end
|
149
122
|
end
|
150
123
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Toiler
|
2
|
+
module Utils
|
3
|
+
# Parses command-line arguments
|
4
|
+
module ArgumentParser
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def parse(argv)
|
8
|
+
opts = { queues: [] }
|
9
|
+
|
10
|
+
parser = OptionParser.new do |o|
|
11
|
+
o.on '-d', '--daemon', 'Daemonize process' do |arg|
|
12
|
+
opts[:daemon] = arg
|
13
|
+
end
|
14
|
+
|
15
|
+
o.on '-r', '--require [PATH|DIR]', 'Location of the worker' do |arg|
|
16
|
+
opts[:require] = arg
|
17
|
+
end
|
18
|
+
|
19
|
+
o.on '-C', '--config PATH', 'Path to YAML config file' do |arg|
|
20
|
+
opts[:config_file] = arg
|
21
|
+
end
|
22
|
+
|
23
|
+
o.on '-R', '--rails', 'Load Rails' do |arg|
|
24
|
+
opts[:rails] = arg
|
25
|
+
end
|
26
|
+
|
27
|
+
o.on '-L', '--logfile PATH', 'Path to writable logfile' do |arg|
|
28
|
+
opts[:logfile] = arg
|
29
|
+
end
|
30
|
+
|
31
|
+
o.on '-P', '--pidfile PATH', 'Path to pidfile' do |arg|
|
32
|
+
opts[:pidfile] = arg
|
33
|
+
end
|
34
|
+
|
35
|
+
o.on '-v', '--verbose', 'Print more verbose output' do |arg|
|
36
|
+
opts[:verbose] = arg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
parser.banner = 'toiler [options]'
|
41
|
+
parser.on_tail '-h', '--help', 'Show help' do
|
42
|
+
puts parser
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
parser.parse!(argv)
|
46
|
+
opts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|