qwirk 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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)