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
data/README.rdoc CHANGED
@@ -6,15 +6,13 @@
6
6
 
7
7
  JRuby library for performing background tasks via JMS.
8
8
 
9
- Very much alpha stage at this point.
9
+ Still alpha but approaching beta. API still subject to change.
10
10
 
11
11
  == Features/Problems:
12
12
 
13
- * jms_test issues (doesn't exit, doesn't work when doing more than 1 type of marshaling)
14
- * jms_requestor needs testing for dummy requesting
15
- * Allow options (durable queues, etc)
13
+ * jms_test and jms_requestor_test don't exit
16
14
  * Railsable needs testing
17
- * Fail options (fail queues, etc.)
15
+ * failure_queue needs testing
18
16
  * Currently tested only for ActiveMQ
19
17
 
20
18
  == Install:
@@ -27,77 +25,102 @@ TODO: This section needs updating for JMS
27
25
 
28
26
  Create config/jms.yml which might look as follows:
29
27
 
30
- development:
31
- :connection: hornetq://invm
28
+ development_server: &defaults
29
+ :factory: org.apache.activemq.ActiveMQConnectionFactory
30
+ :broker_url: tcp://127.0.0.1:61616
31
+ :require_jars:
32
+ - <%= Rails.root %>/lib/activemq/activemq-all.jar
33
+
34
+ development_vm:
35
+ <<: *defaults
36
+ :broker_url: vm://127.0.0.1
37
+ :object_message_serialization_defered: true
38
+
39
+ staging:
40
+ <<: *defaults
41
+ :broker_url: tcp://stage2:61616
42
+ :username: myuser
43
+ :password: <%=Encryption.try_decrypt "fadsfdasfdsfdasfdsafas" %>
32
44
 
33
45
  production:
34
- :connection:
35
- :factory: org.apache.activemq.ActiveMQConnectionFactory
36
- :broker_url: tcp://msghost:61616
37
- :require_jars:
38
- - <%= Dir.glob("#{ENV['ACTIVEMQ_HOME']}/activemq-all-*.jar")[0] %>
39
- - <%= Dir.glob("#{ENV['ACTIVEMQ_HOME']}/lib/optional/slf4j-log4j*.jar")[0] %>
40
- - <%= Dir.glob("#{ENV['ACTIVEMQ_HOME']}/lib/optional/log4j-*.jar")[0] %>
41
- :session:
42
- :username: myuser
43
- :password: mypassword
44
-
45
- In config/environment.rb, add the following lines:
46
+ <<: *defaults
47
+ :broker_url: failover://(tcp://msg1:61616,tcp://msg2:61616)?randomize=false&timeout=60000&initialReconnectDelay=100&useExponentialBackOff=true
48
+ :username: myuser
49
+ :password: <%=Encryption.try_decrypt "afsadsfasdfa/afasdfdasf\n" %>
50
+
51
+ In development and test mode, you will notice that there is no configuration defined. In this case, published messages will cause
52
+ synchronous calls to the Worker's perform method which matches the destination queue or topic.
53
+ This will allow your coworkers to use the functionality
54
+ of the queueing system without having to startup a JMS server. If you wanted to start up in an actual server-type mode, you
55
+ might set the MODERN_TIMES_ENV environment variable to "development_server" to override the Rails.env. This will allow you to test
56
+ the queueing system without having to make temporary changes to the config file which could accidentally get checked in.
57
+ For staging and production
58
+ modes, you will need to have a JMS server running. Note that this library has only been tested with ActiveMQ.
59
+
60
+ Create config/workers.yml which might look as follows:
61
+
62
+ development:
63
+ Analytics:
64
+ :count: 1
65
+ Dashboard:
66
+ :count: 1
67
+
68
+ stage1:
69
+ Analytics:
70
+ :count: 2
71
+ Dashboard:
72
+ :count: 2
73
+
74
+ app1: &default_setup
75
+ Analytics:
76
+ :count: 2
77
+ Dashboard:
78
+ :count: 2
79
+
80
+ app2:
81
+ <<: *default_setup
82
+
83
+ app3:
84
+ <<: *default_setup
85
+
86
+ In this file, the count represents the number of threads dedicated per worker. The worker first looks for a key that matches
87
+ the Rails.env. If it doesn't find one, it will look for a key matching the non-qualified hostname of the machine. (TODO: Show how to add options
88
+ that get passed to the constructor and a single worker class that operates on 2 different queues). This file is optional and workers
89
+ can be configured ad-hoc instead.
90
+
91
+ If you don't want to explicitly define your workers in a config file, you can create them ad-hoc instead.
92
+ Configure your workers by starting jconsole and connecting to
93
+ the manager process. Go to the MBeans tab and open the tree to
94
+ ModernTimes => Manager => Operations => start_worker
95
+
96
+ Start/stop/increase/decrease workers as needed. The state is stored in the log directory (by default)
97
+ so you can stop and start the manager and not have to reconfigure your workers.
98
+
99
+ Create config/initializers/modern_times.rb which might look as follows (TODO: Maybe add or refer to
100
+ examples for registering marshal strategies):
46
101
 
47
102
  ModernTimes.init_rails
48
- # Publishers can be defined wherever appropriate
103
+ # Publishers can be defined wherever appropriate, probably as class variables within the class that uses it
49
104
  $foo_publisher = ModernTimes::JMS::Publisher.new('Foo')
50
105
 
106
+ When creating publishers, you will probably want to store the value in a class variable. Publishers internally
107
+ make use of a session pool for communicating with the JMS server so you wouldn't want to create a new connection
108
+ every time you published an object.
109
+
51
110
  In your code, queue foo objects:
52
111
 
53
112
  $foo_publisher.publish(my_foo_object)
54
113
 
55
114
  In app/workers, create a FooWorker class:
56
115
 
57
- class FooWorker < ModernTimes::HornetQ::Worker
116
+ class FooWorker
117
+ include ModernTimes::JMS::Worker
58
118
  def perform(my_foo_object)
59
119
  # Operate on my_foo_object
60
120
  end
61
121
  end
62
122
 
63
- Based on the hornetq.yml file above, a development mode connection will be created such that
64
- the queueing is handled in-memory (since the uri is 'hornetq://invm').
65
- When this is used, a single thread (by default) will be created for each defined Worker so queued
66
- messages will be handled asynchronously within the same process.
67
-
68
- In test mode, there is no configuration defined. In this case, published messages will cause
69
- synchronous calls to the Worker's perform method.
70
-
71
- In production mode, you will need to start up a hornetq server to distribute the messages. For the
72
- configuration above, you might create a hornetq_server.yml file as follows:
73
-
74
- # backup server runs on msg2
75
- backup_server:
76
- :uri: hornetq://0.0.0.0
77
- :backup: true
78
- :data_directory: /var/lib/hornetq/data
79
- :persistence_enabled: true
80
- :security_enabled: true
81
- :cluster_user: mycluster_username
82
- :cluster_password: mycluster_password
83
-
84
- # live server runs on msg1
85
- live_server:
86
- :uri: hornetq://0.0.0.0,msg2
87
- :data_directory: /var/lib/hornetq/data
88
- :persistence_enabled: true
89
- :security_enabled: true
90
- :cluster_user: mycluster_username
91
- :cluster_password: mycluster_password
92
-
93
- Then on host msg2 and msg1, you could startup the servers with the following commands:
94
-
95
- # On msg2
96
- hornetq_server hornetq_server.yml backup_server
97
- # On msg1
98
- hornetq_server hornetq_server.yml live_server
99
-
100
- For the production environment, you will need to startup a Manager process to handle messages. You
123
+ For the staging and production environment, you will need to startup a Manager process on each machine that handles messages. You
101
124
  might create script/worker_manager as follows (assumes Rails.root/script is in your PATH):
102
125
 
103
126
  #!/usr/bin/env runner
@@ -107,38 +130,49 @@ might create script/worker_manager as follows (assumes Rails.root/script is in y
107
130
 
108
131
  TODO: Refer to example jsvc daemon script
109
132
 
110
- Configure your workers by starting jconsole and connecting to
111
- the manager process. Go to the MBeans tab and open the tree to
112
- ModernTimes => Manager => Operations => start_worker
113
133
 
114
- Start/stop/increase/decrease workers as needed. The state is stored in the log directory (by default)
115
- so you can stop and start the manager and not have to reconfigure your workers.
116
- TODO: This is flaky right now due to bugs in jruby-jmx.
134
+ == Multiple Workers For a Virtual Topic:
117
135
 
118
- == Multiple Workers For a Single Address:
136
+ By default, a worker operates on the queue with the same name as the class minus the Worker postfix. You can override
137
+ this by explicitily by specifying a queue or a virtual topic instead. A virtual_topic (ActiveMQ only) allows you to publish to one destination
138
+ and allow for multiple workers to subscribe. (TODO: need to completely remove the use of topics as every thread for every worker
139
+ receives all messages instead of a group of workers (threads) collectively receiving all messages. Virtual topics get around this
140
+ problem). For instance, suppose you have the following workers:
119
141
 
120
- By default, a worker operates on the address and queue with the same name as the class. You can override
121
- this by explicitily stating the address and queue that the worker should operate on. For instance, the
122
- Fooworker defined above is equivalent to the following:
142
+ class FooWorker
143
+ include ModernTimes::JMS::Worker
144
+ virtual_topic 'inquiry'
123
145
 
124
- class FooWorker < ModernTimes::HornetQ::Worker
125
- address 'Foo'
126
- queue 'Foo'
127
- def perform(my_foo_object)
128
- # Operate on my_foo_object
146
+ def perform(my_inquiry)
147
+ # Operate on my_inquiry
129
148
  end
130
149
  end
131
150
 
132
- You can setup multiple workers to operate on the same address. For instance, the following worker would
133
- create a separate queue that would also receive all the messages published on address 'Foo':
151
+ class BarWorker
152
+ include ModernTimes::JMS::Worker
153
+ virtual_topic 'inquiry'
134
154
 
135
- class BarWorker < ModernTimes::HornetQ::Worker
136
- address 'Foo'
137
- def perform(my_foo_object)
138
- # Operate on my_foo_object
155
+ def perform(my_inquiry)
156
+ # Operate on my_inquiry
139
157
  end
140
158
  end
141
159
 
160
+ Then you can create a publisher where messages are delivered to both workers:
161
+
162
+ @@publisher = ModernTimes::JMS::Publisher.new(:virtual_topic_name => 'inquiry')
163
+ ...
164
+ @@publisher.publish(my_inquiry)
165
+
166
+
167
+ == Requestor Pattern:
168
+
169
+ TODO: See examples/requestor
170
+
171
+
172
+ == Requestor Pattern with Multiple RequestWorkers:
173
+
174
+ TODO: See examples/advanced_requestor
175
+
142
176
 
143
177
  == What's with the name?
144
178
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.11
1
+ 0.3.0
@@ -0,0 +1,15 @@
1
+ # Step 0
2
+ # Follow the directions for configuring jms.yml located in examples/README
3
+
4
+ # Step 1
5
+ # Start an ActiveMQ Server
6
+
7
+ # Step 2
8
+ # Start up the manager
9
+ jruby manager.rb
10
+
11
+ # Step 3
12
+ jruby publish.rb foobar 4 2
13
+
14
+ # Step 4
15
+ # Play around with the options in modern_times.yml and repeat steps 2&3.
@@ -0,0 +1,13 @@
1
+ class BaseRequestWorker
2
+ include ModernTimes::JMS::RequestWorker
3
+
4
+ def perform(obj)
5
+ puts "#{self}: Received #{obj} at #{Time.now}"
6
+ sleep_time = options[:sleep] && options[:sleep].to_f
7
+ if sleep_time && sleep_time > 0.0
8
+ puts "#{self}: Sleeping for #{sleep_time} at #{Time.now}"
9
+ sleep sleep_time
10
+ end
11
+ super
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class CharCountWorker < BaseRequestWorker
2
+
3
+ virtual_topic 'test_string'
4
+ response :marshal => :bson, :time_to_live => 5000
5
+
6
+ def request(obj)
7
+ hash = Hash.new(0)
8
+ obj.each_char {|c| hash[c] += 1}
9
+ hash
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ class ExceptionRaiserWorker < BaseRequestWorker
2
+
3
+ virtual_topic 'test_string'
4
+ response :marshal => :string, :time_to_live => 5000
5
+
6
+ def request(obj)
7
+ raise "Raising dummy exception on #{obj}" if options[:raise]
8
+ "We decided not to raise on #{obj}"
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ class LengthWorker < BaseRequestWorker
2
+
3
+ virtual_topic 'test_string'
4
+ response :marshal => :ruby, :time_to_live => 5000
5
+
6
+ def request(obj)
7
+ obj.length
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
3
+
4
+ require 'modern_times'
5
+ require 'rubygems'
6
+ require 'erb'
7
+ require 'yaml'
8
+ require 'base_request_worker'
9
+ require 'char_count_worker'
10
+ require 'exception_raiser_worker'
11
+ require 'length_worker'
12
+ require 'print_worker'
13
+ require 'reverse_worker'
14
+ require 'triple_worker'
15
+
16
+ config = YAML.load(ERB.new(File.read(File.join(File.dirname(__FILE__), '..', 'jms.yml'))).result(binding))
17
+ ModernTimes::JMS::Connection.init(config)
18
+
19
+ manager = ModernTimes::Manager.new
20
+ manager.stop_on_signal
21
+ manager.persist_file = 'modern_times.yml'
22
+ manager.join
@@ -0,0 +1,32 @@
1
+ ---
2
+ CharCount:
3
+ :klass: CharCountWorker
4
+ :count: 1
5
+ :options:
6
+ :sleep: 0
7
+ ExceptionRaiser:
8
+ :klass: ExceptionRaiserWorker
9
+ :count: 1
10
+ :options:
11
+ :sleep: 0
12
+ :raise: false
13
+ Length:
14
+ :klass: LengthWorker
15
+ :count: 1
16
+ :options:
17
+ :sleep: 0
18
+ Print:
19
+ :klass: PrintWorker
20
+ :count: 1
21
+ :options:
22
+ :sleep: 0
23
+ Reverse:
24
+ :klass: ReverseWorker
25
+ :count: 1
26
+ :options:
27
+ :sleep: 0
28
+ Triple:
29
+ :klass: TripleWorker
30
+ :count: 1
31
+ :options:
32
+ :sleep: 0
@@ -0,0 +1,9 @@
1
+ class PrintWorker
2
+ include ModernTimes::JMS::Worker
3
+
4
+ virtual_topic 'test_string'
5
+
6
+ def perform(obj)
7
+ puts "#{self}: Received #{obj} at #{Time.now}"
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
3
+
4
+ require 'rubygems'
5
+ require 'erb'
6
+ require 'modern_times'
7
+ require 'yaml'
8
+
9
+ if ARGV.size < 1
10
+ $stderr.puts "Usage: {$0} <string> [<timeout>] [<sleep>]"
11
+ exit 1
12
+ end
13
+
14
+ string = ARGV[0]
15
+ timeout = (ARGV[1] || 4).to_f
16
+ sleep_time = (ARGV[2] || 2).to_f
17
+
18
+ config = YAML.load(ERB.new(File.read(File.join(File.dirname(__FILE__), '..', 'jms.yml'))).result(binding))
19
+ ModernTimes::JMS::Connection.init(config)
20
+ publisher = ModernTimes::JMS::Publisher.new(:virtual_topic_name => 'test_string', :response => true, :marshal => :string)
21
+ handle = publisher.publish(string)
22
+ sleep sleep_time
23
+
24
+ handle.read_response(timeout) do |response|
25
+ response.on_message 'CharCount' do |hash|
26
+ puts "CharCount returned #{hash.inspect}"
27
+ end
28
+ response.on_message 'Length', 'Reverse', 'Triple' do |val|
29
+ puts "#{response.name} returned #{val}"
30
+ end
31
+ response.on_message 'ExceptionRaiser' do |val|
32
+ puts "#{response.name} didn't raise an exception, returned \"#{val}\""
33
+ end
34
+ response.on_timeout 'Reverse' do
35
+ puts "Reverse has it's own timeout handler"
36
+ end
37
+ response.on_timeout do
38
+ puts "#{response.name} did not respond in time"
39
+ end
40
+ response.on_remote_exception 'ExceptionRaiser' do |e|
41
+ puts "It figures that ExceptionRaiser would raise an exception: #{e.message}"
42
+ end
43
+ response.on_remote_exception do |e|
44
+ puts "#{response.name} raised an exception: #{e.message}"
45
+ end
46
+ end
@@ -0,0 +1,9 @@
1
+ class ReverseWorker < BaseRequestWorker
2
+
3
+ virtual_topic 'test_string'
4
+ response :marshal => :string, :time_to_live => 5000
5
+
6
+ def request(obj)
7
+ obj.reverse
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class TripleWorker < BaseRequestWorker
2
+
3
+ virtual_topic 'test_string'
4
+ response :marshal => :string, :time_to_live => 5000
5
+
6
+ def request(obj)
7
+ obj * 3
8
+ end
9
+ end