qwirk 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +5 -0
- data/lib/qwirk.rb +51 -10
- data/lib/qwirk/adapter/base/expanding_worker_config.rb +31 -18
- data/lib/qwirk/adapter/base/worker_config.rb +34 -11
- data/lib/qwirk/adapter/in_memory/publisher.rb +1 -1
- data/lib/qwirk/adapter/in_memory/queue.rb +27 -29
- data/lib/qwirk/adapter/in_memory/topic.rb +10 -12
- data/lib/qwirk/adapter/in_memory/worker.rb +9 -0
- data/lib/qwirk/adapter/in_memory/worker_config.rb +4 -0
- data/lib/qwirk/adapter/inline/worker.rb +4 -0
- data/lib/qwirk/adapter/inline/worker_config.rb +4 -0
- data/lib/qwirk/adapter_factory.rb +5 -1
- data/lib/qwirk/base_worker.rb +1 -5
- data/lib/qwirk/engine.rb +2 -6
- data/lib/qwirk/manager.rb +18 -10
- data/lib/qwirk/marshal_strategy/json.rb +1 -1
- data/lib/qwirk/remote_exception.rb +9 -1
- data/lib/qwirk/task.rb +61 -44
- data/lib/qwirk/worker.rb +16 -7
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +1 -1
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/views/layouts/application.html.erb +3 -3
- data/test/dummy/config/application.rb +22 -8
- data/test/dummy/config/database.yml +3 -0
- data/test/dummy/config/environments/development.rb +15 -4
- data/test/dummy/config/environments/production.rb +31 -13
- data/test/dummy/config/environments/test.rb +9 -7
- data/test/dummy/config/initializers/inflections.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +1 -1
- data/test/dummy/config/initializers/session_store.rb +1 -1
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +1 -1
- data/test/dummy/config/routes.rb +2 -56
- data/test/dummy/public/500.html +0 -1
- data/test/dummy/script/rails +1 -1
- data/test/fixtures/qwirk/jobs.yml +11 -0
- data/test/functional/qwirk/jobs_controller_test.rb +9 -0
- data/test/{base_test.rb → models/base_worker_test.rb} +1 -4
- data/test/models/marshal_strategy_test.rb +61 -0
- data/test/{unit → models}/qwirk/batch/acquire_file_strategy_test.rb +0 -0
- data/test/{unit → models}/qwirk/batch/active_record/batch_job_test.rb +0 -0
- data/test/{unit → models}/qwirk/batch/parse_file_strategy_test.rb +0 -0
- data/test/test_helper.rb +7 -4
- data/test/unit/helpers/qwirk/jobs_helper_test.rb +6 -0
- data/test/unit/qwirk/job_test.rb +9 -0
- metadata +179 -117
- data/lib/qwirk/task.rb.sav +0 -194
- 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/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -191
- data/test/marshal_strategy_test.rb +0 -62
data/History.md
CHANGED
data/lib/qwirk.rb
CHANGED
@@ -7,8 +7,9 @@ module Qwirk
|
|
7
7
|
|
8
8
|
DEFAULT_NAME = 'Qwirk'
|
9
9
|
|
10
|
-
@@config
|
11
|
-
@@
|
10
|
+
@@config = nil
|
11
|
+
@@environment = nil
|
12
|
+
@@hash = {}
|
12
13
|
|
13
14
|
class MyBean
|
14
15
|
include Rumx::Bean
|
@@ -22,27 +23,48 @@ module Qwirk
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.config=(config)
|
26
|
+
#if config.has_key?(:adapter)
|
25
27
|
@@config = config
|
26
28
|
Rumx::Bean.root.bean_add_child(DEFAULT_NAME, MyBean.new(@@hash))
|
27
29
|
end
|
28
30
|
|
31
|
+
def self.environment=(environment)
|
32
|
+
@@environment = environment
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.config_file=(config_file)
|
36
|
+
raise "No such file: #{config_file}" unless File.exist?(config_file)
|
37
|
+
config = YAML.load(ERB.new(File.read(config_file), nil, '-').result(binding))
|
38
|
+
config = config[@@environment] if config && @@environment
|
39
|
+
if config.has_key?(:adapter) || config.has_key?('adapter')
|
40
|
+
# Single level, one default adapter
|
41
|
+
@@config = {'default' => config}
|
42
|
+
else
|
43
|
+
@@config = config
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
29
47
|
def self.[](adapter_key)
|
30
48
|
if @@config.nil?
|
31
49
|
if defined?(Rails)
|
32
|
-
# Allow user to use
|
33
|
-
|
34
|
-
self.
|
50
|
+
# Allow user to use a different adapter w/o modifying qwirk.yml which could be checked in and hose other users
|
51
|
+
self.environment = ENV['QWIRK_ENV'] || Rails.env
|
52
|
+
self.config_file = Rails.root.join('config', 'qwirk.yml')
|
35
53
|
Manager.default_options = {
|
36
|
-
:persist_file => Rails.root.join('log',
|
54
|
+
:persist_file => Rails.root.join('log', 'qwirk_persist.yml'),
|
37
55
|
:worker_file => Rails.root.join('config', 'qwirk_workers.yml'),
|
38
|
-
:
|
39
|
-
:env => env,
|
56
|
+
:env => @@environment,
|
40
57
|
}
|
41
58
|
end
|
42
59
|
end
|
43
60
|
raise 'Qwirk not configured' unless @@config && @@config[adapter_key]
|
44
|
-
|
45
|
-
|
61
|
+
@@hash[adapter_key] ||= begin
|
62
|
+
Qwirk.logger.debug {"Creating Qwirk::AdapterFactory key=#{adapter_key} config=#{@@config[adapter_key].inspect}"}
|
63
|
+
config = @@config[adapter_key]
|
64
|
+
raise "No config for key #{adapter_key}, keys=#{config.keys.inspect}" unless config
|
65
|
+
# Create the adapter, turning all the keys into symbols
|
66
|
+
Qwirk::AdapterFactory.new(adapter_key, Hash[config.map{|(k,v)| [k.to_sym,v]}])
|
67
|
+
end
|
46
68
|
end
|
47
69
|
|
48
70
|
def self.register_adapter(key, publisher_class, worker_config_class, &block)
|
@@ -52,6 +74,25 @@ module Qwirk
|
|
52
74
|
def self.fail_queue_name(queue_name)
|
53
75
|
return "#{queue_name.to_s}Fail"
|
54
76
|
end
|
77
|
+
|
78
|
+
## From here on down are proxies to the default adapter to keep the API simpler for setups with a single adapter
|
79
|
+
# TODO: Allow the setting of the default adapter
|
80
|
+
|
81
|
+
def self.create_publisher(options={})
|
82
|
+
self['default'].create_publisher(options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.create_manager(options={})
|
86
|
+
self['default'].create_manager(options)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.create_publisher_impl(queue_name, topic_name, options, response_options)
|
90
|
+
self['default'].create_publisher_impl(queue_name, topic_name, options, response_options)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.in_process?
|
94
|
+
self['default'].in_process?
|
95
|
+
end
|
55
96
|
end
|
56
97
|
|
57
98
|
# We have to define the above before we define the adapters so the Qwirk#register_adapter call will work
|
@@ -31,7 +31,7 @@ module Qwirk
|
|
31
31
|
|
32
32
|
def min_count=(new_min_count)
|
33
33
|
return if @min_count == new_min_count
|
34
|
-
raise "#{self.worker_class.name}-#{self.name}: Can't change count since we've been stopped" if self.stopped
|
34
|
+
raise "#{self.worker_class.name}-#{self.name}: Can't change count since we've been stopped" if self.stopped?
|
35
35
|
Qwirk.logger.info "#{self.worker_class.name}: Changing min number of workers from #{@min_count} to #{new_min_count}"
|
36
36
|
self.max_count = new_min_count if @max_count < new_min_count
|
37
37
|
@worker_mutex.synchronize do
|
@@ -42,34 +42,47 @@ module Qwirk
|
|
42
42
|
|
43
43
|
def max_count=(new_max_count)
|
44
44
|
return if @max_count == new_max_count
|
45
|
-
raise "#{self.worker_class.name}-#{self.name}: Can't change count since we've been stopped" if self.stopped
|
45
|
+
raise "#{self.worker_class.name}-#{self.name}: Can't change count since we've been stopped" if self.stopped?
|
46
46
|
Qwirk.logger.info "#{self.worker_class.name}: Changing max number of workers from #{@max_count} to #{new_max_count}"
|
47
47
|
self.min_count = new_max_count if @min_count > new_max_count
|
48
48
|
@min_count = 1 if @min_count == 0 && new_max_count > 0
|
49
|
+
deleted_workers = []
|
49
50
|
@worker_mutex.synchronize do
|
50
51
|
@timer ||= Rumx::Beans::TimerAndError.new
|
51
52
|
if @workers.size > new_max_count
|
52
|
-
@workers[new_max_count..-1]
|
53
|
-
|
54
|
-
@workers.last.stop
|
55
|
-
@worker_condition.wait(@worker_mutex)
|
56
|
-
end
|
53
|
+
deleted_workers = @workers[new_max_count..-1]
|
54
|
+
deleted_workers.each { |worker| worker.stop }
|
57
55
|
end
|
58
56
|
@max_count = new_max_count
|
59
57
|
end
|
58
|
+
deleted_workers.each { |worker| worker.join }
|
60
59
|
end
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
61
|
+
# TODO: Need this? Should I only be calling worker.stop when stopping individual workers?
|
62
|
+
#def stop
|
63
|
+
# Qwirk.logger.debug { "#{self}: In expanding_worker_config stop" }
|
64
|
+
# # First stop the impl. For InMemory, this will not return until all the messages in the queue have
|
65
|
+
# # been processed since these messages are not persistent.
|
66
|
+
# @worker_mutex.synchronize do
|
67
|
+
# @workers.each { |worker| worker.stop }
|
68
|
+
# while @workers.size > 0
|
69
|
+
# @worker_condition.wait(@worker_mutex)
|
70
|
+
# end
|
71
|
+
# super
|
72
|
+
# end
|
73
|
+
#end
|
74
|
+
|
75
|
+
def join(timeout=nil)
|
76
|
+
workers = @worker_mutex.synchronize { @workers.dup }
|
77
|
+
if timeout
|
78
|
+
end_time = Time.now + timeout
|
79
|
+
workers.each do |worker|
|
80
|
+
t = end_time - Time.now
|
81
|
+
t = 0 if t < 0
|
82
|
+
worker.join(t)
|
71
83
|
end
|
72
|
-
|
84
|
+
else
|
85
|
+
workers.each { |worker| worker.join }
|
73
86
|
end
|
74
87
|
end
|
75
88
|
|
@@ -103,7 +116,7 @@ module Qwirk
|
|
103
116
|
#Qwirk.logger.debug { "#{self}: start=#{start_read_time} end=#{end_read_time} thres=#{@max_read_threshold} add_new_worker=#{add_new_worker}" }
|
104
117
|
add_new_worker = false if (end_read_time - start_read_time) > @max_read_threshold
|
105
118
|
end
|
106
|
-
add_worker if add_new_worker && @workers.size < @max_count
|
119
|
+
add_worker if !self.stopped? && add_new_worker && @workers.size < @max_count
|
107
120
|
end
|
108
121
|
end
|
109
122
|
|
@@ -8,10 +8,10 @@ module Qwirk
|
|
8
8
|
|
9
9
|
# Make explicit the instance variables available to the derived adapter classes
|
10
10
|
attr_reader :adapter_factory, :name, :manager, :worker_class, :default_options, :options,
|
11
|
-
:
|
11
|
+
:queue_options, :response_options, :marshaler
|
12
12
|
attr_accessor :queue_name, :topic_name
|
13
13
|
|
14
|
-
|
14
|
+
bean_attr_reader :timer, :bean, 'Track the times for this worker'
|
15
15
|
bean_attr_accessor :log_times, :boolean, 'Log the times for this worker'
|
16
16
|
|
17
17
|
# Define the default config values for the attributes all workers will share. These will be sent as options to the constructor
|
@@ -23,18 +23,35 @@ module Qwirk
|
|
23
23
|
:ruby
|
24
24
|
end
|
25
25
|
|
26
|
+
# By default, workers do not run in the same process as the publishers
|
27
|
+
def self.in_process?(config)
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
26
31
|
# Create new WorkerConfig to manage workers of a common class
|
27
32
|
def initialize(adapter_factory, name, manager, worker_class, default_options, options)
|
28
33
|
@adapter_factory = adapter_factory
|
29
34
|
@name = name
|
30
35
|
@manager = manager
|
31
36
|
@worker_class = worker_class
|
32
|
-
@default_options = default_options
|
33
|
-
@options = options
|
34
|
-
|
35
|
-
@queue_name =
|
36
|
-
@topic_name =
|
37
|
-
|
37
|
+
@default_options = default_options.dup
|
38
|
+
@options = options.dup
|
39
|
+
# First option is getting the queue or topic from the given options
|
40
|
+
@queue_name = options.delete(:queue_name)
|
41
|
+
@topic_name = !@queue_name && options.delete(:queue_name)
|
42
|
+
# Second option is getting them from the default options
|
43
|
+
@queue_name ||= !@topic_name && default_options.delete(:queue_name)
|
44
|
+
@topic_name ||= !@queue_name && default_options.delete(:topic_name)
|
45
|
+
# Third (and most likely) option is getting it based on the class topic or queue DSL or defaulting to the default queue
|
46
|
+
@queue_name ||= !@topic_name && worker_class.queue_name(@name)
|
47
|
+
@topic_name ||= !@queue_name && worker_class.topic_name
|
48
|
+
|
49
|
+
if @queue_name
|
50
|
+
more_queue_options = (default_options.delete(:queue_options) || {}).merge(options.delete(:queue_options) || {})
|
51
|
+
else
|
52
|
+
more_queue_options = (default_options.delete(:topic_options) || {}).merge(options.delete(:topic_options) || {})
|
53
|
+
end
|
54
|
+
@queue_options = worker_class.queue_options.merge(more_queue_options)
|
38
55
|
@response_options = @queue_options[:response] || {}
|
39
56
|
|
40
57
|
# Defines how we will marshal the response
|
@@ -53,7 +70,7 @@ module Qwirk
|
|
53
70
|
begin
|
54
71
|
instance_variable_set("@#{key}", value)
|
55
72
|
rescue Exception => e
|
56
|
-
Qwirk.logger.
|
73
|
+
Qwirk.logger.debug "DEBUG: During initialization of #{worker_class.name} config=#{@name}, default assignment of #{key}=#{value} was ignored"
|
57
74
|
end
|
58
75
|
end
|
59
76
|
end
|
@@ -62,7 +79,7 @@ module Qwirk
|
|
62
79
|
begin
|
63
80
|
send(key.to_s+'=', value)
|
64
81
|
rescue Exception => e
|
65
|
-
Qwirk.logger.
|
82
|
+
Qwirk.logger.debug "DEBUG: During initialization of #{worker_class.name} config=#{@name}, assignment of #{key}=#{value} was ignored"
|
66
83
|
end
|
67
84
|
end
|
68
85
|
end
|
@@ -72,7 +89,13 @@ module Qwirk
|
|
72
89
|
end
|
73
90
|
|
74
91
|
def stop
|
75
|
-
|
92
|
+
end
|
93
|
+
|
94
|
+
def join(timeout=nil)
|
95
|
+
end
|
96
|
+
|
97
|
+
def stopped?
|
98
|
+
self.manager.stopped?
|
76
99
|
end
|
77
100
|
|
78
101
|
def worker_stopped(worker)
|
@@ -85,9 +85,9 @@ module Qwirk
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def stop
|
88
|
+
# TODO - Need to think about what should actually occur here?
|
88
89
|
return if @stopped
|
89
90
|
Qwirk.logger.info "Stopping Task worker #{@consumer_queue}"
|
90
|
-
# Don't clobber the session before a reply
|
91
91
|
@producer_queue.interrupt_read
|
92
92
|
@stopped = true
|
93
93
|
end
|
@@ -9,14 +9,13 @@ module Qwirk
|
|
9
9
|
attr_accessor :name, :max_size
|
10
10
|
|
11
11
|
def initialize(name)
|
12
|
-
@name
|
13
|
-
@max_size
|
14
|
-
@
|
15
|
-
@read_condition
|
16
|
-
@write_condition
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@stopped = false
|
12
|
+
@name = name
|
13
|
+
@max_size = 0
|
14
|
+
@array_mutex = Mutex.new
|
15
|
+
@read_condition = ConditionVariable.new
|
16
|
+
@write_condition = ConditionVariable.new
|
17
|
+
@array = []
|
18
|
+
@stopping = false
|
20
19
|
end
|
21
20
|
|
22
21
|
def size
|
@@ -24,47 +23,45 @@ module Qwirk
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def stop
|
27
|
-
return if @
|
28
|
-
@
|
29
|
-
@
|
26
|
+
return if @stopping
|
27
|
+
@stopping = true
|
28
|
+
@array_mutex.synchronize do
|
30
29
|
@write_condition.broadcast
|
31
|
-
until @array.empty?
|
32
|
-
@stop_condition.wait(@outstanding_hash_mutex)
|
33
|
-
end
|
34
30
|
@read_condition.broadcast
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
34
|
+
def stopped?
|
35
|
+
@array_mutex.synchronize do
|
36
|
+
return @stopping && @array.empty?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
38
40
|
def interrupt_read
|
39
|
-
@
|
41
|
+
@array_mutex.synchronize do
|
40
42
|
@read_condition.broadcast
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
# Block read until a message or we get stopped.
|
46
|
+
# Block read until a message or we get stopped.
|
45
47
|
def read(stoppable)
|
46
|
-
@
|
47
|
-
until
|
48
|
-
|
48
|
+
@array_mutex.synchronize do
|
49
|
+
until stoppable.stopped || (@stopping && @array.empty?)
|
50
|
+
if @array.empty?
|
51
|
+
@read_condition.wait(@array_mutex)
|
52
|
+
else
|
49
53
|
@write_condition.signal
|
50
54
|
return @array.shift
|
51
55
|
end
|
52
|
-
@read_condition.wait(@outstanding_hash_mutex)
|
53
|
-
end
|
54
|
-
return if stoppable.stopped
|
55
|
-
# We're not persistent, so even though we're stopped we're going to allow our stoppables to keep reading until the queue's empty
|
56
|
-
unless @array.empty?
|
57
|
-
@stop_condition.signal
|
58
|
-
return @array.shift
|
59
56
|
end
|
60
57
|
end
|
61
58
|
return nil
|
62
59
|
end
|
63
60
|
|
64
61
|
def write(obj)
|
65
|
-
@
|
62
|
+
@array_mutex.synchronize do
|
66
63
|
# We just drop the message if no workers have been configured yet
|
67
|
-
while !@
|
64
|
+
while !@stopping
|
68
65
|
if @max_size == 0
|
69
66
|
Qwirk.logger.warn "No worker for queue #{@name}, dropping message #{obj.inspect}"
|
70
67
|
return
|
@@ -75,8 +72,9 @@ module Qwirk
|
|
75
72
|
return
|
76
73
|
end
|
77
74
|
# TODO: Let's allow various write_full_modes such as :block, :remove_oldest, ? (Currently only blocks)
|
78
|
-
@write_condition.wait(@
|
75
|
+
@write_condition.wait(@array_mutex)
|
79
76
|
end
|
77
|
+
Qwirk.logger.warn "Queue has been stopped #{@name}, dropping message #{obj.inspect}"
|
80
78
|
end
|
81
79
|
end
|
82
80
|
|
@@ -4,14 +4,13 @@ module Qwirk
|
|
4
4
|
|
5
5
|
class Topic
|
6
6
|
def initialize(name)
|
7
|
-
@name
|
8
|
-
@
|
9
|
-
@worker_hash
|
10
|
-
@stopped = false
|
7
|
+
@name = name
|
8
|
+
@worker_hash_mutex = Mutex.new
|
9
|
+
@worker_hash = {}
|
11
10
|
end
|
12
11
|
|
13
12
|
def get_worker_queue(worker_name, queue_max_size)
|
14
|
-
@
|
13
|
+
@worker_hash_mutex.synchronize do
|
15
14
|
queue = @worker_hash[worker_name] ||= Queue.new("#{@name}:#{worker_name}")
|
16
15
|
queue.max_size = queue_max_size
|
17
16
|
return queue
|
@@ -19,9 +18,10 @@ module Qwirk
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def stop
|
22
|
-
@
|
23
|
-
|
24
|
-
|
21
|
+
@worker_hash_mutex.synchronize do
|
22
|
+
@worker_hash.each_value do |queue|
|
23
|
+
queue.stop
|
24
|
+
end
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -30,11 +30,9 @@ module Qwirk
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def write(obj)
|
33
|
-
@
|
33
|
+
@worker_hash_mutex.synchronize do
|
34
34
|
@worker_hash.each_value do |queue|
|
35
|
-
|
36
|
-
queue.write(obj)
|
37
|
-
end
|
35
|
+
queue.write(obj)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
@@ -46,6 +46,15 @@ module Qwirk
|
|
46
46
|
@queue.interrupt_read
|
47
47
|
end
|
48
48
|
|
49
|
+
# If the worker_config has been commanded to stop, workers will continue processing messages until this returns true
|
50
|
+
def ready_to_stop?
|
51
|
+
@queue.stopped?
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#{@name} (InMemory)"
|
56
|
+
end
|
57
|
+
|
49
58
|
## End of required override methods for worker impl
|
50
59
|
private
|
51
60
|
|