serverengine 2.2.5 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/linux.yml +1 -1
- data/.github/workflows/windows.yml +1 -1
- data/Changelog +12 -0
- data/README.md +3 -2
- data/Rakefile +0 -16
- data/lib/serverengine/daemon_logger.rb +37 -0
- data/lib/serverengine/multi_process_server.rb +2 -0
- data/lib/serverengine/multi_thread_server.rb +3 -0
- data/lib/serverengine/multi_worker_server.rb +35 -12
- data/lib/serverengine/process_manager.rb +9 -1
- data/lib/serverengine/server.rb +1 -1
- data/lib/serverengine/socket_manager.rb +2 -0
- data/lib/serverengine/version.rb +1 -1
- data/lib/serverengine/winsock.rb +0 -1
- data/lib/serverengine.rb +20 -5
- data/serverengine.gemspec +1 -7
- data/spec/daemon_logger_spec.rb +25 -0
- data/spec/multi_spawn_server_spec.rb +196 -13
- data/spec/server_worker_context.rb +16 -5
- data/spec/spec_helper.rb +5 -0
- data/spec/winsock_spec.rb +2 -3
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1c09d476bdc08a078882ba14ee74f487ab988a397d2228c3e83f611fff94df1
|
4
|
+
data.tar.gz: 33732af3d1c591ec28c67249b1b67b16b142f80048f70c3cf5c769b3e267b76a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 383aaf8822f01aec3ce2e4a25243da8ca741da9d11f97c601c31cc6985e285743a088cafdd4fd918b58a966960376f65b28645b1fca74d0741e64052bd329ac8
|
7
|
+
data.tar.gz: ca10bd75948760060c8ae433cb2021a7024d6b076d84157012db5ae6809d01c9a8bb2eb2bb1f9f8d84f0881e4cbaf3bebfb25ef959130574e0cf717550ee1867
|
data/.github/workflows/linux.yml
CHANGED
data/Changelog
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
2022-06-13 version 2.3.0
|
2
|
+
|
3
|
+
* Add restart_worker_interval option to prevent workers restart immediately
|
4
|
+
after kill
|
5
|
+
* Reopen log file when rotation done by external tool is detected
|
6
|
+
* Fix unexpected behavior of start_worker_delay option
|
7
|
+
* Remove windows-pr dependency
|
8
|
+
* Fix a potential crash that command_sender_pipe of ProcessManager::Monitor
|
9
|
+
raises error on shutdown
|
10
|
+
* Allow to load serverengine/socket_manager without servernegine/utils
|
11
|
+
* Fix unstable tests
|
12
|
+
|
1
13
|
2022-01-13 version 2.2.5:
|
2
14
|
|
3
15
|
* Fix DLL load error on Ruby 3.1 on Windows
|
data/README.md
CHANGED
@@ -478,10 +478,11 @@ Available methods are different depending on `worker_type`. ServerEngine support
|
|
478
478
|
- **disable_reload** disables USR2 signal (default: false)
|
479
479
|
- **server_restart_wait** sets wait time before restarting server after last restarting (default: 1.0) [dynamic reloadable]
|
480
480
|
- **server_detach_wait** sets wait time before starting live restart (default: 10.0) [dynamic reloadable]
|
481
|
-
- Multithread server and multiprocess server: available only when `worker_type` is thread or process
|
481
|
+
- Multithread server and multiprocess server: available only when `worker_type` is "thread" or "process" or "spawn"
|
482
482
|
- **workers** sets number of workers (default: 1) [dynamic reloadable]
|
483
|
-
- **start_worker_delay** sets
|
483
|
+
- **start_worker_delay** sets the delay between each worker-start when starting/restarting multiple workers at once (default: 0) [dynamic reloadable]
|
484
484
|
- **start_worker_delay_rand** randomizes start_worker_delay at this ratio (default: 0.2) [dynamic reloadable]
|
485
|
+
- **restart_worker_interval** sets wait time before restarting a stopped worker (default: 0) [dynamic reloadable]
|
485
486
|
- Multiprocess server: available only when `worker_type` is "process"
|
486
487
|
- **worker_process_name** changes process name ($0) of workers [dynamic reloadable]
|
487
488
|
- **worker_heartbeat_interval** sets interval of heartbeats in seconds (default: 1.0) [dynamic reloadable]
|
data/Rakefile
CHANGED
@@ -8,19 +8,3 @@ require 'rspec/core/rake_task'
|
|
8
8
|
|
9
9
|
RSpec::Core::RakeTask.new(:spec)
|
10
10
|
task :default => [:spec, :build]
|
11
|
-
|
12
|
-
# 1. update Changelog and lib/serverengine/version.rb
|
13
|
-
# 2. bundle && bundle exec rake build:all
|
14
|
-
# 3. release 3 packages built on pkg/ directory
|
15
|
-
namespace :build do
|
16
|
-
desc 'Build gems for all platforms'
|
17
|
-
task :all do
|
18
|
-
Bundler.with_clean_env do
|
19
|
-
%w[ruby x86-mingw32 x64-mingw32].each do |name|
|
20
|
-
ENV['GEM_BUILD_FAKE_PLATFORM'] = name
|
21
|
-
Rake::Task["build"].execute
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
@@ -55,6 +55,9 @@ module ServerEngine
|
|
55
55
|
# update path string
|
56
56
|
old_file_dev = @file_dev
|
57
57
|
@file_dev = LogDevice.new(logdev, shift_age: @rotate_age, shift_size: @rotate_size)
|
58
|
+
# Enable to detect rotation done by external tools.
|
59
|
+
# Otherwise it continues writing logs to old file unexpectedly.
|
60
|
+
@file_dev.extend(RotationAware)
|
58
61
|
old_file_dev.close if old_file_dev
|
59
62
|
@logdev = @file_dev
|
60
63
|
end
|
@@ -130,6 +133,40 @@ module ServerEngine
|
|
130
133
|
nil
|
131
134
|
end
|
132
135
|
|
136
|
+
module RotationAware
|
137
|
+
def self.extended(obj)
|
138
|
+
obj.update_ino
|
139
|
+
end
|
140
|
+
|
141
|
+
def update_ino
|
142
|
+
(@ino_mutex ||= Mutex.new).synchronize do
|
143
|
+
@ino = File.stat(filename).ino rescue nil
|
144
|
+
@last_ino_time = Time.now
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def reopen(log = nil)
|
149
|
+
super(log)
|
150
|
+
update_ino
|
151
|
+
end
|
152
|
+
|
153
|
+
def reopen!
|
154
|
+
super
|
155
|
+
update_ino
|
156
|
+
end
|
157
|
+
|
158
|
+
def write(message)
|
159
|
+
reopen_needed = false
|
160
|
+
@ino_mutex.synchronize do
|
161
|
+
if (Time.now - @last_ino_time).abs > 1
|
162
|
+
ino = File.stat(filename).ino rescue nil
|
163
|
+
reopen_needed = true if ino && ino != @ino
|
164
|
+
end
|
165
|
+
end
|
166
|
+
reopen! if reopen_needed
|
167
|
+
super(message)
|
168
|
+
end
|
169
|
+
end
|
133
170
|
end
|
134
171
|
|
135
172
|
end
|
@@ -105,9 +105,11 @@ module ServerEngine
|
|
105
105
|
@unrecoverable_exit_codes = unrecoverable_exit_codes
|
106
106
|
@unrecoverable_exit = false
|
107
107
|
@exitstatus = nil
|
108
|
+
@restart_at = nil
|
108
109
|
end
|
109
110
|
|
110
111
|
attr_reader :exitstatus
|
112
|
+
attr_accessor :restart_at
|
111
113
|
|
112
114
|
def send_stop(stop_graceful)
|
113
115
|
@stop = true
|
@@ -55,8 +55,8 @@ module ServerEngine
|
|
55
55
|
|
56
56
|
def run
|
57
57
|
while true
|
58
|
-
|
59
|
-
break if
|
58
|
+
num_alive_or_restarting = keepalive_workers
|
59
|
+
break if num_alive_or_restarting == 0
|
60
60
|
wait_tick
|
61
61
|
end
|
62
62
|
end
|
@@ -85,6 +85,7 @@ module ServerEngine
|
|
85
85
|
|
86
86
|
@start_worker_delay = @config[:start_worker_delay] || 0
|
87
87
|
@start_worker_delay_rand = @config[:start_worker_delay_rand] || 0.2
|
88
|
+
@restart_worker_interval = @config[:restart_worker_interval] || 0
|
88
89
|
|
89
90
|
scale_workers(@config[:workers] || 1)
|
90
91
|
|
@@ -96,12 +97,12 @@ module ServerEngine
|
|
96
97
|
end
|
97
98
|
|
98
99
|
def keepalive_workers
|
99
|
-
|
100
|
+
num_alive_or_restarting = 0
|
100
101
|
|
101
102
|
@monitors.each_with_index do |m,wid|
|
102
103
|
if m && m.alive?
|
103
104
|
# alive
|
104
|
-
|
105
|
+
num_alive_or_restarting += 1
|
105
106
|
|
106
107
|
elsif m && m.respond_to?(:recoverable?) && !m.recoverable?
|
107
108
|
# exited, with unrecoverable exit code
|
@@ -116,8 +117,12 @@ module ServerEngine
|
|
116
117
|
elsif wid < @num_workers
|
117
118
|
# scale up or reboot
|
118
119
|
unless @stop
|
119
|
-
|
120
|
-
|
120
|
+
if m
|
121
|
+
restart_worker(wid)
|
122
|
+
else
|
123
|
+
start_new_worker(wid)
|
124
|
+
end
|
125
|
+
num_alive_or_restarting += 1
|
121
126
|
end
|
122
127
|
|
123
128
|
elsif m
|
@@ -126,7 +131,27 @@ module ServerEngine
|
|
126
131
|
end
|
127
132
|
end
|
128
133
|
|
129
|
-
return
|
134
|
+
return num_alive_or_restarting
|
135
|
+
end
|
136
|
+
|
137
|
+
def start_new_worker(wid)
|
138
|
+
delayed_start_worker(wid)
|
139
|
+
end
|
140
|
+
|
141
|
+
def restart_worker(wid)
|
142
|
+
m = @monitors[wid]
|
143
|
+
|
144
|
+
is_already_restarting = !m.restart_at.nil?
|
145
|
+
if is_already_restarting
|
146
|
+
delayed_start_worker(wid) if m.restart_at <= Time.now()
|
147
|
+
return
|
148
|
+
end
|
149
|
+
|
150
|
+
if @restart_worker_interval > 0
|
151
|
+
m.restart_at = Time.now() + @restart_worker_interval
|
152
|
+
else
|
153
|
+
delayed_start_worker(wid)
|
154
|
+
end
|
130
155
|
end
|
131
156
|
|
132
157
|
def delayed_start_worker(wid)
|
@@ -135,15 +160,13 @@ module ServerEngine
|
|
135
160
|
Kernel.rand * @start_worker_delay * @start_worker_delay_rand -
|
136
161
|
@start_worker_delay * @start_worker_delay_rand / 2
|
137
162
|
|
138
|
-
|
139
|
-
|
140
|
-
wait = delay - (now - @last_start_worker_time)
|
163
|
+
wait = delay - (Time.now.to_f - @last_start_worker_time)
|
141
164
|
sleep wait if wait > 0
|
142
165
|
|
143
|
-
@last_start_worker_time = now
|
166
|
+
@last_start_worker_time = Time.now.to_f
|
144
167
|
end
|
145
168
|
|
146
|
-
start_worker(wid)
|
169
|
+
@monitors[wid] = start_worker(wid)
|
147
170
|
end
|
148
171
|
end
|
149
172
|
|
@@ -333,7 +333,15 @@ module ServerEngine
|
|
333
333
|
end
|
334
334
|
|
335
335
|
def send_command(command)
|
336
|
-
|
336
|
+
pid = @pid
|
337
|
+
return false unless pid
|
338
|
+
|
339
|
+
begin
|
340
|
+
@command_sender_pipe.write(command)
|
341
|
+
return true
|
342
|
+
rescue #Errno::EPIPE
|
343
|
+
return false
|
344
|
+
end
|
337
345
|
end
|
338
346
|
|
339
347
|
def try_join
|
data/lib/serverengine/server.rb
CHANGED
@@ -22,6 +22,8 @@ require 'securerandom'
|
|
22
22
|
require 'json'
|
23
23
|
require 'base64'
|
24
24
|
|
25
|
+
require_relative 'utils' # for ServerEngine.windows?
|
26
|
+
|
25
27
|
module ServerEngine
|
26
28
|
module SocketManager
|
27
29
|
# This token is used for communication between peers. If token is mismatched, messages will be discarded
|
data/lib/serverengine/version.rb
CHANGED
data/lib/serverengine/winsock.rb
CHANGED
data/lib/serverengine.rb
CHANGED
@@ -35,12 +35,27 @@ module ServerEngine
|
|
35
35
|
|
36
36
|
def self.ruby_bin_path
|
37
37
|
if ServerEngine.windows?
|
38
|
-
|
39
|
-
ruby_path = "\0" * 256
|
40
|
-
Windows::Library::GetModuleFileName.call(0, ruby_path, 256)
|
41
|
-
return ruby_path.rstrip.gsub(/\\/, '/')
|
38
|
+
ServerEngine::Win32.ruby_bin_path
|
42
39
|
else
|
43
|
-
|
40
|
+
File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["RUBY_INSTALL_NAME"]) + RbConfig::CONFIG["EXEEXT"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if ServerEngine.windows?
|
45
|
+
module Win32
|
46
|
+
require 'fiddle/import'
|
47
|
+
|
48
|
+
extend Fiddle::Importer
|
49
|
+
|
50
|
+
dlload "kernel32"
|
51
|
+
extern "int GetModuleFileNameW(int, void *, int)"
|
52
|
+
|
53
|
+
def self.ruby_bin_path
|
54
|
+
ruby_bin_path_buf = Fiddle::Pointer.malloc(1024)
|
55
|
+
len = GetModuleFileNameW(0, ruby_bin_path_buf, ruby_bin_path_buf.size / 2)
|
56
|
+
path_bytes = ruby_bin_path_buf[0, len * 2]
|
57
|
+
path_bytes.encode('UTF-8', 'UTF-16LE').gsub(/\\/, '/')
|
58
|
+
end
|
44
59
|
end
|
45
60
|
end
|
46
61
|
end
|
data/serverengine.gemspec
CHANGED
@@ -27,11 +27,5 @@ Gem::Specification.new do |gem|
|
|
27
27
|
gem.add_development_dependency 'rake-compiler-dock', ['~> 0.5.0']
|
28
28
|
gem.add_development_dependency 'rake-compiler', ['~> 0.9.4']
|
29
29
|
|
30
|
-
|
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
|
30
|
+
gem.add_development_dependency "timecop", ["~> 0.9.5"]
|
37
31
|
end
|
data/spec/daemon_logger_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'stringio'
|
2
|
+
require 'timecop'
|
2
3
|
|
3
4
|
describe ServerEngine::DaemonLogger do
|
4
5
|
before { FileUtils.rm_rf("tmp") }
|
@@ -172,4 +173,28 @@ describe ServerEngine::DaemonLogger do
|
|
172
173
|
$stderr = STDERR
|
173
174
|
stderr.should_not =~ /(log shifting failed|log writing failed|log rotation inter-process lock failed)/
|
174
175
|
end
|
176
|
+
|
177
|
+
it 'reopen log when path is renamed' do
|
178
|
+
pending "rename isn't supported on windows" if ServerEngine.windows?
|
179
|
+
|
180
|
+
log = DaemonLogger.new("tmp/rotate.log", { level: 'info', log_rotate_age: 0 })
|
181
|
+
|
182
|
+
log.info '11111'
|
183
|
+
File.read("tmp/rotate.log").should include('11111')
|
184
|
+
File.rename("tmp/rotate.log", "tmp/rotate.log.1")
|
185
|
+
|
186
|
+
Timecop.travel(Time.now + 1)
|
187
|
+
|
188
|
+
log.info '22222'
|
189
|
+
contents = File.read("tmp/rotate.log.1")
|
190
|
+
contents.should include('11111')
|
191
|
+
contents.should include('22222')
|
192
|
+
|
193
|
+
FileUtils.touch("tmp/rotate.log")
|
194
|
+
Timecop.travel(Time.now + 1)
|
195
|
+
|
196
|
+
log.info '33333'
|
197
|
+
File.read("tmp/rotate.log").should include('33333')
|
198
|
+
File.read("tmp/rotate.log.1").should_not include('33333')
|
199
|
+
end
|
175
200
|
end
|
@@ -1,25 +1,208 @@
|
|
1
1
|
require 'timeout'
|
2
|
+
require 'timecop'
|
2
3
|
|
3
4
|
describe ServerEngine::MultiSpawnServer do
|
4
5
|
include_context 'test server and worker'
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
describe 'starts worker processes' do
|
8
|
+
context 'with command_sender=pipe' do
|
9
|
+
it do
|
10
|
+
config = {workers: 2, command_sender: 'pipe', log_stdout: false, log_stderr: false}
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
s = ServerEngine::MultiSpawnServer.new(TestWorker) { config.dup }
|
13
|
+
t = Thread.new { s.main }
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
+
begin
|
16
|
+
wait_for_fork
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
+
Timeout.timeout(5) do
|
19
|
+
sleep(0.5) until test_state(:worker_run) == 2
|
20
|
+
end
|
21
|
+
test_state(:worker_run).should == 2
|
22
|
+
ensure
|
23
|
+
s.stop(true)
|
24
|
+
t.join
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'keepalive_workers' do
|
31
|
+
let(:config) {
|
32
|
+
{
|
33
|
+
workers: workers,
|
34
|
+
command_sender: 'pipe',
|
35
|
+
log_stdout: false,
|
36
|
+
log_stderr: false,
|
37
|
+
start_worker_delay: start_worker_delay,
|
38
|
+
start_worker_delay_rand: 0,
|
39
|
+
restart_worker_interval: restart_worker_interval,
|
40
|
+
}
|
41
|
+
}
|
42
|
+
let(:workers) { 3 }
|
43
|
+
let(:server) { ServerEngine::MultiSpawnServer.new(TestWorker) { config.dup } }
|
44
|
+
let(:monitors) { server.instance_variable_get(:@monitors) }
|
45
|
+
|
46
|
+
context 'default' do
|
47
|
+
let(:start_worker_delay) { 0 }
|
48
|
+
let(:restart_worker_interval) { 0 }
|
49
|
+
|
50
|
+
it do
|
51
|
+
t = Thread.new { server.main }
|
52
|
+
|
53
|
+
begin
|
54
|
+
wait_for_fork
|
55
|
+
|
56
|
+
Timeout.timeout(5) do
|
57
|
+
sleep(0.5) until monitors.count { |m| m && m.alive? } == workers
|
58
|
+
end
|
59
|
+
|
60
|
+
monitors.each do |m|
|
61
|
+
m.send_stop(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
# To prevent the judge before stopping once
|
65
|
+
wait_for_stop
|
66
|
+
|
67
|
+
-> {
|
68
|
+
Timeout.timeout(5) do
|
69
|
+
sleep(0.5) until monitors.count { |m| m.alive? } == workers
|
70
|
+
end
|
71
|
+
}.should_not raise_error, "Not all workers restarted correctly."
|
72
|
+
ensure
|
73
|
+
server.stop(true)
|
74
|
+
t.join
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with only restart_worker_interval' do
|
80
|
+
let(:start_worker_delay) { 0 }
|
81
|
+
let(:restart_worker_interval) { 10 }
|
82
|
+
|
83
|
+
it do
|
84
|
+
t = Thread.new { server.main }
|
85
|
+
|
86
|
+
begin
|
87
|
+
wait_for_fork
|
88
|
+
|
89
|
+
# Wait for initial starting
|
90
|
+
Timeout.timeout(5) do
|
91
|
+
sleep(0.5) until monitors.count { |m| m && m.alive? } == workers
|
92
|
+
end
|
93
|
+
|
94
|
+
monitors.each do |m|
|
95
|
+
m.send_stop(true)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Wait for all workers to stop and to be set restarting time
|
99
|
+
Timeout.timeout(5) do
|
100
|
+
sleep(0.5) until monitors.count { |m| m.alive? || m.restart_at.nil? } == 0
|
101
|
+
end
|
102
|
+
|
103
|
+
Timecop.freeze
|
104
|
+
|
105
|
+
mergin_time = 3
|
106
|
+
|
107
|
+
Timecop.freeze(Time.now + restart_worker_interval - mergin_time)
|
108
|
+
sleep(1.5)
|
109
|
+
monitors.count { |m| m.alive? }.should == 0
|
110
|
+
|
111
|
+
Timecop.freeze(Time.now + 2 * mergin_time)
|
112
|
+
-> {
|
113
|
+
Timeout.timeout(5) do
|
114
|
+
sleep(0.5) until monitors.count { |m| m.alive? } == workers
|
115
|
+
end
|
116
|
+
}.should_not raise_error, "Not all workers restarted correctly."
|
117
|
+
ensure
|
118
|
+
server.stop(true)
|
119
|
+
t.join
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'with only start_worker_delay' do
|
125
|
+
let(:start_worker_delay) { 3 }
|
126
|
+
let(:restart_worker_interval) { 0 }
|
127
|
+
|
128
|
+
it do
|
129
|
+
t = Thread.new { server.main }
|
130
|
+
|
131
|
+
begin
|
132
|
+
wait_for_fork
|
133
|
+
|
134
|
+
# Initial starts are delayed too, so set longer timeout.
|
135
|
+
# (`start_worker_delay` uses `sleep` inside, so Timecop can't skip this wait.)
|
136
|
+
Timeout.timeout(start_worker_delay * workers) do
|
137
|
+
sleep(0.5) until monitors.count { |m| m && m.alive? } == workers
|
138
|
+
end
|
139
|
+
|
140
|
+
# Skip time to avoid getting a delay for the initial starts.
|
141
|
+
Timecop.travel(Time.now + start_worker_delay)
|
142
|
+
|
143
|
+
monitors.each do |m|
|
144
|
+
m.send_stop(true)
|
145
|
+
end
|
146
|
+
|
147
|
+
sleep(3)
|
148
|
+
|
149
|
+
# The first worker should restart immediately.
|
150
|
+
monitors.count { |m| m.alive? }.should satisfy { |c| 0 < c && c < workers }
|
151
|
+
|
152
|
+
# `start_worker_delay` uses `sleep` inside, so Timecop can't skip this wait.
|
153
|
+
sleep(start_worker_delay * workers)
|
154
|
+
monitors.count { |m| m.alive? }.should == workers
|
155
|
+
ensure
|
156
|
+
server.stop(true)
|
157
|
+
t.join
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with both options' do
|
163
|
+
let(:start_worker_delay) { 3 }
|
164
|
+
let(:restart_worker_interval) { 10 }
|
165
|
+
|
166
|
+
it do
|
167
|
+
t = Thread.new { server.main }
|
168
|
+
|
169
|
+
begin
|
170
|
+
wait_for_fork
|
171
|
+
|
172
|
+
# Initial starts are delayed too, so set longer timeout.
|
173
|
+
# (`start_worker_delay` uses `sleep` inside, so Timecop can't skip this wait.)
|
174
|
+
Timeout.timeout(start_worker_delay * workers) do
|
175
|
+
sleep(0.5) until monitors.count { |m| m && m.alive? } == workers
|
176
|
+
end
|
177
|
+
|
178
|
+
monitors.each do |m|
|
179
|
+
m.send_stop(true)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Wait for all workers to stop and to be set restarting time
|
183
|
+
Timeout.timeout(5) do
|
184
|
+
sleep(0.5) until monitors.count { |m| m.alive? || m.restart_at.nil? } == 0
|
185
|
+
end
|
186
|
+
|
187
|
+
Timecop.freeze
|
188
|
+
|
189
|
+
mergin_time = 3
|
190
|
+
|
191
|
+
Timecop.freeze(Time.now + restart_worker_interval - mergin_time)
|
192
|
+
sleep(1.5)
|
193
|
+
monitors.count { |m| m.alive? }.should == 0
|
194
|
+
|
195
|
+
Timecop.travel(Time.now + 2 * mergin_time)
|
196
|
+
sleep(1.5)
|
197
|
+
monitors.count { |m| m.alive? }.should satisfy { |c| 0 < c && c < workers }
|
198
|
+
|
199
|
+
# `start_worker_delay` uses `sleep` inside, so Timecop can't skip this wait.
|
200
|
+
sleep(start_worker_delay * workers)
|
201
|
+
monitors.count { |m| m.alive? }.should == workers
|
202
|
+
ensure
|
203
|
+
server.stop(true)
|
204
|
+
t.join
|
18
205
|
end
|
19
|
-
test_state(:worker_run).should == 2
|
20
|
-
ensure
|
21
|
-
s.stop(true)
|
22
|
-
t.join
|
23
206
|
end
|
24
207
|
end
|
25
208
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
require 'thread'
|
3
3
|
require 'yaml'
|
4
|
+
require 'timecop'
|
4
5
|
|
5
6
|
def reset_test_state
|
6
7
|
FileUtils.mkdir_p 'tmp'
|
@@ -165,8 +166,9 @@ module TestWorker
|
|
165
166
|
|
166
167
|
def run
|
167
168
|
incr_test_state :worker_run
|
168
|
-
|
169
|
-
|
169
|
+
# This means this worker will automatically finish after 50 seconds.
|
170
|
+
10.times do
|
171
|
+
# repeats multiple times because signal handlers
|
170
172
|
# interrupts wait
|
171
173
|
@stop_flag.wait(5.0)
|
172
174
|
end
|
@@ -252,16 +254,25 @@ end
|
|
252
254
|
|
253
255
|
shared_context 'test server and worker' do
|
254
256
|
before { reset_test_state }
|
257
|
+
after { Timecop.return }
|
258
|
+
|
259
|
+
unless self.const_defined?(:WAIT_RATIO)
|
260
|
+
if ServerEngine.windows?
|
261
|
+
WAIT_RATIO = 2
|
262
|
+
else
|
263
|
+
WAIT_RATIO = 1
|
264
|
+
end
|
265
|
+
end
|
255
266
|
|
256
267
|
def wait_for_fork
|
257
|
-
sleep 1.5
|
268
|
+
sleep 1.5 * WAIT_RATIO
|
258
269
|
end
|
259
270
|
|
260
271
|
def wait_for_stop
|
261
|
-
sleep 0.8
|
272
|
+
sleep 0.8 * WAIT_RATIO
|
262
273
|
end
|
263
274
|
|
264
275
|
def wait_for_restart
|
265
|
-
sleep 1.5
|
276
|
+
sleep 1.5 * WAIT_RATIO
|
266
277
|
end
|
267
278
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/winsock_spec.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'windows/error' if ServerEngine.windows?
|
2
|
-
|
3
1
|
describe ServerEngine::WinSock do
|
4
2
|
# On Ruby 3.0, you need to use fiddle 1.0.8 or later to retrieve a correct
|
5
3
|
# error code. In addition, you need to specify the path of fiddle by RUBYLIB
|
@@ -12,7 +10,8 @@ describe ServerEngine::WinSock do
|
|
12
10
|
context 'last_error' do
|
13
11
|
it 'bind error' do
|
14
12
|
expect(WinSock.bind(0, nil, 0)).to be -1
|
15
|
-
|
13
|
+
WSAENOTSOCK = 10038
|
14
|
+
expect(WinSock.last_error).to be WSAENOTSOCK
|
16
15
|
end
|
17
16
|
end
|
18
17
|
end if ServerEngine.windows?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serverengine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sigdump
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.9.4
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: timecop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.9.5
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.9.5
|
83
97
|
description: A framework to implement robust multiprocess servers like Unicorn
|
84
98
|
email:
|
85
99
|
- frsyuki@gmail.com
|
@@ -154,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
168
|
- !ruby/object:Gem::Version
|
155
169
|
version: '0'
|
156
170
|
requirements: []
|
157
|
-
rubygems_version: 3.
|
171
|
+
rubygems_version: 3.3.7
|
158
172
|
signing_key:
|
159
173
|
specification_version: 4
|
160
174
|
summary: ServerEngine - multiprocess server framework
|