arsenicum 0.1.0 → 0.1.1

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: 5e91e416b5ebf874c42c0f1f5c8e434b7c9018a2
4
- data.tar.gz: 44069bbc46faf4e1f8867a5b763773f81c9263d6
3
+ metadata.gz: 4866821d70fe2b9fe4dff4f307112af69edbfc2d
4
+ data.tar.gz: fdbaf8062df32ef04e239294e8aea76fafe0718f
5
5
  SHA512:
6
- metadata.gz: dcf5f344a81eb8ccc9158c62b8a40b5304bc5d0d313c75092b2d059f8df0f1d23bad18528e43cf5cb04de5ee21536d6ea9006fc3688b99689312b18381308666
7
- data.tar.gz: a8f8f594bd478b1c484f7a08a3bad399827c392dd470723246f3cfd02a786cf4d62606165c4319c204983cc8ed91c7c89eed8700f3bfe4d9fd0038ec377bc0ee
6
+ metadata.gz: d5b2b277ddd87b3af7c1878c0da83f35eb9b155f1c5f7f50edd39421ee3b9d5dc82cde8ce43407b08370aa6ceed5a5a871d76346981d308d38bf497fdb5307d0
7
+ data.tar.gz: 9939c248c291b176fd0368ad4065e341a3a166ae8f13139282f5ab2ed01acd9544503df2cb8deb1b19cd40453c493c5949d1b8c63146dd97fcd151435e937ac1
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -4,16 +4,33 @@ module Arsenicum
4
4
  class MisconfigurationError < StandardError;end
5
5
 
6
6
  class Configuration
7
- attr_reader :pidfile_path, :queue_configurations
7
+ attr_reader :pidfile_path, :daemon, :stdout_path, :stderr_path, :logger_config
8
8
 
9
9
  def initialize
10
- @pidfile_path = 'arsenicum.pid'
10
+ @pidfile_path = 'arsenicum.pid'
11
+ @logger_config = LoggerConfiguration.new
11
12
  end
12
13
 
13
14
  def queue_configurations
14
15
  @queue_configurations ||= []
15
16
  end
16
17
 
18
+ def daemonize
19
+ @daemon = true
20
+ end
21
+
22
+ def stdout(path)
23
+ @stdout_path = path
24
+ end
25
+
26
+ def stderr(path)
27
+ @stderr_path = path
28
+ end
29
+
30
+ def logger(&block)
31
+ logger_config.instance_eval &block
32
+ end
33
+
17
34
  def queue(name, &block)
18
35
  queue_config = QueueConfiguration.new name
19
36
  queue_config.instance_eval &block if block_given?
@@ -67,14 +84,20 @@ module Arsenicum
67
84
  end
68
85
 
69
86
  class QueueConfiguration < Arsenicum::Configuration::InstanceConfiguration
70
- attr_reader :worker_count, :task_configurations
71
- namespace Arsenicum::Async::Queue
87
+ attr_reader :worker_count, :router_class
88
+ namespace Arsenicum::Queue
72
89
 
73
90
  def initialize(name)
74
91
  super(name)
75
92
  @worker_count = 2
76
93
  end
77
94
 
95
+ def router(name)
96
+ @router_class = constantize(classify(name))
97
+ rescue NameError
98
+ @router_class = constantize(classify(name), inside: Arsenicum::Routing)
99
+ end
100
+
78
101
  def workers(count)
79
102
  @worker_count = count
80
103
  end
@@ -90,7 +113,7 @@ module Arsenicum
90
113
  end
91
114
 
92
115
  def build
93
- super.tap do |queue|
116
+ klass.new(name, init_parameters.merge(router_class: router_class)).tap do |queue|
94
117
  task_configurations.each do |task_config|
95
118
  queue.register task_config.build
96
119
  end
@@ -102,6 +125,41 @@ module Arsenicum
102
125
  namespace Arsenicum::Task
103
126
  end
104
127
 
128
+ class LoggerConfiguration
129
+ attr_reader :log_path, :log_level, :log_format
130
+
131
+ def initialize
132
+ @log_path = STDOUT
133
+ @log_level = :info
134
+ end
135
+
136
+ def path path
137
+ @log_path = path
138
+ end
139
+
140
+ def level level
141
+ @log_level = level
142
+ end
143
+
144
+ def format &format
145
+ @log_format = format
146
+ end
147
+
148
+ def build
149
+ logger = ::Logger.new(output_stream)
150
+ logger.level = ::Logger.const_get log_level.to_s.upcase.to_sym
151
+ logger.formatter = log_format if log_format
152
+ logger
153
+ end
154
+
155
+ private
156
+ def output_stream
157
+ return log_path if log_path.respond_to? :write
158
+ return File.open(log_path, 'w:UTF-8') if log_path.is_a? String
159
+ STDOUT
160
+ end
161
+ end
162
+
105
163
  class ConfigurationHash < Hash
106
164
  def in_configuration?
107
165
  @in_configuration
@@ -1,10 +1,10 @@
1
1
  class Arsenicum::Core::Broker
2
2
  include Arsenicum::Core::IOHelper
3
3
 
4
- attr_reader :router
4
+ attr_reader :router
5
5
 
6
- attr_reader :workers, :available_workers, :mutex
7
- attr_reader :worker_count, :worker_options, :tasks
6
+ attr_reader :workers, :available_workers, :mutex
7
+ attr_reader :worker_count, :worker_options, :tasks
8
8
  attr_accessor :default_task
9
9
 
10
10
  PROCESSOR_COUNT_DEFAULT = 2
@@ -21,10 +21,6 @@ class Arsenicum::Core::Broker
21
21
  @worker_options.merge! serializer: serializer, formatter: formatter
22
22
  end
23
23
 
24
- def register_task(task_id, task)
25
- tasks[task_id.to_sym] = task
26
- end
27
-
28
24
  def [](task_id)
29
25
  tasks[task_id.to_sym] || default_task
30
26
  end
@@ -33,48 +29,30 @@ class Arsenicum::Core::Broker
33
29
  tasks[task_id.to_sym] = task
34
30
  end
35
31
 
32
+ alias_method :register, :[]=
33
+
36
34
  def run
37
35
  @workers = {}
38
36
  @available_workers = {}
39
37
 
40
- @worker_count.times do
41
- prepare_worker
42
- end
38
+ prepare_workers
43
39
  end
44
40
 
45
- def prepare_worker
46
- worker = Arsenicum::Core::Worker.new(self, worker_options)
47
- stock(worker)
48
- end
41
+ def broker(success_handler, failure_handler, task_id, *parameters)
42
+ worker = loop do
43
+ w = next_worker
44
+ break w if w
49
45
 
50
- def stock(worker)
51
- mutex.synchronize do
52
- pid = worker.run
53
- workers[pid] = worker
54
- available_workers[pid] = worker
55
- end
56
- end
57
-
58
- def broker(task_id, *parameters)
59
- until (worker = next_worker)
60
46
  sleep 0.5
61
47
  end
62
48
 
63
- begin
64
- worker.ask task_id, *parameters
65
- ensure
66
- if worker.active?
67
- get_back_worker(worker)
68
- else
69
- remove(worker)
70
- prepare_worker
71
- end
72
- end
49
+ Arsenicum::Logger.info { "[broker][Task brokering]id=#{task_id}, params=#{parameters.inspect}" }
50
+ worker.ask_async success_handler, failure_handler, task_id, *parameters
73
51
  end
74
52
 
75
- def delegate(message)
53
+ def delegate(message, success_handler, failure_handler)
76
54
  (task_id, parameters) = router.route(message)
77
- broker task_id, *parameters
55
+ broker success_handler, failure_handler, task_id, parameters
78
56
  end
79
57
 
80
58
  def remove(worker)
@@ -84,14 +62,46 @@ class Arsenicum::Core::Broker
84
62
  end
85
63
  end
86
64
 
87
- def next_worker
88
- mutex.synchronize{available_workers.shift.last}
65
+ def reload
66
+ workers.each(&:stop)
67
+
68
+ workers.clear
69
+ available_workers.clear
70
+
71
+ prepare_workers
89
72
  end
90
73
 
91
74
  def get_back_worker(worker)
92
75
  mutex.synchronize{available_workers[worker.pid] = worker}
93
76
  end
94
77
 
78
+ private
79
+ def prepare_workers
80
+ @worker_count.times do
81
+ prepare_worker
82
+ end
83
+ end
84
+
85
+ def prepare_worker
86
+ worker = Arsenicum::Core::Worker.new(self, worker_options)
87
+ stock(worker)
88
+ end
89
+
90
+ def stock(worker)
91
+ mutex.synchronize do
92
+ pid = worker.run
93
+ workers[pid] = worker
94
+ available_workers[pid] = worker
95
+ end
96
+ end
97
+
98
+ def next_worker
99
+ mutex.synchronize do
100
+ (_, worker) = available_workers.shift
101
+ worker
102
+ end
103
+ end
104
+
95
105
  def serialize(value = {})
96
106
  serializer.serialize value
97
107
  end
@@ -3,7 +3,7 @@ module Arsenicum::Core::IOHelper
3
3
  string_to_write = string.dup
4
4
  string_to_write.force_encoding 'BINARY'
5
5
 
6
- io.write [string.length].pack('N')
6
+ io.write [string.length].pack('N').force_encoding 'BINARY'
7
7
  io.write string
8
8
  end
9
9
 
@@ -4,19 +4,20 @@ 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,
7
+ attr_reader :pid, :in_parent, :out_parent, :thread,
8
8
  :in_child, :out_child, :active, :broker, :serializer, :formatter
9
9
  alias_method :active?, :active
10
10
 
11
11
  def initialize(broker, worker_configuration)
12
- @broker = WeakRef.new broker # avoiding circular references.
12
+ @broker = WeakRef.new broker # avoiding circular references.
13
13
  @serializer = worker_configuration[:serializer]
14
14
  @formatter = worker_configuration[:formatter]
15
+ @thread = InvokerThread.new(self)
15
16
  end
16
17
 
17
18
  def run
18
- (@in_parent, @out_child) = IO.pipe
19
- (@in_child, @out_parent) = IO.pipe
19
+ (@in_parent, @out_child) = open_binary_pipes
20
+ (@in_child, @out_parent) = open_binary_pipes
20
21
 
21
22
  @pid = fork do
22
23
  [in_parent, out_parent].each(&:close)
@@ -24,7 +25,9 @@ class Arsenicum::Core::Worker
24
25
  begin
25
26
  loop do
26
27
  begin
28
+ Arsenicum::Logger.debug {log_message_for 'to read initial command'}
27
29
  command = read_code in_child
30
+ Arsenicum::Logger.debug {log_message_for 'Command: 0x%02x' % command}
28
31
  break if command == COMMAND_STOP
29
32
  task_id_string = read_string in_child
30
33
  content = read_string in_child
@@ -57,17 +60,33 @@ class Arsenicum::Core::Worker
57
60
  pid
58
61
  end
59
62
 
63
+ def open_binary_pipes
64
+ IO.pipe.each do |io|
65
+ io.set_encoding 'BINARY'
66
+ end
67
+ end
68
+
69
+ def ask_async(success_handler, failure_handler, task_id, *parameters)
70
+ thread.ask success_handler, failure_handler, task_id, *parameters
71
+ end
72
+
60
73
  def ask(task_id, *parameter)
74
+ Arsenicum::Logger.info {log_message_for "Task ID: #{task_id}"}
75
+
61
76
  write_code out_parent, COMMAND_TASK
62
77
  write_string out_parent, task_id.to_s
63
78
  write_string out_parent, serialize(parameter)
64
79
 
80
+ Arsenicum::Logger.debug { log_message_for "Task ID: #{task_id}: Request completed. Begin waiting for the reply." }
81
+
65
82
  result = read_code in_parent
66
83
  return if result == 0
67
84
  raise Marshal.restore(read_string in_parent, encoding: 'BINARY')
85
+ ensure
86
+ return_to_broker
68
87
  end
69
88
 
70
- def terminate
89
+ def stop
71
90
  write_code out_parent, COMMAND_STOP
72
91
  Process.waitpid pid
73
92
  end
@@ -88,4 +107,44 @@ class Arsenicum::Core::Worker
88
107
  end
89
108
  end
90
109
  end
110
+
111
+ def return_to_broker
112
+ broker.get_back_worker self
113
+ end
114
+
115
+ def log_message_for(message)
116
+ "[Worker ##{object_id}]#{message}"
117
+ end
118
+
119
+ class InvokerThread < Thread
120
+ attr_accessor :task_request
121
+ private :task_request, :task_request=
122
+
123
+ def ask(success_handler, failure_handler, task_id, *parameters)
124
+ self.task_request = [success_handler, failure_handler, task_id, parameters]
125
+ end
126
+
127
+ def initialize(worker)
128
+ super do
129
+ loop do
130
+ next sleep(0.5) unless task_request
131
+ (success_handler, failure_handler, task_id, parameter) = task_request
132
+
133
+ begin
134
+ worker.ask task_id, *parameter
135
+ success_handler.call
136
+ rescue Exception => e
137
+ Arsenicum::Logger.error {log_message_for worker, "Exception: #{e.class.name}"}
138
+ failure_handler.call e
139
+ ensure
140
+ self.task_request = nil
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def log_message_for(worker, message)
147
+ "[Worker ##{worker.object_id}][thread]#{message}"
148
+ end
149
+ end
91
150
  end
@@ -0,0 +1,20 @@
1
+ require 'logger'
2
+
3
+ module Arsenicum::Logger
4
+ class << self
5
+ attr_reader :logger
6
+
7
+ def configure(logger_config)
8
+ @logger = logger_config.build
9
+ end
10
+
11
+ [:debug, :info, :warn, :error, :fatal].each do |method|
12
+ eval <<-METHOD, binding, __FILE__, __LINE__ + 1
13
+ def #{method}(*args, &block)
14
+ return unless logger
15
+ logger.#{method}(*args, &block)
16
+ end
17
+ METHOD
18
+ end
19
+ end
20
+ end
@@ -7,9 +7,17 @@ module Arsenicum
7
7
  script = File.read config_file
8
8
  config.instance_eval script, config_file, 1
9
9
 
10
- File.open(config.pidfile_path, 'w:UTF-8') do |f|
11
- f.puts $$
10
+ if config.daemon
11
+ Process.daemonize
12
+
13
+ File.open(config.pidfile_path, 'w:UTF-8') do |f|
14
+ f.puts $$
15
+ end
12
16
  end
17
+
18
+ configure_io config
19
+ configure_log config
20
+
13
21
  threads = config.queue_configurations.map{|qc|qc.build.start_async}
14
22
 
15
23
  begin
@@ -18,6 +26,23 @@ module Arsenicum
18
26
  end
19
27
  end
20
28
 
21
- module_function :run
29
+ private
30
+ def configure_io(config)
31
+ $stdout = File.open(config.stdout_path, 'a:UTF-8') if config.stdout_path
32
+
33
+ if config.stderr_path
34
+ if config.stdout_path && config.stdout_path == config.stderr_path
35
+ $stderr = $stdout
36
+ else
37
+ $stderr = File.open(config.stderr_path, 'a:UTF-8')
38
+ end
39
+ end
40
+ end
41
+
42
+ def configure_log(config)
43
+ Arsenicum::Logger.configure config.logger_config
44
+ end
45
+
46
+ module_function :run, :configure_io, :configure_log
22
47
  end
23
48
  end
@@ -1,7 +1,7 @@
1
1
  require 'aws-sdk'
2
2
  require 'multi_json'
3
3
 
4
- class Arsenicum::Async::Queue::Sqs < Arsenicum::Async::Queue
4
+ class Arsenicum::Queue::Sqs < Arsenicum::Queue
5
5
  attr_reader :sqs_queue, :via_sns
6
6
 
7
7
  def initialize(name, options = {})
@@ -13,15 +13,14 @@ class Arsenicum::Async::Queue::Sqs < Arsenicum::Async::Queue
13
13
  end
14
14
 
15
15
  def pick
16
- loop do
17
- message = sqs_queue.receive_message
18
- next sleep(0.5) unless message
16
+ message = sqs_message = sqs_queue.receive_message
17
+ return unless message
18
+ message = message.as_sns_message if via_sns
19
19
 
20
- message = message.as_sns_message if via_sns
21
- [
22
- MultiJson.decode(message.body),
23
- -> { message.delete },
24
- ]
25
- end
20
+ [MultiJson.decode(message.body), sqs_message]
21
+ end
22
+
23
+ def handle_success(original_message)
24
+ original_message.delete
26
25
  end
27
26
  end
@@ -0,0 +1,51 @@
1
+ require 'weakref'
2
+
3
+ class Arsenicum::Queue
4
+
5
+ attr_reader :name, :worker_count, :router
6
+ attr_reader :broker
7
+
8
+ def initialize(name, options)
9
+ @name = name
10
+ @worker_count = options.delete(:worker_count)
11
+ @router = build_router options.delete(:router_class)
12
+ @broker = Arsenicum::Core::Broker.new worker_count: worker_count, router: router
13
+ end
14
+
15
+ def start
16
+ Arsenicum::Logger.info "[queue]Queue #{name} is now starting"
17
+ broker.run
18
+ Arsenicum::Logger.info "[queue]Queue #{name} start-up completed"
19
+
20
+ loop do
21
+ (message, original_message) = pick
22
+ next sleep(0.5) unless message
23
+
24
+ broker.delegate message, -> { handle_success(original_message) }, -> e { handle_failure(e, original_message) }
25
+ end
26
+ end
27
+
28
+ def register(task)
29
+ broker[task.id] = task
30
+ end
31
+
32
+ def handle_success(original_message)
33
+ #TODO implement correctly in your derived classes.
34
+ end
35
+
36
+ def handle_failure(e, original_message)
37
+ #TODO implement correctly in your derived classes.
38
+ end
39
+
40
+ def start_async
41
+ Thread.new{start}
42
+ end
43
+
44
+ private
45
+ def build_router(router_class)
46
+ return unless router_class
47
+ router_class.new self
48
+ end
49
+
50
+ autoload :Sqs, 'arsenicum/queue/sqs'
51
+ end
@@ -1,3 +1,5 @@
1
- class Arsenicum::Routing::Default
2
-
1
+ class Arsenicum::Routing::Default < Arsenicum::Routing::Router
2
+ def route(message)
3
+ return [:default, message]
4
+ end
3
5
  end
@@ -0,0 +1,9 @@
1
+ require 'weakref'
2
+
3
+ class Arsenicum::Routing::Router
4
+ attr_reader :broker
5
+
6
+ def initialize(broker)
7
+ @broker = WeakRef.new broker
8
+ end
9
+ end
@@ -1,3 +1,4 @@
1
1
  module Arsenicum::Routing
2
2
  autoload :Default, 'arsenicum/routing/default'
3
+ autoload :Router, 'arsenicum/routing/router'
3
4
  end
@@ -1,15 +1,18 @@
1
1
  class Arsenicum::Task::ClassDispatcher < Arsenicum::Task
2
+ include Arsenicum::Util
3
+
2
4
  attr_reader :target_class, :target_method
3
5
  private :target_class, :target_method
4
6
 
5
7
  def initialize(id, options)
6
8
  super(id)
7
- @target_class = options.delete :type
8
- @target_method = options.delete :target
9
+ (klass, method) = options[:target].split('#', 2)
10
+ @target_class = constantize klass
11
+ @target_method = target_class.instance_method method.to_sym
9
12
  end
10
13
 
11
14
  def run(*parameters)
12
- target_class.new.__send__ target_method, *parameters
15
+ target_method.bind(target_class.new).call *parameters
13
16
  end
14
17
 
15
18
  end
@@ -1,3 +1,3 @@
1
1
  module Arsenicum
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
data/lib/arsenicum.rb CHANGED
@@ -6,10 +6,12 @@ module Arsenicum
6
6
  autoload :Version, 'arsenicum/version'
7
7
  autoload :Serializer, 'arsenicum/serializer'
8
8
  autoload :Formatter, 'arsenicum/formatter'
9
- autoload :Async, 'arsenicum/async'
9
+ autoload :Queue, 'arsenicum/queue'
10
10
  autoload :Main, 'arsenicum/main'
11
11
  autoload :IO, 'arsenicum/io'
12
12
  autoload :Task, 'arsenicum/task'
13
+ autoload :Routing, 'arsenicum/routing'
14
+ autoload :Logger, 'arsenicum/logger'
13
15
 
14
16
  class << self
15
17
  def configure(arg = nil, &block)
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.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - condor
@@ -135,14 +135,12 @@ files:
135
135
  - Gemfile
136
136
  - LICENSE.txt
137
137
  - README.md
138
+ - Rakefile
138
139
  - arsenicum.gemspec
139
140
  - bin/arsenicum
140
141
  - bin/arsenicum_rails
141
142
  - lib/arsenicum-rails.rb
142
143
  - lib/arsenicum.rb
143
- - lib/arsenicum/async.rb
144
- - lib/arsenicum/async/queue.rb
145
- - lib/arsenicum/async/queue/sqs.rb
146
144
  - lib/arsenicum/configuration.rb
147
145
  - lib/arsenicum/core.rb
148
146
  - lib/arsenicum/core/broker.rb
@@ -151,9 +149,13 @@ files:
151
149
  - lib/arsenicum/core/worker.rb
152
150
  - lib/arsenicum/formatter.rb
153
151
  - lib/arsenicum/io.rb
152
+ - lib/arsenicum/logger.rb
154
153
  - lib/arsenicum/main.rb
154
+ - lib/arsenicum/queue.rb
155
+ - lib/arsenicum/queue/sqs.rb
155
156
  - lib/arsenicum/routing.rb
156
157
  - lib/arsenicum/routing/default.rb
158
+ - lib/arsenicum/routing/router.rb
157
159
  - lib/arsenicum/serializer.rb
158
160
  - lib/arsenicum/serializer/json.rb
159
161
  - lib/arsenicum/task.rb
@@ -1,37 +0,0 @@
1
- class Arsenicum::Async::Queue < Arsenicum::Core::Broker
2
-
3
- attr_accessor :broker
4
- attr_reader :name, :worker_count, :router
5
- attr_reader :broker
6
-
7
- def initialize(name, options)
8
- @name = name
9
- @worker_count = options.delete(:worker_count)
10
- @router = options.delete(:router)
11
- @broker = Arsenicum::Core::Broker.new worker_count: worker_count, router: router
12
- end
13
-
14
- def start
15
- loop do
16
- (message, success_handler, failure_handler) = pick
17
- next sleep(0.5) unless message
18
-
19
- begin
20
- broker.delegate message
21
- success_handler.call if success_handler
22
- rescue Exception => e
23
- failure_handler.call(e) if failure_handler
24
- end
25
- end
26
- end
27
-
28
- def register_task(task)
29
- broker[task.id] = task
30
- end
31
-
32
- def start_async
33
- Thread.new{start}
34
- end
35
-
36
- autoload :Sqs, 'arsenicum/async/queue/sqs'
37
- end
@@ -1,3 +0,0 @@
1
- module Arsenicum::Async
2
- autoload :Queue, 'arsenicum/async/queue'
3
- end