qwirk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +7 -0
- data/LICENSE.txt +201 -0
- data/README.md +180 -0
- data/Rakefile +34 -0
- data/examples/README +1 -0
- data/examples/activemq.xml +84 -0
- data/examples/advanced_requestor/README.md +15 -0
- data/examples/advanced_requestor/base_request_worker.rb +18 -0
- data/examples/advanced_requestor/char_count_worker.rb +16 -0
- data/examples/advanced_requestor/config.ru +24 -0
- data/examples/advanced_requestor/exception_raiser_worker.rb +17 -0
- data/examples/advanced_requestor/length_worker.rb +14 -0
- data/examples/advanced_requestor/print_worker.rb +14 -0
- data/examples/advanced_requestor/publisher.rb +49 -0
- data/examples/advanced_requestor/qwirk.yml +16 -0
- data/examples/advanced_requestor/reverse_worker.rb +14 -0
- data/examples/advanced_requestor/triple_worker.rb +14 -0
- data/examples/batch/my_batch_worker.rb +30 -0
- data/examples/batch/my_line_worker.rb +8 -0
- data/examples/qwirk.yml +20 -0
- data/examples/requestor/README.md +13 -0
- data/examples/requestor/config.ru +13 -0
- data/examples/requestor/qwirk_persist.yml +5 -0
- data/examples/requestor/requestor.rb +68 -0
- data/examples/requestor/reverse_echo_worker.rb +15 -0
- data/examples/setup.rb +13 -0
- data/examples/shared/README.md +24 -0
- data/examples/shared/config.ru +13 -0
- data/examples/shared/publisher.rb +49 -0
- data/examples/shared/qwirk_persist.yml +5 -0
- data/examples/shared/shared_worker.rb +16 -0
- data/examples/simple/README +53 -0
- data/examples/simple/bar_worker.rb +10 -0
- data/examples/simple/baz_worker.rb +10 -0
- data/examples/simple/config.ru +14 -0
- data/examples/simple/publisher.rb +49 -0
- data/examples/simple/qwirk_persist.yml +4 -0
- data/examples/simple/tmp/kahadb/db-1.log +0 -0
- data/examples/simple/tmp/kahadb/db.data +0 -0
- data/examples/simple/tmp/kahadb/db.redo +0 -0
- data/examples/task/README +47 -0
- data/examples/task/config.ru +14 -0
- data/examples/task/foo_worker.rb +10 -0
- data/examples/task/messages.out +1000 -0
- data/examples/task/publisher.rb +25 -0
- data/examples/task/qwirk_persist.yml +5 -0
- data/examples/task/task.rb +36 -0
- data/lib/qwirk.rb +63 -0
- data/lib/qwirk/adapter.rb +45 -0
- data/lib/qwirk/base_worker.rb +96 -0
- data/lib/qwirk/batch.rb +4 -0
- data/lib/qwirk/batch/acquire_file_strategy.rb +47 -0
- data/lib/qwirk/batch/active_record.rb +3 -0
- data/lib/qwirk/batch/active_record/batch_job.rb +111 -0
- data/lib/qwirk/batch/active_record/failed_record.rb +5 -0
- data/lib/qwirk/batch/active_record/outstanding_record.rb +6 -0
- data/lib/qwirk/batch/file_status_strategy.rb +86 -0
- data/lib/qwirk/batch/file_worker.rb +228 -0
- data/lib/qwirk/batch/job_status.rb +29 -0
- data/lib/qwirk/batch/parse_file_strategy.rb +48 -0
- data/lib/qwirk/engine.rb +9 -0
- data/lib/qwirk/loggable.rb +23 -0
- data/lib/qwirk/manager.rb +140 -0
- data/lib/qwirk/marshal_strategy.rb +74 -0
- data/lib/qwirk/marshal_strategy/bson.rb +37 -0
- data/lib/qwirk/marshal_strategy/json.rb +37 -0
- data/lib/qwirk/marshal_strategy/none.rb +26 -0
- data/lib/qwirk/marshal_strategy/ruby.rb +26 -0
- data/lib/qwirk/marshal_strategy/string.rb +25 -0
- data/lib/qwirk/marshal_strategy/yaml.rb +25 -0
- data/lib/qwirk/publish_handle.rb +170 -0
- data/lib/qwirk/publisher.rb +67 -0
- data/lib/qwirk/queue_adapter.rb +3 -0
- data/lib/qwirk/queue_adapter/active_mq.rb +13 -0
- data/lib/qwirk/queue_adapter/active_mq/publisher.rb +12 -0
- data/lib/qwirk/queue_adapter/active_mq/worker_config.rb +16 -0
- data/lib/qwirk/queue_adapter/in_mem.rb +7 -0
- data/lib/qwirk/queue_adapter/in_mem/factory.rb +45 -0
- data/lib/qwirk/queue_adapter/in_mem/publisher.rb +98 -0
- data/lib/qwirk/queue_adapter/in_mem/queue.rb +88 -0
- data/lib/qwirk/queue_adapter/in_mem/reply_queue.rb +56 -0
- data/lib/qwirk/queue_adapter/in_mem/topic.rb +48 -0
- data/lib/qwirk/queue_adapter/in_mem/worker.rb +63 -0
- data/lib/qwirk/queue_adapter/in_mem/worker_config.rb +59 -0
- data/lib/qwirk/queue_adapter/jms.rb +50 -0
- data/lib/qwirk/queue_adapter/jms/connection.rb +42 -0
- data/lib/qwirk/queue_adapter/jms/consumer.rb +37 -0
- data/lib/qwirk/queue_adapter/jms/publisher.rb +126 -0
- data/lib/qwirk/queue_adapter/jms/worker.rb +89 -0
- data/lib/qwirk/queue_adapter/jms/worker_config.rb +38 -0
- data/lib/qwirk/remote_exception.rb +42 -0
- data/lib/qwirk/request_worker.rb +62 -0
- data/lib/qwirk/task.rb +177 -0
- data/lib/qwirk/task.rb.sav +194 -0
- data/lib/qwirk/version.rb +3 -0
- data/lib/qwirk/worker.rb +222 -0
- data/lib/qwirk/worker_config.rb +187 -0
- data/lib/rails/generators/qwirk/qwirk_generator.rb +82 -0
- data/lib/rails/generators/qwirk/templates/initializer.rb +6 -0
- data/lib/rails/generators/qwirk/templates/migration.rb +9 -0
- data/lib/rails/generators/qwirk/templates/schema.rb +28 -0
- data/lib/rails/railties/tasks.rake +8 -0
- data/lib/tasks/qwirk_tasks.rake +4 -0
- data/test/base_test.rb +248 -0
- data/test/database.yml +14 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +26 -0
- data/test/dummy/config/environments/production.rb +49 -0
- data/test/dummy/config/environments/test.rb +35 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/log/development.log +0 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/log/server.log +0 -0
- data/test/dummy/log/test.log +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +2 -0
- data/test/dummy/public/javascripts/controls.js +965 -0
- data/test/dummy/public/javascripts/dragdrop.js +974 -0
- data/test/dummy/public/javascripts/effects.js +1123 -0
- data/test/dummy/public/javascripts/prototype.js +6001 -0
- data/test/dummy/public/javascripts/rails.js +191 -0
- data/test/dummy/script/rails +6 -0
- data/test/integration/navigation_test.rb +7 -0
- data/test/jms.yml +9 -0
- data/test/jms_fail_test.rb +149 -0
- data/test/jms_requestor_block_test.rb +278 -0
- data/test/jms_requestor_test.rb +238 -0
- data/test/jms_test.rb +287 -0
- data/test/marshal_strategy_test.rb +62 -0
- data/test/support/integration_case.rb +5 -0
- data/test/test_helper.rb +7 -0
- data/test/test_helper.rbold +22 -0
- data/test/test_helper_active_record.rb +61 -0
- data/test/unit/qwirk/batch/acquire_file_strategy_test.rb +101 -0
- data/test/unit/qwirk/batch/active_record/batch_job_test.rb +35 -0
- data/test/unit/qwirk/batch/parse_file_strategy_test.rb +49 -0
- metadata +366 -0
data/lib/qwirk/worker.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
module Qwirk
|
2
|
+
|
3
|
+
# Base Worker Class for any class that will be processing messages from topics or queues
|
4
|
+
# By default, it will consume messages from a queue with the class name minus the Worker postfix.
|
5
|
+
# For example, the queue call is unnecessary as it will default to a value of 'Foo' anyways:
|
6
|
+
# class FooWorker
|
7
|
+
# include Qwirk::QueueAdapter::JMS::Worker
|
8
|
+
# queue 'Foo'
|
9
|
+
# def perform(obj)
|
10
|
+
# # Perform work on obj
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# A topic can also be specified. Note that for JMS, this is only supported under ActiveMQ. On others,
|
15
|
+
# each thread for a given worker will act as a separate subscriber.
|
16
|
+
# (For ActiveMQ - see http://activemq.apache.org/virtual-destinations.html):
|
17
|
+
# class FooWorker
|
18
|
+
# include Qwirk::QueueAdapter::JMS::Worker
|
19
|
+
# topic 'Zulu'
|
20
|
+
# def perform(obj)
|
21
|
+
# # Perform work on obj
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# TODO (maybe):
|
26
|
+
# Filters can also be specified within the class:
|
27
|
+
# class FooWorker
|
28
|
+
# include Qwirk::QueueAdapter::JMS::Worker
|
29
|
+
# filter 'age > 30'
|
30
|
+
# def perform(obj)
|
31
|
+
# # Perform work on obj
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
#
|
36
|
+
module Worker
|
37
|
+
include Qwirk::BaseWorker
|
38
|
+
|
39
|
+
attr_accessor :message
|
40
|
+
attr_reader :status, :adapter, :start_worker_time, :start_read_time, :start_processing_time
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def queue(name, opts={})
|
44
|
+
# If we're using the default name but we still want to set queue options, then a name won't be given.
|
45
|
+
if name.kind_of?(Hash)
|
46
|
+
@queue_options = name
|
47
|
+
else
|
48
|
+
@queue_name = name.to_s
|
49
|
+
@queue_options = opts
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def topic(name, options={})
|
54
|
+
@topic_name = name.to_s
|
55
|
+
@queue_options = options
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set the fail_queue
|
59
|
+
# target =>
|
60
|
+
# boolean
|
61
|
+
# true - exceptions in the worker will cause the message to be forwarded to the queue of <default-name>Fail
|
62
|
+
# For instance, an Exception in FooWorker#perform will forward the message to the queue FooFail
|
63
|
+
# false - exceptions will not result in the message being forwarded to a fail queue
|
64
|
+
# string - equivalent to true but the string defines the name of the fail queue
|
65
|
+
def fail_queue(target, opts={})
|
66
|
+
@fail_queue_target = target
|
67
|
+
end
|
68
|
+
|
69
|
+
def fail_queue_target
|
70
|
+
@fail_queue_target
|
71
|
+
end
|
72
|
+
|
73
|
+
# Defines the default value of the fail_queue_target. For extenders of this class, the default will be true
|
74
|
+
# but extenders can change this (RequestWorker returns exceptions to the caller so it defaults to false).
|
75
|
+
def default_fail_queue_target
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def queue_name(default_name)
|
80
|
+
puts "getting queue_name queue=#{@queue_name} topic=#{@topic_name} default=#{default_name}"
|
81
|
+
return @queue_name if @queue_name
|
82
|
+
return nil if @topic_name
|
83
|
+
return default_name
|
84
|
+
end
|
85
|
+
|
86
|
+
def topic_name
|
87
|
+
@topic_name
|
88
|
+
end
|
89
|
+
|
90
|
+
def queue_options
|
91
|
+
@queue_options ||= {}
|
92
|
+
end
|
93
|
+
|
94
|
+
def fail_queue_name(worker_config)
|
95
|
+
# TBD - Set up fail_queue as a config
|
96
|
+
target = self.class.fail_queue_target
|
97
|
+
# Don't overwrite if the user set to false, only if it was never set
|
98
|
+
target = self.class.default_fail_queue_target if target.nil?
|
99
|
+
if target == true
|
100
|
+
return Qwirk.fail_queue_name(config.name)
|
101
|
+
elsif target == false
|
102
|
+
return nil
|
103
|
+
elsif target.kind_of?(String)
|
104
|
+
return target
|
105
|
+
else
|
106
|
+
raise "Invalid fail queue: #{target}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.included(base)
|
112
|
+
Qwirk::BaseWorker.included(base)
|
113
|
+
base.extend(ClassMethods)
|
114
|
+
end
|
115
|
+
|
116
|
+
def start(index, worker_config)
|
117
|
+
@status = 'Started'
|
118
|
+
@stopped = false
|
119
|
+
@processing_mutex = Mutex.new
|
120
|
+
self.index = index
|
121
|
+
self.config = worker_config
|
122
|
+
@adapter = worker_config.adapter.create_worker
|
123
|
+
self.thread = Thread.new do
|
124
|
+
java.lang.Thread.current_thread.name = "Qwirk worker: #{self}" if RUBY_PLATFORM == 'jruby'
|
125
|
+
#Qwirk.logger.debug "#{worker}: Started thread with priority #{Thread.current.priority}"
|
126
|
+
event_loop
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Workers will be starting and stopping on an as needed basis. Thus, when they get a stop command they should
|
131
|
+
# clean up any resources. We don't want to clobber resources while a message is being processed so processing_mutex will surround
|
132
|
+
# message processessing and worker closing.
|
133
|
+
# From a JMS perspective, stop all workers (close consumer and session), stop the config.
|
134
|
+
# From an InMem perspective, we don't want the workers stopping until all messages in the queue have been processed.
|
135
|
+
# Therefore we want to stop the
|
136
|
+
def stop
|
137
|
+
puts "#{self}: In base worker stop"
|
138
|
+
@status = 'Stopping'
|
139
|
+
@stopped = true
|
140
|
+
@processing_mutex.synchronize do
|
141
|
+
# This should interrupt @adapter.receive_message above and cause it to return nil
|
142
|
+
@adapter.stop
|
143
|
+
end
|
144
|
+
puts "#{self}: base worker stop complete"
|
145
|
+
end
|
146
|
+
|
147
|
+
def perform(object)
|
148
|
+
raise "#{self}: Need to override perform method in #{self.class.name} in order to act on #{object}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
"#{config.name}:#{index}"
|
153
|
+
end
|
154
|
+
|
155
|
+
# Allow override of backtrace logging in case the client doesn't want to get spammed with it (maybe just config instead?)
|
156
|
+
def log_backtrace(e)
|
157
|
+
Qwirk.logger.error "\t#{e.backtrace.join("\n\t")}"
|
158
|
+
end
|
159
|
+
|
160
|
+
#########
|
161
|
+
protected
|
162
|
+
#########
|
163
|
+
|
164
|
+
# Start the event loop for handling messages off the queue
|
165
|
+
def event_loop
|
166
|
+
Qwirk.logger.debug "#{self}: Starting receive loop"
|
167
|
+
@start_worker_time = Time.now
|
168
|
+
while !@stopped && !config.adapter.stopped
|
169
|
+
puts "#{self}: Waiting for read"
|
170
|
+
@start_read_time = Time.now
|
171
|
+
msg = @adapter.receive_message
|
172
|
+
if msg
|
173
|
+
@start_processing_time = Time.now
|
174
|
+
Qwirk.logger.debug {"#{self}: Done waiting for read in #{@start_processing_time - @start_read_time} seconds"}
|
175
|
+
delta = config.timer.measure do
|
176
|
+
@processing_mutex.synchronize do
|
177
|
+
on_message(msg)
|
178
|
+
@adapter.acknowledge_message(msg)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
Qwirk.logger.info {"#{self}::on_message (#{'%.1f' % delta}ms)"} if self.config.log_times
|
182
|
+
Qwirk.logger.flush if Qwirk.logger.respond_to?(:flush)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
Qwirk.logger.info "#{self}: Exiting"
|
186
|
+
rescue Exception => e
|
187
|
+
@status = "Terminated: #{e.message}"
|
188
|
+
Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
189
|
+
ensure
|
190
|
+
@status = 'Stopped'
|
191
|
+
# TODO: necessary?
|
192
|
+
@adapter.stop
|
193
|
+
Qwirk.logger.flush if Qwirk.logger.respond_to?(:flush)
|
194
|
+
config.worker_stopped(self)
|
195
|
+
end
|
196
|
+
|
197
|
+
def on_message(message)
|
198
|
+
# TBD - Is it necessary to provide underlying message to worker? Should we generically provide access to message attributes? Do filters somehow fit in here?
|
199
|
+
@message = message
|
200
|
+
object = @adapter.message_to_object(message)
|
201
|
+
Qwirk.logger.debug {"#{self}: Received Object: #{object}"}
|
202
|
+
perform(object)
|
203
|
+
rescue Exception => e
|
204
|
+
on_exception(e)
|
205
|
+
ensure
|
206
|
+
Qwirk.logger.debug {"#{self}: Finished processing message"}
|
207
|
+
end
|
208
|
+
|
209
|
+
def on_exception(e)
|
210
|
+
Qwirk.logger.error "#{self}: Messaging Exception: #{e.message}"
|
211
|
+
log_backtrace(e)
|
212
|
+
@adapter.handle_failure(message, @fail_queue_name) if @fail_queue_name
|
213
|
+
rescue Exception => e
|
214
|
+
Qwirk.logger.error "#{self}: Exception in exception reply: #{e.message}"
|
215
|
+
log_backtrace(e)
|
216
|
+
end
|
217
|
+
|
218
|
+
def fail_queue_name
|
219
|
+
@fail_queue_name
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'rumx'
|
2
|
+
|
3
|
+
module Qwirk
|
4
|
+
class WorkerConfig
|
5
|
+
include Rumx::Bean
|
6
|
+
|
7
|
+
attr_reader :name, :marshaler
|
8
|
+
|
9
|
+
bean_reader :count, :integer, 'Current number of workers'
|
10
|
+
bean_attr_accessor :min_count, :integer, 'Min number of workers allowed', :config_item => true
|
11
|
+
bean_attr_accessor :max_count, :integer, 'Max number of workers allowed', :config_item => true
|
12
|
+
bean_attr_accessor :idle_worker_timeout, :integer, 'Timeout where an idle worker will be removed from the worker pool and it\'s resources closed (0 for no removal)', :config_item => true
|
13
|
+
bean_attr_accessor :max_read_threshold, :float, 'Threshold where a new worker will be added if none of the workers have had to wait this amount of time on a read', :config_item => true
|
14
|
+
# The adapter refers to the corresponding class in Qwirk::QueueAdapter::<type>::WorkerConfig
|
15
|
+
bean_attr_reader :adapter, :bean, 'Adapter for worker queue interface'
|
16
|
+
bean_attr_reader :timer, :bean, 'Track the times for this worker'
|
17
|
+
bean_attr_accessor :log_times, :boolean, 'Log the times for this worker'
|
18
|
+
|
19
|
+
# Define the default config values for the attributes all workers will share. These will be sent as options to the constructor
|
20
|
+
def self.initial_default_config
|
21
|
+
{:min_count => 0, :max_count => 0, :idle_worker_timeout => 60, :max_read_threshold => 1.0}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create new WorkerConfig to manage workers of a common class
|
25
|
+
def initialize(queue_adapter, name, manager, worker_class, default_options, options)
|
26
|
+
@name = name
|
27
|
+
@manager = manager
|
28
|
+
@worker_class = worker_class
|
29
|
+
@workers = []
|
30
|
+
@stopped = false
|
31
|
+
@min_count = 0
|
32
|
+
@max_count = 0
|
33
|
+
@index_count = 0
|
34
|
+
@index_mutex = Mutex.new
|
35
|
+
@worker_mutex = Mutex.new
|
36
|
+
@worker_condition = ConditionVariable.new
|
37
|
+
response_options = worker_class.queue_options[:response] || {}
|
38
|
+
@adapter = queue_adapter.create_adapter_worker_config(self, worker_class.queue_name(@name), worker_class.topic_name, worker_class.queue_options, response_options)
|
39
|
+
# Defines how we will marshal the response
|
40
|
+
marshal_sym = (response_options[:marshal] || @adapter.default_marshal_sym)
|
41
|
+
@marshaler = MarshalStrategy.find(marshal_sym)
|
42
|
+
@log_times = queue_adapter.log_times
|
43
|
+
|
44
|
+
#Qwirk.logger.debug { "options=#{options.inspect}" }
|
45
|
+
default_options.each do |key, value|
|
46
|
+
begin
|
47
|
+
send(key.to_s+'=', value)
|
48
|
+
rescue Exception => e
|
49
|
+
# Let config_reader's set a default value
|
50
|
+
begin
|
51
|
+
instance_variable_set("@#{key}", value)
|
52
|
+
rescue Exception => e
|
53
|
+
Qwirk.logger.warn "WARNING: During initialization of #{worker_class.name} config=#{@name}, default assignment of #{key}=#{value} was invalid"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# Run the specified options after the default options, so that codependant options don't get overwritten (like min_count/max_count)
|
58
|
+
options.each do |key, value|
|
59
|
+
begin
|
60
|
+
send(key.to_s+'=', value)
|
61
|
+
rescue Exception => e
|
62
|
+
Qwirk.logger.warn "WARNING: During initialization of #{worker_class.name} config=#{@name}, assignment of #{key}=#{value} was invalid"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def count
|
68
|
+
@worker_mutex.synchronize { return @workers.size }
|
69
|
+
end
|
70
|
+
|
71
|
+
def min_count=(new_min_count)
|
72
|
+
return if @min_count == new_min_count
|
73
|
+
raise "#{@worker_class.name}-#{@name}: Can't change count since we've been stopped" if @stopped
|
74
|
+
Qwirk.logger.info "#{@worker_class.name}: Changing min number of workers from #{@min_count} to #{new_min_count}"
|
75
|
+
self.max_count = new_min_count if @max_count < new_min_count
|
76
|
+
@worker_mutex.synchronize do
|
77
|
+
add_worker while @workers.size < new_min_count
|
78
|
+
@min_count = new_min_count
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def max_count=(new_max_count)
|
83
|
+
return if @max_count == new_max_count
|
84
|
+
raise "#{@worker_class.name}-#{@name}: Can't change count since we've been stopped" if @stopped
|
85
|
+
Qwirk.logger.info "#{@worker_class.name}: Changing max number of workers from #{@max_count} to #{new_max_count}"
|
86
|
+
self.min_count = new_max_count if @min_count > new_max_count
|
87
|
+
@min_count = 1 if @min_count == 0 && new_max_count > 0
|
88
|
+
@worker_mutex.synchronize do
|
89
|
+
@timer ||= Rumx::Beans::TimerAndError.new
|
90
|
+
if @workers.size > new_max_count
|
91
|
+
@workers[new_max_count..-1].each { |worker| worker.stop }
|
92
|
+
while @workers.size > new_max_count
|
93
|
+
@workers.last.stop
|
94
|
+
@worker_condition.wait(@worker_mutex)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@max_count = new_max_count
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def stop
|
102
|
+
Qwirk.logger.debug { "In Base worker_config stop" }
|
103
|
+
# First stop the adapter. For InMem, this will not return until all the messages in the queue have
|
104
|
+
# been processed since these messages are not persistent.
|
105
|
+
@adapter.stop
|
106
|
+
@worker_mutex.synchronize do
|
107
|
+
@workers.each { |worker| worker.stop }
|
108
|
+
while @workers.size > 0
|
109
|
+
@worker_condition.wait(@worker_mutex)
|
110
|
+
end
|
111
|
+
@stopped = true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def worker_stopped(worker)
|
116
|
+
remove_worker(worker)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Override rumx bean method
|
120
|
+
def bean_attributes_changed
|
121
|
+
super
|
122
|
+
@manager.save_persist_state
|
123
|
+
end
|
124
|
+
|
125
|
+
def marshal_response(object)
|
126
|
+
@marshaler.marshal(object)
|
127
|
+
end
|
128
|
+
|
129
|
+
def unmarshal_response(marshaled_object)
|
130
|
+
@marshaler.unmarshal(marshaled_object)
|
131
|
+
end
|
132
|
+
|
133
|
+
def periodic_call(poll_time)
|
134
|
+
now = Time.now
|
135
|
+
add_new_worker = true
|
136
|
+
worker_stopped = false
|
137
|
+
@worker_mutex.synchronize do
|
138
|
+
# reverse_each to remove later workers first
|
139
|
+
@workers.reverse_each do |worker|
|
140
|
+
start_worker_time = worker.start_worker_time
|
141
|
+
start_read_time = worker.start_read_time
|
142
|
+
if !start_read_time || (now - start_worker_time) < (poll_time + @max_read_threshold)
|
143
|
+
#Qwirk.logger.debug { "#{self}: Skipping newly created worker" }
|
144
|
+
add_new_worker = false
|
145
|
+
next
|
146
|
+
end
|
147
|
+
end_read_time = worker.start_processing_time
|
148
|
+
# If the processing time is actually from the previous processing, then we're probably still waiting for the read to complete.
|
149
|
+
if !end_read_time || end_read_time < start_read_time
|
150
|
+
if !worker_stopped && @workers.size > @min_count && (now - start_read_time) > @idle_worker_timeout
|
151
|
+
worker.stop
|
152
|
+
worker_stopped = true
|
153
|
+
end
|
154
|
+
end_read_time = now
|
155
|
+
end
|
156
|
+
#Qwirk.logger.debug { "#{self}: start=#{start_read_time} end=#{end_read_time} thres=#{@max_read_threshold} add_new_worker=#{add_new_worker}" }
|
157
|
+
add_new_worker = false if (end_read_time - start_read_time) > @max_read_threshold
|
158
|
+
end
|
159
|
+
add_worker if add_new_worker && @workers.size < @max_count
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
@name
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def add_worker
|
170
|
+
worker = @worker_class.new
|
171
|
+
worker.start(@index_count, self)
|
172
|
+
Qwirk.logger.debug {"#{self}: Adding worker #{worker}"}
|
173
|
+
@index_mutex.synchronize { @index_count += 1 }
|
174
|
+
@workers << worker
|
175
|
+
rescue Exception => e
|
176
|
+
Qwirk.logger.error("Unable to add #{@worker_class} worker: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
|
177
|
+
end
|
178
|
+
|
179
|
+
def remove_worker(worker)
|
180
|
+
Qwirk.logger.debug {"#{self}: Deleting worker #{worker}"}
|
181
|
+
@worker_mutex.synchronize do
|
182
|
+
@workers.delete(worker)
|
183
|
+
@worker_condition.broadcast
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class QwirkGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
def self.source_root
|
8
|
+
File.join(File.dirname(__FILE__), 'templates')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.next_migration_number(dirname) #:nodoc:
|
12
|
+
if ActiveRecord::Base.timestamped_migrations
|
13
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
14
|
+
else
|
15
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(args, *options) #:nodoc:
|
20
|
+
# Unfreeze name in case it's given as a frozen string
|
21
|
+
args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen?
|
22
|
+
super
|
23
|
+
assign_names!(self.name)
|
24
|
+
parse_attributes! if respond_to?(:attributes)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Every method that is declared below will be automatically executed when the generator is run
|
28
|
+
|
29
|
+
def create_migration_file
|
30
|
+
f = File.open File.join(File.dirname(__FILE__), 'templates', 'schema.rb')
|
31
|
+
schema = f.read; f.close
|
32
|
+
|
33
|
+
schema.gsub!(/ActiveRecord::Schema.*\n/, '')
|
34
|
+
schema.gsub!(/^end\n*$/, '')
|
35
|
+
|
36
|
+
f = File.open File.join(File.dirname(__FILE__), 'templates', 'migration.rb')
|
37
|
+
migration = f.read; f.close
|
38
|
+
migration.gsub!(/SCHEMA_AUTO_INSERTED_HERE/, schema)
|
39
|
+
|
40
|
+
tmp = File.open "tmp/~migration_ready.rb", "w"
|
41
|
+
tmp.write migration
|
42
|
+
tmp.close
|
43
|
+
|
44
|
+
migration_template '../../../tmp/~migration_ready.rb',
|
45
|
+
'db/migrate/create_qwirk_tables.rb'
|
46
|
+
remove_file 'tmp/~migration_ready.rb'
|
47
|
+
end
|
48
|
+
|
49
|
+
def copy_initializer_file
|
50
|
+
copy_file 'initializer.rb', 'config/initializers/qwirk.rb'
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_application_template
|
54
|
+
f = File.open "app/views/layouts/application.html.erb"
|
55
|
+
layout = f.read; f.close
|
56
|
+
|
57
|
+
if layout =~ /<%=[ ]+yield[ ]+%>/
|
58
|
+
print " \e[1m\e[34mquestion\e[0m Your layouts/application.html.erb layout currently has the line <%= yield %>. This gem needs to change this line to <%= content_for?(:content) ? yield(:content) : yield %> to support its nested layouts. This change should not affect any of your existing layouts or views. Is this okay? [y/n] "
|
59
|
+
begin
|
60
|
+
answer = gets.chomp
|
61
|
+
end while not answer =~ /[yn]/i
|
62
|
+
|
63
|
+
if answer =~ /y/i
|
64
|
+
|
65
|
+
layout.gsub!(/<%=[ ]+yield[ ]+%>/, '<%= content_for?(:content) ? yield(:content) : yield %>')
|
66
|
+
|
67
|
+
tmp = File.open "tmp/~application.html.erb", "w"
|
68
|
+
tmp.write layout; tmp.close
|
69
|
+
|
70
|
+
remove_file 'app/views/layouts/application.html.erb'
|
71
|
+
copy_file '../../../tmp/~application.html.erb',
|
72
|
+
'app/views/layouts/application.html.erb'
|
73
|
+
remove_file 'tmp/~application.html.erb'
|
74
|
+
end
|
75
|
+
elsif layout =~ /<%=[ ]+content_for\?\(:content\) \? yield\(:content\) : yield[ ]+%>/
|
76
|
+
puts " \e[1m\e[33mskipping\e[0m layouts/application.html.erb modification is already done."
|
77
|
+
else
|
78
|
+
puts " \e[1m\e[31mconflict\e[0m The gem is confused by your layouts/application.html.erb. It does not contain the default line <%= yield %>, you may need to make manual changes to get this gem's nested layouts working. Visit ###### for details."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|