serverengine 2.0.7 → 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 +31 -0
- data/.github/workflows/windows.yml +42 -0
- data/Changelog +45 -0
- data/NOTICE +1 -1
- data/README.md +3 -2
- data/Rakefile +0 -16
- data/lib/serverengine/daemon_logger.rb +37 -0
- data/lib/serverengine/multi_process_server.rb +17 -2
- data/lib/serverengine/multi_spawn_server.rb +0 -7
- data/lib/serverengine/multi_thread_server.rb +3 -0
- data/lib/serverengine/multi_worker_server.rb +35 -12
- data/lib/serverengine/process_manager.rb +38 -11
- data/lib/serverengine/server.rb +1 -1
- data/lib/serverengine/signal_thread.rb +1 -1
- data/lib/serverengine/signals.rb +1 -1
- data/lib/serverengine/socket_manager.rb +55 -3
- data/lib/serverengine/socket_manager_unix.rb +35 -12
- data/lib/serverengine/version.rb +1 -1
- data/lib/serverengine/winsock.rb +16 -10
- data/lib/serverengine.rb +20 -5
- data/serverengine.gemspec +1 -7
- data/spec/blocking_flag_spec.rb +16 -4
- 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/socket_manager_spec.rb +36 -2
- data/spec/spec_helper.rb +5 -0
- data/spec/supervisor_spec.rb +8 -2
- data/spec/winsock_spec.rb +17 -0
- metadata +21 -6
- data/.travis.yml +0 -21
- data/appveyor.yml +0 -24
@@ -50,19 +50,30 @@ module ServerEngine
|
|
50
50
|
private
|
51
51
|
|
52
52
|
def listen_tcp_new(bind_ip, port)
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
if ENV['SERVERENGINE_USE_SOCKET_REUSEPORT'] == '1'
|
54
|
+
# Based on Addrinfo#listen
|
55
|
+
tsock = Socket.new(bind_ip.ipv6? ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
|
56
|
+
tsock.ipv6only! if bind_ip.ipv6?
|
57
|
+
tsock.setsockopt(:SOCKET, :REUSEPORT, true)
|
58
|
+
tsock.setsockopt(:SOCKET, :REUSEADDR, true)
|
59
|
+
tsock.bind(Addrinfo.tcp(bind_ip.to_s, port))
|
60
|
+
tsock.listen(::Socket::SOMAXCONN)
|
61
|
+
tsock.autoclose = false
|
62
|
+
TCPServer.for_fd(tsock.fileno)
|
63
|
+
else
|
64
|
+
# TCPServer.new doesn't set IPV6_V6ONLY flag, so use Addrinfo class instead.
|
65
|
+
# TODO: make backlog configurable if necessary
|
66
|
+
tsock = Addrinfo.tcp(bind_ip.to_s, port).listen(::Socket::SOMAXCONN)
|
67
|
+
tsock.autoclose = false
|
68
|
+
TCPServer.for_fd(tsock.fileno)
|
69
|
+
end
|
56
70
|
end
|
57
71
|
|
58
72
|
def listen_udp_new(bind_ip, port)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
sock.bind(bind_ip.to_s, port)
|
65
|
-
return sock
|
73
|
+
# UDPSocket.new doesn't set IPV6_V6ONLY flag, so use Addrinfo class instead.
|
74
|
+
usock = Addrinfo.udp(bind_ip.to_s, port).bind
|
75
|
+
usock.autoclose = false
|
76
|
+
UDPSocket.for_fd(usock.fileno)
|
66
77
|
end
|
67
78
|
|
68
79
|
def start_server(path)
|
@@ -70,7 +81,12 @@ module ServerEngine
|
|
70
81
|
# when client changed working directory
|
71
82
|
path = File.expand_path(path)
|
72
83
|
|
73
|
-
|
84
|
+
begin
|
85
|
+
old_umask = File.umask(0077) # Protect unix socket from other users
|
86
|
+
@server = UNIXServer.new(path)
|
87
|
+
ensure
|
88
|
+
File.umask(old_umask)
|
89
|
+
end
|
74
90
|
|
75
91
|
@thread = Thread.new do
|
76
92
|
begin
|
@@ -96,7 +112,14 @@ module ServerEngine
|
|
96
112
|
end
|
97
113
|
|
98
114
|
def send_socket(peer, pid, method, bind, port)
|
99
|
-
sock =
|
115
|
+
sock = case method
|
116
|
+
when :listen_tcp
|
117
|
+
listen_tcp(bind, port)
|
118
|
+
when :listen_udp
|
119
|
+
listen_udp(bind, port)
|
120
|
+
else
|
121
|
+
raise ArgumentError, "Unknown method: #{method.inspect}"
|
122
|
+
end
|
100
123
|
|
101
124
|
SocketManager.send_peer(peer, nil)
|
102
125
|
|
data/lib/serverengine/version.rb
CHANGED
data/lib/serverengine/winsock.rb
CHANGED
@@ -21,6 +21,7 @@ module ServerEngine
|
|
21
21
|
require 'fiddle/import'
|
22
22
|
require 'fiddle/types'
|
23
23
|
require 'socket'
|
24
|
+
require 'rbconfig'
|
24
25
|
|
25
26
|
extend Fiddle::Importer
|
26
27
|
|
@@ -78,6 +79,19 @@ module ServerEngine
|
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
82
|
+
def self.last_error
|
83
|
+
# On Ruby 3.0 calling WSAGetLastError here can't retrieve correct error
|
84
|
+
# code because Ruby's internal code resets it.
|
85
|
+
# See also:
|
86
|
+
# * https://github.com/ruby/fiddle/issues/72
|
87
|
+
# * https://bugs.ruby-lang.org/issues/17813
|
88
|
+
if Fiddle.respond_to?(:win32_last_socket_error)
|
89
|
+
Fiddle.win32_last_socket_error || 0
|
90
|
+
else
|
91
|
+
self.WSAGetLastError
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
81
95
|
INVALID_SOCKET = -1
|
82
96
|
end
|
83
97
|
|
@@ -85,21 +99,13 @@ module ServerEngine
|
|
85
99
|
extend Fiddle::Importer
|
86
100
|
|
87
101
|
dlload "kernel32"
|
88
|
-
extern "int GetModuleFileNameA(int, char *, int)"
|
89
102
|
extern "int CloseHandle(int)"
|
90
103
|
|
91
|
-
|
92
|
-
GetModuleFileNameA(0, ruby_bin_path_buf, ruby_bin_path_buf.size)
|
93
|
-
|
94
|
-
ruby_bin_path = ruby_bin_path_buf.to_s.gsub(/\\/, '/')
|
95
|
-
ruby_dll_paths = File.dirname(ruby_bin_path) + '/*msvcr*ruby*.dll'
|
96
|
-
ruby_dll_path = Dir.glob(ruby_dll_paths).first
|
97
|
-
dlload ruby_dll_path
|
98
|
-
|
104
|
+
dlload RbConfig::CONFIG['LIBRUBY_SO']
|
99
105
|
extern "int rb_w32_map_errno(int)"
|
100
106
|
|
101
107
|
def self.raise_last_error(name)
|
102
|
-
errno = rb_w32_map_errno(WinSock.
|
108
|
+
errno = rb_w32_map_errno(WinSock.last_error)
|
103
109
|
raise SystemCallError.new(name, errno)
|
104
110
|
end
|
105
111
|
|
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/blocking_flag_spec.rb
CHANGED
@@ -20,10 +20,16 @@ describe ServerEngine::BlockingFlag do
|
|
20
20
|
it 'wait_for_set timeout' do
|
21
21
|
start = Time.now
|
22
22
|
|
23
|
-
subject.wait_for_set(0.
|
23
|
+
subject.wait_for_set(0.1)
|
24
24
|
elapsed = Time.now - start
|
25
25
|
|
26
|
-
|
26
|
+
if ServerEngine.windows? && ENV['CI'] == 'True'
|
27
|
+
# timer seems low accuracy on Windows CI container, often a bit shorter
|
28
|
+
# than expected
|
29
|
+
elapsed.should >= 0.1 * 0.95
|
30
|
+
else
|
31
|
+
elapsed.should >= 0.1
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
35
|
it 'wait_for_reset timeout' do
|
@@ -31,10 +37,16 @@ describe ServerEngine::BlockingFlag do
|
|
31
37
|
|
32
38
|
start = Time.now
|
33
39
|
|
34
|
-
subject.wait_for_reset(0.
|
40
|
+
subject.wait_for_reset(0.1)
|
35
41
|
elapsed = Time.now - start
|
36
42
|
|
37
|
-
|
43
|
+
if ServerEngine.windows? && ENV['CI'] == 'True'
|
44
|
+
# timer seems low accuracy on Windows CI container, often a bit shorter
|
45
|
+
# than expected
|
46
|
+
elapsed.should >= 0.1 * 0.95
|
47
|
+
else
|
48
|
+
elapsed.should >= 0.1
|
49
|
+
end
|
38
50
|
end
|
39
51
|
|
40
52
|
it 'wait' do
|
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
|
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/socket_manager_spec.rb
CHANGED
@@ -19,7 +19,21 @@ describe ServerEngine::SocketManager do
|
|
19
19
|
File.unlink(server_path) if server_path.is_a?(String) && File.exist?(server_path)
|
20
20
|
end
|
21
21
|
|
22
|
-
if
|
22
|
+
if ServerEngine.windows?
|
23
|
+
context 'Server.generate_path' do
|
24
|
+
it 'returns socket path as port number' do
|
25
|
+
path = SocketManager::Server.generate_path
|
26
|
+
expect(path).to be_between(49152, 65535)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'can be changed via environment variable' do
|
30
|
+
ENV['SERVERENGINE_SOCKETMANAGER_PORT'] = '54321'
|
31
|
+
path = SocketManager::Server.generate_path
|
32
|
+
expect(path).to be 54321
|
33
|
+
ENV.delete('SERVERENGINE_SOCKETMANAGER_PORT')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
else
|
23
37
|
context 'Server.generate_path' do
|
24
38
|
it 'returns socket path under /tmp' do
|
25
39
|
path = SocketManager::Server.generate_path
|
@@ -155,7 +169,27 @@ describe ServerEngine::SocketManager do
|
|
155
169
|
test_state(:is_udp_socket).should == 1
|
156
170
|
test_state(:udp_data_sent).should == 1
|
157
171
|
end
|
158
|
-
end if (TCPServer.open("::1",0) rescue nil)
|
172
|
+
end if (TCPServer.open("::1", 0) rescue nil)
|
173
|
+
|
174
|
+
unless ServerEngine.windows?
|
175
|
+
context 'using ipv4/ipv6' do
|
176
|
+
it 'can bind ipv4/ipv6 together' do
|
177
|
+
server = SocketManager::Server.open(server_path)
|
178
|
+
client = ServerEngine::SocketManager::Client.new(server_path)
|
179
|
+
|
180
|
+
tcp_v4 = client.listen_tcp('0.0.0.0', test_port)
|
181
|
+
udp_v4 = client.listen_udp('0.0.0.0', test_port)
|
182
|
+
tcp_v6 = client.listen_tcp('::', test_port)
|
183
|
+
udp_v6 = client.listen_udp('::', test_port)
|
184
|
+
|
185
|
+
tcp_v4.close
|
186
|
+
udp_v4.close
|
187
|
+
tcp_v6.close
|
188
|
+
udp_v6.close
|
189
|
+
server.close
|
190
|
+
end
|
191
|
+
end if (TCPServer.open("::", 0) rescue nil)
|
192
|
+
end
|
159
193
|
end
|
160
194
|
|
161
195
|
if ServerEngine.windows?
|
data/spec/spec_helper.rb
CHANGED
data/spec/supervisor_spec.rb
CHANGED
@@ -191,13 +191,19 @@ describe ServerEngine::Supervisor do
|
|
191
191
|
sv, t = start_supervisor(RunErrorWorker, server_restart_wait: 1, command_sender: sender)
|
192
192
|
|
193
193
|
begin
|
194
|
-
sleep 2.
|
194
|
+
sleep 2.5
|
195
195
|
ensure
|
196
196
|
sv.stop(true)
|
197
197
|
t.join
|
198
198
|
end
|
199
199
|
|
200
|
-
|
200
|
+
if ServerEngine.windows?
|
201
|
+
# Because launching a process on Windows is high cost,
|
202
|
+
# it doesn't often reach to 3.
|
203
|
+
test_state(:worker_run).should <= 3
|
204
|
+
else
|
205
|
+
test_state(:worker_run).should == 3
|
206
|
+
end
|
201
207
|
end
|
202
208
|
end
|
203
209
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
describe ServerEngine::WinSock do
|
2
|
+
# On Ruby 3.0, you need to use fiddle 1.0.8 or later to retrieve a correct
|
3
|
+
# error code. In addition, you need to specify the path of fiddle by RUBYLIB
|
4
|
+
# or `ruby -I` when you use RubyInstaller because it loads Ruby's bundled
|
5
|
+
# fiddle before initializing gem.
|
6
|
+
# See also:
|
7
|
+
# * https://github.com/ruby/fiddle/issues/72
|
8
|
+
# * https://bugs.ruby-lang.org/issues/17813
|
9
|
+
# * https://github.com/oneclick/rubyinstaller2/blob/8225034c22152d8195bc0aabc42a956c79d6c712/lib/ruby_installer/build/dll_directory.rb
|
10
|
+
context 'last_error' do
|
11
|
+
it 'bind error' do
|
12
|
+
expect(WinSock.bind(0, nil, 0)).to be -1
|
13
|
+
WSAENOTSOCK = 10038
|
14
|
+
expect(WinSock.last_error).to be WSAENOTSOCK
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end if ServerEngine.windows?
|