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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 51962f53bdcb4a46365d2e73dc6730fb663b1436
4
- data.tar.gz: 75cee77e64c8c6620006b6d6cb6452544319cbbe
3
+ metadata.gz: 5e91e416b5ebf874c42c0f1f5c8e434b7c9018a2
4
+ data.tar.gz: 44069bbc46faf4e1f8867a5b763773f81c9263d6
5
5
  SHA512:
6
- metadata.gz: 2307adda63e934e0eab37770afd623cbd5d81d9fe08ed2c9277ec4b5021f039b42230109cb8fdbb1364526bfa58c5ec9da1fa9b0a71f7c7ffd0f9bdd8c05d31e
7
- data.tar.gz: 0cabdc6010016709c9b0ca3dde02838812a7eda4af21837ff202af6b9a4b26d6084468486f51a61c952f33612349b368c25b5a39d1e12f9d5ab26a875d3d6433
6
+ metadata.gz: dcf5f344a81eb8ccc9158c62b8a40b5304bc5d0d313c75092b2d059f8df0f1d23bad18528e43cf5cb04de5ee21536d6ea9006fc3688b99689312b18381308666
7
+ data.tar.gz: a8f8f594bd478b1c484f7a08a3bad399827c392dd470723246f3cfd02a786cf4d62606165c4319c204983cc8ed91c7c89eed8700f3bfe4d9fd0038ec377bc0ee
data/.gitignore CHANGED
@@ -12,7 +12,8 @@ lib/bundler/man
12
12
  pkg
13
13
  rdoc
14
14
  spec/reports
15
- spec/config.yml
15
+ spec/config/config.yml
16
+ spec/config/config.rb
16
17
  test/tmp
17
18
  test/version_tmp
18
19
  tmp
@@ -19,7 +19,12 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency 'aws-sdk'
22
+ spec.add_dependency 'multi_json'
22
23
 
23
24
  spec.add_development_dependency "bundler", "~> 1.3"
24
25
  spec.add_development_dependency "rake"
26
+ spec.add_development_dependency 'rspec'
27
+ spec.add_development_dependency 'pry'
28
+ spec.add_development_dependency 'rails', '~> 4.0'
29
+ spec.add_development_dependency 'sqlite3'
25
30
  end
@@ -4,10 +4,4 @@ require 'arsenicum'
4
4
  require 'optparse'
5
5
  require 'yaml'
6
6
 
7
- config = {}
8
- opt = OptionParser.new
9
-
10
- opt.on('-c', '--config-file=CONFIG_FILE'){|v|config.merge! YAML.load(File.read(v))}
11
- # TODO implement other options
12
-
13
- Arsenicum::Server.start config
7
+ Arsenicum::CLI.new(ARGV).boot
@@ -1,14 +1,25 @@
1
1
  module Arsenicum
2
- autoload :Version, 'arsenicum/version'
3
- autoload :Queue, 'arsenicum/queue'
4
- autoload :Task, 'arsenicum/task'
5
- autoload :Configuration, 'arsenicum/configuration'
6
- autoload :Serialization, 'arsenicum/serialization'
7
- autoload :WatchDog, 'arsenicum/watchdog'
8
- autoload :QueueProxy, 'arsenicum/queue_proxy'
9
- autoload :Syntax, 'arsenicum/syntax'
10
- autoload :Sqs, 'arsenicum/sqs'
11
- autoload :CLI, 'arsenicum/cli'
12
- autoload :Server, 'arsenicum/server'
13
- autoload :Actor, 'arsenicum/actor'
2
+ autoload :Configuration, 'arsenicum/configuration'
3
+ autoload :MisconfigurationError, 'arsenicum/configuration'
4
+ autoload :Core, 'arsenicum/core'
5
+ autoload :Util, 'arsenicum/util'
6
+ autoload :Version, 'arsenicum/version'
7
+ autoload :Serializer, 'arsenicum/serializer'
8
+ autoload :Formatter, 'arsenicum/formatter'
9
+ autoload :Async, 'arsenicum/async'
10
+ autoload :Main, 'arsenicum/main'
11
+ autoload :IO, 'arsenicum/io'
12
+ autoload :Task, 'arsenicum/task'
13
+
14
+ class << self
15
+ def configure(arg = nil, &block)
16
+ Arsenicum::Configuration.configure arg, &block
17
+ end
18
+
19
+ def configuration
20
+ Configuration.instance
21
+ end
22
+
23
+ alias_method :config, :configuration
24
+ end
14
25
  end
@@ -0,0 +1,3 @@
1
+ module Arsenicum::Async
2
+ autoload :Queue, 'arsenicum/async/queue'
3
+ end
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,27 @@
1
+ require 'aws-sdk'
2
+ require 'multi_json'
3
+
4
+ class Arsenicum::Async::Queue::Sqs < Arsenicum::Async::Queue
5
+ attr_reader :sqs_queue, :via_sns
6
+
7
+ def initialize(name, options = {})
8
+ super name, options
9
+ sqs_args = [options[:aws_account]].tap(&:compact!)
10
+ sqs = AWS::SQS.new *sqs_args
11
+ @sqs_queue = sqs.queues.named(name)
12
+ @via_sns = options[:via_sns]
13
+ end
14
+
15
+ def pick
16
+ loop do
17
+ message = sqs_queue.receive_message
18
+ next sleep(0.5) unless message
19
+
20
+ message = message.as_sns_message if via_sns
21
+ [
22
+ MultiJson.decode(message.body),
23
+ -> { message.delete },
24
+ ]
25
+ end
26
+ end
27
+ end
@@ -1,92 +1,143 @@
1
1
  require 'logger'
2
2
 
3
3
  module Arsenicum
4
+ class MisconfigurationError < StandardError;end
5
+
4
6
  class Configuration
5
- attr_accessor :queue_namespace, :queue_type, :queue_configurations, :engine_configuration, :pidfile, :background, :log_level
6
- attr_reader :logger
7
-
8
- DEFAULT_QUEUES = {
9
- default: {
10
- concurrency: 2,
11
- },
12
- }.freeze
13
-
14
- class << self
15
- def configure(configuration)
16
- @instance = new(configuration)
17
- end
18
- attr_reader :instance
7
+ attr_reader :pidfile_path, :queue_configurations
19
8
 
20
- def reconfigure(settings)
21
- instance.reconfigure additional_settings: settings
22
- end
9
+ def initialize
10
+ @pidfile_path = 'arsenicum.pid'
23
11
  end
24
12
 
25
- def initialize(settings)
26
- @log_level = Logger::INFO
27
- @logger = Logger.new(STDOUT)
13
+ def queue_configurations
14
+ @queue_configurations ||= []
15
+ end
28
16
 
29
- @original_settings = {queues: DEFAULT_QUEUES}.merge(normalize_hash_key(settings))
30
- raise MisconfigurationError, "queue_type is required" unless @original_settings[:queue_type]
17
+ def queue(name, &block)
18
+ queue_config = QueueConfiguration.new name
19
+ queue_config.instance_eval &block if block_given?
20
+ queue_configurations << queue_config
21
+ end
31
22
 
32
- reconfigure
23
+ def pidfile(path)
24
+ @pidfile_path = path
33
25
  end
34
26
 
35
- def reconfigure(additional_settings: nil)
36
- @original_settings.merge! additional_settings if additional_settings
37
- settings = @original_settings.dup
38
-
39
- @pidfile = settings.delete(:pidfile)
40
- @background = (settings.delete(:background).to_s.downcase == "true")
41
- @queue_type = settings.delete(:queue_type).to_s
42
- @engine_configuration = settings[queue_type.to_sym]
43
- @queue_namespace = queue_type.gsub(/_([a-z])/){|_|$1.upcase}.gsub(/^([a-z])/){|_|$1.upcase}.to_sym
44
-
45
- queue_settings = settings.delete(:queues)
46
- @queue_configurations = queue_settings.inject({}) do |h, kv|
47
- (queue_name, queue_setting) = kv
48
- h[queue_name] = queue_setting
49
- h
27
+ class InstanceConfiguration
28
+ include Arsenicum::Util
29
+
30
+ attr_reader :name, :init_parameters, :klass
31
+
32
+ class << self
33
+ attr_reader :inside
34
+ private
35
+ def namespace(mod)
36
+ @inside = mod
37
+ end
50
38
  end
51
39
 
52
- if log_level = settings.delete(:log_level)
53
- @log_level = Logger.const_get(log_level.upcase.to_sym)
40
+ def initialize(name)
41
+ @name = name
54
42
  end
55
43
 
56
- if log_file = settings.delete(:log_file)
57
- self.logger = Logger.new(log_file)
44
+ def inside
45
+ self.class.inside
46
+ end
47
+
48
+ def type(type_name)
49
+ @klass = constantize(classify(type_name))
50
+ rescue NameError
51
+ @klass = constantize(classify(type_name), inside: inside)
58
52
  end
59
- end
60
53
 
61
- def logger=(new_logger)
62
- @logger = new_logger
63
- if @logger
64
- @logger.level = log_level
54
+ def init_params(&block)
55
+ params = ConfigurationHash.new
56
+ if block
57
+ params.under_configuration do
58
+ params.instance_eval(&block)
59
+ end
60
+ end
61
+ @init_parameters = params
62
+ end
63
+
64
+ def build
65
+ klass.new(name, init_parameters)
65
66
  end
66
67
  end
67
68
 
68
- def create_queues
69
- queue_configurations.map do |queue_name_config|
70
- (queue_name, queue_config) = queue_name_config
71
- queue_class.new(queue_name, engine_configuration.merge(queue_config), logger)
69
+ class QueueConfiguration < Arsenicum::Configuration::InstanceConfiguration
70
+ attr_reader :worker_count, :task_configurations
71
+ namespace Arsenicum::Async::Queue
72
+
73
+ def initialize(name)
74
+ super(name)
75
+ @worker_count = 2
76
+ end
77
+
78
+ def workers(count)
79
+ @worker_count = count
80
+ end
81
+
82
+ def task_configurations
83
+ @task_configurations ||= []
84
+ end
85
+
86
+ def task(name, &block)
87
+ task_config = TaskConfiguration.new name
88
+ task_config.instance_eval &block if block_given?
89
+ task_configurations << task_config
90
+ end
91
+
92
+ def build
93
+ super.tap do |queue|
94
+ task_configurations.each do |task_config|
95
+ queue.register task_config.build
96
+ end
97
+ end
72
98
  end
73
99
  end
74
100
 
75
- def queue_class
76
- Arsenicum.const_get(queue_namespace).const_get(:Queue)
101
+ class TaskConfiguration < Arsenicum::Configuration::InstanceConfiguration
102
+ namespace Arsenicum::Task
77
103
  end
78
104
 
79
- private
80
- def normalize_hash_key(hash, to_sym: true)
81
- hash.inject({}) do |h, kv|
82
- (key, value) = kv
83
- value = normalize_hash_key(value, to_sym: to_sym) if value.is_a? Hash
84
- key = to_sym ? key.to_sym : key.to_s
85
- h[key] = value
86
- h
105
+ class ConfigurationHash < Hash
106
+ def in_configuration?
107
+ @in_configuration
108
+ end
109
+
110
+ def under_configuration(&_)
111
+ @in_configuration = true
112
+ yield if block_given?
113
+ ensure
114
+ @in_configuration = false
115
+ end
116
+
117
+ def method_missing(method_id, *args, &block)
118
+ case args.length
119
+ when 0
120
+ return self[method_id] unless in_configuration?
121
+
122
+ if block_given?
123
+ new_value = ConfigurationHash.new
124
+ new_value.under_configuration do
125
+ new_value.instance_eval &block
126
+ end
127
+ self[method_id] = new_value
128
+ else
129
+ self[method_id] ||= ConfigurationHash.new
130
+ end
131
+ when 1
132
+ if (method_name = method_id.to_s)[-1] == '='
133
+ return self[method_name[0...-1].to_sym] = args.first
134
+ end
135
+
136
+ self[method_id] = args.first
137
+ else
138
+ super
139
+ end
87
140
  end
88
141
  end
89
142
  end
90
-
91
- class MisconfigurationError < StandardError;end
92
143
  end
@@ -0,0 +1,6 @@
1
+ module Arsenicum::Core
2
+ autoload :Broker, 'arsenicum/core/broker'
3
+ autoload :Commands, 'arsenicum/core/commands'
4
+ autoload :IOHelper, 'arsenicum/core/io_helper'
5
+ autoload :Worker, 'arsenicum/core/worker'
6
+ end
@@ -0,0 +1,98 @@
1
+ class Arsenicum::Core::Broker
2
+ include Arsenicum::Core::IOHelper
3
+
4
+ attr_reader :router
5
+
6
+ attr_reader :workers, :available_workers, :mutex
7
+ attr_reader :worker_count, :worker_options, :tasks
8
+ attr_accessor :default_task
9
+
10
+ PROCESSOR_COUNT_DEFAULT = 2
11
+
12
+ def initialize(options = {})
13
+ @worker_count = (options.delete(:worker_count) || PROCESSOR_COUNT_DEFAULT).to_i
14
+ @mutex = Mutex.new
15
+ @tasks = {}
16
+ @router = options.delete :router
17
+
18
+ serializer = options[:serializer] || Arsenicum::Serializer::JSON.new
19
+ formatter = options[:formatter] || Arsenicum::Formatter.new
20
+ @worker_options = options.delete(:worker_options) || {}
21
+ @worker_options.merge! serializer: serializer, formatter: formatter
22
+ end
23
+
24
+ def register_task(task_id, task)
25
+ tasks[task_id.to_sym] = task
26
+ end
27
+
28
+ def [](task_id)
29
+ tasks[task_id.to_sym] || default_task
30
+ end
31
+
32
+ def []=(task_id, task)
33
+ tasks[task_id.to_sym] = task
34
+ end
35
+
36
+ def run
37
+ @workers = {}
38
+ @available_workers = {}
39
+
40
+ @worker_count.times do
41
+ prepare_worker
42
+ end
43
+ end
44
+
45
+ def prepare_worker
46
+ worker = Arsenicum::Core::Worker.new(self, worker_options)
47
+ stock(worker)
48
+ end
49
+
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
+ sleep 0.5
61
+ end
62
+
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
73
+ end
74
+
75
+ def delegate(message)
76
+ (task_id, parameters) = router.route(message)
77
+ broker task_id, *parameters
78
+ end
79
+
80
+ def remove(worker)
81
+ mutex.synchronize do
82
+ workers.delete(worker.pid)
83
+ available_workers.delete(worker.pid)
84
+ end
85
+ end
86
+
87
+ def next_worker
88
+ mutex.synchronize{available_workers.shift.last}
89
+ end
90
+
91
+ def get_back_worker(worker)
92
+ mutex.synchronize{available_workers[worker.pid] = worker}
93
+ end
94
+
95
+ def serialize(value = {})
96
+ serializer.serialize value
97
+ end
98
+ end