arsenicum 0.1.0 → 0.1.1

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: 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