arsenicum 0.2.1.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a97f7f6c419ed5ba11e0b4cc8ec0551af8400e5e
4
- data.tar.gz: 6b3fdcf89f6ff38246d0486fc556ce181d3407c2
3
+ metadata.gz: 227759f26fc6a576603f6ea5454a801b952c61e5
4
+ data.tar.gz: c6ce1168091c355e7a2258a1f181c28c18899121
5
5
  SHA512:
6
- metadata.gz: 2ec82a578e2770bac8bc5c10c125ad4681a648a6da7d331d79abb877417ea1549e3047f699042045bc3997f2116f4aa317b93fd45138d0e151ba32c4b2213772
7
- data.tar.gz: 948332737fc834471d3330ab0f2572f9fcb6e2a38e5cd53f2550e36021e065ad16f93e59c548f8ee24c9090bab9ecd52410ef9e91a16332db5b98d50aad0bb68
6
+ metadata.gz: de846aa8417608539d5d28a85cb66a8c3f6737d25300474d6adf6fa339185de70b9a115c8ec509ce864b1dfa013da41f5e253055de17eb0a9792b1cda5b9e59c
7
+ data.tar.gz: 928123fa45f80afb3ae8f98117b3b00da665f42017e77f46d80279f7aba28c801d770e23f8768a365eb61ea668e0d94c0d7c97f6d23143b8efde10ba6b5d31b3
data/lib/arsenicum/cli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'optparse'
2
+
1
3
  class Arsenicum::CLI
2
4
  autoload :RailsCLI, 'arsenicum/cli/rails_cli'
3
5
 
@@ -1,34 +1,71 @@
1
1
  module Arsenicum::Core::IOHelper
2
- def write_string(io, string)
3
- string_to_write = string.dup
4
- string_to_write.force_encoding 'BINARY'
2
+ TYPE_INT = "\xa0".force_encoding(Encoding::BINARY).freeze
3
+ TYPE_STRING = "\xfc".force_encoding(Encoding::BINARY).freeze
5
4
 
6
- io.write [string.length].pack('N').force_encoding 'BINARY'
7
- io.write string
8
- end
5
+ def write_message(io, *items)
6
+ buffer = StringIO.new
7
+ buffer.set_encoding Encoding::BINARY
8
+ buffer.seek 4# length of integer.
9
9
 
10
- def read_string(io, encoding: 'UTF-8')
11
- bytes_for_length = read_from io, 4
12
- length = bytes_for_length.unpack('N').first
13
- return if length == 0
10
+ items.each do |item|
11
+ case item
12
+ when Fixnum
13
+ buffer.write TYPE_INT
14
+ buffer.write [item].pack('N')
15
+ when String, Symbol
16
+ item = item.to_s.force_encoding Encoding::BINARY
17
+ buffer.write TYPE_STRING
18
+ length = item.length
19
+ buffer.write int2bin(length)
20
+ buffer.write item
21
+ end
22
+ end
23
+ buffer.seek 0
24
+ buffer.write int2bin(buffer.length - 4)
14
25
 
15
- read_from(io, length).tap{|s|s.force_encoding encoding}
26
+ io.write buffer.string
16
27
  end
17
28
 
18
- def write_code(io, integer_value)
19
- value = [integer_value].pack('C')
20
- io.write value
21
- end
29
+ def read_message(io, encoding: Encoding::UTF_8)
30
+ bytes_for_length = read_from io, 4
31
+ length = bin2int(bytes_for_length)
32
+ return [] if length == 0
33
+
34
+ bytes = read_from(io, length)
35
+ ptr = 0
22
36
 
23
- def read_code(io)
24
- string = read_from io, 1
25
- string.unpack('C').first
37
+ result = []
38
+ while ptr < bytes.length
39
+ type_byte = bytes[ptr]
40
+ ptr += 1
41
+ case type_byte
42
+ when TYPE_INT
43
+ result << bin2int(bytes[ptr...ptr + 4])
44
+ ptr += 4
45
+ when TYPE_STRING
46
+ length = bin2int(bytes[ptr...ptr + 4])
47
+ ptr += 4
48
+ next result << '' if length == 0
49
+ result << bytes[ptr...ptr + length].force_encoding(encoding)
50
+ ptr += length
51
+ end
52
+ end
53
+ result
26
54
  end
27
55
 
28
56
  private
29
57
  def read_from(io, length)
30
58
  bytes = io.read length
31
59
  raise Arsenicum::IO::EOFException unless bytes
32
- bytes
60
+ bytes.force_encoding Encoding::BINARY
33
61
  end
62
+
63
+ def int2bin(number)
64
+ [number].pack('N')
65
+ end
66
+
67
+ def bin2int(bytes)
68
+ bytes.unpack('N').first
69
+ end
70
+
34
71
  end
@@ -4,8 +4,19 @@ class Arsenicum::Core::Worker
4
4
  include Arsenicum::Core::Commands
5
5
  include Arsenicum::Core::IOHelper
6
6
 
7
- attr_reader :pid, :in_parent, :out_parent, :thread,
8
- :in_child, :out_child, :active, :broker, :serializer, :formatter, :index
7
+ RESULT_SUCCESS = 0
8
+ RESULT_FAILURE = 0x80
9
+
10
+ CONTROL_STOP = 0xFF
11
+ CONTROL_PING = 0x30
12
+
13
+ attr_reader :pid,
14
+ :in_parent, :out_parent, :in_child, :out_child,
15
+ :ctrl_in_parent, :ctrl_out_parent, :ctrl_in_child, :ctrl_out_child,
16
+ :work_at,
17
+ :thread,
18
+ :active, :broker, :serializer, :formatter, :index,
19
+ :state
9
20
  alias_method :active?, :active
10
21
 
11
22
  def initialize(broker, index, worker_configuration)
@@ -14,55 +25,21 @@ class Arsenicum::Core::Worker
14
25
  @serializer = worker_configuration[:serializer]
15
26
  @formatter = worker_configuration[:formatter]
16
27
  @thread = InvokerThread.new(self)
28
+ @work_at = :parent
29
+ @state = :parent
17
30
  end
18
31
 
19
32
  def run
20
- (@in_parent, @out_child) = open_binary_pipes
21
- (@in_child, @out_parent) = open_binary_pipes
33
+ (@in_parent, @out_child) = open_binary_pipes
34
+ (@in_child, @out_parent) = open_binary_pipes
35
+ (@ctrl_in_parent, @ctrl_out_child) = open_binary_pipes
36
+ (@ctrl_in_child, @ctrl_out_parent) = open_binary_pipes
22
37
 
23
- @pid = fork do
24
- $0 = "arsenicum[worker][#{index}]"
25
- [in_parent, out_parent].each(&:close)
38
+ @pid = fork &method(:run_in_child)
39
+ return unless @pid
26
40
 
27
- begin
28
- loop do
29
- begin
30
- Arsenicum::Logger.debug {log_message_for '[child]to read initial command'}
31
- command = read_code in_child
32
- Arsenicum::Logger.debug {log_message_for '[child]Command: 0x%02x' % command}
33
- break if command == COMMAND_STOP
34
- task_id_string = read_string in_child
35
- content = read_string in_child
36
- rescue Arsenicum::IO::EOFException
37
- # Interrupted request: No required GC.
38
- break
39
- end
40
-
41
- task_id = task_id_string.to_sym
42
- task = broker[task_id]
43
-
44
- parameters = deserialize content
45
-
46
- begin
47
- Arsenicum::Logger.info {log_message_for "[child]Task start"}
48
- Arsenicum::Logger.info {log_message_for "[child]Parameters: #{parameters.inspect}"
49
- task.run *parameters
50
- Arsenicum::Logger.info {log_message_for "[child]Task success"}
51
- write_code out_child, 0
52
- rescue Exception => e
53
- Arsenicum::Logger.error {log_message_for "[child]Task Failure with #{e.class.name}#{":#{e.message}" if e.message}"}
54
- write_code out_child, 1
55
- write_string out_child, Marshal.dump(e)
56
- end
57
- end
58
- ensure
59
- [in_child, out_child].each do |io|
60
- begin io.close rescue nil end
61
- end
62
- end
63
- end
64
41
  @active = true
65
- [in_child, out_child].each(&:close)
42
+ [in_child, out_child, ctrl_in_child, ctrl_out_child].each(&:close)
66
43
  pid
67
44
  end
68
45
 
@@ -72,32 +49,104 @@ class Arsenicum::Core::Worker
72
49
  end
73
50
  end
74
51
 
52
+ def ask(task_id, *parameters)
53
+ write_message out_parent, task_id, serialize(parameters)
54
+ loop do
55
+ rs, = select([in_parent], [], [], 5)
56
+ break if rs
57
+ sleep 0.5
58
+ end
59
+
60
+ result, marshaled_exception = read_message in_parent
61
+ return if result == RESULT_SUCCESS
62
+ raise Marshal.load(marshaled_exception)
63
+ end
64
+
75
65
  def ask_async(success_handler, failure_handler, task_id, *parameters)
76
66
  thread.ask success_handler, failure_handler, task_id, *parameters
77
67
  end
78
68
 
79
- def ask(task_id, *parameter)
80
- Arsenicum::Logger.info {log_message_for "Task ID: #{task_id}"}
69
+ def stop
70
+ write_message ctrl_out_parent, COMMAND_STOP
71
+ Process.waitpid pid
72
+ end
73
+
74
+ private
75
+ def run_in_child
76
+ switch_state :waiting
77
+ [in_parent, out_parent, ctrl_in_parent, ctrl_out_parent].each(&:close)
78
+ @work_at = :child
81
79
 
82
- write_code out_parent, COMMAND_TASK
83
- write_string out_parent, task_id.to_s
84
- write_string out_parent, serialize(parameter)
80
+ hook_signal
85
81
 
86
- Arsenicum::Logger.debug { log_message_for "Task ID: #{task_id}: Request completed. Begin waiting for the reply." }
82
+ begin
83
+ loop do
84
+ server_loop
85
+ break unless state == :waiting
86
+ end
87
+ ensure
88
+ [in_child, out_child, ctrl_in_child, ctrl_out_child].each do |io|
89
+ begin io.close rescue nil end
90
+ end
91
+ end
92
+ end
93
+
94
+ def switch_state(state)
95
+ @state = state
96
+ $0 = process_name
97
+ end
87
98
 
88
- result = read_code in_parent
89
- return if result == 0
90
- raise Marshal.restore(read_string in_parent, encoding: 'BINARY')
99
+ def server_loop
100
+ begin
101
+ rs, = select [in_child, ctrl_in_child], [], [], 0.5
102
+ return unless rs
103
+ rescue Interrupt
104
+ switch_state :interrupted
105
+ return
106
+ end
107
+
108
+ rs.first == in_child ? handle_request : handle_control
109
+ end
110
+
111
+ def handle_request
112
+ switch_state :busy
113
+ begin
114
+ task_id_string, content = read_message in_child, encoding: Encoding::UTF_8
115
+ rescue Arsenicum::IO::EOFException
116
+ # Interrupted request: No required GC.
117
+ return
118
+ end
119
+
120
+ task_id = task_id_string.to_sym
121
+ task = broker[task_id]
122
+ parameters = content.length == 0 ? [] : deserialize(content)
123
+
124
+ begin
125
+ info message: "Task[#{task_id}] start"
126
+ info message: "Parameters: #{parameters.inspect}"
127
+
128
+ task.run *parameters
129
+ info message: 'Task success'
130
+ write_message out_child, RESULT_SUCCESS
131
+ rescue Exception => e
132
+ error message: "Task #{task_id} Failed", exception: e
133
+ write_message out_child, RESULT_FAILURE, Marshal.dump(e)
134
+ end
91
135
  ensure
92
- return_to_broker
136
+ switch_state :waiting
93
137
  end
94
138
 
95
- def stop
96
- write_code out_parent, COMMAND_STOP
97
- Process.waitpid pid
139
+ def handle_control
140
+ begin
141
+ control, = read_message ctrl_in_child
142
+ case control
143
+ when CONTROL_STOP
144
+ thread.stop
145
+ switch_state :stopped
146
+ end
147
+ end
98
148
  end
99
149
 
100
- private
101
150
  def serialize(parameter)
102
151
  serializer.serialize(formatter.format(parameter))
103
152
  end
@@ -114,14 +163,37 @@ class Arsenicum::Core::Worker
114
163
  end
115
164
  end
116
165
 
166
+ def process_name
167
+ "arsenicum[Worker ##{index}] - #{state}"
168
+ end
169
+
117
170
  def return_to_broker
118
171
  broker.get_back_worker self
119
172
  end
120
173
 
174
+ [:debug, :info, :warn, :error, :fatal].each do |level|
175
+ eval <<-SCRIPT, binding, __FILE__, __LINE__ + 1
176
+ def #{level}(message: nil, exception: nil)
177
+ Arsenicum::Logger.#{level} do
178
+ message = "[Worker #\#{index}][\#{work_at}]\#{message}" if message
179
+ [message, exception]
180
+ end
181
+ end
182
+ SCRIPT
183
+ end
184
+
121
185
  def log_message_for(message)
122
186
  "[Worker ##{object_id}]#{message}"
123
187
  end
124
188
 
189
+ def hook_signal
190
+ [:USR1, :USR2, ].each do |sig|
191
+ Signal.trap sig do
192
+ exit 1
193
+ end
194
+ end
195
+ end
196
+
125
197
  class InvokerThread < Thread
126
198
  attr_accessor :task_request
127
199
  private :task_request, :task_request=
@@ -138,19 +210,28 @@ class Arsenicum::Core::Worker
138
210
 
139
211
  begin
140
212
  worker.ask task_id, *parameter
213
+ info worker, message: "Completed processing: #{task_id}"
141
214
  success_handler.call
142
215
  rescue Exception => e
143
- Arsenicum::Logger.error {log_message_for worker, "Exception: #{e.class.name}"}
216
+ error worker, exception: e
144
217
  failure_handler.call e
145
218
  ensure
146
219
  self.task_request = nil
220
+ return_to_broker
147
221
  end
148
222
  end
149
223
  end
150
224
  end
151
225
 
152
- def log_message_for(worker, message)
153
- "[Worker ##{worker.object_id}][thread]#{message}"
226
+ [:debug, :info, :warn, :error, :fatal].each do |level|
227
+ eval <<-SCRIPT, binding, __FILE__, __LINE__ + 1
228
+ def #{level}(worker, message: nil, exception: nil)
229
+ Arsenicum::Logger.#{level} do
230
+ message = "[Worker #\#{worker.index}][\#{worker.work_at}][thread]\#{message}" if message
231
+ [message, exception]
232
+ end
233
+ end
234
+ SCRIPT
154
235
  end
155
236
  end
156
237
  end
@@ -10,9 +10,9 @@ module Arsenicum::Logger
10
10
 
11
11
  [:debug, :info, :warn, :error, :fatal].each do |method|
12
12
  eval <<-METHOD, binding, __FILE__, __LINE__ + 1
13
- def #{method}(*args, &block)
13
+ def #{method}(&block)
14
14
  return unless logger
15
- logger.#{method}(*args, &block)
15
+ logger.#{method} &block
16
16
  end
17
17
  METHOD
18
18
  end
@@ -1,5 +1,7 @@
1
1
  module Arsenicum
2
2
  class Main
3
+ attr_reader :queues
4
+
3
5
  def run(config)
4
6
  $0 = 'arsenicum[main]'
5
7
 
@@ -18,8 +20,12 @@ module Arsenicum
18
20
 
19
21
  before_boot(config)
20
22
 
21
- threads = config.queue_configurations.map{|qc|qc.build.start_async}
23
+ @queues = config.queue_configurations.map{|qc|qc.build}
24
+ threads = @queues.map(&:start_async)
22
25
  begin
26
+ sleep 10
27
+ trap_signal
28
+
23
29
  threads.each(&:join)
24
30
  rescue Interrupt
25
31
  end
@@ -44,6 +50,13 @@ module Arsenicum
44
50
  Arsenicum::Logger.configure config.logger_config
45
51
  end
46
52
 
53
+ def trap_signal
54
+ [:TERM, :INT,].each do |sig|
55
+ queues.each(&:stop)
56
+ exit 1
57
+ end
58
+ end
59
+
47
60
  autoload :RailsMain, 'arsenicum/main/rails_main'
48
61
  end
49
62
  end
@@ -13,18 +13,28 @@ class Arsenicum::Queue
13
13
  end
14
14
 
15
15
  def start
16
- Arsenicum::Logger.info "[queue]Queue #{name} is now starting"
16
+ Arsenicum::Logger.info{"[queue]Queue #{name} is now starting"}
17
17
  broker.run
18
- Arsenicum::Logger.info "[queue]Queue #{name} start-up completed"
18
+ Arsenicum::Logger.info{"[queue]Queue #{name} start-up completed"}
19
19
 
20
20
  loop do
21
- (message, original_message) = pick
21
+ begin
22
+ (message, original_message) = pick
23
+ rescue => e
24
+ handle_failure e, original_message
25
+ next
26
+ end
27
+
22
28
  next sleep(0.5) unless message
23
29
 
24
30
  broker.delegate message, -> { handle_success(original_message) }, -> e { handle_failure(e, original_message) }
25
31
  end
26
32
  end
27
33
 
34
+ def stop
35
+ broker.stop
36
+ end
37
+
28
38
  def register(task)
29
39
  broker[task.id] = task
30
40
  end
@@ -1,3 +1,3 @@
1
1
  module Arsenicum
2
- VERSION = '0.2.1.1'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arsenicum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - condor