qs 0.6.1 → 0.7.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.
@@ -2,37 +2,38 @@ module Qs
2
2
 
3
3
  class DaemonData
4
4
 
5
- # The daemon uses this to "compile" its configuration for speed. NsOptions
6
- # is relatively slow everytime an option is read. To avoid this, we read the
7
- # options one time here and memoize their values. This way, we don't pay the
8
- # NsOptions overhead when reading them while handling a message.
5
+ # The daemon uses this to "compile" the common configuration data used
6
+ # by the daemon instances, error handlers and routes. The goal here is
7
+ # to provide these with a simplified interface with the minimal data needed
8
+ # and to decouple the configuration from each thing that needs its data.
9
9
 
10
- attr_reader :name, :process_label, :pid_file
10
+ attr_reader :name, :pid_file, :shutdown_timeout
11
11
  attr_reader :worker_class, :worker_params, :num_workers
12
- attr_reader :debug, :logger, :dwp_logger, :verbose_logging
13
- attr_reader :shutdown_timeout
14
- attr_reader :error_procs, :queue_redis_keys, :routes
12
+ attr_reader :error_procs, :logger, :queue_redis_keys
13
+ attr_reader :verbose_logging
14
+ attr_reader :debug, :dwp_logger, :routes, :process_label
15
15
 
16
16
  def initialize(args = nil)
17
17
  args ||= {}
18
- @name = args[:name]
19
- @process_label = !(v = ENV['QS_PROCESS_LABEL'].to_s).empty? ? v : args[:name]
20
- @pid_file = args[:pid_file]
18
+ @name = args[:name]
19
+ @pid_file = args[:pid_file]
21
20
 
22
- @worker_class = args[:worker_class]
23
- @worker_params = args[:worker_params] || {}
24
- @num_workers = args[:num_workers]
21
+ @shutdown_timeout = args[:shutdown_timeout]
22
+
23
+ @worker_class = args[:worker_class]
24
+ @worker_params = args[:worker_params] || {}
25
+ @num_workers = args[:num_workers]
26
+ @error_procs = args[:error_procs] || []
27
+ @logger = args[:logger]
28
+ @queue_redis_keys = (args[:queues] || []).map(&:redis_key)
25
29
 
26
- @debug = !ENV['QS_DEBUG'].to_s.empty?
27
- @logger = args[:logger]
28
- @dwp_logger = @logger if @debug
29
30
  @verbose_logging = !!args[:verbose_logging]
30
31
 
31
- @shutdown_timeout = args[:shutdown_timeout]
32
+ @debug = !ENV['QS_DEBUG'].to_s.empty?
33
+ @dwp_logger = @logger if @debug
34
+ @routes = build_routes(args[:routes] || [])
32
35
 
33
- @error_procs = args[:error_procs] || []
34
- @queue_redis_keys = args[:queue_redis_keys] || []
35
- @routes = build_routes(args[:routes] || [])
36
+ @process_label = !(v = ENV['QS_PROCESS_LABEL'].to_s).empty? ? v : @name
36
37
  end
37
38
 
38
39
  def route_for(route_id)
@@ -50,6 +50,7 @@ module Qs
50
50
 
51
51
  def logger; @qs_runner.logger; end
52
52
  def params; @qs_runner.params; end
53
+ def halt; @qs_runner.halt; end
53
54
 
54
55
  end
55
56
 
@@ -51,17 +51,15 @@ module Qs
51
51
  @pid_file.remove
52
52
  end
53
53
 
54
- def daemonize?
55
- @daemonize
56
- end
54
+ def daemonize?; @daemonize; end
57
55
 
58
56
  private
59
57
 
60
58
  def start_daemon(daemon)
61
- @daemon.start
62
- log "#{@daemon.name} daemon started and ready."
59
+ daemon.start
60
+ log "#{daemon.name} daemon started and ready."
63
61
  rescue StandardError => exception
64
- log "#{@daemon.name} daemon never started."
62
+ log "#{daemon.name} daemon never started."
65
63
  raise exception
66
64
  end
67
65
 
@@ -102,7 +100,6 @@ module Qs
102
100
 
103
101
  def run_restart_cmd(daemon, restart_cmd)
104
102
  log "Restarting #{daemon.name} daemon"
105
- ENV['QS_SKIP_DAEMONIZE'] = 'yes'
106
103
  restart_cmd.run
107
104
  end
108
105
 
@@ -126,9 +123,22 @@ module Qs
126
123
  @argv = [Gem.ruby, $0, ARGV.dup].flatten
127
124
  end
128
125
 
129
- def run
130
- Dir.chdir self.dir
131
- Kernel.exec(*self.argv)
126
+ if RUBY_VERSION == '1.8.7'
127
+
128
+ def run
129
+ ENV['QS_SKIP_DAEMONIZE'] = 'yes'
130
+ Dir.chdir self.dir
131
+ Kernel.exec(*self.argv)
132
+ end
133
+
134
+ else
135
+
136
+ def run
137
+ env = { 'QS_SKIP_DAEMONIZE' => 'yes' }
138
+ options = { :chdir => self.dir }
139
+ Kernel.exec(*([env] + self.argv + [options]))
140
+ end
141
+
132
142
  end
133
143
 
134
144
  private
@@ -1,4 +1,4 @@
1
- require 'system_timer'
1
+ require 'much-timeout'
2
2
  require 'qs'
3
3
  require 'qs/runner'
4
4
 
@@ -14,32 +14,24 @@ module Qs
14
14
  end
15
15
 
16
16
  def run
17
- OptionalTimeout.new(self.timeout) do
18
- self.handler.qs_run_callback 'before'
19
- self.handler.qs_init
20
- self.handler.qs_run
21
- self.handler.qs_run_callback 'after'
17
+ MuchTimeout.optional_timeout(self.timeout, TimeoutInterrupt) do
18
+ catch(:halt) do
19
+ self.handler.qs_run_callback 'before'
20
+ catch(:halt){ self.handler.qs_init; self.handler.qs_run }
21
+ self.handler.qs_run_callback 'after'
22
+ end
22
23
  end
23
- rescue TimeoutError => exception
24
- error = TimeoutError.new "#{handler_class} timed out (#{timeout}s)"
24
+ rescue TimeoutInterrupt => exception
25
+ error = Qs::TimeoutError.new "#{handler_class} timed out (#{timeout}s)"
25
26
  error.set_backtrace(exception.backtrace)
26
27
  raise error
27
28
  end
28
29
 
29
- private
30
-
31
- module OptionalTimeout
32
- def self.new(timeout, &block)
33
- if !timeout.nil?
34
- SystemTimer.timeout_after(timeout, TimeoutError, &block)
35
- else
36
- block.call
37
- end
38
- end
39
- end
30
+ # this error should never be "swallowed", if it is caught be sure to re-raise
31
+ # it so the workers will be able to honor their timeout setting. otherwise
32
+ # workers will never timeout.
33
+ TimeoutInterrupt = Class.new(Interrupt)
40
34
 
41
35
  end
42
36
 
43
- TimeoutError = Class.new(RuntimeError)
44
-
45
37
  end
@@ -21,6 +21,10 @@ module Qs
21
21
  raise NotImplementedError
22
22
  end
23
23
 
24
+ def halt
25
+ throw :halt
26
+ end
27
+
24
28
  end
25
29
 
26
30
  end
@@ -17,11 +17,21 @@ module Qs
17
17
  })
18
18
  a.each{ |key, value| self.handler.send("#{key}=", value) }
19
19
 
20
- self.handler.qs_init
20
+ @halted = false
21
+ catch(:halt){ self.handler.qs_init }
21
22
  end
22
23
 
24
+ def halted?; @halted; end
25
+
23
26
  def run
24
- self.handler.qs_run
27
+ catch(:halt){ self.handler.qs_run } if !self.halted?
28
+ end
29
+
30
+ # helpers
31
+
32
+ def halt
33
+ @halted = true
34
+ super
25
35
  end
26
36
 
27
37
  private
@@ -1,3 +1,3 @@
1
1
  module Qs
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
data/qs.gemspec CHANGED
@@ -18,13 +18,12 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_development_dependency("assert", ["~> 2.15.1"])
22
- gem.add_development_dependency("scmd", ["~> 3.0.1"])
21
+ gem.add_development_dependency("assert", ["~> 2.16.1"])
22
+ gem.add_development_dependency("scmd", ["~> 3.0.2"])
23
23
 
24
- gem.add_dependency("dat-worker-pool", ["~> 0.6.0"])
25
- gem.add_dependency("hella-redis", ["~> 0.3.0"])
26
- gem.add_dependency("much-plugin", ["~> 0.1.0"])
27
- gem.add_dependency("ns-options", ["~> 1.1.6"])
28
- gem.add_dependency("SystemTimer", ["~> 1.2"])
24
+ gem.add_dependency("dat-worker-pool", ["~> 0.6.1"])
25
+ gem.add_dependency("hella-redis", ["~> 0.3.1"])
26
+ gem.add_dependency("much-plugin", ["~> 0.2.0"])
27
+ gem.add_dependency("much-timeout", ["~> 0.1.0"])
29
28
 
30
29
  end
@@ -12,3 +12,14 @@ ROOT_PATH = Pathname.new(File.expand_path('../..', __FILE__))
12
12
  require 'test/support/factory'
13
13
 
14
14
  require 'json' # so the default encoder/decoder procs will work
15
+
16
+ JOIN_SECONDS = 0.1
17
+
18
+ # 1.8.7 backfills
19
+
20
+ # Array#sample
21
+ if !(a = Array.new).respond_to?(:sample) && a.respond_to?(:choice)
22
+ class Array
23
+ alias_method :sample, :choice
24
+ end
25
+ end
@@ -0,0 +1,104 @@
1
+ require 'qs'
2
+
3
+ AppQueue = Qs::Queue.new do
4
+ name 'qs-app-main'
5
+
6
+ job_handler_ns 'AppHandlers'
7
+
8
+ job 'basic', 'Basic'
9
+ job 'basic1', 'Basic'
10
+ job 'error', 'Error'
11
+ job 'timeout', 'Timeout'
12
+ job 'slow', 'Slow'
13
+ job 'slow1', 'Slow'
14
+ job 'slow2', 'Slow'
15
+
16
+ event_handler_ns 'AppHandlers'
17
+
18
+ event 'qs-app', 'basic', 'BasicEvent'
19
+ event 'qs-app', 'error', 'ErrorEvent'
20
+ event 'qs-app', 'timeout', 'TimeoutEvent'
21
+ event 'qs-app', 'slow', 'SlowEvent'
22
+ end
23
+
24
+ module AppHandlers
25
+
26
+ class Basic
27
+ include Qs::JobHandler
28
+
29
+ def run!
30
+ Qs.redis.with{ |c| c.set("qs-app:#{params['key']}", params['value']) }
31
+ end
32
+ end
33
+
34
+ class Error
35
+ include Qs::JobHandler
36
+
37
+ def run!
38
+ raise params['error_message']
39
+ end
40
+ end
41
+
42
+ class Timeout
43
+ include Qs::JobHandler
44
+
45
+ TIMEOUT_TIME = 0.1
46
+
47
+ timeout TIMEOUT_TIME
48
+
49
+ def run!
50
+ sleep 2*TIMEOUT_TIME
51
+ end
52
+ end
53
+
54
+ class Slow
55
+ include Qs::JobHandler
56
+
57
+ SLOW_TIME = 1
58
+
59
+ def run!
60
+ sleep SLOW_TIME
61
+ Qs.redis.with{ |c| c.set('qs-app:slow', 'finished') }
62
+ end
63
+ end
64
+
65
+ class BasicEvent
66
+ include Qs::EventHandler
67
+
68
+ def run!
69
+ Qs.redis.with{ |c| c.set("qs-app:#{params['key']}", params['value']) }
70
+ end
71
+ end
72
+
73
+ class ErrorEvent
74
+ include Qs::EventHandler
75
+
76
+ def run!
77
+ raise params['error_message']
78
+ end
79
+ end
80
+
81
+ class TimeoutEvent
82
+ include Qs::EventHandler
83
+
84
+ TIMEOUT_TIME = 0.1
85
+
86
+ timeout TIMEOUT_TIME
87
+
88
+ def run!
89
+ sleep 2*TIMEOUT_TIME
90
+ end
91
+ end
92
+
93
+ class SlowEvent
94
+ include Qs::EventHandler
95
+
96
+ SLOW_TIME = 1
97
+
98
+ def run!
99
+ sleep SLOW_TIME
100
+ Qs.redis.with{ |c| c.set('qs-app:slow:event', 'finished') }
101
+ end
102
+ end
103
+
104
+ end
@@ -3,8 +3,8 @@ require 'qs/client'
3
3
  class ClientSpy < Qs::TestClient
4
4
  attr_reader :calls
5
5
 
6
- def initialize(redis_config = nil)
7
- super(redis_config || {})
6
+ def initialize(redis_connect_hash = nil)
7
+ super(redis_connect_hash || {})
8
8
  @calls = []
9
9
  @list = []
10
10
  @mutex = Mutex.new
@@ -1,7 +1,14 @@
1
- require 'test/support/app_daemon'
1
+ require 'test/support/app_queue'
2
2
 
3
3
  if !defined?(TestConstant)
4
4
  TestConstant = Class.new
5
5
  end
6
6
 
7
- run AppDaemon.new
7
+ class ConfigFileTestDaemon
8
+ include Qs::Daemon
9
+
10
+ name 'qs-config-file-test'
11
+ queue AppQueue
12
+ end
13
+
14
+ run ConfigFileTestDaemon.new
@@ -16,7 +16,7 @@ module Factory
16
16
  end
17
17
 
18
18
  def self.message(params = nil)
19
- self.send([:job, :event].choice, params)
19
+ self.send([:job, :event].sample, params)
20
20
  end
21
21
 
22
22
  def self.job(args = nil)
@@ -1,7 +1,7 @@
1
1
  require 'assert'
2
2
  require 'qs/daemon'
3
3
 
4
- require 'test/support/app_daemon'
4
+ require 'test/support/app_queue'
5
5
 
6
6
  module Qs::Daemon
7
7
 
@@ -11,15 +11,15 @@ module Qs::Daemon
11
11
  Qs.reset!
12
12
  @qs_test_mode = ENV['QS_TEST_MODE']
13
13
  ENV['QS_TEST_MODE'] = nil
14
- Qs.config.dispatcher.queue_name = 'qs-app-dispatcher'
14
+ Qs.config.dispatcher_queue_name = 'qs-app-dispatcher'
15
15
  Qs.config.event_publisher = 'Daemon System Tests'
16
16
  Qs.init
17
17
  AppQueue.sync_subscriptions
18
- @orig_config = AppDaemon.configuration.to_hash
18
+
19
+ @app_daemon_class = build_app_daemon_class
19
20
  end
20
21
  teardown do
21
22
  @daemon_runner.stop if @daemon_runner
22
- AppDaemon.configuration.apply(@orig_config) # reset daemon config
23
23
  Qs.redis.with do |c|
24
24
  keys = c.keys('*qs-app*')
25
25
  c.pipelined{ keys.each{ |k| c.del(k) } }
@@ -32,10 +32,42 @@ module Qs::Daemon
32
32
 
33
33
  private
34
34
 
35
+ # manually build new anonymous app daemon classes for each run. We do this
36
+ # both to not mess with global state when tweaking config values for tests
37
+ # and b/c there is no way to "reset" an existing class's config.
38
+ def build_app_daemon_class
39
+ Class.new do
40
+ include Qs::Daemon
41
+
42
+ name 'qs-app'
43
+
44
+ logger Logger.new(ROOT_PATH.join('log/app_daemon.log').to_s)
45
+ logger.datetime_format = "" # turn off the datetime in the logs
46
+
47
+ verbose_logging true
48
+
49
+ queue AppQueue
50
+
51
+ error do |exception, context|
52
+ return unless (message = context.message)
53
+ payload_type = message.payload_type
54
+ route_name = message.route_name
55
+ case(route_name)
56
+ when 'error', 'timeout', 'qs-app:error', 'qs-app:timeout'
57
+ error = "#{exception.class}: #{exception.message}"
58
+ Qs.redis.with{ |c| c.set("qs-app:last_#{payload_type}_error", error) }
59
+ when 'slow', 'qs-app:slow'
60
+ error = exception.class.to_s
61
+ Qs.redis.with{ |c| c.set("qs-app:last_#{payload_type}_error", error) }
62
+ end
63
+ end
64
+ end
65
+ end
66
+
35
67
  def setup_app_and_dispatcher_daemon
36
- @app_daemon = AppDaemon.new
37
- @dispatcher_daemon = DispatcherDaemon.new
38
- @daemon_runner = DaemonRunner.new(@app_daemon, @dispatcher_daemon)
68
+ @app_daemon = @app_daemon_class.new
69
+ @dispatcher_daemon = AppDispatcherDaemon.new
70
+ @daemon_runner = AppDaemonRunner.new(@app_daemon, @dispatcher_daemon)
39
71
  @app_thread = @daemon_runner.start
40
72
  end
41
73
 
@@ -56,7 +88,7 @@ module Qs::Daemon
56
88
  'key' => @key,
57
89
  'value' => @value
58
90
  })
59
- @app_thread.join 0.5
91
+ @app_thread.join(JOIN_SECONDS)
60
92
  end
61
93
 
62
94
  should "run the job" do
@@ -70,7 +102,7 @@ module Qs::Daemon
70
102
  setup do
71
103
  @error_message = Factory.text
72
104
  AppQueue.add('error', 'error_message' => @error_message)
73
- @app_thread.join 0.5
105
+ @app_thread.join(JOIN_SECONDS)
74
106
  end
75
107
 
76
108
  should "run the configured error handler procs" do
@@ -84,7 +116,7 @@ module Qs::Daemon
84
116
  desc "with a job that times out"
85
117
  setup do
86
118
  AppQueue.add('timeout')
87
- @app_thread.join 1 # let the daemon have time to process the job
119
+ @app_thread.join(AppHandlers::Timeout::TIMEOUT_TIME + JOIN_SECONDS)
88
120
  end
89
121
 
90
122
  should "run the configured error handler procs" do
@@ -104,7 +136,7 @@ module Qs::Daemon
104
136
  'key' => @key,
105
137
  'value' => @value
106
138
  })
107
- @app_thread.join 0.5
139
+ @app_thread.join(JOIN_SECONDS)
108
140
  end
109
141
 
110
142
  should "run the event" do
@@ -118,7 +150,7 @@ module Qs::Daemon
118
150
  setup do
119
151
  @error_message = Factory.text
120
152
  Qs.publish('qs-app', 'error', 'error_message' => @error_message)
121
- @app_thread.join 0.5
153
+ @app_thread.join(JOIN_SECONDS)
122
154
  end
123
155
 
124
156
  should "run the configured error handler procs" do
@@ -132,7 +164,7 @@ module Qs::Daemon
132
164
  desc "with an event that times out"
133
165
  setup do
134
166
  Qs.publish('qs-app', 'timeout')
135
- @app_thread.join 1 # let the daemon have time to process the job
167
+ @app_thread.join(AppHandlers::Timeout::TIMEOUT_TIME + JOIN_SECONDS)
136
168
  end
137
169
 
138
170
  should "run the configured error handler procs" do
@@ -147,17 +179,22 @@ module Qs::Daemon
147
179
  class ShutdownWithoutTimeoutTests < SystemTests
148
180
  desc "without a shutdown timeout"
149
181
  setup do
150
- AppDaemon.shutdown_timeout nil # disable shutdown timeout
182
+ @app_daemon_class.shutdown_timeout nil # disable shutdown timeout
183
+ @nil_shutdown_timeout = 10 # something absurdly long, it should be faster
184
+ # than this but want some timout to keep tests
185
+ # from hanging in case it never shuts down
186
+
151
187
  setup_app_and_dispatcher_daemon
152
188
 
153
189
  AppQueue.add('slow')
154
190
  Qs.publish('qs-app', 'slow')
155
- @app_thread.join 1 # let the daemon have time to process the job and event
191
+ @app_thread.join(JOIN_SECONDS)
156
192
  end
157
193
 
158
194
  should "shutdown and let the job and event finish" do
159
195
  @app_daemon.stop
160
- @app_thread.join 10 # give it time to shutdown, should be faster
196
+ @app_thread.join(@nil_shutdown_timeout)
197
+
161
198
  assert_false @app_thread.alive?
162
199
  assert_equal 'finished', Qs.redis.with{ |c| c.get('qs-app:slow') }
163
200
  assert_equal 'finished', Qs.redis.with{ |c| c.get('qs-app:slow:event') }
@@ -165,12 +202,15 @@ module Qs::Daemon
165
202
 
166
203
  should "shutdown and not let the job or event finish" do
167
204
  @app_daemon.halt
168
- @app_thread.join 2 # give it time to shutdown, should be faster
205
+ @app_thread.join(@nil_shutdown_timeout)
206
+
169
207
  assert_false @app_thread.alive?
170
208
  assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
209
+
171
210
  exp = "Qs::ShutdownError"
172
211
  assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
173
212
  assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
213
+
174
214
  exp = "Qs::ShutdownError"
175
215
  assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
176
216
  end
@@ -180,34 +220,41 @@ module Qs::Daemon
180
220
  class ShutdownWithTimeoutTests < SystemTests
181
221
  desc "with a shutdown timeout"
182
222
  setup do
183
- AppDaemon.shutdown_timeout 1
223
+ @shutdown_timeout = AppHandlers::Slow::SLOW_TIME * 0.5
224
+ @app_daemon_class.shutdown_timeout @shutdown_timeout
184
225
  setup_app_and_dispatcher_daemon
185
226
 
186
227
  AppQueue.add('slow')
187
228
  Qs.publish('qs-app', 'slow')
188
- @app_thread.join 1 # let the daemon have time to process the job and event
229
+ @app_thread.join(JOIN_SECONDS)
189
230
  end
190
231
 
191
232
  should "shutdown and not let the job or event finish" do
192
233
  @app_daemon.stop
193
- @app_thread.join 2 # give it time to shutdown, should be faster
234
+ @app_thread.join(@shutdown_timeout + JOIN_SECONDS)
235
+
194
236
  assert_false @app_thread.alive?
195
237
  assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
238
+
196
239
  exp = "Qs::ShutdownError"
197
240
  assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
198
241
  assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
242
+
199
243
  exp = "Qs::ShutdownError"
200
244
  assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
201
245
  end
202
246
 
203
247
  should "shutdown and not let the job or event finish" do
204
248
  @app_daemon.halt
205
- @app_thread.join 2 # give it time to shutdown, should be faster
249
+ @app_thread.join(@shutdown_timeout + JOIN_SECONDS)
250
+
206
251
  assert_false @app_thread.alive?
207
252
  assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
253
+
208
254
  exp = "Qs::ShutdownError"
209
255
  assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
210
256
  assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
257
+
211
258
  exp = "Qs::ShutdownError"
212
259
  assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
213
260
  end
@@ -217,34 +264,44 @@ module Qs::Daemon
217
264
  class ShutdownWithUnprocessedQueueItemTests < SystemTests
218
265
  desc "with a queue item that gets picked up but doesn't get processed"
219
266
  setup do
220
- Assert.stub(Qs::PayloadHandler, :new){ sleep 5 }
267
+ Assert.stub(Qs::PayloadHandler, :new) do
268
+ sleep AppHandlers::Slow::SLOW_TIME + JOIN_SECONDS
269
+ end
221
270
 
222
- AppDaemon.shutdown_timeout 1
223
- AppDaemon.workers 2
271
+ @shutdown_timeout = AppHandlers::Slow::SLOW_TIME * 0.5
272
+ @app_daemon_class.shutdown_timeout @shutdown_timeout
273
+ @app_daemon_class.workers 2
224
274
  setup_app_and_dispatcher_daemon
225
275
 
226
- AppQueue.add('slow')
227
- AppQueue.add('slow')
228
- AppQueue.add('basic')
229
- @app_thread.join 1 # let the daemon have time to process jobs
276
+ AppQueue.add('slow1')
277
+ AppQueue.add('slow2')
278
+ AppQueue.add('basic1')
279
+
280
+ @app_thread.join(JOIN_SECONDS)
230
281
  end
231
282
 
232
283
  should "shutdown and requeue the queue item" do
233
284
  @app_daemon.stop
234
- @app_thread.join 2 # give it time to shutdown, should be faster
285
+ @app_thread.join(@shutdown_timeout + JOIN_SECONDS)
286
+
235
287
  assert_false @app_thread.alive?
288
+
236
289
  encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
237
290
  names = encoded_payloads.map{ |sp| Qs::Payload.deserialize(sp).name }
238
- assert_equal ['basic', 'slow', 'slow'], names
291
+
292
+ ['slow1', 'slow2', 'basic1'].each{ |n| assert_includes n, names }
239
293
  end
240
294
 
241
295
  should "shutdown and requeue the queue item" do
242
296
  @app_daemon.halt
243
- @app_thread.join 2 # give it time to shutdown, should be faster
297
+ @app_thread.join(@shutdown_timeout + JOIN_SECONDS)
298
+
244
299
  assert_false @app_thread.alive?
245
- encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
300
+
301
+ encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 4) }
246
302
  names = encoded_payloads.map{ |sp| Qs::Payload.deserialize(sp).name }
247
- assert_equal ['basic', 'slow', 'slow'], names
303
+
304
+ ['slow1', 'slow2', 'basic1'].each{ |n| assert_includes n, names }
248
305
  end
249
306
 
250
307
  end
@@ -254,7 +311,7 @@ module Qs::Daemon
254
311
  setup do
255
312
  ENV['QS_PROCESS_LABEL'] = Factory.string
256
313
 
257
- @daemon = AppDaemon.new
314
+ @daemon = @app_daemon_class.new
258
315
  end
259
316
  teardown do
260
317
  ENV.delete('QS_PROCESS_LABEL')
@@ -267,7 +324,27 @@ module Qs::Daemon
267
324
 
268
325
  end
269
326
 
270
- class DaemonRunner
327
+ class AppDispatcherDaemon
328
+ include Qs::Daemon
329
+
330
+ name 'qs-app-dispatcher'
331
+
332
+ logger Logger.new(ROOT_PATH.join('log/app_dispatcher_daemon.log').to_s)
333
+ logger.datetime_format = "" # turn off the datetime in the logs
334
+
335
+ verbose_logging true
336
+
337
+ # we build a "custom" dispatcher because we can't rely on Qs being initialized
338
+ # when this is required
339
+ queue Qs::DispatcherQueue.new({
340
+ :queue_class => Qs.config.dispatcher_queue_class,
341
+ :queue_name => 'qs-app-dispatcher',
342
+ :job_name => Qs.config.dispatcher_job_name,
343
+ :job_handler_class_name => Qs.config.dispatcher_job_handler_class_name
344
+ })
345
+ end
346
+
347
+ class AppDaemonRunner
271
348
  def initialize(app_daemon, dispatcher_daemon = nil)
272
349
  @app_daemon = app_daemon
273
350
  @dispatcher_daemon = dispatcher_daemon
@@ -277,7 +354,11 @@ module Qs::Daemon
277
354
 
278
355
  def start
279
356
  @app_thread = @app_daemon.start
280
- @dispatcher_thread = @dispatcher_daemon.start if @dispatcher_daemon
357
+ @app_thread.join(JOIN_SECONDS)
358
+ if @dispatcher_daemon
359
+ @dispatcher_thread = @dispatcher_daemon.start
360
+ @dispatcher_thread.join(JOIN_SECONDS)
361
+ end
281
362
  @app_thread
282
363
  end
283
364