qwirk 0.0.1
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/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
|