serverengine 2.0.0pre1-x86-mingw32
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 +7 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +20 -0
- data/Changelog +122 -0
- data/Gemfile +2 -0
- data/LICENSE +202 -0
- data/NOTICE +3 -0
- data/README.md +514 -0
- data/Rakefile +26 -0
- data/appveyor.yml +24 -0
- data/examples/server.rb +138 -0
- data/examples/spawn_worker_script.rb +38 -0
- data/lib/serverengine.rb +46 -0
- data/lib/serverengine/blocking_flag.rb +77 -0
- data/lib/serverengine/command_sender.rb +89 -0
- data/lib/serverengine/config_loader.rb +82 -0
- data/lib/serverengine/daemon.rb +233 -0
- data/lib/serverengine/daemon_logger.rb +135 -0
- data/lib/serverengine/embedded_server.rb +67 -0
- data/lib/serverengine/multi_process_server.rb +155 -0
- data/lib/serverengine/multi_spawn_server.rb +95 -0
- data/lib/serverengine/multi_thread_server.rb +80 -0
- data/lib/serverengine/multi_worker_server.rb +150 -0
- data/lib/serverengine/privilege.rb +57 -0
- data/lib/serverengine/process_manager.rb +508 -0
- data/lib/serverengine/server.rb +178 -0
- data/lib/serverengine/signal_thread.rb +116 -0
- data/lib/serverengine/signals.rb +31 -0
- data/lib/serverengine/socket_manager.rb +171 -0
- data/lib/serverengine/socket_manager_unix.rb +98 -0
- data/lib/serverengine/socket_manager_win.rb +154 -0
- data/lib/serverengine/supervisor.rb +313 -0
- data/lib/serverengine/utils.rb +62 -0
- data/lib/serverengine/version.rb +3 -0
- data/lib/serverengine/winsock.rb +128 -0
- data/lib/serverengine/worker.rb +81 -0
- data/serverengine.gemspec +37 -0
- data/spec/blocking_flag_spec.rb +59 -0
- data/spec/daemon_logger_spec.rb +175 -0
- data/spec/daemon_spec.rb +169 -0
- data/spec/multi_process_server_spec.rb +113 -0
- data/spec/server_worker_context.rb +232 -0
- data/spec/signal_thread_spec.rb +94 -0
- data/spec/socket_manager_spec.rb +119 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/supervisor_spec.rb +215 -0
- metadata +184 -0
data/spec/daemon_spec.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
|
2
|
+
describe ServerEngine::Daemon do
|
3
|
+
include_context 'test server and worker'
|
4
|
+
|
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")
|
9
|
+
dm.main
|
10
|
+
|
11
|
+
wait_for_fork
|
12
|
+
|
13
|
+
test_state(:server_initialize).should == 1
|
14
|
+
|
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
|
+
|
32
|
+
wait_for_fork
|
33
|
+
|
34
|
+
begin
|
35
|
+
dm.reload
|
36
|
+
wait_for_stop
|
37
|
+
test_state(:server_reload).should == 1
|
38
|
+
|
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
|
53
|
+
end
|
54
|
+
|
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")
|
77
|
+
dm.main
|
78
|
+
|
79
|
+
wait_for_fork
|
80
|
+
|
81
|
+
begin
|
82
|
+
dm.reload
|
83
|
+
wait_for_stop
|
84
|
+
test_state(:server_reload).should == 1
|
85
|
+
|
86
|
+
dm.restart(true)
|
87
|
+
wait_for_stop
|
88
|
+
test_state(:server_restart_graceful).should == 1
|
89
|
+
|
90
|
+
dm.restart(false)
|
91
|
+
wait_for_stop
|
92
|
+
test_state(:server_restart_immediate).should == 1
|
93
|
+
|
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
|
100
|
+
end
|
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
|
@@ -0,0 +1,113 @@
|
|
1
|
+
[ServerEngine::MultiThreadServer, ServerEngine::MultiProcessServer].each do |impl_class|
|
2
|
+
# MultiProcessServer uses fork(2) internally, then it doesn't support Windows.
|
3
|
+
|
4
|
+
describe impl_class do
|
5
|
+
include_context 'test server and worker'
|
6
|
+
|
7
|
+
it 'scale up' do
|
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}
|
11
|
+
|
12
|
+
s = impl_class.new(TestWorker) { config.dup }
|
13
|
+
t = Thread.new { s.main }
|
14
|
+
|
15
|
+
begin
|
16
|
+
wait_for_fork
|
17
|
+
test_state(:worker_run).should == 2
|
18
|
+
|
19
|
+
config[:workers] = 3
|
20
|
+
s.reload
|
21
|
+
|
22
|
+
wait_for_restart
|
23
|
+
test_state(:worker_run).should == 3
|
24
|
+
|
25
|
+
test_state(:worker_stop).should == 0
|
26
|
+
|
27
|
+
ensure
|
28
|
+
s.stop(true)
|
29
|
+
t.join
|
30
|
+
end
|
31
|
+
|
32
|
+
test_state(:worker_stop).should == 3
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'scale down' do
|
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}
|
39
|
+
|
40
|
+
s = impl_class.new(TestWorker) { config.dup }
|
41
|
+
t = Thread.new { s.main }
|
42
|
+
|
43
|
+
begin
|
44
|
+
wait_for_fork
|
45
|
+
test_state(:worker_run).should == 2
|
46
|
+
|
47
|
+
config[:workers] = 1
|
48
|
+
s.restart(true)
|
49
|
+
|
50
|
+
wait_for_restart
|
51
|
+
test_state(:worker_run).should == 3
|
52
|
+
|
53
|
+
test_state(:worker_stop).should == 2
|
54
|
+
|
55
|
+
ensure
|
56
|
+
s.stop(true)
|
57
|
+
t.join
|
58
|
+
end
|
59
|
+
|
60
|
+
test_state(:worker_stop).should == 3
|
61
|
+
end
|
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
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
def reset_test_state
|
6
|
+
FileUtils.mkdir_p 'tmp'
|
7
|
+
FileUtils.rm_f 'tmp/state.yml'
|
8
|
+
FileUtils.touch 'tmp/state.yml'
|
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
|
80
|
+
end
|
81
|
+
|
82
|
+
def incr_test_state(key)
|
83
|
+
File.open('tmp/state.yml', 'r+') do |f|
|
84
|
+
f.flock(File::LOCK_EX)
|
85
|
+
|
86
|
+
$state_file_mutex.synchronize do
|
87
|
+
data = YAML.load(f.read) || {} rescue {}
|
88
|
+
data[key] ||= 0
|
89
|
+
data[key] += 1
|
90
|
+
|
91
|
+
f.pos = 0
|
92
|
+
f.write YAML.dump(data)
|
93
|
+
data[key]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_state(key)
|
99
|
+
data = YAML.load_file('tmp/state.yml') || {} rescue {}
|
100
|
+
return data[key] || 0
|
101
|
+
end
|
102
|
+
|
103
|
+
module TestServer
|
104
|
+
def initialize
|
105
|
+
incr_test_state :server_initialize
|
106
|
+
end
|
107
|
+
|
108
|
+
def before_run
|
109
|
+
incr_test_state :server_before_run
|
110
|
+
end
|
111
|
+
|
112
|
+
def after_run
|
113
|
+
incr_test_state :server_after_run
|
114
|
+
end
|
115
|
+
|
116
|
+
def after_start
|
117
|
+
incr_test_state :server_after_start
|
118
|
+
end
|
119
|
+
|
120
|
+
def stop(stop_graceful)
|
121
|
+
incr_test_state :server_stop
|
122
|
+
if stop_graceful
|
123
|
+
incr_test_state :server_stop_graceful
|
124
|
+
else
|
125
|
+
incr_test_state :server_stop_immediate
|
126
|
+
end
|
127
|
+
super
|
128
|
+
end
|
129
|
+
|
130
|
+
def restart(stop_graceful)
|
131
|
+
incr_test_state :server_restart
|
132
|
+
if stop_graceful
|
133
|
+
incr_test_state :server_restart_graceful
|
134
|
+
else
|
135
|
+
incr_test_state :server_restart_immediate
|
136
|
+
end
|
137
|
+
super
|
138
|
+
end
|
139
|
+
|
140
|
+
def reload
|
141
|
+
incr_test_state :server_reload
|
142
|
+
super
|
143
|
+
end
|
144
|
+
|
145
|
+
def detach
|
146
|
+
incr_test_state :server_detach
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
module TestWorker
|
152
|
+
def initialize
|
153
|
+
incr_test_state :worker_initialize
|
154
|
+
@stop_flag = BlockingFlag.new
|
155
|
+
end
|
156
|
+
|
157
|
+
def before_fork
|
158
|
+
incr_test_state :worker_before_fork
|
159
|
+
end
|
160
|
+
|
161
|
+
def run
|
162
|
+
incr_test_state :worker_run
|
163
|
+
5.times do
|
164
|
+
# repeats 5 times because signal handlers
|
165
|
+
# interrupts wait
|
166
|
+
@stop_flag.wait(5.0)
|
167
|
+
end
|
168
|
+
@stop_flag.reset!
|
169
|
+
end
|
170
|
+
|
171
|
+
def stop
|
172
|
+
incr_test_state :worker_stop
|
173
|
+
@stop_flag.set!
|
174
|
+
end
|
175
|
+
|
176
|
+
def reload
|
177
|
+
incr_test_state :worker_reload
|
178
|
+
end
|
179
|
+
|
180
|
+
def after_start
|
181
|
+
incr_test_state :worker_after_start
|
182
|
+
end
|
183
|
+
end
|
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
|
+
|
218
|
+
shared_context 'test server and worker' do
|
219
|
+
before { reset_test_state }
|
220
|
+
|
221
|
+
def wait_for_fork
|
222
|
+
sleep 0.8
|
223
|
+
end
|
224
|
+
|
225
|
+
def wait_for_stop
|
226
|
+
sleep 0.8
|
227
|
+
end
|
228
|
+
|
229
|
+
def wait_for_restart
|
230
|
+
sleep 1.5
|
231
|
+
end
|
232
|
+
end
|