arsenicum 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/arsenicum.gemspec +5 -0
  4. data/bin/arsenicum +1 -7
  5. data/lib/arsenicum.rb +23 -12
  6. data/lib/arsenicum/async.rb +3 -0
  7. data/lib/arsenicum/async/queue.rb +37 -0
  8. data/lib/arsenicum/async/queue/sqs.rb +27 -0
  9. data/lib/arsenicum/configuration.rb +114 -63
  10. data/lib/arsenicum/core.rb +6 -0
  11. data/lib/arsenicum/core/broker.rb +98 -0
  12. data/lib/arsenicum/core/commands.rb +15 -0
  13. data/lib/arsenicum/core/io_helper.rb +34 -0
  14. data/lib/arsenicum/core/worker.rb +91 -0
  15. data/lib/arsenicum/formatter.rb +127 -0
  16. data/lib/arsenicum/io.rb +3 -0
  17. data/lib/arsenicum/main.rb +23 -0
  18. data/lib/arsenicum/routing.rb +3 -0
  19. data/lib/arsenicum/routing/default.rb +3 -0
  20. data/lib/arsenicum/serializer.rb +3 -0
  21. data/lib/arsenicum/serializer/json.rb +11 -0
  22. data/lib/arsenicum/task.rb +10 -50
  23. data/lib/arsenicum/task/class_dispatcher.rb +15 -0
  24. data/lib/arsenicum/util.rb +48 -0
  25. data/lib/arsenicum/version.rb +1 -1
  26. data/lib/generators/arsenicum/migration/migration_generator.rb +32 -0
  27. data/lib/generators/arsenicum/migration/templates/active_record/migration.rb +15 -0
  28. data/spec/arsenicum/queueing/post_office_spec.rb +123 -0
  29. data/spec/arsenicum/queueing/serializer_spec.rb +70 -0
  30. data/spec/config/config.example.yml +22 -0
  31. data/spec/config/post_office_spec.yml +24 -0
  32. data/spec/rails_project/.gitignore +16 -0
  33. data/spec/rails_project/Gemfile +45 -0
  34. data/spec/rails_project/README.rdoc +28 -0
  35. data/spec/rails_project/Rakefile +6 -0
  36. data/spec/rails_project/app/assets/images/.keep +0 -0
  37. data/spec/rails_project/app/assets/javascripts/application.js +16 -0
  38. data/spec/rails_project/app/assets/stylesheets/application.css +13 -0
  39. data/spec/rails_project/app/controllers/application_controller.rb +5 -0
  40. data/spec/rails_project/app/controllers/concerns/.keep +0 -0
  41. data/spec/rails_project/app/helpers/application_helper.rb +2 -0
  42. data/spec/rails_project/app/mailers/.keep +0 -0
  43. data/spec/rails_project/app/models/.keep +0 -0
  44. data/spec/rails_project/app/models/concerns/.keep +0 -0
  45. data/spec/rails_project/app/models/sample.rb +2 -0
  46. data/spec/rails_project/app/views/layouts/application.html.erb +14 -0
  47. data/spec/rails_project/bin/bundle +3 -0
  48. data/spec/rails_project/bin/rails +4 -0
  49. data/spec/rails_project/bin/rake +4 -0
  50. data/spec/rails_project/config.ru +4 -0
  51. data/spec/rails_project/config/application.rb +23 -0
  52. data/spec/rails_project/config/boot.rb +4 -0
  53. data/spec/rails_project/config/database.yml +25 -0
  54. data/spec/rails_project/config/environment.rb +5 -0
  55. data/spec/rails_project/config/environments/development.rb +29 -0
  56. data/spec/rails_project/config/environments/production.rb +80 -0
  57. data/spec/rails_project/config/environments/test.rb +36 -0
  58. data/spec/rails_project/config/initializers/backtrace_silencers.rb +7 -0
  59. data/spec/rails_project/config/initializers/filter_parameter_logging.rb +4 -0
  60. data/spec/rails_project/config/initializers/inflections.rb +16 -0
  61. data/spec/rails_project/config/initializers/mime_types.rb +5 -0
  62. data/spec/rails_project/config/initializers/secret_token.rb +12 -0
  63. data/spec/rails_project/config/initializers/session_store.rb +3 -0
  64. data/spec/rails_project/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/rails_project/config/locales/en.yml +23 -0
  66. data/spec/rails_project/config/routes.rb +56 -0
  67. data/spec/rails_project/db/migrate/20131210025434_create_samples.rb +9 -0
  68. data/spec/rails_project/db/schema.rb +22 -0
  69. data/spec/rails_project/db/seeds.rb +7 -0
  70. data/spec/rails_project/lib/assets/.keep +0 -0
  71. data/spec/rails_project/lib/tasks/.keep +0 -0
  72. data/spec/rails_project/log/.keep +0 -0
  73. data/spec/rails_project/public/404.html +58 -0
  74. data/spec/rails_project/public/422.html +58 -0
  75. data/spec/rails_project/public/500.html +57 -0
  76. data/spec/rails_project/public/favicon.ico +0 -0
  77. data/spec/rails_project/public/robots.txt +5 -0
  78. data/spec/rails_project/test/controllers/.keep +0 -0
  79. data/spec/rails_project/test/helpers/.keep +0 -0
  80. data/spec/rails_project/test/integration/.keep +0 -0
  81. data/spec/rails_project/test/mailers/.keep +0 -0
  82. data/spec/rails_project/test/test_helper.rb +15 -0
  83. data/spec/rails_project/vendor/assets/javascripts/.keep +0 -0
  84. data/spec/rails_project/vendor/assets/stylesheets/.keep +0 -0
  85. data/spec/spec_helper.rb +5 -0
  86. metadata +217 -27
  87. data/lib/arsenicum/actor.rb +0 -14
  88. data/lib/arsenicum/cli.rb +0 -54
  89. data/lib/arsenicum/cli/rails.rb +0 -26
  90. data/lib/arsenicum/queue.rb +0 -58
  91. data/lib/arsenicum/queue_proxy.rb +0 -73
  92. data/lib/arsenicum/rake_tasks.rake +0 -24
  93. data/lib/arsenicum/serialization.rb +0 -110
  94. data/lib/arsenicum/server.rb +0 -40
  95. data/lib/arsenicum/sqs.rb +0 -5
  96. data/lib/arsenicum/sqs/queue.rb +0 -76
  97. data/lib/arsenicum/syntax.rb +0 -11
  98. data/lib/arsenicum/syntax/delayed_job.rb +0 -25
  99. data/lib/arsenicum/watchdog.rb +0 -68
  100. data/spec/config.example.yml +0 -21
@@ -0,0 +1,15 @@
1
+ module Arsenicum::Core::Commands
2
+ COMMAND_STOP = 0xff
3
+ COMMAND_TASK = 0x10
4
+
5
+ class << self
6
+ private
7
+ def code_to_string(code)
8
+ [code].pack('C')
9
+ end
10
+ end
11
+
12
+ COMMAND_STRING_STOP = code_to_string(COMMAND_STOP)
13
+ COMMAND_STRING_TASK = code_to_string(COMMAND_TASK)
14
+
15
+ end
@@ -0,0 +1,34 @@
1
+ module Arsenicum::Core::IOHelper
2
+ def write_string(io, string)
3
+ string_to_write = string.dup
4
+ string_to_write.force_encoding 'BINARY'
5
+
6
+ io.write [string.length].pack('N')
7
+ io.write string
8
+ end
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
14
+
15
+ read_from(io, length).tap{|s|s.force_encoding encoding}
16
+ end
17
+
18
+ def write_code(io, integer_value)
19
+ value = [integer_value].pack('C')
20
+ io.write value
21
+ end
22
+
23
+ def read_code(io)
24
+ string = read_from io, 1
25
+ string.unpack('C').first
26
+ end
27
+
28
+ private
29
+ def read_from(io, length)
30
+ bytes = io.read length
31
+ raise Arsenicum::IO::EOFException unless bytes
32
+ bytes
33
+ end
34
+ end
@@ -0,0 +1,91 @@
1
+ require 'weakref'
2
+
3
+ class Arsenicum::Core::Worker
4
+ include Arsenicum::Core::Commands
5
+ include Arsenicum::Core::IOHelper
6
+
7
+ attr_reader :pid, :in_parent, :out_parent,
8
+ :in_child, :out_child, :active, :broker, :serializer, :formatter
9
+ alias_method :active?, :active
10
+
11
+ def initialize(broker, worker_configuration)
12
+ @broker = WeakRef.new broker # avoiding circular references.
13
+ @serializer = worker_configuration[:serializer]
14
+ @formatter = worker_configuration[:formatter]
15
+ end
16
+
17
+ def run
18
+ (@in_parent, @out_child) = IO.pipe
19
+ (@in_child, @out_parent) = IO.pipe
20
+
21
+ @pid = fork do
22
+ [in_parent, out_parent].each(&:close)
23
+
24
+ begin
25
+ loop do
26
+ begin
27
+ command = read_code in_child
28
+ break if command == COMMAND_STOP
29
+ task_id_string = read_string in_child
30
+ content = read_string in_child
31
+ rescue Arsenicum::IO::EOFException
32
+ # Interrupted request: No required GC.
33
+ break
34
+ end
35
+
36
+ task_id = task_id_string.to_sym
37
+ task = broker[task_id]
38
+
39
+ parameters = deserialize content
40
+
41
+ begin
42
+ task.run *parameters
43
+ write_code out_child, 0
44
+ rescue Exception => e
45
+ write_code out_child, 1
46
+ write_string out_child, Marshal.dump(e)
47
+ end
48
+ end
49
+ ensure
50
+ [in_child, out_child].each do |io|
51
+ begin io.close rescue nil end
52
+ end
53
+ end
54
+ end
55
+ @active = true
56
+ [in_child, out_child].each(&:close)
57
+ pid
58
+ end
59
+
60
+ def ask(task_id, *parameter)
61
+ write_code out_parent, COMMAND_TASK
62
+ write_string out_parent, task_id.to_s
63
+ write_string out_parent, serialize(parameter)
64
+
65
+ result = read_code in_parent
66
+ return if result == 0
67
+ raise Marshal.restore(read_string in_parent, encoding: 'BINARY')
68
+ end
69
+
70
+ def terminate
71
+ write_code out_parent, COMMAND_STOP
72
+ Process.waitpid pid
73
+ end
74
+
75
+ private
76
+ def serialize(parameter)
77
+ serializer.serialize(formatter.format(parameter))
78
+ end
79
+
80
+ def deserialize(string)
81
+ formatter.parse(serializer.deserialize(string))
82
+ end
83
+
84
+ def trap_signal
85
+ %w(TERM INT).each do |sig|
86
+ Signal.trap sig do
87
+ exit 5
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,127 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ class Arsenicum::Formatter
5
+ DATE_FORMAT = '%Y-%m-%d'.freeze
6
+ DATE_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S %Z %z'.freeze
7
+
8
+ include Arsenicum::Util
9
+
10
+ def format(value)
11
+ format_for_embedded_classes(value) ||
12
+ format_by_extension(value) ||
13
+ format_by_default(value)
14
+ end
15
+
16
+ TYPE_RAW = 'raw'.freeze
17
+ TYPE_DATE = 'date'.freeze
18
+ TYPE_DATETIME = 'datetime'.freeze
19
+ TYPE_CLASS = 'class'.freeze
20
+ TYPE_ARRAY = 'array'.freeze
21
+ TYPE_HASH = 'hash'.freeze
22
+ TYPE_ANY = 'marshal'.freeze
23
+
24
+ def format_for_embedded_classes(value)
25
+ case value
26
+ when Integer, Float, String, TrueClass, FalseClass, NilClass, Symbol
27
+ {
28
+ type: TYPE_RAW,
29
+ value: value.inspect,
30
+ }
31
+ when DateTime, Time
32
+ {
33
+ type: TYPE_DATETIME,
34
+ value: value.strftime(DATE_TIME_FORMAT),
35
+ }
36
+ when Date
37
+ {
38
+ type: TYPE_DATE,
39
+ value: value.strftime(DATE_FORMAT),
40
+ }
41
+ when Class
42
+ {
43
+ type: TYPE_CLASS,
44
+ value: value.name,
45
+ }
46
+ when Array
47
+ {
48
+ type: TYPE_ARRAY,
49
+ values: value.map{|v|format(v)},
50
+ }
51
+ when Hash
52
+ {
53
+ type: TYPE_HASH,
54
+ values: value.inject({}){|h, kv|(k,v)=kv;h[k.to_s]=format(v);h},
55
+ }
56
+ end
57
+ end
58
+
59
+ def format_by_extension(value)
60
+ nil
61
+ end
62
+
63
+ def format_by_default(value)
64
+ {
65
+ type: TYPE_ANY,
66
+ value: Marshal.dump(value).unpack('H*').first,
67
+ }
68
+ end
69
+
70
+ def parse(value)
71
+ value = normalize_hash(value)
72
+
73
+ return eval value[:value] if value[:type] == TYPE_RAW
74
+ parse_for_embedded_classes(value) ||
75
+ parse_by_extension(value) ||
76
+ parse_by_default(value)
77
+ end
78
+
79
+ def parse_for_embedded_classes(value)
80
+ case value[:type]
81
+ when TYPE_DATE
82
+ Date.strptime(value[:value], DATE_FORMAT)
83
+ when TYPE_DATETIME
84
+ Time.strptime(value[:value], DATE_TIME_FORMAT)
85
+ when TYPE_CLASS
86
+ Module.const_get value[:value].to_sym
87
+ when TYPE_ARRAY
88
+ value[:values].map do |value|
89
+ parse(value)
90
+ end
91
+ when TYPE_HASH
92
+ value[:values].inject({}) do |h, key_value|
93
+ (key, value) = key_value
94
+ h[key.to_s.to_sym] = parse(value)
95
+ h
96
+ end
97
+ end
98
+ end
99
+
100
+ def parse_by_extension(_)
101
+ nil
102
+ end
103
+
104
+ def parse_by_default(value)
105
+ ::Marshal.restore [value[:value]].pack('H*')
106
+ end
107
+
108
+ class ActiveRecord < ::Arsenicum::Formatter
109
+ TYPE_ACTIVE_RECORD = 'active_record'.freeze
110
+
111
+ def format_by_extension(value)
112
+ return {
113
+ type: TYPE_ACTIVE_RECORD,
114
+ class: value.class.name,
115
+ id: value.id,
116
+ } if value.is_a? ActiveRecord::Base
117
+ end
118
+
119
+ def parse_by_extension(value)
120
+ if value[:type] == TYPE_ACTIVE_RECORD
121
+ klass = constaitize value[:class].to_sym
122
+ klass.find value[:id]
123
+ end
124
+ end
125
+ end
126
+
127
+ end
@@ -0,0 +1,3 @@
1
+ module Arsenicum::IO
2
+ class EOFException < StandardError;end
3
+ end
@@ -0,0 +1,23 @@
1
+ module Arsenicum
2
+ module Main
3
+ def run(config_file)
4
+ config = Arsenicum::Configuration.new
5
+ config_file = File.expand_path config_file
6
+
7
+ script = File.read config_file
8
+ config.instance_eval script, config_file, 1
9
+
10
+ File.open(config.pidfile_path, 'w:UTF-8') do |f|
11
+ f.puts $$
12
+ end
13
+ threads = config.queue_configurations.map{|qc|qc.build.start_async}
14
+
15
+ begin
16
+ threads.each(&:join)
17
+ rescue Interrupt
18
+ end
19
+ end
20
+
21
+ module_function :run
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Arsenicum::Routing
2
+ autoload :Default, 'arsenicum/routing/default'
3
+ end
@@ -0,0 +1,3 @@
1
+ class Arsenicum::Routing::Default
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ module Arsenicum::Serializer
2
+ autoload :JSON, 'arsenicum/serializer/json'
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'multi_json'
2
+
3
+ class Arsenicum::Serializer::JSON
4
+ def serialize(hash)
5
+ MultiJson.encode(hash)
6
+ end
7
+
8
+ def deserialize(string)
9
+ MultiJson.decode(string)
10
+ end
11
+ end
@@ -1,53 +1,13 @@
1
- require 'json'
1
+ class Arsenicum::Task
2
+ attr_reader :id
2
3
 
3
- module Arsenicum
4
- class Task
5
- include Serialization
6
-
7
- attr_reader :target, :method, :arguments, :timestamp, :message_id, :exception
8
-
9
- def self.parse(raw_message, message_id)
10
- message_content = JSON(raw_message)
11
-
12
- timestamp = message_content['timestamp']
13
- method = message_content['method_name'].to_sym
14
-
15
- target = restore(message_content['target'])
16
- arguments = message_content['arguments'].nil? ? [] :
17
- message_content['arguments'].map{|arg|restore(arg)}
18
-
19
- new(target, method, arguments, timestamp, message_id)
20
- end
21
-
22
- def initialize(target, method, arguments, timestamp, message_id)
23
- @target = target
24
- @method = method
25
- @arguments = arguments
26
- @timestamp = timestamp
27
- @message_id = message_id
28
- end
29
-
30
- def prepare_serialization
31
- {
32
- target: prepare_serialization(target),
33
- timestamp: (Time.now.to_f * 1000000).to_i,
34
- method_name: method_name,
35
- arguments: arguments.nil? ? nil : arguments.map{|arg|prepare_serialization(arg)},
36
- }
37
- end
38
-
39
- def serialize
40
- JSON(prepare_serialization)
41
- end
42
-
43
- def execute!
44
- target.__send__ method, *arguments
45
- rescue Exception => e
46
- @exception = e
47
- end
4
+ def initialize(id)
5
+ @id = id
6
+ end
48
7
 
49
- def successful?
50
- !exception
51
- end
8
+ def run(*parameters)
9
+ # Originally do nothing. This will be overridden in the derived classes.
52
10
  end
53
- end
11
+
12
+ autoload :ClassDispatcher, 'arsenicum/task/class_dispatcher'
13
+ end
@@ -0,0 +1,15 @@
1
+ class Arsenicum::Task::ClassDispatcher < Arsenicum::Task
2
+ attr_reader :target_class, :target_method
3
+ private :target_class, :target_method
4
+
5
+ def initialize(id, options)
6
+ super(id)
7
+ @target_class = options.delete :type
8
+ @target_method = options.delete :target
9
+ end
10
+
11
+ def run(*parameters)
12
+ target_class.new.__send__ target_method, *parameters
13
+ end
14
+
15
+ end
@@ -0,0 +1,48 @@
1
+ module Arsenicum
2
+ module Util
3
+ def self.included(base)
4
+ mod = self
5
+ base.module_eval{extend mod}
6
+ end
7
+
8
+ def normalize_hash(values)
9
+ values.inject({}) do |h, kv|
10
+ (key, value) = kv
11
+ value = normalize_hash(value) if value.is_a? Hash
12
+ h.tap{|i|i.merge!(key.to_sym => value)}
13
+ end
14
+ end
15
+
16
+ def camelcase(stringlike, upcase_first = true)
17
+ stringlike.to_s.dup.tap do |s|
18
+ s.gsub!(/_([a-z])/){$1.upcase}
19
+ s.gsub!(/^([a-z])/){$1.upcase} if upcase_first
20
+ end.to_sym
21
+ end
22
+
23
+ def classify(stringlike)
24
+ stringlike.to_s.split(/\/+/).map do |s|
25
+ camelcase(s)
26
+ end.join('::')
27
+ end
28
+
29
+ def underscore(stringlike)
30
+ stringlike.to_s.dup.tap do |s|
31
+ s.gsub!(/^([A-Z])/){$1.tap(&:downcase!)}
32
+ s.gsub!(/([A-Z])/){'_' << $1.tap(&:downcase!)}
33
+ end
34
+ end
35
+
36
+ def constantize(klass, inside: Kernel)
37
+ if klass.to_s.start_with?('::')
38
+ klass = klass.to_s[2..-1].to_sym
39
+ inside = Kernel
40
+ end
41
+ klass.to_s.split('::').inject(inside) do |parent, const|
42
+ parent.const_get const.to_sym
43
+ end
44
+ end
45
+
46
+ extend self
47
+ end
48
+ end