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.
- data/README.rdoc +114 -80
- data/VERSION +1 -1
- data/examples/advanced_requestor/README +15 -0
- data/examples/advanced_requestor/base_request_worker.rb +13 -0
- data/examples/advanced_requestor/char_count_worker.rb +11 -0
- data/examples/advanced_requestor/exception_raiser_worker.rb +10 -0
- data/examples/advanced_requestor/length_worker.rb +9 -0
- data/examples/advanced_requestor/manager.rb +22 -0
- data/examples/advanced_requestor/modern_times.yml +32 -0
- data/examples/advanced_requestor/print_worker.rb +9 -0
- data/examples/advanced_requestor/publish.rb +46 -0
- data/examples/advanced_requestor/reverse_worker.rb +9 -0
- data/examples/advanced_requestor/triple_worker.rb +9 -0
- data/examples/requestor/request.rb +3 -3
- data/examples/requestor/reverse_echo_worker.rb +1 -2
- data/lib/modern_times.rb +1 -1
- data/lib/modern_times/base/supervisor.rb +2 -0
- data/lib/modern_times/base/worker.rb +5 -3
- data/lib/modern_times/jms.rb +2 -0
- data/lib/modern_times/jms/connection.rb +7 -0
- data/lib/modern_times/jms/publish_handle.rb +219 -0
- data/lib/modern_times/jms/publisher.rb +55 -29
- data/lib/modern_times/{jms_requestor/worker.rb → jms/request_worker.rb} +29 -51
- data/lib/modern_times/jms/supervisor.rb +30 -0
- data/lib/modern_times/jms/supervisor_mbean.rb +17 -1
- data/lib/modern_times/jms/worker.rb +43 -40
- data/lib/modern_times/manager.rb +6 -2
- data/lib/modern_times/marshal_strategy.rb +14 -17
- data/lib/modern_times/marshal_strategy/bson.rb +2 -0
- data/lib/modern_times/marshal_strategy/json.rb +3 -0
- data/lib/modern_times/marshal_strategy/ruby.rb +3 -0
- data/lib/modern_times/marshal_strategy/string.rb +3 -0
- data/lib/modern_times/marshal_strategy/yaml.rb +3 -0
- data/lib/modern_times/railsable.rb +7 -14
- data/lib/modern_times/time_track.rb +84 -0
- data/test/jms.yml +1 -0
- data/test/jms_failure_test.rb +128 -0
- data/test/jms_requestor_block_test.rb +275 -0
- data/test/jms_requestor_test.rb +71 -96
- data/test/jms_test.rb +59 -78
- data/test/marshal_strategy_test.rb +1 -3
- metadata +29 -14
- data/examples/exception_test/bar_worker.rb +0 -8
- data/examples/exception_test/base_worker.rb +0 -23
- data/examples/exception_test/manager.rb +0 -11
- data/lib/modern_times/jms_requestor.rb +0 -10
- data/lib/modern_times/jms_requestor/request_handle.rb +0 -42
- data/lib/modern_times/jms_requestor/requestor.rb +0 -56
- data/lib/modern_times/jms_requestor/supervisor.rb +0 -45
- 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
|
-
|
9
|
+
Still alpha but approaching beta. API still subject to change.
|
10
10
|
|
11
11
|
== Features/Problems:
|
12
12
|
|
13
|
-
* jms_test
|
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
|
-
*
|
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
|
-
|
31
|
-
:
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
142
|
+
class FooWorker
|
143
|
+
include ModernTimes::JMS::Worker
|
144
|
+
virtual_topic 'inquiry'
|
123
145
|
|
124
|
-
|
125
|
-
|
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
|
-
|
133
|
-
|
151
|
+
class BarWorker
|
152
|
+
include ModernTimes::JMS::Worker
|
153
|
+
virtual_topic 'inquiry'
|
134
154
|
|
135
|
-
|
136
|
-
|
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.
|
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,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,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,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
|