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
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