modern_times 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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