arsenicum 0.2.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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