serverengine 2.2.5 → 2.3.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/.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
|