modern_times 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/LICENSE.txt +201 -0
  2. data/README.rdoc +143 -0
  3. data/Rakefile +28 -0
  4. data/VERSION +1 -0
  5. data/examples/simple/.gitignore +2 -0
  6. data/examples/simple/README +24 -0
  7. data/examples/simple/bar_worker.rb +6 -0
  8. data/examples/simple/baz_worker.rb +8 -0
  9. data/examples/simple/hornetq.yml +14 -0
  10. data/examples/simple/manager.rb +17 -0
  11. data/examples/simple/publish.rb +35 -0
  12. data/lib/modern_times/base/supervisor.rb +97 -0
  13. data/lib/modern_times/base/supervisor_mbean.rb +30 -0
  14. data/lib/modern_times/base/worker.rb +55 -0
  15. data/lib/modern_times/base.rb +3 -0
  16. data/lib/modern_times/exception.rb +4 -0
  17. data/lib/modern_times/hornetq/client.rb +53 -0
  18. data/lib/modern_times/hornetq/marshal_strategy/json.rb +17 -0
  19. data/lib/modern_times/hornetq/marshal_strategy/ruby.rb +17 -0
  20. data/lib/modern_times/hornetq/marshal_strategy/string.rb +17 -0
  21. data/lib/modern_times/hornetq/marshal_strategy.rb +3 -0
  22. data/lib/modern_times/hornetq/publisher.rb +72 -0
  23. data/lib/modern_times/hornetq/supervisor.rb +19 -0
  24. data/lib/modern_times/hornetq/supervisor_mbean.rb +12 -0
  25. data/lib/modern_times/hornetq/worker.rb +121 -0
  26. data/lib/modern_times/hornetq.rb +11 -0
  27. data/lib/modern_times/loggable.rb +23 -0
  28. data/lib/modern_times/manager.rb +92 -0
  29. data/lib/modern_times/manager_mbean.rb +36 -0
  30. data/lib/modern_times/railsable.rb +132 -0
  31. data/lib/modern_times/thread.rb +16 -0
  32. data/lib/modern_times.rb +13 -0
  33. data/test/base/worker_test.rb +38 -0
  34. data/test/messaging/worker_manager_test.rb +58 -0
  35. data/test/messaging/worker_test.rb +58 -0
  36. data/test/worker_manager_test.rb +48 -0
  37. metadata +123 -0
data/LICENSE.txt ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2011 Clarity Services, Inc.
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,143 @@
1
+ = modern_times
2
+
3
+ * http://github.com/ClarityServices/modern_times
4
+
5
+ == Description:
6
+
7
+ JRuby library for performing background tasks via the hornetq messaging library.
8
+
9
+ == Features/Problems:
10
+
11
+ * TBD
12
+
13
+ == Install:
14
+
15
+ gem install modern_times
16
+
17
+ == Rails Usage:
18
+
19
+ Create config/hornetq.yml which might look as follows:
20
+
21
+ development:
22
+ :connection: hornetq://invm
23
+
24
+ production:
25
+ :connection:
26
+ :uri: hornetq://msg1,msg2
27
+ :failover_on_initial_connection: true
28
+ :failover_on_server_shutdown: true
29
+ # 5 Connection attempts takes about 16 seconds before it switches to the backup server
30
+ :reconnect_attempts: 5
31
+ :session:
32
+ :username: mycluster_username
33
+ :password: mycluster_password
34
+
35
+ In config/environment.rb, add the following lines:
36
+
37
+ ModernTimes.init_rails
38
+ # Publishers can be defined wherever appropriate
39
+ $foo_publisher = ModernTimes::HornetQ::Publisher.new('Foo')
40
+
41
+ In your code, queue foo objects:
42
+
43
+ $foo_publisher.publish(my_foo_object)
44
+
45
+ In app/workers, create a FooWorker class:
46
+
47
+ class FooWorker < ModernTimes::HornetQ::Worker
48
+ def perform(my_foo_object)
49
+ # Operate on my_foo_object
50
+ end
51
+ end
52
+
53
+ Based on the hornetq.yml file above, a development mode connection will be created such that
54
+ the queueing is handled in-memory (since the uri is 'hornetq://invm').
55
+ When this is used, a single thread (by default) will be created for each defined Worker so queued
56
+ messages will be handled asynchronously within the same process.
57
+
58
+ In test mode, there is no configuration defined. In this case, published messages will cause
59
+ synchronous calls to the Worker's perform method.
60
+
61
+ In production mode, you will need to start up a hornetq server to distribute the messages. For the
62
+ configuration above, you might create a hornetq_server.yml file as follows:
63
+
64
+ # backup server runs on msg2
65
+ backup_server:
66
+ :uri: hornetq://0.0.0.0
67
+ :backup: true
68
+ :data_directory: /var/lib/hornetq/data
69
+ :persistence_enabled: true
70
+ :security_enabled: true
71
+ :cluster_user: mycluster_username
72
+ :cluster_password: mycluster_password
73
+
74
+ # live server runs on msg1
75
+ live_server:
76
+ :uri: hornetq://0.0.0.0,msg2
77
+ :data_directory: /var/lib/hornetq/data
78
+ :persistence_enabled: true
79
+ :security_enabled: true
80
+ :cluster_user: mycluster_username
81
+ :cluster_password: mycluster_password
82
+
83
+ Then on host msg2 and msg1, you could startup the servers with the following commands:
84
+
85
+ # On msg2
86
+ hornetq_server hornetq_server.yml backup_server
87
+ # On msg1
88
+ hornetq_server hornetq_server.yml live_server
89
+
90
+ For the production environment, you will need to startup a Manager process to handle messages. You
91
+ might create script/worker_manager as follows (assumes Rails.root/script is in your PATH):
92
+
93
+ #!/usr/bin/env runner
94
+
95
+ manager = ModernTimes.create_rails_manager
96
+ manager.join
97
+
98
+ TODO: Refer to example jsvc daemon script
99
+
100
+ Configure your workers by starting jconsole and connecting to
101
+ the manager process. Go to the MBeans tab and open the tree to
102
+ ModernTimes => Manager => Operations => start_worker
103
+
104
+ Start/stop/increase/decrease workers as needed. The state is stored in the log directory (by default)
105
+ so you can stop and start the manager and not have to reconfigure your workers.
106
+ TODO: This is flaky right now due to bugs in jruby-jmx.
107
+
108
+ == Multiple Workers For a Single Address:
109
+
110
+ By default, a worker operates on the address and queue with the same name as the class. You can override
111
+ this by explicitily stating the address and queue that the worker should operate on. For instance, the
112
+ Fooworker defined above is equivalent to the following:
113
+
114
+ class FooWorker < ModernTimes::HornetQ::Worker
115
+ address 'Foo'
116
+ queue 'Foo'
117
+ def perform(my_foo_object)
118
+ # Operate on my_foo_object
119
+ end
120
+ end
121
+
122
+ You can setup multiple workers to operate on the same address. For instance, the following worker would
123
+ create a separate queue that would also receive all the messages published on address 'Foo':
124
+
125
+ class BarWorker < ModernTimes::HornetQ::Worker
126
+ address 'Foo'
127
+ def perform(my_foo_object)
128
+ # Operate on my_foo_object
129
+ end
130
+ end
131
+
132
+
133
+ == What's with the name?
134
+
135
+ I'm a Chaplin fan.
136
+
137
+ == Author
138
+
139
+ Brad Pardee, Reid Morrison
140
+
141
+ == Copyright
142
+
143
+ Copyright (c) 2011 Clarity Services. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = 'modern_times'
8
+ gemspec.summary = 'Asynchronous task library'
9
+ gemspec.description = 'Generic asynchronous task library'
10
+ gemspec.authors = ['Brad Pardee', 'Reid Morrison']
11
+ gemspec.email = ['bradpardee@gmail.com', 'rubywmq@gmail.com']
12
+ gemspec.homepage = 'http://github.com/ClarityServices/modern_times'
13
+ gemspec.add_dependency 'jruby-hornetq', ['>= 0.3.2']
14
+ gemspec.add_dependency 'jmx'
15
+ end
16
+ rescue LoadError
17
+ puts 'Jeweler not available. Install it with: gem install jeweler'
18
+ end
19
+
20
+ desc "Run Test Suite"
21
+ task :test do
22
+ Rake::TestTask.new(:functional) do |t|
23
+ t.test_files = FileList['test/*_test.rb']
24
+ t.verbose = true
25
+ end
26
+
27
+ Rake::Task['functional'].invoke
28
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,2 @@
1
+ data
2
+ *.state
@@ -0,0 +1,24 @@
1
+ # Step 1
2
+ # Start a hornetq server (gem install jruby-hornetq if necessary)
3
+ hornetq_server hornetq.yml server
4
+
5
+ # Step 2
6
+ # Start up the manager
7
+ jruby manager.rb
8
+
9
+ # Step 3
10
+ # Start up jconsole
11
+ # Attach to the manager process
12
+ # Go to the MBeans tab
13
+ # Open up the tree to ModernTimes => Manager => Operations => start_worker
14
+ # Enter BarWorker for worker and 2 for count and click the start_worker button
15
+ # Enter BazWorker for worker and 3 for count and click the start_worker button
16
+
17
+ # Step 4
18
+ # Publish 10 messages to the BarWorker and 20 to the BazWorker
19
+ jruby publish.rb 10 5
20
+
21
+ # Step 5
22
+ # cntl-c the manager.rb process and start it back up. It should come back with
23
+ # the workers that have been configured via jconsole
24
+
@@ -0,0 +1,6 @@
1
+ class BarWorker < ModernTimes::HornetQ::Worker
2
+ def perform(obj)
3
+ puts "#{self}: Received #{obj.inspect} at #{Time.now}"
4
+ sleep 5
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ class BazWorker < ModernTimes::HornetQ::Worker
2
+ include ModernTimes::HornetQ::MarshalStrategy::String
3
+
4
+ def perform(obj)
5
+ puts "#{self}: Received #{obj} at #{Time.now}"
6
+ sleep 10
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ server:
2
+ :uri: hornetq://localhost
3
+ :data_directory: ./data
4
+ :persistence_enabled: true
5
+ :security_enabled: true
6
+ :cluster_user: myuser
7
+ :cluster_password: mypassword
8
+
9
+ client:
10
+ :connection:
11
+ :uri: hornetq://localhost
12
+ :session:
13
+ :username: myuser
14
+ :password: mypassword
@@ -0,0 +1,17 @@
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 'modern_times'
6
+ require 'yaml'
7
+ require 'bar_worker'
8
+ require 'baz_worker'
9
+
10
+ config = YAML.load_file('hornetq.yml')
11
+ ModernTimes::HornetQ::Client.init(config['client'])
12
+
13
+ manager = ModernTimes::Manager.new(:persist_file => 'modern_times.state')
14
+ manager.stop_on_signal
15
+ manager.allowed_workers = [BarWorker,BazWorker]
16
+ #manager.add(BarWorker, 2, {})
17
+ manager.join
@@ -0,0 +1,35 @@
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 'modern_times'
6
+ require 'yaml'
7
+ require 'bar_worker'
8
+ require 'baz_worker'
9
+
10
+ if ARGV.size < 2
11
+ $stderr.puts "Usage: {$0} <bar-publish-count> <baz-publish-count> [sleep-time]"
12
+ end
13
+
14
+ bar_count = ARGV[0].to_i
15
+ baz_count = ARGV[1].to_i
16
+ sleep_time = (ARGV[2] || 0.2).to_f
17
+
18
+ config = YAML.load_file('hornetq.yml')
19
+ ModernTimes::HornetQ::Client.init(config['client'])
20
+ bar_publisher = ModernTimes::HornetQ::Publisher.new(BarWorker.address_name)
21
+ baz_publisher = ModernTimes::HornetQ::Publisher.new(BazWorker.address_name, :marshal => :string)
22
+
23
+ (1..bar_count).each do |i|
24
+ obj = {:message => i}
25
+ puts "Publishing to Bar object: #{obj.inspect}"
26
+ bar_publisher.publish(obj)
27
+ sleep sleep_time
28
+ end
29
+
30
+ (1..baz_count).each do |i|
31
+ obj = "Message ##{i}"
32
+ puts "Publishing to Baz object: #{obj}"
33
+ baz_publisher.publish(obj)
34
+ sleep sleep_time
35
+ end
@@ -0,0 +1,97 @@
1
+ module ModernTimes
2
+ module Base
3
+ class Supervisor
4
+ attr_reader :manager, :worker_klass
5
+
6
+ def initialize(manager, worker_klass, options)
7
+ @stopped = false
8
+ @manager = manager
9
+ @worker_klass = worker_klass
10
+ @workers = []
11
+ @worker_mutex = Mutex.new
12
+ @failure_mutex = Mutex.new
13
+ end
14
+
15
+ def worker_count
16
+ @workers.size
17
+ end
18
+
19
+ def worker_count=(count)
20
+ @worker_mutex.synchronize do
21
+ ModernTimes.logger.info "#{@worker_klass.name}: Changing number of workers from #{@workers.size} to #{count}"
22
+ raise "#{@worker_klass.name}: Can't change worker_count, this manager has been stopped" if stopped?
23
+ curr_count = @workers.size
24
+ if curr_count < count
25
+ (curr_count...count).each do |index|
26
+ worker = @worker_klass.new
27
+ worker.supervisor = self
28
+ worker.index = index
29
+ if index == 0
30
+ # HornetQ hack: If I create the session in the jmx thread, it dies with no feedback
31
+ tmp_thread = Thread.new do
32
+ worker.setup
33
+ end
34
+ tmp_thread.join
35
+ end
36
+ worker.thread = Thread.new do
37
+ #ModernTimes.logger.debug "#{worker}: Started thread with priority #{Thread.current.priority}"
38
+ worker.start
39
+ end
40
+ @workers << worker
41
+ end
42
+ elsif curr_count > count
43
+ (count...curr_count).each { |index| @workers[index].stop }
44
+ (count...curr_count).each { |index| @workers[index].thread.join }
45
+ @workers = @workers[0, count]
46
+ end
47
+ manager.save_persist_state
48
+ end
49
+ end
50
+
51
+ def worker_status(index)
52
+ @workers[index].status
53
+ end
54
+
55
+ def stop
56
+ @worker_mutex.synchronize do
57
+ @stopped = true
58
+ @workers.each { |worker| worker.stop }
59
+ end
60
+ end
61
+
62
+ def stopped?
63
+ @stopped
64
+ end
65
+
66
+ def join
67
+ @workers.each { |worker| worker.thread.join }
68
+ end
69
+
70
+ def failure(worker, message)
71
+
72
+ end
73
+
74
+ def self.mbean(klass, options={})
75
+ # self.class.class_eval do
76
+ # define_method :create_mbean do |domain, supervisor, worker_klass|
77
+ # klass.new("#{domain}.Worker.#{worker_klass.name}", "Supervisor for #{worker_klass.name}", supervisor, options)
78
+ # end
79
+ # end
80
+ # TODO: This is nasty but I'm not sure how to create a dynamic class method within a scope
81
+ eval <<-EOS
82
+ def self.create_mbean(domain, supervisor, worker_klass)
83
+ #{klass.name}.new("\#{domain}.Worker.\#{worker_klass.name}", "Supervisor for \#{worker_klass.name}", supervisor, #{options.inspect})
84
+ end
85
+ EOS
86
+ end
87
+
88
+ def create_mbean(domain)
89
+ self.class.create_mbean(domain, self, @worker_klass)
90
+ end
91
+
92
+ #########
93
+ protected
94
+ #########
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,30 @@
1
+ require 'jmx'
2
+
3
+ module ModernTimes
4
+ module Base
5
+ class SupervisorMBean < RubyDynamicMBean
6
+ attr_reader :supervisor
7
+ rw_attribute :worker_count, :int, "Number of workers"
8
+
9
+ def initialize(name, description, supervisor, options)
10
+ super(name, description)
11
+ @supervisor = supervisor
12
+ end
13
+
14
+ def worker_count
15
+ supervisor.worker_count
16
+ end
17
+
18
+ def worker_count=(count)
19
+ supervisor.worker_count = count
20
+ end
21
+
22
+ operation 'Get the worker status'
23
+ parameter :int, "index", "Index of the worker"
24
+ returns :string
25
+ def worker_status(index)
26
+ supervisor.worker_status(index)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ module ModernTimes
2
+ module Base
3
+ class Worker
4
+ attr_accessor :name, :index, :supervisor, :thread
5
+
6
+ def self.supervisor(klass, options={})
7
+ puts "calling supervisor with klass=#{klass.name} and class=#{self.name}"
8
+ # self.class_eval do
9
+ # define_method :create_supervisor do |manager|
10
+ # puts "calling create_supervisor for klass-#{klass.name} and self=#{self} and manager=#{manager}"
11
+ # klass.new(manager, self, options)
12
+ # end
13
+ # end
14
+ # TODO: This is nasty but I'm not sure how to create a dynamic class method within a scope
15
+ eval <<-EOS
16
+ def self.create_supervisor(manager)
17
+ #{klass.name}.new(manager, self, #{options.inspect})
18
+ end
19
+ EOS
20
+ end
21
+
22
+ # Default supervisor is Base::Supervisor
23
+ supervisor Supervisor
24
+
25
+ def initialize(opts={})
26
+ @name = opts[:name] || self.class.default_name
27
+ end
28
+
29
+ def name
30
+ @name
31
+ end
32
+
33
+ # One time initialization prior to first thread
34
+ def setup
35
+ end
36
+
37
+ def start
38
+ raise "Need to override start method in #{self.class.name}"
39
+ end
40
+
41
+ def stop
42
+ raise "Need to override stop method in #{self.class.name}"
43
+ end
44
+
45
+ def status
46
+ raise "Need to override status method in #{self.class.name}"
47
+ end
48
+
49
+ def self.default_name
50
+ name = self.name.sub(/Worker$/, '')
51
+ name.sub(/::/, '_')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ require 'modern_times/base/supervisor'
2
+ require 'modern_times/base/supervisor_mbean'
3
+ require 'modern_times/base/worker'