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