serverengine 1.6.4 → 2.0.0pre1
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/.gitignore +2 -0
- data/Changelog +11 -0
- data/README.md +31 -3
- data/Rakefile +16 -3
- data/appveyor.yml +11 -5
- data/examples/server.rb +138 -0
- data/examples/spawn_worker_script.rb +38 -0
- data/lib/serverengine/blocking_flag.rb +2 -3
- data/lib/serverengine/command_sender.rb +89 -0
- data/lib/serverengine/config_loader.rb +2 -0
- data/lib/serverengine/daemon.rb +114 -86
- data/lib/serverengine/daemon_logger.rb +3 -139
- data/lib/serverengine/embedded_server.rb +2 -0
- data/lib/serverengine/multi_process_server.rb +28 -7
- data/lib/serverengine/multi_spawn_server.rb +17 -18
- data/lib/serverengine/multi_thread_server.rb +6 -0
- data/lib/serverengine/multi_worker_server.rb +14 -0
- data/lib/serverengine/privilege.rb +57 -0
- data/lib/serverengine/process_manager.rb +66 -48
- data/lib/serverengine/server.rb +45 -11
- data/lib/serverengine/signal_thread.rb +0 -2
- data/lib/serverengine/signals.rb +31 -0
- data/lib/serverengine/socket_manager.rb +3 -5
- data/lib/serverengine/socket_manager_unix.rb +1 -0
- data/lib/serverengine/socket_manager_win.rb +4 -2
- data/lib/serverengine/supervisor.rb +105 -25
- data/lib/serverengine/utils.rb +23 -0
- data/lib/serverengine/version.rb +1 -1
- data/lib/serverengine/worker.rb +10 -7
- data/lib/serverengine.rb +9 -27
- data/serverengine.gemspec +12 -1
- data/spec/daemon_logger_spec.rb +17 -12
- data/spec/daemon_spec.rb +147 -24
- data/spec/multi_process_server_spec.rb +59 -7
- data/spec/server_worker_context.rb +104 -0
- data/spec/signal_thread_spec.rb +61 -56
- data/spec/supervisor_spec.rb +113 -99
- metadata +40 -6
data/lib/serverengine/utils.rb
CHANGED
@@ -32,6 +32,29 @@ module ServerEngine
|
|
32
32
|
}
|
33
33
|
nil
|
34
34
|
end
|
35
|
+
|
36
|
+
def format_signal_name(n)
|
37
|
+
Signal.list.each_pair {|k,v|
|
38
|
+
return "SIG#{k}" if n == v
|
39
|
+
}
|
40
|
+
return n
|
41
|
+
end
|
42
|
+
|
43
|
+
def format_join_status(code)
|
44
|
+
case code
|
45
|
+
when Process::Status
|
46
|
+
if code.signaled?
|
47
|
+
"signal #{format_signal_name(code.termsig)}"
|
48
|
+
else
|
49
|
+
"status #{code.exitstatus}"
|
50
|
+
end
|
51
|
+
when Exception
|
52
|
+
"exception #{code}"
|
53
|
+
when nil
|
54
|
+
"unknown reason"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
35
58
|
end
|
36
59
|
|
37
60
|
extend ClassMethods
|
data/lib/serverengine/version.rb
CHANGED
data/lib/serverengine/worker.rb
CHANGED
@@ -15,6 +15,9 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
+
require 'serverengine/signals'
|
19
|
+
require 'serverengine/signal_thread'
|
20
|
+
|
18
21
|
module ServerEngine
|
19
22
|
|
20
23
|
class Worker
|
@@ -54,19 +57,19 @@ module ServerEngine
|
|
54
57
|
def install_signal_handlers
|
55
58
|
w = self
|
56
59
|
SignalThread.new do |st|
|
57
|
-
st.trap(
|
58
|
-
st.trap(
|
60
|
+
st.trap(Signals::GRACEFUL_STOP) { w.stop }
|
61
|
+
st.trap(Signals::IMMEDIATE_STOP, 'SIG_DFL')
|
59
62
|
|
60
|
-
st.trap(
|
61
|
-
st.trap(
|
63
|
+
st.trap(Signals::GRACEFUL_RESTART) { w.stop }
|
64
|
+
st.trap(Signals::IMMEDIATE_RESTART, 'SIG_DFL')
|
62
65
|
|
63
|
-
st.trap(
|
66
|
+
st.trap(Signals::RELOAD) {
|
64
67
|
w.logger.reopen!
|
65
68
|
w.reload
|
66
69
|
}
|
67
|
-
st.trap(
|
70
|
+
st.trap(Signals::DETACH) { w.stop }
|
68
71
|
|
69
|
-
st.trap(
|
72
|
+
st.trap(Signals::DUMP) { Sigdump.dump }
|
70
73
|
end
|
71
74
|
end
|
72
75
|
|
data/lib/serverengine.rb
CHANGED
@@ -19,34 +19,15 @@ module ServerEngine
|
|
19
19
|
|
20
20
|
require 'sigdump'
|
21
21
|
|
22
|
-
|
22
|
+
require 'serverengine/version'
|
23
23
|
|
24
|
-
|
25
|
-
:BlockingFlag => 'serverengine/blocking_flag',
|
26
|
-
:SignalThread => 'serverengine/signal_thread',
|
27
|
-
:DaemonLogger => 'serverengine/daemon_logger',
|
28
|
-
:ConfigLoader => 'serverengine/config_loader',
|
29
|
-
:Daemon => 'serverengine/daemon',
|
30
|
-
:Supervisor => 'serverengine/supervisor',
|
31
|
-
:Server => 'serverengine/server',
|
32
|
-
:EmbeddedServer => 'serverengine/embedded_server',
|
33
|
-
:MultiWorkerServer => 'serverengine/multi_worker_server',
|
34
|
-
:MultiProcessServer => 'serverengine/multi_process_server',
|
35
|
-
:MultiThreadServer => 'serverengine/multi_thread_server',
|
36
|
-
:MultiSpawnServer => 'serverengine/multi_spawn_server',
|
37
|
-
:ProcessManager => 'serverengine/process_manager',
|
38
|
-
:SocketManager => 'serverengine/socket_manager',
|
39
|
-
:Worker => 'serverengine/worker',
|
40
|
-
:VERSION => 'serverengine/version',
|
41
|
-
}.each_pair {|k,v|
|
42
|
-
autoload k, File.expand_path(v, File.dirname(__FILE__))
|
43
|
-
}
|
24
|
+
require 'serverengine/utils' # ServerEngine.windows? and other util methods
|
44
25
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
26
|
+
require 'serverengine/daemon'
|
27
|
+
require 'serverengine/supervisor'
|
28
|
+
require 'serverengine/server'
|
29
|
+
require 'serverengine/worker'
|
30
|
+
require 'serverengine/socket_manager'
|
50
31
|
|
51
32
|
def self.create(server_module, worker_module, load_config_proc={}, &block)
|
52
33
|
Daemon.new(server_module, worker_module, load_config_proc, &block)
|
@@ -54,8 +35,9 @@ module ServerEngine
|
|
54
35
|
|
55
36
|
def self.ruby_bin_path
|
56
37
|
if ServerEngine.windows?
|
38
|
+
require 'windows/library'
|
57
39
|
ruby_path = "\0" * 256
|
58
|
-
GetModuleFileName.call(0, ruby_path, 256)
|
40
|
+
Windows::Library::GetModuleFileName.call(0, ruby_path, 256)
|
59
41
|
return ruby_path.rstrip.gsub(/\\/, '/')
|
60
42
|
else
|
61
43
|
return File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["RUBY_INSTALL_NAME"]) + RbConfig::CONFIG["EXEEXT"]
|
data/serverengine.gemspec
CHANGED
@@ -17,10 +17,21 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
gem.has_rdoc = false
|
19
19
|
|
20
|
-
gem.required_ruby_version = ">= 1.
|
20
|
+
gem.required_ruby_version = ">= 2.1.0"
|
21
21
|
|
22
22
|
gem.add_dependency "sigdump", ["~> 0.2.2"]
|
23
23
|
|
24
24
|
gem.add_development_dependency "rake", [">= 0.9.2"]
|
25
25
|
gem.add_development_dependency "rspec", ["~> 2.13.0"]
|
26
|
+
|
27
|
+
gem.add_development_dependency 'rake-compiler-dock', ['~> 0.5.0']
|
28
|
+
gem.add_development_dependency 'rake-compiler', ['~> 0.9.4']
|
29
|
+
|
30
|
+
# build gem for a certain platform. see also Rakefile
|
31
|
+
fake_platform = ENV['GEM_BUILD_FAKE_PLATFORM'].to_s
|
32
|
+
gem.platform = fake_platform unless fake_platform.empty?
|
33
|
+
if /mswin|mingw/ =~ fake_platform || (/mswin|mingw/ =~ RUBY_PLATFORM && fake_platform.empty?)
|
34
|
+
# windows dependencies
|
35
|
+
gem.add_runtime_dependency("windows-pr", ["~> 1.2.5"])
|
36
|
+
end
|
26
37
|
end
|
data/spec/daemon_logger_spec.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
|
3
3
|
describe ServerEngine::DaemonLogger do
|
4
|
+
before { FileUtils.rm_rf("tmp") }
|
4
5
|
before { FileUtils.mkdir_p("tmp") }
|
5
6
|
before { FileUtils.rm_f("tmp/se1.log") }
|
6
|
-
before { FileUtils.rm_f Dir["tmp/se1.log.**"] }
|
7
7
|
before { FileUtils.rm_f("tmp/se2.log") }
|
8
|
+
before { FileUtils.rm_f Dir["tmp/se3.log.**"] }
|
9
|
+
before { FileUtils.rm_f Dir["tmp/se4.log.**"] }
|
8
10
|
|
9
11
|
subject { DaemonLogger.new("tmp/se1.log", level: 'trace') }
|
10
12
|
|
@@ -113,26 +115,27 @@ describe ServerEngine::DaemonLogger do
|
|
113
115
|
end
|
114
116
|
|
115
117
|
it 'rotation' do
|
116
|
-
log = DaemonLogger.new("tmp/
|
118
|
+
log = DaemonLogger.new("tmp/se3.log", level: 'trace', log_rotate_age: 3, log_rotate_size: 10000)
|
117
119
|
# 100 bytes
|
118
120
|
log.warn "test1"*20
|
119
|
-
File.exist?("tmp/
|
120
|
-
File.exist?("tmp/
|
121
|
+
File.exist?("tmp/se3.log").should == true
|
122
|
+
File.exist?("tmp/se3.log.0").should == false
|
121
123
|
|
122
124
|
# 10000 bytes
|
123
125
|
100.times { log.warn "test2"*20 }
|
124
|
-
File.exist?("tmp/
|
125
|
-
File.exist?("tmp/
|
126
|
-
File.read("tmp/
|
126
|
+
File.exist?("tmp/se3.log").should == true
|
127
|
+
File.exist?("tmp/se3.log.0").should == true
|
128
|
+
File.read("tmp/se3.log.0") =~ /test2$/
|
127
129
|
|
128
130
|
# 10000 bytes
|
129
131
|
100.times { log.warn "test3"*20 }
|
130
|
-
File.exist?("tmp/
|
131
|
-
File.exist?("tmp/
|
132
|
-
File.exist?("tmp/
|
132
|
+
File.exist?("tmp/se3.log").should == true
|
133
|
+
File.exist?("tmp/se3.log.1").should == true
|
134
|
+
File.exist?("tmp/se3.log.2").should == false
|
133
135
|
|
134
136
|
log.warn "test4"*20
|
135
|
-
File.read("tmp/
|
137
|
+
File.read("tmp/se3.log").should =~ /test4$/
|
138
|
+
File.read("tmp/se3.log.0").should =~ /test3$/
|
136
139
|
end
|
137
140
|
|
138
141
|
it 'IO logger' do
|
@@ -146,7 +149,9 @@ describe ServerEngine::DaemonLogger do
|
|
146
149
|
end
|
147
150
|
|
148
151
|
it 'inter-process locking on rotation' do
|
149
|
-
|
152
|
+
pending "fork is not implemented in Windows" if ServerEngine.windows?
|
153
|
+
|
154
|
+
log = DaemonLogger.new("tmp/se4.log", level: 'trace', log_rotate_age: 3, log_rotate_size: 10)
|
150
155
|
r, w = IO.pipe
|
151
156
|
$stderr = w # To capture #warn output in DaemonLogger
|
152
157
|
pid1 = Process.fork do
|
data/spec/daemon_spec.rb
CHANGED
@@ -2,45 +2,168 @@
|
|
2
2
|
describe ServerEngine::Daemon do
|
3
3
|
include_context 'test server and worker'
|
4
4
|
|
5
|
-
it 'run and graceful stop' do
|
6
|
-
|
5
|
+
it 'run and graceful stop by signal' do
|
6
|
+
pending "not supported signal base commands on Windows" if ServerEngine.windows?
|
7
|
+
|
8
|
+
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", command_sender: "signal")
|
7
9
|
dm.main
|
8
10
|
|
11
|
+
wait_for_fork
|
12
|
+
|
9
13
|
test_state(:server_initialize).should == 1
|
10
14
|
|
11
|
-
|
15
|
+
begin
|
16
|
+
dm.stop(true)
|
17
|
+
wait_for_stop
|
18
|
+
|
19
|
+
test_state(:server_stop_graceful).should == 1
|
20
|
+
test_state(:worker_stop).should == 1
|
21
|
+
test_state(:server_after_run).should == 1
|
22
|
+
ensure
|
23
|
+
dm.stop(false) rescue nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'signals' do
|
28
|
+
pending "not supported signal base commands on Windows" if ServerEngine.windows?
|
29
|
+
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", command_sender: "signal")
|
30
|
+
dm.main
|
31
|
+
|
12
32
|
wait_for_fork
|
13
33
|
|
14
|
-
|
15
|
-
|
34
|
+
begin
|
35
|
+
dm.reload
|
36
|
+
wait_for_stop
|
37
|
+
test_state(:server_reload).should == 1
|
16
38
|
|
17
|
-
|
18
|
-
|
19
|
-
|
39
|
+
dm.restart(true)
|
40
|
+
wait_for_stop
|
41
|
+
test_state(:server_restart_graceful).should == 1
|
42
|
+
|
43
|
+
dm.restart(false)
|
44
|
+
wait_for_stop
|
45
|
+
test_state(:server_restart_immediate).should == 1
|
46
|
+
|
47
|
+
dm.stop(false)
|
48
|
+
wait_for_stop
|
49
|
+
test_state(:server_stop_immediate).should == 1
|
50
|
+
ensure
|
51
|
+
dm.stop(true) rescue nil
|
52
|
+
end
|
20
53
|
end
|
21
54
|
|
22
|
-
it '
|
23
|
-
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid")
|
55
|
+
it 'run and graceful stop by pipe' do
|
56
|
+
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", windows_daemon_cmdline: windows_daemon_cmdline, command_sender: "pipe")
|
57
|
+
dm.main
|
58
|
+
|
59
|
+
wait_for_fork
|
60
|
+
|
61
|
+
test_state(:server_initialize).should == 1
|
62
|
+
|
63
|
+
begin
|
64
|
+
dm.stop(true)
|
65
|
+
wait_for_stop
|
66
|
+
|
67
|
+
test_state(:server_stop_graceful).should == 1
|
68
|
+
test_state(:worker_stop).should == 1
|
69
|
+
test_state(:server_after_run).should == 1
|
70
|
+
ensure
|
71
|
+
dm.stop(false) rescue nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'recieve commands from pipe' do
|
76
|
+
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", windows_daemon_cmdline: windows_daemon_cmdline, command_sender: "pipe")
|
24
77
|
dm.main
|
25
78
|
|
26
|
-
pid = File.read('tmp/pid').to_i
|
27
79
|
wait_for_fork
|
28
80
|
|
29
|
-
|
30
|
-
|
31
|
-
|
81
|
+
begin
|
82
|
+
dm.reload
|
83
|
+
wait_for_stop
|
84
|
+
test_state(:server_reload).should == 1
|
32
85
|
|
33
|
-
|
34
|
-
|
35
|
-
|
86
|
+
dm.restart(true)
|
87
|
+
wait_for_stop
|
88
|
+
test_state(:server_restart_graceful).should == 1
|
36
89
|
|
37
|
-
|
38
|
-
|
39
|
-
|
90
|
+
dm.restart(false)
|
91
|
+
wait_for_stop
|
92
|
+
test_state(:server_restart_immediate).should == 1
|
40
93
|
|
41
|
-
|
42
|
-
|
43
|
-
|
94
|
+
dm.stop(false)
|
95
|
+
wait_for_stop
|
96
|
+
test_state(:server_stop_immediate).should == 1
|
97
|
+
ensure
|
98
|
+
dm.stop(true) rescue nil
|
99
|
+
end
|
44
100
|
end
|
45
|
-
end
|
46
101
|
|
102
|
+
it 'exits with status 0 when it was stopped normally' do
|
103
|
+
pending "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
|
104
|
+
dm = Daemon.new(
|
105
|
+
TestServer,
|
106
|
+
TestWorker,
|
107
|
+
daemonize: false,
|
108
|
+
supervisor: false,
|
109
|
+
pid_path: "tmp/pid",
|
110
|
+
log_stdout: false,
|
111
|
+
log_stderr: false,
|
112
|
+
unrecoverable_exit_codes: [3,4,5],
|
113
|
+
)
|
114
|
+
exit_code = nil
|
115
|
+
t = Thread.new { exit_code = dm.main }
|
116
|
+
sleep 0.1 until dm.instance_eval{ @pid }
|
117
|
+
dm.stop(true)
|
118
|
+
|
119
|
+
t.join
|
120
|
+
|
121
|
+
exit_code.should == 0
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'exits with status of workers if worker exits with status specified in unrecoverable_exit_codes, without supervisor' do
|
125
|
+
pending "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
|
126
|
+
|
127
|
+
dm = Daemon.new(
|
128
|
+
TestServer,
|
129
|
+
TestExitWorker,
|
130
|
+
daemonize: false,
|
131
|
+
supervisor: false,
|
132
|
+
worker_type: 'process',
|
133
|
+
pid_path: "tmp/pid",
|
134
|
+
log_stdout: false,
|
135
|
+
log_stderr: false,
|
136
|
+
unrecoverable_exit_codes: [3,4,5],
|
137
|
+
)
|
138
|
+
exit_code = nil
|
139
|
+
t = Thread.new { exit_code = dm.main }
|
140
|
+
sleep 0.1 until dm.instance_eval{ @pid }
|
141
|
+
|
142
|
+
t.join
|
143
|
+
|
144
|
+
exit_code.should == 5
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'exits with status of workers if worker exits with status specified in unrecoverable_exit_codes, with supervisor' do
|
148
|
+
pending "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
|
149
|
+
|
150
|
+
dm = Daemon.new(
|
151
|
+
TestServer,
|
152
|
+
TestExitWorker,
|
153
|
+
daemonize: false,
|
154
|
+
supervisor: true,
|
155
|
+
worker_type: 'process',
|
156
|
+
pid_path: "tmp/pid",
|
157
|
+
log_stdout: false,
|
158
|
+
log_stderr: false,
|
159
|
+
unrecoverable_exit_codes: [3,4,5],
|
160
|
+
)
|
161
|
+
exit_code = nil
|
162
|
+
t = Thread.new { exit_code = dm.main }
|
163
|
+
sleep 0.1 until dm.instance_eval{ @pid }
|
164
|
+
|
165
|
+
t.join
|
166
|
+
|
167
|
+
exit_code.should == 5
|
168
|
+
end
|
169
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
[ServerEngine::MultiThreadServer, ServerEngine::MultiProcessServer].each do |impl_class|
|
2
|
+
# MultiProcessServer uses fork(2) internally, then it doesn't support Windows.
|
1
3
|
|
2
|
-
describe
|
3
|
-
|
4
|
-
|
5
|
-
[MultiThreadServer, MultiProcessServer].each do |impl_class|
|
4
|
+
describe impl_class do
|
5
|
+
include_context 'test server and worker'
|
6
6
|
|
7
7
|
it 'scale up' do
|
8
|
-
|
8
|
+
pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
9
|
+
|
10
|
+
config = {workers: 2, log_stdout: false, log_stderr: false}
|
9
11
|
|
10
12
|
s = impl_class.new(TestWorker) { config.dup }
|
11
13
|
t = Thread.new { s.main }
|
@@ -31,7 +33,9 @@ describe ServerEngine::MultiWorkerServer do
|
|
31
33
|
end
|
32
34
|
|
33
35
|
it 'scale down' do
|
34
|
-
|
36
|
+
pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
37
|
+
|
38
|
+
config = {workers: 2, log_stdout: false, log_stderr: false}
|
35
39
|
|
36
40
|
s = impl_class.new(TestWorker) { config.dup }
|
37
41
|
t = Thread.new { s.main }
|
@@ -56,6 +60,54 @@ describe ServerEngine::MultiWorkerServer do
|
|
56
60
|
test_state(:worker_stop).should == 3
|
57
61
|
end
|
58
62
|
|
63
|
+
it 'raises SystemExit when all workers exit with specified code by unrecoverable_exit_codes' do
|
64
|
+
pending "unrecoverable_exit_codes supported only for multi process workers" if impl_class == ServerEngine::MultiThreadServer
|
65
|
+
pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
66
|
+
|
67
|
+
config = {workers: 4, log_stdout: false, log_stderr: false, unrecoverable_exit_codes: [3, 4, 5]}
|
68
|
+
|
69
|
+
s = impl_class.new(TestExitWorker) { config.dup }
|
70
|
+
raised_error = nil
|
71
|
+
t = Thread.new do
|
72
|
+
begin
|
73
|
+
s.main
|
74
|
+
rescue SystemExit => e
|
75
|
+
raised_error = e
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
wait_for_fork
|
80
|
+
test_state(:worker_run).should == 4
|
81
|
+
t.join
|
82
|
+
|
83
|
+
test_state(:worker_stop).to_i.should == 0
|
84
|
+
raised_error.status.should == 3 # 4th process's exit status
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'raises SystemExit immediately when a worker exits if stop_immediately_at_unrecoverable_exit specified' do
|
88
|
+
pending "unrecoverable_exit_codes supported only for multi process workers" if impl_class == ServerEngine::MultiThreadServer
|
89
|
+
pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
90
|
+
|
91
|
+
config = {workers: 4, log_stdout: false, log_stderr: false, unrecoverable_exit_codes: [3, 4, 5], stop_immediately_at_unrecoverable_exit: true}
|
92
|
+
|
93
|
+
s = impl_class.new(TestExitWorker) { config.dup }
|
94
|
+
raised_error = nil
|
95
|
+
t = Thread.new do
|
96
|
+
begin
|
97
|
+
s.main
|
98
|
+
rescue SystemExit => e
|
99
|
+
raised_error = e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
wait_for_fork
|
104
|
+
test_state(:worker_run).should == 4
|
105
|
+
t.join
|
106
|
+
|
107
|
+
test_state(:worker_stop).to_i.should == 3
|
108
|
+
test_state(:worker_finished).to_i.should == 3
|
109
|
+
raised_error.should_not be_nil
|
110
|
+
raised_error.status.should == 5 # 1st process's exit status
|
111
|
+
end
|
59
112
|
end
|
60
113
|
end
|
61
|
-
|
@@ -7,6 +7,76 @@ def reset_test_state
|
|
7
7
|
FileUtils.rm_f 'tmp/state.yml'
|
8
8
|
FileUtils.touch 'tmp/state.yml'
|
9
9
|
$state_file_mutex = Mutex.new
|
10
|
+
if ServerEngine.windows?
|
11
|
+
open("tmp/daemon.rb", "w") do |f|
|
12
|
+
f.puts <<-'end_of_script'
|
13
|
+
require "serverengine"
|
14
|
+
require "rspec"
|
15
|
+
$state_file_mutex = Mutex.new # TODO
|
16
|
+
require "server_worker_context"
|
17
|
+
include ServerEngine
|
18
|
+
command_pipe = STDIN.dup
|
19
|
+
STDIN.reopen(File::NULL)
|
20
|
+
Daemon.run_server(TestServer, TestWorker, command_pipe: command_pipe)
|
21
|
+
end_of_script
|
22
|
+
end
|
23
|
+
|
24
|
+
open("tmp/supervisor.rb", "w") do |f|
|
25
|
+
f.puts <<-'end_of_script'
|
26
|
+
require "serverengine"
|
27
|
+
require "rspec"
|
28
|
+
$state_file_mutex = Mutex.new # TODO
|
29
|
+
require "server_worker_context"
|
30
|
+
include ServerEngine
|
31
|
+
server = TestServer
|
32
|
+
worker = TestWorker
|
33
|
+
config = {command_pipe: STDIN.dup}
|
34
|
+
STDIN.reopen(File::NULL)
|
35
|
+
ARGV.each do |arg|
|
36
|
+
case arg
|
37
|
+
when /^server=(.*)$/
|
38
|
+
server = Object.const_get($1)
|
39
|
+
when /^worker=(.*)$/
|
40
|
+
worker = Object.const_get($1)
|
41
|
+
when /^(.*)=(\d+)$/
|
42
|
+
config[$1.to_sym] = $2.to_i
|
43
|
+
when /^(.*)=(.*)$/
|
44
|
+
config[$1.to_sym] = $2
|
45
|
+
else
|
46
|
+
raise "Unknown parameter: [#{arg}]"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
sv = Supervisor.new(server, worker, config)
|
50
|
+
s = sv.create_server(nil)
|
51
|
+
s.install_signal_handlers
|
52
|
+
t = Thread.new{ s.main }
|
53
|
+
s.after_start
|
54
|
+
t.join
|
55
|
+
end_of_script
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def windows_daemon_cmdline
|
61
|
+
if ServerEngine.windows?
|
62
|
+
[ServerEngine.ruby_bin_path, '-I', File.dirname(__FILE__), 'tmp/daemon.rb']
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def windows_supervisor_cmdline(server = nil, worker = nil, config = {})
|
69
|
+
if ServerEngine.windows?
|
70
|
+
cmd = [ServerEngine.ruby_bin_path, '-I', File.dirname(__FILE__), 'tmp/supervisor.rb']
|
71
|
+
cmd << "server=#{server}" if server
|
72
|
+
cmd << "worker=#{worker}" if worker
|
73
|
+
config.each_pair do |k, v|
|
74
|
+
cmd << "#{k}=#{v}"
|
75
|
+
end
|
76
|
+
cmd
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
10
80
|
end
|
11
81
|
|
12
82
|
def incr_test_state(key)
|
@@ -20,6 +90,7 @@ def incr_test_state(key)
|
|
20
90
|
|
21
91
|
f.pos = 0
|
22
92
|
f.write YAML.dump(data)
|
93
|
+
data[key]
|
23
94
|
end
|
24
95
|
end
|
25
96
|
end
|
@@ -111,6 +182,39 @@ module TestWorker
|
|
111
182
|
end
|
112
183
|
end
|
113
184
|
|
185
|
+
module RunErrorWorker
|
186
|
+
def run
|
187
|
+
incr_test_state :worker_run
|
188
|
+
raise StandardError, "error test"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
module TestExitWorker
|
193
|
+
def initialize
|
194
|
+
@stop_flag = BlockingFlag.new
|
195
|
+
@worker_num = incr_test_state :worker_initialize
|
196
|
+
@exit_code = case @worker_num
|
197
|
+
when 1 then 5
|
198
|
+
when 4 then 3
|
199
|
+
else 4
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def run
|
204
|
+
incr_test_state :worker_run
|
205
|
+
exit_at = Time.now + @worker_num * 2
|
206
|
+
until @stop_flag.wait(0.1)
|
207
|
+
exit!(@exit_code) if Time.now >= exit_at
|
208
|
+
end
|
209
|
+
incr_test_state :worker_finished
|
210
|
+
end
|
211
|
+
|
212
|
+
def stop
|
213
|
+
incr_test_state :worker_stop
|
214
|
+
@stop_flag.set!
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
114
218
|
shared_context 'test server and worker' do
|
115
219
|
before { reset_test_state }
|
116
220
|
|