qwirk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
|