modern_times 0.1.2 → 0.2.0

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.
Files changed (64) hide show
  1. data/README.rdoc +24 -12
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/examples/README +4 -0
  5. data/examples/jms.yml +9 -0
  6. data/examples/requestor/README +4 -2
  7. data/examples/requestor/manager.rb +3 -2
  8. data/examples/requestor/request.rb +5 -4
  9. data/examples/requestor/reverse_echo_worker.rb +3 -2
  10. data/examples/simple/README +7 -4
  11. data/examples/simple/bar_worker.rb +4 -1
  12. data/examples/simple/baz_worker.rb +4 -3
  13. data/examples/simple/manager.rb +3 -2
  14. data/examples/simple/publish.rb +6 -5
  15. data/lib/modern_times.rb +20 -2
  16. data/lib/modern_times/base/supervisor.rb +14 -21
  17. data/lib/modern_times/base/supervisor_mbean.rb +4 -6
  18. data/lib/modern_times/base/worker.rb +17 -26
  19. data/lib/modern_times/jms.rb +23 -0
  20. data/lib/modern_times/{hornetq/client.rb → jms/connection.rb} +19 -12
  21. data/lib/modern_times/jms/publisher.rb +91 -0
  22. data/lib/modern_times/jms/supervisor.rb +19 -0
  23. data/lib/modern_times/jms/supervisor_mbean.rb +11 -0
  24. data/lib/modern_times/jms/worker.rb +166 -0
  25. data/lib/modern_times/jms_requestor.rb +10 -0
  26. data/lib/modern_times/jms_requestor/request_handle.rb +33 -0
  27. data/lib/modern_times/jms_requestor/requestor.rb +45 -0
  28. data/lib/modern_times/jms_requestor/supervisor.rb +45 -0
  29. data/lib/modern_times/jms_requestor/supervisor_mbean.rb +21 -0
  30. data/lib/modern_times/jms_requestor/worker.rb +78 -0
  31. data/lib/modern_times/manager.rb +14 -9
  32. data/lib/modern_times/manager_mbean.rb +14 -7
  33. data/lib/modern_times/marshal_strategy.rb +47 -0
  34. data/lib/modern_times/marshal_strategy/bson.rb +31 -0
  35. data/lib/modern_times/marshal_strategy/json.rb +30 -0
  36. data/lib/modern_times/marshal_strategy/ruby.rb +20 -0
  37. data/lib/modern_times/marshal_strategy/string.rb +19 -0
  38. data/lib/modern_times/railsable.rb +17 -74
  39. data/test/base_test.rb +248 -0
  40. data/test/jms.yml +8 -0
  41. data/test/jms_requestor_test.rb +263 -0
  42. data/test/jms_test.rb +296 -0
  43. data/test/marshal_strategy_test.rb +39 -0
  44. metadata +49 -46
  45. data/examples/requestor/hornetq.yml +0 -14
  46. data/examples/simple/hornetq.yml +0 -14
  47. data/lib/modern_times/hornetq.rb +0 -11
  48. data/lib/modern_times/hornetq/marshal_strategy.rb +0 -3
  49. data/lib/modern_times/hornetq/marshal_strategy/json.rb +0 -17
  50. data/lib/modern_times/hornetq/marshal_strategy/ruby.rb +0 -17
  51. data/lib/modern_times/hornetq/marshal_strategy/string.rb +0 -17
  52. data/lib/modern_times/hornetq/publisher.rb +0 -65
  53. data/lib/modern_times/hornetq/supervisor.rb +0 -22
  54. data/lib/modern_times/hornetq/supervisor_mbean.rb +0 -12
  55. data/lib/modern_times/hornetq/worker.rb +0 -127
  56. data/lib/modern_times/hornetq_requestor.rb +0 -9
  57. data/lib/modern_times/hornetq_requestor/request_handle.rb +0 -49
  58. data/lib/modern_times/hornetq_requestor/requestor.rb +0 -48
  59. data/lib/modern_times/hornetq_requestor/worker.rb +0 -29
  60. data/lib/modern_times/thread.rb +0 -16
  61. data/test/base/worker_test.rb +0 -38
  62. data/test/messaging/worker_manager_test.rb +0 -58
  63. data/test/messaging/worker_test.rb +0 -58
  64. data/test/worker_manager_test.rb +0 -48
@@ -0,0 +1,45 @@
1
+ module ModernTimes
2
+ module JMSRequestor
3
+ class Supervisor < ModernTimes::JMS::Supervisor
4
+
5
+ def initialize(manager, worker_name, supervisor_options, worker_options)
6
+ super
7
+ end
8
+
9
+ def average_response_time
10
+ count = 0
11
+ total = 0.0
12
+ workers.each do |w|
13
+ pair = w.total_time
14
+ count += pair.first
15
+ total += pair.last
16
+ end
17
+ return 0.0 if count == 0
18
+ return total / count
19
+ end
20
+
21
+ def min_response_time
22
+ min_time = nil
23
+ workers.each do |w|
24
+ wmin_time = w.min_time
25
+ min_time = wmin_time if wmin_time && (!min_time || wmin_time < min_time)
26
+ end
27
+ return min_time || 0.0
28
+ end
29
+
30
+ def max_response_time
31
+ max_time = 0.0
32
+ workers.each do |w|
33
+ wmax_time = w.max_time
34
+ max_time = wmax_time if wmax_time > max_time
35
+ end
36
+ return max_time
37
+ end
38
+
39
+ # Make JMSRequestor::SupervisorMBean our mbean
40
+ def create_mbean(domain)
41
+ SupervisorMBean.new(mbean_name(domain), mbean_description, self, {})
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ module ModernTimes
2
+ module JMSRequestor
3
+ class SupervisorMBean < ModernTimes::JMS::SupervisorMBean
4
+ r_attribute :average_response_time, :float, 'Average response time', :average_response_time
5
+ r_attribute :min_response_time, :float, 'Minimum response time', :min_response_time
6
+ r_attribute :max_response_time, :float, 'Maximum response time', :max_response_time
7
+
8
+ def average_response_time
9
+ supervisor.average_response_time
10
+ end
11
+
12
+ def min_response_time
13
+ supervisor.min_response_time
14
+ end
15
+
16
+ def max_response_time
17
+ supervisor.max_response_time
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,78 @@
1
+ module ModernTimes
2
+ module JMSRequestor
3
+
4
+ # Base Worker Class for any class that will be processing messages from queues
5
+ module Worker
6
+ include ModernTimes::JMS::Worker
7
+
8
+ module ClassMethods
9
+ def create_supervisor(manager, worker_options)
10
+ Supervisor.new(manager, self, {}, worker_options)
11
+ end
12
+ end
13
+
14
+ def self.included(base)
15
+ # The price we pay for including rather than extending
16
+ base.extend(ModernTimes::Base::Worker::ClassMethods)
17
+ base.extend(ModernTimes::JMS::Worker::ClassMethods)
18
+ base.extend(ClassMethods)
19
+ end
20
+
21
+ def initialize(opts={})
22
+ super
23
+ @time_mutex = Mutex.new
24
+ @count = 0
25
+ @min_time = nil
26
+ @max_time = 0.0
27
+ @total_time = 0.0
28
+ end
29
+
30
+ def perform(object)
31
+ start_time = Time.now
32
+ response = request(object)
33
+ response_time = Time.now - start_time
34
+ session.producer(:destination => message.reply_to) do |producer|
35
+ reply_message = session.message(self.class.marshaler.marshal(response))
36
+ reply_message.jms_correlation_id = message.jms_message_id
37
+ #producer.send_with_retry(reply_message)
38
+ producer.send(reply_message)
39
+ end
40
+ @time_mutex.synchronize do
41
+ @count += 1
42
+ @total_time += response_time
43
+ @min_time = response_time if !@min_time || response_time < @min_time
44
+ @max_time = response_time if response_time > @max_time
45
+ end
46
+ end
47
+
48
+ def total_time
49
+ @time_mutex.synchronize do
50
+ retval = [@count, @total_time]
51
+ @count = 0
52
+ @total_time = 0.0
53
+ return retval
54
+ end
55
+ end
56
+
57
+ def min_time
58
+ @time_mutex.synchronize do
59
+ val = @min_time
60
+ @min_time = nil
61
+ return val
62
+ end
63
+ end
64
+
65
+ def max_time
66
+ @time_mutex.synchronize do
67
+ val = @max_time
68
+ @max_time = 0.0
69
+ return val
70
+ end
71
+ end
72
+
73
+ def request(object)
74
+ raise "#{self}: Need to override request method in #{self.class.name} in order to act on #{object}"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,46 +1,51 @@
1
+ require 'yaml'
2
+
1
3
  module ModernTimes
2
4
  class Manager
3
5
  attr_accessor :allowed_workers
6
+ attr_reader :supervisors
4
7
 
5
8
  def initialize(config={})
6
9
  @stopped = false
7
10
  @config = config
8
- @domain = config[:domain] || 'ModernTimes'
11
+ @domain = config[:domain] || ModernTimes::DEFAULT_DOMAIN
9
12
  @supervisors = []
10
13
  @jmx_server = JMX::MBeanServer.new
11
- bean = ManagerMBean.new("#{@domain}.Manager", "Manager", self)
12
- @jmx_server.register_mbean(bean, "#{@domain}:type=Manager")
14
+ bean = ManagerMBean.new(@domain, self)
15
+ @jmx_server.register_mbean(bean, ModernTimes.manager_mbean_object_name(@domain))
13
16
  self.persist_file = config[:persist_file]
14
17
  end
15
18
 
16
- def add(worker_klass, num_workers, worker_options)
19
+ def add(worker_klass, num_workers, worker_options={})
17
20
  ModernTimes.logger.info "Starting #{worker_klass} with #{num_workers} workers with options #{worker_options.inspect}"
18
21
  unless worker_klass.kind_of?(Class)
19
22
  begin
20
23
  worker_klass = Object.const_get(worker_klass.to_s)
21
24
  rescue
22
- raise ModernTimes::Exception.new("Invalid class: #{worker_klass}")
25
+ raise ModernTimes::Exception, "Invalid class: #{worker_klass}"
23
26
  end
24
27
  end
25
28
  if @allowed_workers && !@allowed_workers.include?(worker_klass)
26
- raise ModernTimes::Exception.new("Error: #{worker_klass.name} is not an allowed worker")
29
+ raise ModernTimes::Exception, "Error: #{worker_klass.name} is not an allowed worker"
27
30
  end
28
31
  supervisor = worker_klass.create_supervisor(self, worker_options)
29
32
  mbean = supervisor.create_mbean(@domain)
30
33
  @supervisors << supervisor
31
34
  supervisor.worker_count = num_workers
32
35
  @jmx_server.register_mbean(mbean, "#{@domain}:worker=#{supervisor.name},type=Worker")
33
- ModernTimes.logger.info "Started #{worker_klass.name} with #{num_workers} workers"
36
+ ModernTimes.logger.info "Started #{worker_klass.name} named #{supervisor.name} with #{num_workers} workers"
34
37
  rescue Exception => e
35
- ModernTimes.logger.error "Exception trying to add #{worker_klass.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
38
+ ModernTimes.logger.error "Exception trying to add #{worker_klass}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
39
+ raise
36
40
  rescue java.lang.Exception => e
37
41
  ModernTimes.logger.error "Java exception trying to add #{worker_klass.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
42
+ raise
38
43
  end
39
44
 
45
+ # TODO: Get rid of this or prevent worker thread creation until it's been called?
40
46
  def start
41
47
  return if @started
42
48
  @started = true
43
-
44
49
  end
45
50
 
46
51
  def stop
@@ -5,8 +5,8 @@ module ModernTimes
5
5
  attr_reader :manager
6
6
  r_attribute :allowed_workers, :list, 'Allowed workers'
7
7
 
8
- def initialize(name, description, manager)
9
- super(name, description)
8
+ def initialize(domain, manager)
9
+ super(ModernTimes.manager_mbean_name(domain), 'Manager')
10
10
  @manager = manager
11
11
  end
12
12
 
@@ -17,12 +17,19 @@ module ModernTimes
17
17
  end
18
18
 
19
19
  operation 'Start worker'
20
- parameter :string, "worker", "The worker class to start"
21
- parameter :int, "count", "Number of workers"
20
+ parameter :string, 'worker', 'The worker class to start'
21
+ parameter :int, 'count', 'Number of workers'
22
+ parameter :string, 'options', 'Hash of options in json format (optional)'
22
23
  returns :string
23
- def start_worker(worker, count)
24
- ModernTimes.logger.debug "Attempting to start #{worker} with count=#{count}"
25
- manager.add(worker, count, {})
24
+ def start_worker(worker, count, options)
25
+ ModernTimes.logger.debug "Attempting to start #{worker} with count=#{count} and options=#{options}"
26
+ opts = {}
27
+ unless options.empty?
28
+ require 'json'
29
+ opts_string_keys = ::JSON::Parser.new(options).parse
30
+ opts_string_keys.each { |k,v| opts[k.to_sym] = v }
31
+ end
32
+ manager.add(worker, count, opts)
26
33
  return 'Successfuly started'
27
34
  rescue Exception => e
28
35
  ModernTimes.logger.error "Exception starting worker #{worker}: {e.message}\n\t#{e.backtrace.join("\n\t")}"
@@ -0,0 +1,47 @@
1
+ require 'modern_times/marshal_strategy/bson'
2
+ require 'modern_times/marshal_strategy/json'
3
+ require 'modern_times/marshal_strategy/ruby'
4
+ require 'modern_times/marshal_strategy/string'
5
+
6
+ # Defines some default marshaling strategies for use in marshaling/unmarshaling objects
7
+ # written and read via jms. Implementing classes must define the following methods:
8
+ #
9
+ # # Return symbol
10
+ # # :text if session.create_text_message should be used to generate the JMS message
11
+ # # :bytes if session.create_bytes_message should be used to generate the JMS message
12
+ # def marshal_type
13
+ # # Return either :text or :bytes
14
+ # :text
15
+ # end
16
+ #
17
+ # # Defines the conversion to wire format by the publisher of the message
18
+ # def marshal(object)
19
+ # # Operate on object and convert to message format
20
+ # end
21
+ #
22
+ # # Defines the conversion from wire format by the consumer of the message
23
+ # def unmarshal(msg)
24
+ # # Operate on message to convert it from wire protocol back to object format
25
+ # end
26
+
27
+ module ModernTimes
28
+ module MarshalStrategy
29
+ def self.find(marshal_option)
30
+ if marshal_option.nil?
31
+ return Ruby
32
+ elsif marshal_option.kind_of? Symbol
33
+ return case marshal_option
34
+ when :ruby then Ruby
35
+ when :string then String
36
+ when :json then JSON
37
+ when :bson then BSON
38
+ else raise "Invalid marshal strategy: #{options[:marshal]}"
39
+ end
40
+ elsif marshal_option.respond_to?(:marshal_type)
41
+ return marshal_option
42
+ else
43
+ raise "Invalid marshal strategy: #{marshal_option}"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,31 @@
1
+ module ModernTimes
2
+ module MarshalStrategy
3
+ module BSON
4
+ extend self
5
+
6
+ def marshal_type
7
+ :bytes
8
+ end
9
+
10
+ begin
11
+ require 'bson'
12
+ def marshal(object)
13
+ ::BSON.serialize(object).to_s
14
+ end
15
+
16
+ def unmarshal(msg)
17
+ ::BSON.deserialize(msg)
18
+ end
19
+
20
+ rescue LoadError => e
21
+ def marshal(object)
22
+ raise 'Error: BSON marshaling specified but bson gem has not been installed'
23
+ end
24
+
25
+ def unmarshal(msg)
26
+ raise 'Error: BSON marshaling specified but bson gem has not been installed'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module ModernTimes
2
+ module MarshalStrategy
3
+ module JSON
4
+ extend self
5
+
6
+ def marshal_type
7
+ :text
8
+ end
9
+
10
+ begin
11
+ require 'json'
12
+ def marshal(object)
13
+ object.to_json
14
+ end
15
+
16
+ def unmarshal(msg)
17
+ ::JSON::Parser.new(msg).parse
18
+ end
19
+ rescue LoadError => e
20
+ def marshal(object)
21
+ raise 'Error: JSON marshaling specified but json gem has not been installed'
22
+ end
23
+
24
+ def unmarshal(msg)
25
+ raise 'Error: JSON marshaling specified but json gem has not been installed'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ module ModernTimes
2
+ module MarshalStrategy
3
+ module Ruby
4
+ extend self
5
+
6
+ def marshal_type
7
+ :bytes
8
+ end
9
+
10
+ def marshal(object)
11
+ ::Marshal.dump(object)
12
+ end
13
+
14
+ def unmarshal(msg)
15
+ msg = ::String.from_java_bytes(msg) unless msg.kind_of?(::String)
16
+ ::Marshal.load(msg)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module ModernTimes
2
+ module MarshalStrategy
3
+ module String
4
+ extend self
5
+
6
+ def marshal_type
7
+ :text
8
+ end
9
+
10
+ def marshal(object)
11
+ object.to_s
12
+ end
13
+
14
+ def unmarshal(msg)
15
+ msg
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,20 +1,22 @@
1
1
  module ModernTimes
2
2
  module Railsable
3
3
  def init_rails
4
- if cfg = YAML.load_file(File.join(Rails.root, "config", "hornetq.yml"))[Rails.env]
4
+ if @cfg = YAML.load_file(File.join(Rails.root, "config", "jms.yml"))[Rails.env]
5
5
  ModernTimes.logger.info "Messaging Enabled"
6
- ModernTimes::HornetQ::Client.init(cfg)
7
- @is_hornetq_enabled = true
6
+ ModernTimes::JMS::Connection.init(@cfg)
7
+ @is_jms_enabled = true
8
8
 
9
- # Need to start the HornetQ Server in this VM
10
- if ModernTimes::HornetQ::Client.invm?
11
- @server = ::HornetQ::Server.create_server('hornetq://invm')
9
+ # Need to start the JMS Server in this VM
10
+ # TODO: Still want to support this?
11
+ if false
12
+ #if ModernTimes::JMS::Connection.invm?
13
+ @server = ::JMS::Server.create_server('vm://127.0.0.1')
12
14
  @server.start
13
15
 
14
16
  # Handle messages within this process
15
17
  @manager = ModernTimes::Manager.new
16
18
  # TODO: Formatting of configured workers in invm state with name and options
17
- if worker_cfg = cfg[:workers]
19
+ if worker_cfg = @cfg[:workers]
18
20
  worker_cfg.each do |klass, count|
19
21
  @manager.add(klass, count, {})
20
22
  end
@@ -38,20 +40,19 @@ module ModernTimes
38
40
 
39
41
  else
40
42
  Rails.logger.info "Messaging disabled"
41
- @is_hornetq_enabled = false
42
- ModernTimes::HornetQ::Publisher.setup_dummy_publishing(rails_workers)
43
+ @is_jms_enabled = false
44
+ ModernTimes::JMS::Publisher.setup_dummy_publishing(rails_workers)
45
+ ModernTimes::JMSRequestor::Requestor.setup_dummy_publishing(rails_workers)
43
46
  end
44
47
  end
45
48
 
46
49
  def create_rails_manager
47
- cfg = YAML.load_file(File.join(Rails.root, "config", "hornetq.yml"))[Rails.env]
48
- raise "No valid configuration" unless cfg
49
- ModernTimes::HornetQ::Client.init(cfg)
50
-
50
+ raise 'init_rails has not been called, modify your config/environment.rb to include this call' if @is_jms_enabled.nil?
51
+ raise 'Messaging is not enabled, modify your config/jms.yml file' unless @is_jms_enabled
51
52
  manager = ModernTimes::Manager.new
52
53
  manager.stop_on_signal
53
54
  manager.allowed_workers = rails_workers
54
- manager.persist_file = cfg[:persist_file] || File.join(Rails.root, "log", "modern_times.persist")
55
+ manager.persist_file = @cfg[:persist_file] || File.join(Rails.root, "log", "modern_times.persist")
55
56
  return manager
56
57
  end
57
58
 
@@ -68,66 +69,8 @@ module ModernTimes
68
69
  #raise "No worker config file #{file}" unless File.exist?(file)
69
70
  end
70
71
 
71
- def hornetq_enabled?
72
- @is_hornetq_enabled
72
+ def jms_enabled?
73
+ @is_jms_enabled
73
74
  end
74
75
  end
75
76
  end
76
-
77
-
78
- ## Protocol independent class to handle Messaging and Queuing
79
- #module Messaging
80
- # class Client
81
- #
82
- # # Publish to the specified address
83
- # # If the supplied object is kind_of? String, then a string is published
84
- # # Otherwise the Ruby Object is unmarshaled and sent as a Binary message
85
- #
86
- # # Asynchronously invoke the supplied method
87
- # #
88
- # # Example:
89
- # # Messaging::Client.async(Dashboard, :update_dashboard_for_inquiry, xml_response)
90
- # def self.async(klass, method, *param)
91
- # @@session_pool.producer(self.async_address) do |session, producer|
92
- # request = AsyncRequest.new
93
- # request.klass = if klass.kind_of?(String)
94
- # klass
95
- # elsif klass.kind_of?(Symbol)
96
- # klass.to_s
97
- # else
98
- # klass.name.to_s
99
- # end
100
- # request.method = method
101
- # request.params = *param
102
- # message = session.create_message(4,false) #HornetQ::Client::Message::BYTES_TYPE
103
- # message['format'] = 'ruby'
104
- # message.body = Marshal.dump(request)
105
- # producer.send(message)
106
- # end
107
- # end
108
- #
109
- # private
110
- # # Call the specified class passing in the required parameters
111
- # # If the method matches a class method, it is called, otherwise
112
- # # an instance of the class is created and the method is called
113
- # # on the new instance
114
- # #
115
- # # Note: Instance methods are more expensive because the class is instantiated
116
- # # for every call
117
- # def self.async_on_message(request)
118
- # klass = request.klass.constantize
119
- # method = request.method.to_sym
120
- # if klass.respond_to?(method, false)
121
- # klass.send(method, *request.params)
122
- # else
123
- # klass.new.send(method, *request.params)
124
- # end
125
- # end
126
- #
127
- # # Passed as the request message, used to hold all required parameters
128
- # class AsyncRequest
129
- # attr_accessor :klass, :method, :params
130
- # end
131
- #
132
- # end
133
- #end