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.
Files changed (60) hide show
  1. data/History.md +5 -0
  2. data/lib/qwirk.rb +51 -10
  3. data/lib/qwirk/adapter/base/expanding_worker_config.rb +31 -18
  4. data/lib/qwirk/adapter/base/worker_config.rb +34 -11
  5. data/lib/qwirk/adapter/in_memory/publisher.rb +1 -1
  6. data/lib/qwirk/adapter/in_memory/queue.rb +27 -29
  7. data/lib/qwirk/adapter/in_memory/topic.rb +10 -12
  8. data/lib/qwirk/adapter/in_memory/worker.rb +9 -0
  9. data/lib/qwirk/adapter/in_memory/worker_config.rb +4 -0
  10. data/lib/qwirk/adapter/inline/worker.rb +4 -0
  11. data/lib/qwirk/adapter/inline/worker_config.rb +4 -0
  12. data/lib/qwirk/adapter_factory.rb +5 -1
  13. data/lib/qwirk/base_worker.rb +1 -5
  14. data/lib/qwirk/engine.rb +2 -6
  15. data/lib/qwirk/manager.rb +18 -10
  16. data/lib/qwirk/marshal_strategy/json.rb +1 -1
  17. data/lib/qwirk/remote_exception.rb +9 -1
  18. data/lib/qwirk/task.rb +61 -44
  19. data/lib/qwirk/worker.rb +16 -7
  20. data/test/dummy/README.rdoc +261 -0
  21. data/test/dummy/Rakefile +1 -1
  22. data/test/dummy/app/assets/javascripts/application.js +15 -0
  23. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  24. data/test/dummy/app/views/layouts/application.html.erb +3 -3
  25. data/test/dummy/config/application.rb +22 -8
  26. data/test/dummy/config/database.yml +3 -0
  27. data/test/dummy/config/environments/development.rb +15 -4
  28. data/test/dummy/config/environments/production.rb +31 -13
  29. data/test/dummy/config/environments/test.rb +9 -7
  30. data/test/dummy/config/initializers/inflections.rb +5 -0
  31. data/test/dummy/config/initializers/secret_token.rb +1 -1
  32. data/test/dummy/config/initializers/session_store.rb +1 -1
  33. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  34. data/test/dummy/config/locales/en.yml +1 -1
  35. data/test/dummy/config/routes.rb +2 -56
  36. data/test/dummy/public/500.html +0 -1
  37. data/test/dummy/script/rails +1 -1
  38. data/test/fixtures/qwirk/jobs.yml +11 -0
  39. data/test/functional/qwirk/jobs_controller_test.rb +9 -0
  40. data/test/{base_test.rb → models/base_worker_test.rb} +1 -4
  41. data/test/models/marshal_strategy_test.rb +61 -0
  42. data/test/{unit → models}/qwirk/batch/acquire_file_strategy_test.rb +0 -0
  43. data/test/{unit → models}/qwirk/batch/active_record/batch_job_test.rb +0 -0
  44. data/test/{unit → models}/qwirk/batch/parse_file_strategy_test.rb +0 -0
  45. data/test/test_helper.rb +7 -4
  46. data/test/unit/helpers/qwirk/jobs_helper_test.rb +6 -0
  47. data/test/unit/qwirk/job_test.rb +9 -0
  48. metadata +179 -117
  49. data/lib/qwirk/task.rb.sav +0 -194
  50. data/test/dummy/log/development.log +0 -0
  51. data/test/dummy/log/production.log +0 -0
  52. data/test/dummy/log/server.log +0 -0
  53. data/test/dummy/log/test.log +0 -0
  54. data/test/dummy/public/javascripts/application.js +0 -2
  55. data/test/dummy/public/javascripts/controls.js +0 -965
  56. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  57. data/test/dummy/public/javascripts/effects.js +0 -1123
  58. data/test/dummy/public/javascripts/prototype.js +0 -6001
  59. data/test/dummy/public/javascripts/rails.js +0 -191
  60. data/test/marshal_strategy_test.rb +0 -62
data/History.md CHANGED
@@ -1,6 +1,11 @@
1
1
  Qwirk Changelog
2
2
  =====================
3
3
 
4
+ 0.2.0
5
+ -----
6
+
7
+ - Various fixes and improving of the shutdown logic.
8
+
4
9
  0.0.1
5
10
  -----
6
11
 
@@ -7,8 +7,9 @@ module Qwirk
7
7
 
8
8
  DEFAULT_NAME = 'Qwirk'
9
9
 
10
- @@config = nil
11
- @@hash = {}
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 JMS w/o modifying qwirk.yml which could be checked in and hose other users
33
- env = ENV['QWIRK_ENV'] || Rails.env
34
- self.config = YAML.load(ERB.new(File.read(Rails.root.join("config", "qwirk.yml")), nil, '-').result(binding))[env]
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', 'qwirk_persist.yml'),
54
+ :persist_file => Rails.root.join('log', 'qwirk_persist.yml'),
37
55
  :worker_file => Rails.root.join('config', 'qwirk_workers.yml'),
38
- :stop_on_signal => true,
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
- Qwirk.logger.debug {"Creating Qwirk::AdapterFactory key=#{adapter_key} config=#{@@config[adapter_key].inspect}"}
45
- @@hash[adapter_key] ||= Qwirk::AdapterFactory.new(adapter_key, @@config[adapter_key])
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].each { |worker| worker.stop }
53
- while @workers.size > new_max_count
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
- def stop
63
- Qwirk.logger.debug { "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
- @impl.stop
67
- @worker_mutex.synchronize do
68
- @workers.each { |worker| worker.stop }
69
- while @workers.size > 0
70
- @worker_condition.wait(@worker_mutex)
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
- super
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
- :stopped, :queue_options, :response_options, :marshaler
11
+ :queue_options, :response_options, :marshaler
12
12
  attr_accessor :queue_name, :topic_name
13
13
 
14
- bean_attr_reader :timer, :bean, 'Track the times for this worker'
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
- @stopped = false
35
- @queue_name = worker_class.queue_name(@name)
36
- @topic_name = worker_class.topic_name
37
- @queue_options = worker_class.queue_options
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.warn "WARNING: During initialization of #{worker_class.name} config=#{@name}, default assignment of #{key}=#{value} was invalid"
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.warn "WARNING: During initialization of #{worker_class.name} config=#{@name}, assignment of #{key}=#{value} was invalid"
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
- @stopped = true
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 = name
13
- @max_size = 0
14
- @outstanding_hash_mutex = Mutex.new
15
- @read_condition = ConditionVariable.new
16
- @write_condition = ConditionVariable.new
17
- @stop_condition = ConditionVariable.new
18
- @array = []
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 @stopped
28
- @stopped = true
29
- @outstanding_hash_mutex.synchronize do
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
- @outstanding_hash_mutex.synchronize do
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. stoppable is an object that responds to stopped (a worker or some kind of consumer)
46
+ # Block read until a message or we get stopped.
45
47
  def read(stoppable)
46
- @outstanding_hash_mutex.synchronize do
47
- until @stopped || stoppable.stopped do
48
- unless @array.empty?
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
- @outstanding_hash_mutex.synchronize do
62
+ @array_mutex.synchronize do
66
63
  # We just drop the message if no workers have been configured yet
67
- while !@stopped
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(@outstanding_hash_mutex)
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 = name
8
- @outstanding_hash_mutex = Mutex.new
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
- @outstanding_hash_mutex.synchronize do
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
- @stopped = true
23
- @worker_hash.each_value do |queue|
24
- queue.stop
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
- @outstanding_hash_mutex.synchronize do
33
+ @worker_hash_mutex.synchronize do
34
34
  @worker_hash.each_value do |queue|
35
- if !@stopped
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
 
@@ -16,6 +16,10 @@ module Qwirk
16
16
  super.merge(:queue_max_size => 100)
17
17
  end
18
18
 
19
+ def self.in_process?(config)
20
+ true
21
+ end
22
+
19
23
  def init
20
24
  super
21
25
  @queue = Factory.get_worker_queue(self.name, self.queue_name, self.topic_name, @queue_max_size)