modern_times 0.2.11 → 0.3.0

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