modern_times 0.2.11 → 0.3.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 (50) hide show
  1. data/README.rdoc +114 -80
  2. data/VERSION +1 -1
  3. data/examples/advanced_requestor/README +15 -0
  4. data/examples/advanced_requestor/base_request_worker.rb +13 -0
  5. data/examples/advanced_requestor/char_count_worker.rb +11 -0
  6. data/examples/advanced_requestor/exception_raiser_worker.rb +10 -0
  7. data/examples/advanced_requestor/length_worker.rb +9 -0
  8. data/examples/advanced_requestor/manager.rb +22 -0
  9. data/examples/advanced_requestor/modern_times.yml +32 -0
  10. data/examples/advanced_requestor/print_worker.rb +9 -0
  11. data/examples/advanced_requestor/publish.rb +46 -0
  12. data/examples/advanced_requestor/reverse_worker.rb +9 -0
  13. data/examples/advanced_requestor/triple_worker.rb +9 -0
  14. data/examples/requestor/request.rb +3 -3
  15. data/examples/requestor/reverse_echo_worker.rb +1 -2
  16. data/lib/modern_times.rb +1 -1
  17. data/lib/modern_times/base/supervisor.rb +2 -0
  18. data/lib/modern_times/base/worker.rb +5 -3
  19. data/lib/modern_times/jms.rb +2 -0
  20. data/lib/modern_times/jms/connection.rb +7 -0
  21. data/lib/modern_times/jms/publish_handle.rb +219 -0
  22. data/lib/modern_times/jms/publisher.rb +55 -29
  23. data/lib/modern_times/{jms_requestor/worker.rb → jms/request_worker.rb} +29 -51
  24. data/lib/modern_times/jms/supervisor.rb +30 -0
  25. data/lib/modern_times/jms/supervisor_mbean.rb +17 -1
  26. data/lib/modern_times/jms/worker.rb +43 -40
  27. data/lib/modern_times/manager.rb +6 -2
  28. data/lib/modern_times/marshal_strategy.rb +14 -17
  29. data/lib/modern_times/marshal_strategy/bson.rb +2 -0
  30. data/lib/modern_times/marshal_strategy/json.rb +3 -0
  31. data/lib/modern_times/marshal_strategy/ruby.rb +3 -0
  32. data/lib/modern_times/marshal_strategy/string.rb +3 -0
  33. data/lib/modern_times/marshal_strategy/yaml.rb +3 -0
  34. data/lib/modern_times/railsable.rb +7 -14
  35. data/lib/modern_times/time_track.rb +84 -0
  36. data/test/jms.yml +1 -0
  37. data/test/jms_failure_test.rb +128 -0
  38. data/test/jms_requestor_block_test.rb +275 -0
  39. data/test/jms_requestor_test.rb +71 -96
  40. data/test/jms_test.rb +59 -78
  41. data/test/marshal_strategy_test.rb +1 -3
  42. metadata +29 -14
  43. data/examples/exception_test/bar_worker.rb +0 -8
  44. data/examples/exception_test/base_worker.rb +0 -23
  45. data/examples/exception_test/manager.rb +0 -11
  46. data/lib/modern_times/jms_requestor.rb +0 -10
  47. data/lib/modern_times/jms_requestor/request_handle.rb +0 -42
  48. data/lib/modern_times/jms_requestor/requestor.rb +0 -56
  49. data/lib/modern_times/jms_requestor/supervisor.rb +0 -45
  50. data/lib/modern_times/jms_requestor/supervisor_mbean.rb +0 -21
@@ -1,88 +1,66 @@
1
1
  module ModernTimes
2
- module JMSRequestor
2
+ module JMS
3
3
 
4
4
  # Base Worker Class for any class that will be processing messages from queues
5
- module Worker
6
- include ModernTimes::JMS::Worker
7
- attr_reader :error_count
5
+ module RequestWorker
6
+ include Worker
7
+ # Dummy requesting needs access to this
8
+ attr_reader :marshaler
8
9
 
9
10
  module ClassMethods
10
- def create_supervisor(manager, worker_options)
11
- Supervisor.new(manager, self, {}, worker_options)
11
+ # Define the marshaling and time_to_live that will occur on the response
12
+ def response(options)
13
+ @response_options = options
14
+ end
15
+
16
+ def response_options
17
+ # Get the response marshaler, defaulting to the request marshaler
18
+ @response_options
12
19
  end
13
20
  end
14
21
 
15
22
  def self.included(base)
16
23
  # The price we pay for including rather than extending
17
24
  base.extend(ModernTimes::Base::Worker::ClassMethods)
18
- base.extend(ModernTimes::JMS::Worker::ClassMethods)
25
+ base.extend(Worker::ClassMethods)
19
26
  base.extend(ClassMethods)
20
27
  end
21
28
 
22
29
  def initialize(opts={})
23
30
  super
24
- @time_mutex = Mutex.new
25
- @count = 0
26
- @error_count = 0
27
- @min_time = nil
28
- @max_time = 0.0
29
- @total_time = 0.0
31
+ response_options = self.class.response_options || {}
32
+ @marshal_type = (response_options[:marshal] || :ruby).to_s
33
+ @marshaler = MarshalStrategy.find(@marshal_type)
34
+ # Time in msec until the message gets discarded, should be more than the timeout on the requestor side
35
+ @time_to_live = response_options[:time_to_live] || 10000
30
36
  end
31
37
 
32
38
  def perform(object)
33
- start_time = Time.now
34
39
  response = request(object)
35
- response_time = Time.now - start_time
36
40
  session.producer(:destination => message.reply_to) do |producer|
37
- reply_message = ModernTimes::JMS.create_message(session, self.class.marshaler, response)
41
+ producer.time_to_live = @time_to_live
42
+ reply_message = ModernTimes::JMS.create_message(session, @marshaler, response)
38
43
  reply_message.jms_correlation_id = message.jms_message_id
44
+ reply_message.jms_delivery_mode = ::JMS::DeliveryMode::NON_PERSISTENT
45
+ reply_message['worker'] = self.name
46
+ reply_message['marshal'] = @marshal_type
39
47
  producer.send(reply_message)
40
48
  end
41
- @time_mutex.synchronize do
42
- @count += 1
43
- @total_time += response_time
44
- @min_time = response_time if !@min_time || response_time < @min_time
45
- @max_time = response_time if response_time > @max_time
46
- end
47
49
  rescue Exception => e
48
- @time_mutex.synchronize do
49
- @error_count += 1
50
- end
50
+ ModernTimes.logger.error("Exception: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
51
51
  begin
52
52
  session.producer(:destination => message.reply_to) do |producer|
53
+ producer.time_to_live = @time_to_live
53
54
  reply_message = ModernTimes::JMS.create_message(session, ModernTimes::MarshalStrategy::String, "Exception: #{e.message}")
54
55
  reply_message.jms_correlation_id = message.jms_message_id
55
- reply_message['Exception'] = ModernTimes::RemoteException.new(e).to_hash.to_yaml
56
+ reply_message['worker'] = self.name
57
+ reply_message['exception'] = ModernTimes::RemoteException.new(e).to_hash.to_yaml
56
58
  producer.send(reply_message)
57
59
  end
58
60
  rescue Exception => e
59
61
  ModernTimes.logger.error("Exception in exception reply: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
60
62
  end
61
- end
62
-
63
- def total_time
64
- @time_mutex.synchronize do
65
- retval = [@count, @total_time]
66
- @count = 0
67
- @total_time = 0.0
68
- return retval
69
- end
70
- end
71
-
72
- def min_time
73
- @time_mutex.synchronize do
74
- val = @min_time
75
- @min_time = nil
76
- return val
77
- end
78
- end
79
-
80
- def max_time
81
- @time_mutex.synchronize do
82
- val = @max_time
83
- @max_time = 0.0
84
- return val
85
- end
63
+ raise
86
64
  end
87
65
 
88
66
  def request(object)
@@ -10,6 +10,36 @@ module ModernTimes
10
10
  workers.map { |w| w.message_count }
11
11
  end
12
12
 
13
+ def average_response_time
14
+ count = 0
15
+ total = 0.0
16
+ workers.each do |w|
17
+ pair = w.time_track.total_time_reset
18
+ count += pair.first
19
+ total += pair.last
20
+ end
21
+ return 0.0 if count == 0
22
+ return total / count
23
+ end
24
+
25
+ def min_response_time
26
+ min_time = nil
27
+ workers.each do |w|
28
+ wmin_time = w.time_track.min_time_reset
29
+ min_time = wmin_time if wmin_time && (!min_time || wmin_time < min_time)
30
+ end
31
+ return min_time || 0.0
32
+ end
33
+
34
+ def max_response_time
35
+ max_time = 0.0
36
+ workers.each do |w|
37
+ wmax_time = w.time_track.max_time_reset
38
+ max_time = wmax_time if wmax_time > max_time
39
+ end
40
+ return max_time
41
+ end
42
+
13
43
  # Make JMS::SupervisorMBean our mbean
14
44
  def create_mbean(domain)
15
45
  SupervisorMBean.new(mbean_name(domain), mbean_description, self, {})
@@ -1,11 +1,27 @@
1
1
  module ModernTimes
2
2
  module JMS
3
3
  class SupervisorMBean < ModernTimes::Base::SupervisorMBean
4
- r_attribute :message_counts, :list, 'Message counts for the workers', :message_counts
4
+ r_attribute :message_counts, :list, 'Message counts for the workers', :message_counts
5
+ r_attribute :average_response_time, :float, 'Average response time', :average_response_time
6
+ r_attribute :min_response_time, :float, 'Minimum response time', :min_response_time
7
+ r_attribute :max_response_time, :float, 'Maximum response time', :max_response_time
5
8
 
6
9
  def message_counts
7
10
  java.util.ArrayList.new(supervisor.message_counts)
8
11
  end
12
+
13
+ def average_response_time
14
+ supervisor.average_response_time
15
+ end
16
+
17
+ def min_response_time
18
+ supervisor.min_response_time
19
+ end
20
+
21
+ def max_response_time
22
+ supervisor.max_response_time
23
+ end
24
+
9
25
  end
10
26
  end
11
27
  end
@@ -34,7 +34,7 @@ module ModernTimes
34
34
  module Worker
35
35
  include ModernTimes::Base::Worker
36
36
 
37
- attr_reader :session, :message_count
37
+ attr_reader :session, :error_count, :time_track
38
38
  attr_accessor :message
39
39
 
40
40
  module ClassMethods
@@ -42,10 +42,6 @@ module ModernTimes
42
42
  Supervisor.new(manager, self, {}, worker_options)
43
43
  end
44
44
 
45
- def marshal(option)
46
- @marshaler = ModernTimes::MarshalStrategy.find(option)
47
- end
48
-
49
45
  def destination_options
50
46
  options = dest_options.dup
51
47
  # Default the queue name to the Worker name if a destinations hasn't been specified
@@ -64,13 +60,18 @@ module ModernTimes
64
60
  dest_options[:queue_name] = name.to_s
65
61
  end
66
62
 
67
- def dest_options
63
+ def dest_options
68
64
  @dest_options ||= {}
69
65
  end
70
66
 
71
- def marshaler
72
- # Default to ruby marshaling, but extenders can override as necessary
73
- @marshaler ||= ModernTimes::MarshalStrategy::Ruby
67
+ def failure_queue(name, opts={})
68
+ @failure_queue_name = name.to_s
69
+ end
70
+
71
+ def failure_queue_name
72
+ # Don't overwrite if the user set to false, only if it was never set
73
+ @failure_queue_name = "#{default_name}Failure" if @failure_queue_name.nil?
74
+ @failure_queue_name
74
75
  end
75
76
  end
76
77
 
@@ -81,15 +82,18 @@ module ModernTimes
81
82
 
82
83
  def initialize(opts={})
83
84
  super
84
- @status = 'initialized'
85
- @message_count = 0
85
+ @status = 'initialized'
86
+ # Supervisor will ask for counts in a separate thread
87
+ @time_track = ModernTimes::TimeTrack.new
88
+ @error_count = 0
86
89
  end
87
90
 
88
- def setup
91
+ def message_count
92
+ @time_track.total_count
89
93
  end
90
94
 
91
95
  def status
92
- @status || "Processing message #{message_count}"
96
+ @status || "Processing message #{@time_track.total_count}"
93
97
  end
94
98
 
95
99
  def real_destination_options
@@ -101,8 +105,6 @@ module ModernTimes
101
105
 
102
106
  # Start the event loop for handling messages off the queue
103
107
  def start
104
- # Grab this to prevent lookup with every message
105
- @message_marshaler = self.class.marshaler
106
108
  @session = Connection.create_consumer_session
107
109
  @consumer = @session.consumer(real_destination_options)
108
110
  @session.start
@@ -110,24 +112,23 @@ module ModernTimes
110
112
  ModernTimes.logger.debug "#{self}: Starting receive loop"
111
113
  @status = nil
112
114
  while msg = @consumer.receive
113
- sec = Benchmark.realtime do
114
- @message_count += 1
115
+ @time_track.perform do
115
116
  on_message(msg)
116
117
  msg.acknowledge
117
118
  end
118
- ModernTimes.logger.info {"#{self}::perform (#{('%.1f' % (sec*1000))}ms)"}
119
+ ModernTimes.logger.info {"#{self}::on_message (#{('%.1f' % @time_track.last_time)}ms)"} if ModernTimes::JMS::Connection.log_times?
119
120
  end
120
121
  @status = 'Exited'
121
122
  ModernTimes.logger.info "#{self}: Exiting"
122
- rescue javax.jms.IllegalStateException => e
123
- #if e.cause.code == Java::org.jms.api.core.JMSException::OBJECT_CLOSED
124
- # Normal exit
125
- @status = 'Exited'
126
- ModernTimes.logger.info "#{self}: Exiting due to close"
127
- #else
128
- # @status = "Exited with JMS exception #{e.message}"
129
- # ModernTImes.logger.error "#{self} JMSException: #{e.message}\n#{e.backtrace.join("\n")}"
130
- #end
123
+ # rescue javax.jms.IllegalStateException => e
124
+ # #if e.cause.code == Java::org.jms.api.core.JMSException::OBJECT_CLOSED
125
+ # # Normal exit
126
+ # @status = 'Exited'
127
+ # ModernTimes.logger.info "#{self}: Exiting due to possible close: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
128
+ # #else
129
+ # # @status = "Exited with JMS exception #{e.message}"
130
+ # # ModernTImes.logger.error "#{self} JMSException: #{e.message}\n#{e.backtrace.join("\n")}"
131
+ # #end
131
132
  rescue Exception => e
132
133
  @status = "Exited with exception #{e.message}"
133
134
  ModernTimes.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
@@ -143,31 +144,33 @@ module ModernTimes
143
144
 
144
145
  def on_message(message)
145
146
  @message = message
146
- object = @message_marshaler.unmarshal(message.data)
147
+ marshaler = ModernTimes::MarshalStrategy.find(message['marshal'] || :ruby)
148
+ object = marshaler.unmarshal(message.data)
147
149
  ModernTimes.logger.debug {"#{self}: Received Object: #{object}"}
148
150
  perform(object)
151
+ rescue Exception => e
152
+ @error_count += 1
153
+ on_exception(e)
154
+ ensure
149
155
  ModernTimes.logger.debug {"#{self}: Finished processing message"}
150
156
  ModernTimes.logger.flush if ModernTimes.logger.respond_to?(:flush)
151
- rescue Exception => e
152
- ModernTimes.logger.error "#{self}: Messaging Exception: #{e.inspect}\n#{e.backtrace.inspect}"
153
- rescue java.lang.Exception => e
154
- ModernTimes.logger.error "#{self}: Java Messaging Exception: #{e.inspect}\n#{e.backtrace.inspect}"
155
157
  end
156
158
 
157
159
  def perform(object)
158
160
  raise "#{self}: Need to override perform method in #{self.class.name} in order to act on #{object}"
159
161
  end
160
162
 
161
- def to_s
162
- "#{real_destination_options.to_a.join('=>')}:#{index}"
163
+ def on_exception(e)
164
+ ModernTimes.logger.error "#{self}: Messaging Exception: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
165
+ if self.class.failure_queue_name
166
+ session.producer(:queue_name => self.class.failure_queue_name) do |producer|
167
+ producer.send(message)
168
+ end
169
+ end
163
170
  end
164
171
 
165
- #########
166
- protected
167
- #########
168
-
169
- # Create session information and allow extenders to initialize anything necessary prior to the event loop
170
- def session_init
172
+ def to_s
173
+ "#{real_destination_options.to_a.join('=>')}:#{index}"
171
174
  end
172
175
  end
173
176
  end
@@ -11,11 +11,11 @@ module ModernTimes
11
11
  @config = config
12
12
  @domain = config[:domain] || ModernTimes::DEFAULT_DOMAIN
13
13
  @supervisors = []
14
+ @dummy_host = config[:dummy_host]
14
15
  self.persist_file = config[:persist_file]
15
16
  self.worker_file = config[:worker_file]
16
17
  @allowed_workers = config[:allowed_workers]
17
18
  stop_on_signal if config[:stop_on_signal]
18
- @dummy_host = config[:dummy_host]
19
19
  # Unless specifically unconfigured (i.e., Rails.env == test), then enable jmx
20
20
  if config[:jmx] != false
21
21
  @jmx_server = JMX::MBeanServer.new
@@ -60,6 +60,7 @@ module ModernTimes
60
60
  end
61
61
 
62
62
  def stop
63
+ return if @stopped
63
64
  @stopped = true
64
65
  @supervisors.each { |supervisor| supervisor.stop }
65
66
  end
@@ -81,8 +82,9 @@ module ModernTimes
81
82
  end
82
83
 
83
84
  def persist_file=(file)
85
+ return if @persist_file == file
86
+ @persist_file = nil
84
87
  return unless file
85
- @persist_file = file
86
88
  if File.exist?(file)
87
89
  hash = YAML.load_file(file)
88
90
  hash.each do |worker_name, worker_hash|
@@ -93,6 +95,7 @@ module ModernTimes
93
95
  add(klass, count, options)
94
96
  end
95
97
  end
98
+ @persist_file = file
96
99
  end
97
100
 
98
101
  def save_persist_state
@@ -107,6 +110,7 @@ module ModernTimes
107
110
  end
108
111
  File.open(@persist_file, 'w') do |out|
109
112
  YAML.dump(hash, out )
113
+ YAML.dump(hash, out )
110
114
  end
111
115
  end
112
116
 
@@ -1,9 +1,3 @@
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
- require 'modern_times/marshal_strategy/yaml'
6
-
7
1
  # Defines some default marshaling strategies for use in marshaling/unmarshaling objects
8
2
  # written and read via jms. Implementing classes must define the following methods:
9
3
  #
@@ -27,26 +21,23 @@ require 'modern_times/marshal_strategy/yaml'
27
21
 
28
22
  module ModernTimes
29
23
  module MarshalStrategy
30
- @options = {
31
- :ruby => Ruby,
32
- :string => String,
33
- :json => JSON,
34
- :bson => BSON,
35
- :yaml => YAML,
36
- }
24
+
25
+ @options = {}
37
26
 
38
27
  def self.find(marshaler)
39
28
  if marshaler.nil?
40
29
  return Ruby
41
- elsif marshaler.kind_of? Symbol
42
- val = @options[marshaler]
30
+ else
31
+ val = @options[marshaler.to_sym]
43
32
  return val if val
44
- elsif valid?(marshaler)
45
- return marshaler
46
33
  end
47
34
  raise "Invalid marshal strategy: #{marshaler}"
48
35
  end
49
36
 
37
+ def self.registered?(marshaler)
38
+ @options.has_key?(marshaler.to_sym)
39
+ end
40
+
50
41
  # Allow user-defined marshal strategies
51
42
  def self.register(hash)
52
43
  hash.each do |key, marshaler|
@@ -66,3 +57,9 @@ module ModernTimes
66
57
  end
67
58
  end
68
59
  end
60
+
61
+ require 'modern_times/marshal_strategy/bson'
62
+ require 'modern_times/marshal_strategy/json'
63
+ require 'modern_times/marshal_strategy/ruby'
64
+ require 'modern_times/marshal_strategy/string'
65
+ require 'modern_times/marshal_strategy/yaml'
@@ -17,6 +17,8 @@ module ModernTimes
17
17
  ::BSON.deserialize(msg)
18
18
  end
19
19
 
20
+ MarshalStrategy.register(:bson => self)
21
+
20
22
  rescue LoadError => e
21
23
  def marshal(object)
22
24
  raise 'Error: BSON marshaling specified but bson gem has not been installed'