serverengine 2.0.7 → 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 +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?
|