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.
- checksums.yaml +4 -4
- data/bench/config.qs +1 -0
- data/bench/dispatcher.qs +1 -0
- data/bench/report.rb +2 -0
- data/bench/setup.rb +1 -1
- data/lib/qs.rb +95 -41
- data/lib/qs/client.rb +5 -5
- data/lib/qs/config_file.rb +1 -1
- data/lib/qs/daemon.rb +133 -99
- data/lib/qs/daemon_data.rb +22 -21
- data/lib/qs/message_handler.rb +1 -0
- data/lib/qs/process.rb +20 -10
- data/lib/qs/qs_runner.rb +13 -21
- data/lib/qs/runner.rb +4 -0
- data/lib/qs/test_runner.rb +12 -2
- data/lib/qs/version.rb +1 -1
- data/qs.gemspec +6 -7
- data/test/helper.rb +11 -0
- data/test/support/app_queue.rb +104 -0
- data/test/support/client_spy.rb +2 -2
- data/test/support/config.qs +9 -2
- data/test/support/factory.rb +1 -1
- data/test/system/daemon_tests.rb +117 -36
- data/test/system/queue_tests.rb +1 -1
- data/test/unit/cli_tests.rb +2 -2
- data/test/unit/client_tests.rb +11 -11
- data/test/unit/config_file_tests.rb +2 -2
- data/test/unit/daemon_data_tests.rb +53 -48
- data/test/unit/daemon_tests.rb +221 -214
- data/test/unit/message_handler_tests.rb +14 -1
- data/test/unit/process_tests.rb +50 -25
- data/test/unit/qs_runner_tests.rb +120 -34
- data/test/unit/qs_tests.rb +180 -75
- data/test/unit/queue_tests.rb +5 -5
- data/test/unit/runner_tests.rb +9 -1
- data/test/unit/test_runner_tests.rb +12 -1
- metadata +13 -23
- data/test/support/app_daemon.rb +0 -143
data/lib/qs/daemon_data.rb
CHANGED
@@ -2,37 +2,38 @@ module Qs
|
|
2
2
|
|
3
3
|
class DaemonData
|
4
4
|
|
5
|
-
# The daemon uses this to "compile"
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
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, :
|
10
|
+
attr_reader :name, :pid_file, :shutdown_timeout
|
11
11
|
attr_reader :worker_class, :worker_params, :num_workers
|
12
|
-
attr_reader :
|
13
|
-
attr_reader :
|
14
|
-
attr_reader :
|
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
|
19
|
-
@
|
20
|
-
@pid_file = args[:pid_file]
|
18
|
+
@name = args[:name]
|
19
|
+
@pid_file = args[:pid_file]
|
21
20
|
|
22
|
-
@
|
23
|
-
|
24
|
-
@
|
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
|
-
@
|
32
|
+
@debug = !ENV['QS_DEBUG'].to_s.empty?
|
33
|
+
@dwp_logger = @logger if @debug
|
34
|
+
@routes = build_routes(args[:routes] || [])
|
32
35
|
|
33
|
-
@
|
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)
|
data/lib/qs/message_handler.rb
CHANGED
data/lib/qs/process.rb
CHANGED
@@ -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
|
-
|
62
|
-
log "#{
|
59
|
+
daemon.start
|
60
|
+
log "#{daemon.name} daemon started and ready."
|
63
61
|
rescue StandardError => exception
|
64
|
-
log "#{
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
data/lib/qs/qs_runner.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
data/lib/qs/runner.rb
CHANGED
data/lib/qs/test_runner.rb
CHANGED
@@ -17,11 +17,21 @@ module Qs
|
|
17
17
|
})
|
18
18
|
a.each{ |key, value| self.handler.send("#{key}=", value) }
|
19
19
|
|
20
|
-
|
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
|
data/lib/qs/version.rb
CHANGED
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.
|
22
|
-
gem.add_development_dependency("scmd", ["~> 3.0.
|
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.
|
25
|
-
gem.add_dependency("hella-redis", ["~> 0.3.
|
26
|
-
gem.add_dependency("much-plugin", ["~> 0.
|
27
|
-
gem.add_dependency("
|
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
|
data/test/helper.rb
CHANGED
@@ -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
|
data/test/support/client_spy.rb
CHANGED
@@ -3,8 +3,8 @@ require 'qs/client'
|
|
3
3
|
class ClientSpy < Qs::TestClient
|
4
4
|
attr_reader :calls
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
super(
|
6
|
+
def initialize(redis_connect_hash = nil)
|
7
|
+
super(redis_connect_hash || {})
|
8
8
|
@calls = []
|
9
9
|
@list = []
|
10
10
|
@mutex = Mutex.new
|
data/test/support/config.qs
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
-
require 'test/support/
|
1
|
+
require 'test/support/app_queue'
|
2
2
|
|
3
3
|
if !defined?(TestConstant)
|
4
4
|
TestConstant = Class.new
|
5
5
|
end
|
6
6
|
|
7
|
-
|
7
|
+
class ConfigFileTestDaemon
|
8
|
+
include Qs::Daemon
|
9
|
+
|
10
|
+
name 'qs-config-file-test'
|
11
|
+
queue AppQueue
|
12
|
+
end
|
13
|
+
|
14
|
+
run ConfigFileTestDaemon.new
|
data/test/support/factory.rb
CHANGED
data/test/system/daemon_tests.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'assert'
|
2
2
|
require 'qs/daemon'
|
3
3
|
|
4
|
-
require 'test/support/
|
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.
|
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
|
-
|
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 =
|
37
|
-
@dispatcher_daemon =
|
38
|
-
@daemon_runner =
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
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)
|
267
|
+
Assert.stub(Qs::PayloadHandler, :new) do
|
268
|
+
sleep AppHandlers::Slow::SLOW_TIME + JOIN_SECONDS
|
269
|
+
end
|
221
270
|
|
222
|
-
|
223
|
-
|
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('
|
227
|
-
AppQueue.add('
|
228
|
-
AppQueue.add('
|
229
|
-
|
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
|
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
|
-
|
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
|
297
|
+
@app_thread.join(@shutdown_timeout + JOIN_SECONDS)
|
298
|
+
|
244
299
|
assert_false @app_thread.alive?
|
245
|
-
|
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
|
-
|
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 =
|
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
|
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
|
-
@
|
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
|
|