serverengine 2.3.1 → 2.4.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 → test.yml} +5 -8
- data/Changelog +11 -0
- data/README.md +10 -3
- data/examples/server.rb +2 -2
- data/lib/serverengine/socket_manager.rb +49 -11
- data/lib/serverengine/socket_manager_unix.rb +70 -21
- data/lib/serverengine/socket_manager_win.rb +4 -3
- data/lib/serverengine/version.rb +1 -1
- data/serverengine.gemspec +5 -3
- data/spec/daemon_logger_spec.rb +2 -2
- data/spec/daemon_spec.rb +5 -5
- data/spec/multi_process_server_spec.rb +7 -7
- data/spec/socket_manager_spec.rb +200 -3
- data/spec/spec_helper.rb +1 -4
- data/spec/supervisor_spec.rb +6 -6
- metadata +40 -13
- data/.github/workflows/windows.yml +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 516efcdedbeba3c7d7988c91cc121a37b52efca99726d833447fe374680cac43
|
4
|
+
data.tar.gz: b4265b4b425eb2d165881d579f6b2115c8e7bf2298c56f1cf75d6ed1cb804993
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc1d3263b08bd87e645563000b46a79a58437617006eb993b959d50105a875d1592bdce2e45c9bd1a4fbfd7740ab672d1e61a13e39654cdae798dbe0fe18f606
|
7
|
+
data.tar.gz: 62fca1438f15432ce570216dce14eabef6235835c2f43f875ba220b3ba6d6a86e8aaedcfa4e5752a7ba5529a55d6bfeca9618ce0f59924dd6ef9666e69d00cf2
|
@@ -1,4 +1,4 @@
|
|
1
|
-
name:
|
1
|
+
name: Test
|
2
2
|
on:
|
3
3
|
push:
|
4
4
|
branches: [master]
|
@@ -11,10 +11,9 @@ jobs:
|
|
11
11
|
strategy:
|
12
12
|
fail-fast: false
|
13
13
|
matrix:
|
14
|
-
ruby: [ '3.1', '3.0', '2.7' ]
|
15
|
-
os:
|
16
|
-
|
17
|
-
name: Unit testing with Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
|
14
|
+
ruby: [ '3.3', '3.2', '3.1', '3.0', '2.7' ]
|
15
|
+
os: ['ubuntu-latest', 'windows-latest']
|
16
|
+
name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
|
18
17
|
steps:
|
19
18
|
- uses: actions/checkout@v2
|
20
19
|
- name: Set up Ruby
|
@@ -22,9 +21,7 @@ jobs:
|
|
22
21
|
with:
|
23
22
|
ruby-version: ${{ matrix.ruby }}
|
24
23
|
- name: Install dependencies
|
25
|
-
run:
|
26
|
-
gem install bundler rake
|
27
|
-
bundle install --jobs 4 --retry 3
|
24
|
+
run: bundle install --jobs 4 --retry 3
|
28
25
|
- name: Run tests
|
29
26
|
env:
|
30
27
|
CI: true
|
data/Changelog
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
2024-10-22 version 2.4.0
|
2
|
+
|
3
|
+
* Add missing base64 and logger gem as dependency for Ruby 3.4 and 3.5.
|
4
|
+
* socket_manager: add feature to share sockets with another server.
|
5
|
+
|
6
|
+
2023-03-14 version 2.3.2
|
7
|
+
|
8
|
+
* Accept `nil` for `ServerEngine::SocketManager::Server.open` to select path automatically
|
9
|
+
* Care excluded port ranges of Windows in `ServerEngine::SocketManager.generate_path`
|
10
|
+
* Update to Rake 13 and RSpec 3 to support running tests on Ruby 3.2
|
11
|
+
|
1
12
|
2022-12-22 version 2.3.1
|
2
13
|
|
3
14
|
* Don't treat as error when worker shuts down with exit status 0
|
data/README.md
CHANGED
@@ -370,8 +370,8 @@ se.run
|
|
370
370
|
```ruby
|
371
371
|
module MyServer
|
372
372
|
def before_run
|
373
|
-
@
|
374
|
-
@
|
373
|
+
@socket_manager_server = ServerEngine::SocketManager::Server.open
|
374
|
+
@socket_manager_path = @socket_manager_server.path
|
375
375
|
end
|
376
376
|
|
377
377
|
def after_run
|
@@ -413,8 +413,15 @@ se = ServerEngine.create(MyServer, MyWorker, {
|
|
413
413
|
se.run
|
414
414
|
```
|
415
415
|
|
416
|
-
|
416
|
+
Other features:
|
417
|
+
|
418
|
+
- `socket_manager_server = SocketManager::Server.share_sockets_with_another_server(path)`
|
419
|
+
- It starts a new manager server that shares all UDP/TCP sockets with the existing manager.
|
420
|
+
- We can use this for live restart for network servers.
|
421
|
+
- The old process should stop without removing the file for the socket after the new process starts.
|
422
|
+
- Limitation: This feature would not work well if the process opens new TCP ports frequently.
|
417
423
|
|
424
|
+
See also [examples](https://github.com/fluent/serverengine/tree/master/examples).
|
418
425
|
|
419
426
|
## Module API
|
420
427
|
|
data/examples/server.rb
CHANGED
@@ -41,8 +41,8 @@ module MyServer
|
|
41
41
|
attr_reader :socket_manager_path
|
42
42
|
|
43
43
|
def before_run
|
44
|
-
@
|
45
|
-
@
|
44
|
+
@socket_manager_server = ServerEngine::SocketManager::Server.open
|
45
|
+
@socket_manager_path = @socket_manager_server.path
|
46
46
|
rescue Exception => e
|
47
47
|
logger.error "unexpected error in server, class #{e.class}: #{e.message}"
|
48
48
|
raise
|
@@ -77,34 +77,51 @@ module ServerEngine
|
|
77
77
|
port = ENV['SERVERENGINE_SOCKETMANAGER_PORT']
|
78
78
|
return port.to_i if port
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
80
|
+
excluded_port_ranges = get_excluded_port_ranges
|
81
|
+
get_dynamic_port_range
|
82
|
+
.reject { |port| excluded_port_ranges.any? { |range| range.cover?(port) } }
|
83
|
+
.find { |port| `netstat -na | findstr "#{port}"`.length == 0 }
|
85
84
|
else
|
86
85
|
base_dir = (ENV['SERVERENGINE_SOCKETMANAGER_SOCK_DIR'] || '/tmp')
|
87
86
|
File.join(base_dir, 'SERVERENGINE_SOCKETMANAGER_' + Time.now.utc.iso8601 + '_' + Process.pid.to_s)
|
88
87
|
end
|
89
88
|
end
|
90
89
|
|
91
|
-
def self.open(path)
|
92
|
-
new(path)
|
90
|
+
def self.open(path = nil)
|
91
|
+
return new(path) unless path.nil?
|
92
|
+
if ServerEngine.windows?
|
93
|
+
new(0)
|
94
|
+
else
|
95
|
+
new(self.generate_path)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.share_sockets_with_another_server(path)
|
100
|
+
raise NotImplementedError, "Not supported on Windows." if ServerEngine.windows?
|
101
|
+
server = new(path, start: false)
|
102
|
+
server.share_sockets_with_another_server
|
103
|
+
server
|
93
104
|
end
|
94
105
|
|
95
|
-
def initialize(path)
|
106
|
+
def initialize(path, start: true)
|
96
107
|
@tcp_sockets = {}
|
97
108
|
@udp_sockets = {}
|
98
109
|
@mutex = Mutex.new
|
99
|
-
@path = start_server(path)
|
110
|
+
@path = start ? start_server(path) : path
|
100
111
|
end
|
101
112
|
|
102
113
|
attr_reader :path
|
114
|
+
attr_reader :tcp_sockets, :udp_sockets # for tests
|
103
115
|
|
104
116
|
def new_client
|
105
117
|
Client.new(@path)
|
106
118
|
end
|
107
119
|
|
120
|
+
def start
|
121
|
+
start_server(path)
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
108
125
|
def close
|
109
126
|
stop_server
|
110
127
|
nil
|
@@ -155,9 +172,9 @@ module ServerEngine
|
|
155
172
|
res = SocketManager.recv_peer(peer)
|
156
173
|
return if res.nil?
|
157
174
|
|
158
|
-
pid, method,
|
175
|
+
pid, method, *opts = res
|
159
176
|
begin
|
160
|
-
send_socket(peer, pid, method,
|
177
|
+
send_socket(peer, pid, method, *opts)
|
161
178
|
rescue => e
|
162
179
|
SocketManager.send_peer(peer, e)
|
163
180
|
end
|
@@ -198,6 +215,27 @@ module ServerEngine
|
|
198
215
|
return 49152..65535
|
199
216
|
end
|
200
217
|
end
|
218
|
+
|
219
|
+
def self.get_excluded_port_ranges
|
220
|
+
# Example output of netsh:
|
221
|
+
#
|
222
|
+
# Protocol tcp Port Exclusion Ranges
|
223
|
+
#
|
224
|
+
# Start Port End Port
|
225
|
+
# ---------- --------
|
226
|
+
# 2869 2869
|
227
|
+
# 49152 49251
|
228
|
+
# 50000 50059 *
|
229
|
+
# 57095 57194
|
230
|
+
#
|
231
|
+
# * - Administered port exclusions.
|
232
|
+
#
|
233
|
+
`netsh int ipv4 show excludedportrange tcp`
|
234
|
+
.force_encoding("ASCII-8BIT")
|
235
|
+
.lines
|
236
|
+
.map { |line| line.match(/\s*(\d+)\s*(\d+)[\s\*]*/) ? $1.to_i..$2.to_i : nil }
|
237
|
+
.compact
|
238
|
+
end
|
201
239
|
end
|
202
240
|
end
|
203
241
|
|
@@ -47,6 +47,38 @@ module ServerEngine
|
|
47
47
|
end
|
48
48
|
|
49
49
|
module ServerModule
|
50
|
+
def share_sockets_with_another_server
|
51
|
+
another_server = UNIXSocket.new(@path)
|
52
|
+
begin
|
53
|
+
idx = 0
|
54
|
+
while true
|
55
|
+
SocketManager.send_peer(another_server, [Process.pid, :share_udp, idx])
|
56
|
+
key = SocketManager.recv_peer(another_server)
|
57
|
+
break if key.nil?
|
58
|
+
@udp_sockets[key] = another_server.recv_io UDPSocket
|
59
|
+
idx += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
idx = 0
|
63
|
+
while true
|
64
|
+
SocketManager.send_peer(another_server, [Process.pid, :share_tcp, idx])
|
65
|
+
key = SocketManager.recv_peer(another_server)
|
66
|
+
break if key.nil?
|
67
|
+
@tcp_sockets[key] = another_server.recv_io TCPServer
|
68
|
+
idx += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
SocketManager.send_peer(another_server, [Process.pid, :share_unix])
|
72
|
+
res = SocketManager.recv_peer(another_server)
|
73
|
+
raise res if res.is_a?(Exception)
|
74
|
+
@server = another_server.recv_io UNIXServer
|
75
|
+
|
76
|
+
start_server(@path)
|
77
|
+
ensure
|
78
|
+
another_server.close
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
50
82
|
private
|
51
83
|
|
52
84
|
def listen_tcp_new(bind_ip, port)
|
@@ -77,15 +109,17 @@ module ServerEngine
|
|
77
109
|
end
|
78
110
|
|
79
111
|
def start_server(path)
|
80
|
-
|
81
|
-
|
82
|
-
|
112
|
+
unless @server
|
113
|
+
# return absolute path so that client can connect to this path
|
114
|
+
# when client changed working directory
|
115
|
+
path = File.expand_path(path)
|
83
116
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
117
|
+
begin
|
118
|
+
old_umask = File.umask(0077) # Protect unix socket from other users
|
119
|
+
@server = UNIXServer.new(path)
|
120
|
+
ensure
|
121
|
+
File.umask(old_umask)
|
122
|
+
end
|
89
123
|
end
|
90
124
|
|
91
125
|
@thread = Thread.new do
|
@@ -111,19 +145,34 @@ module ServerEngine
|
|
111
145
|
@thread.join if RUBY_VERSION >= "2.2"
|
112
146
|
end
|
113
147
|
|
114
|
-
def send_socket(peer, pid, method,
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
148
|
+
def send_socket(peer, pid, method, *opts)
|
149
|
+
case method
|
150
|
+
when :listen_tcp
|
151
|
+
bind, port = opts
|
152
|
+
sock = listen_tcp(bind, port)
|
153
|
+
SocketManager.send_peer(peer, nil)
|
154
|
+
peer.send_io sock
|
155
|
+
when :listen_udp
|
156
|
+
bind, port = opts
|
157
|
+
sock = listen_udp(bind, port)
|
158
|
+
SocketManager.send_peer(peer, nil)
|
159
|
+
peer.send_io sock
|
160
|
+
when :share_tcp
|
161
|
+
idx, = opts
|
162
|
+
key = @tcp_sockets.keys[idx]
|
163
|
+
SocketManager.send_peer(peer, key)
|
164
|
+
peer.send_io(@tcp_sockets.values[idx]) if key
|
165
|
+
when :share_udp
|
166
|
+
idx, = opts
|
167
|
+
key = @udp_sockets.keys[idx]
|
168
|
+
SocketManager.send_peer(peer, key)
|
169
|
+
peer.send_io(@udp_sockets.values[idx]) if key
|
170
|
+
when :share_unix
|
171
|
+
SocketManager.send_peer(peer, nil)
|
172
|
+
peer.send_io @server
|
173
|
+
else
|
174
|
+
raise ArgumentError, "Unknown method: #{method.inspect}"
|
175
|
+
end
|
127
176
|
end
|
128
177
|
end
|
129
178
|
|
@@ -108,8 +108,9 @@ module ServerEngine
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def start_server(addr)
|
111
|
-
#
|
112
|
-
#
|
111
|
+
# We need to take care about selecting an available port.
|
112
|
+
# By passing `nil` or `0` as `addr`, an available port is automatically selected.
|
113
|
+
# However, we should consider using NamedPipe instead of TCPServer.
|
113
114
|
@server = TCPServer.new("127.0.0.1", addr)
|
114
115
|
@thread = Thread.new do
|
115
116
|
begin
|
@@ -123,7 +124,7 @@ module ServerEngine
|
|
123
124
|
end
|
124
125
|
end
|
125
126
|
|
126
|
-
return
|
127
|
+
return @server.addr[1]
|
127
128
|
end
|
128
129
|
|
129
130
|
def stop_server
|
data/lib/serverengine/version.rb
CHANGED
data/serverengine.gemspec
CHANGED
@@ -16,13 +16,15 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
-
gem.required_ruby_version = ">= 2.
|
19
|
+
gem.required_ruby_version = ">= 2.3.0"
|
20
20
|
|
21
21
|
gem.add_dependency "sigdump", ["~> 0.2.2"]
|
22
|
+
gem.add_dependency "base64", ["~> 0.1"]
|
23
|
+
gem.add_dependency "logger", ["~> 1.4"]
|
22
24
|
|
23
25
|
# rake v12.x doesn't work with rspec 2. rspec should be updated to 3
|
24
|
-
gem.add_development_dependency "rake", ["~>
|
25
|
-
gem.add_development_dependency "rspec", ["~>
|
26
|
+
gem.add_development_dependency "rake", ["~> 13.0"]
|
27
|
+
gem.add_development_dependency "rspec", ["~> 3.12.0"]
|
26
28
|
|
27
29
|
gem.add_development_dependency 'rake-compiler-dock', ['~> 0.5.0']
|
28
30
|
gem.add_development_dependency 'rake-compiler', ['~> 0.9.4']
|
data/spec/daemon_logger_spec.rb
CHANGED
@@ -150,7 +150,7 @@ describe ServerEngine::DaemonLogger do
|
|
150
150
|
end
|
151
151
|
|
152
152
|
it 'inter-process locking on rotation' do
|
153
|
-
|
153
|
+
skip "fork is not implemented in Windows" if ServerEngine.windows?
|
154
154
|
|
155
155
|
log = DaemonLogger.new("tmp/se4.log", level: 'trace', log_rotate_age: 3, log_rotate_size: 10)
|
156
156
|
r, w = IO.pipe
|
@@ -175,7 +175,7 @@ describe ServerEngine::DaemonLogger do
|
|
175
175
|
end
|
176
176
|
|
177
177
|
it 'reopen log when path is renamed' do
|
178
|
-
|
178
|
+
skip "rename isn't supported on windows" if ServerEngine.windows?
|
179
179
|
|
180
180
|
log = DaemonLogger.new("tmp/rotate.log", { level: 'info', log_rotate_age: 0 })
|
181
181
|
|
data/spec/daemon_spec.rb
CHANGED
@@ -11,7 +11,7 @@ describe ServerEngine::Daemon do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'run and graceful stop by signal' do
|
14
|
-
|
14
|
+
skip "not supported signal base commands on Windows" if ServerEngine.windows?
|
15
15
|
|
16
16
|
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", command_sender: "signal")
|
17
17
|
dm.main
|
@@ -33,7 +33,7 @@ describe ServerEngine::Daemon do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'signals' do
|
36
|
-
|
36
|
+
skip "not supported signal base commands on Windows" if ServerEngine.windows?
|
37
37
|
dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", command_sender: "signal")
|
38
38
|
dm.main
|
39
39
|
|
@@ -112,7 +112,7 @@ describe ServerEngine::Daemon do
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'exits with status 0 when it was stopped normally' do
|
115
|
-
|
115
|
+
skip "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
|
116
116
|
dm = Daemon.new(
|
117
117
|
TestServer,
|
118
118
|
TestWorker,
|
@@ -135,7 +135,7 @@ describe ServerEngine::Daemon do
|
|
135
135
|
end
|
136
136
|
|
137
137
|
it 'exits with status of workers if worker exits with status specified in unrecoverable_exit_codes, without supervisor' do
|
138
|
-
|
138
|
+
skip "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
|
139
139
|
|
140
140
|
dm = Daemon.new(
|
141
141
|
TestServer,
|
@@ -159,7 +159,7 @@ describe ServerEngine::Daemon do
|
|
159
159
|
end
|
160
160
|
|
161
161
|
it 'exits with status of workers if worker exits with status specified in unrecoverable_exit_codes, with supervisor' do
|
162
|
-
|
162
|
+
skip "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
|
163
163
|
|
164
164
|
dm = Daemon.new(
|
165
165
|
TestServer,
|
@@ -17,7 +17,7 @@ require 'securerandom'
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'scale up' do
|
20
|
-
|
20
|
+
skip "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
21
21
|
|
22
22
|
config = {
|
23
23
|
workers: 2,
|
@@ -50,7 +50,7 @@ require 'securerandom'
|
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'scale down' do
|
53
|
-
|
53
|
+
skip "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
54
54
|
|
55
55
|
config = {
|
56
56
|
workers: 2,
|
@@ -98,7 +98,7 @@ end
|
|
98
98
|
end
|
99
99
|
|
100
100
|
it 'raises SystemExit when all workers exit with specified code by unrecoverable_exit_codes' do
|
101
|
-
|
101
|
+
skip "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
102
102
|
|
103
103
|
config = {
|
104
104
|
workers: 4,
|
@@ -127,7 +127,7 @@ end
|
|
127
127
|
end
|
128
128
|
|
129
129
|
it 'raises SystemExit immediately when a worker exits if stop_immediately_at_unrecoverable_exit specified' do
|
130
|
-
|
130
|
+
skip "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
|
131
131
|
|
132
132
|
config = {
|
133
133
|
workers: 4,
|
@@ -172,7 +172,7 @@ describe "log level for exited proccess" do
|
|
172
172
|
end
|
173
173
|
|
174
174
|
it 'stop' do
|
175
|
-
|
175
|
+
skip "Windows environment does not support fork" if ServerEngine.windows?
|
176
176
|
|
177
177
|
config = {
|
178
178
|
workers: 1,
|
@@ -197,7 +197,7 @@ describe "log level for exited proccess" do
|
|
197
197
|
end
|
198
198
|
|
199
199
|
it 'non zero exit status' do
|
200
|
-
|
200
|
+
skip "Windows environment does not support fork" if ServerEngine.windows?
|
201
201
|
|
202
202
|
config = {
|
203
203
|
workers: 1,
|
@@ -232,7 +232,7 @@ describe "log level for exited proccess" do
|
|
232
232
|
end
|
233
233
|
|
234
234
|
it 'zero exit status' do
|
235
|
-
|
235
|
+
skip "Windows environment does not support fork" if ServerEngine.windows?
|
236
236
|
|
237
237
|
config = {
|
238
238
|
workers: 1,
|
data/spec/socket_manager_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'rr'
|
2
3
|
|
3
4
|
describe ServerEngine::SocketManager do
|
4
5
|
include_context 'test server and worker'
|
@@ -21,9 +22,23 @@ describe ServerEngine::SocketManager do
|
|
21
22
|
|
22
23
|
if ServerEngine.windows?
|
23
24
|
context 'Server.generate_path' do
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
context 'with socket path as port number' do
|
26
|
+
it 'returns a port in the dynamic port range' do
|
27
|
+
path = SocketManager::Server.generate_path
|
28
|
+
expect(path).to be_between(49152, 65535)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns a port which is not excluded' do
|
32
|
+
excluded_port_ranges = [
|
33
|
+
49152..49251,
|
34
|
+
50000..50059,
|
35
|
+
]
|
36
|
+
RR.stub(SocketManager::Server).get_excluded_port_ranges { excluded_port_ranges }
|
37
|
+
path = SocketManager::Server.generate_path
|
38
|
+
excluded_port_ranges.each do |range|
|
39
|
+
expect(path).not_to be_between(range.first, range.last)
|
40
|
+
end
|
41
|
+
end
|
27
42
|
end
|
28
43
|
|
29
44
|
it 'can be changed via environment variable' do
|
@@ -33,6 +48,22 @@ describe ServerEngine::SocketManager do
|
|
33
48
|
ENV.delete('SERVERENGINE_SOCKETMANAGER_PORT')
|
34
49
|
end
|
35
50
|
end
|
51
|
+
|
52
|
+
context 'Server.open' do
|
53
|
+
it 'returns server with automatically selected socket path as port number' do
|
54
|
+
server = SocketManager::Server.open
|
55
|
+
expect(server.path).to be_between(49152, 65535)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'Server.share_sockets_with_another_server' do
|
60
|
+
it 'not supported' do
|
61
|
+
server = SocketManager::Server.open(server_path)
|
62
|
+
expect { SocketManager::Server.share_sockets_with_another_server(server_path) }.to raise_error(NotImplementedError)
|
63
|
+
ensure
|
64
|
+
server.close
|
65
|
+
end
|
66
|
+
end
|
36
67
|
else
|
37
68
|
context 'Server.generate_path' do
|
38
69
|
it 'returns socket path under /tmp' do
|
@@ -47,6 +78,172 @@ describe ServerEngine::SocketManager do
|
|
47
78
|
ENV.delete('SERVERENGINE_SOCKETMANAGER_SOCK_DIR')
|
48
79
|
end
|
49
80
|
end
|
81
|
+
|
82
|
+
context 'Server.open' do
|
83
|
+
it 'returns server with automatically selected socket path under /tmp' do
|
84
|
+
server = SocketManager::Server.open
|
85
|
+
expect(server.path).to include('/tmp/SERVERENGINE_SOCKETMANAGER_')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'Server.share_sockets_with_another_server' do
|
90
|
+
it 'shares listen sockets to another server' do
|
91
|
+
server = SocketManager::Server.open(server_path)
|
92
|
+
|
93
|
+
client = SocketManager::Client.new(server_path)
|
94
|
+
tcp1 = client.listen_tcp('127.0.0.1', 55551)
|
95
|
+
udp1 = client.listen_udp('127.0.0.1', 55561)
|
96
|
+
udp2 = client.listen_udp('127.0.0.1', 55562)
|
97
|
+
|
98
|
+
another_server = SocketManager::Server.share_sockets_with_another_server(server_path)
|
99
|
+
|
100
|
+
expect([
|
101
|
+
another_server.tcp_sockets.keys,
|
102
|
+
another_server.tcp_sockets.values.map(&:addr),
|
103
|
+
another_server.udp_sockets.keys,
|
104
|
+
another_server.udp_sockets.values.map(&:addr),
|
105
|
+
]).to eq([
|
106
|
+
server.tcp_sockets.keys,
|
107
|
+
server.tcp_sockets.values.map(&:addr),
|
108
|
+
server.udp_sockets.keys,
|
109
|
+
server.udp_sockets.values.map(&:addr),
|
110
|
+
])
|
111
|
+
ensure
|
112
|
+
tcp1&.close
|
113
|
+
udp1&.close
|
114
|
+
udp2&.close
|
115
|
+
server&.close
|
116
|
+
another_server&.close
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'takes over TCP sockets without downtime' do
|
120
|
+
manager_server = SocketManager::Server.open(server_path)
|
121
|
+
manager_client = SocketManager::Client.new(server_path)
|
122
|
+
|
123
|
+
has_server_started = false
|
124
|
+
# The old server starts listening
|
125
|
+
thread_server = Thread.new do
|
126
|
+
server = manager_client.listen_tcp('127.0.0.1', test_port)
|
127
|
+
has_server_started = true
|
128
|
+
while socket = server.accept
|
129
|
+
incr_test_state(:count)
|
130
|
+
socket.close
|
131
|
+
end
|
132
|
+
ensure
|
133
|
+
server&.close
|
134
|
+
end
|
135
|
+
|
136
|
+
sleep 0.1 until has_server_started
|
137
|
+
|
138
|
+
# The client starts sending data
|
139
|
+
thread_client = Thread.new do
|
140
|
+
100.times do |i|
|
141
|
+
socket = TCPSocket.new('127.0.0.1', test_port)
|
142
|
+
begin
|
143
|
+
socket.write("Hello #{i}\n")
|
144
|
+
ensure
|
145
|
+
socket.close
|
146
|
+
end
|
147
|
+
sleep 0.01
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
sleep 0.5
|
152
|
+
|
153
|
+
# The new server shares the sockets and starts listening in parallel with the old one
|
154
|
+
thread_new_server = Thread.new do
|
155
|
+
new_manager_server = SocketManager::Server.share_sockets_with_another_server(server_path)
|
156
|
+
server = manager_client.listen_tcp('127.0.0.1', test_port)
|
157
|
+
while socket = server.accept
|
158
|
+
incr_test_state(:count)
|
159
|
+
socket.close
|
160
|
+
end
|
161
|
+
ensure
|
162
|
+
new_manager_server&.close
|
163
|
+
server&.close
|
164
|
+
end
|
165
|
+
|
166
|
+
# Stop the old server
|
167
|
+
sleep 0.1
|
168
|
+
thread_server.kill
|
169
|
+
thread_server.join
|
170
|
+
|
171
|
+
thread_client.join
|
172
|
+
wait_for_stop
|
173
|
+
|
174
|
+
# Confirm that server switching was completed without data loss
|
175
|
+
expect(test_state(:count)).to eq(100)
|
176
|
+
ensure
|
177
|
+
manager_server&.close
|
178
|
+
thread_server&.kill
|
179
|
+
thread_new_server&.kill
|
180
|
+
thread_server&.join
|
181
|
+
thread_new_server&.join
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'takes over UDP sockets without downtime' do
|
185
|
+
manager_server = SocketManager::Server.open(server_path)
|
186
|
+
manager_client = SocketManager::Client.new(server_path)
|
187
|
+
|
188
|
+
has_server_started = false
|
189
|
+
# The old server starts listening
|
190
|
+
thread_server = Thread.new do
|
191
|
+
server = manager_client.listen_udp('127.0.0.1', test_port)
|
192
|
+
has_server_started = true
|
193
|
+
while server.recv(10)
|
194
|
+
incr_test_state(:count)
|
195
|
+
end
|
196
|
+
ensure
|
197
|
+
server&.close
|
198
|
+
end
|
199
|
+
|
200
|
+
sleep 0.1 until has_server_started
|
201
|
+
|
202
|
+
# The client starts sending data
|
203
|
+
thread_client = Thread.new do
|
204
|
+
100.times do |i|
|
205
|
+
socket = UDPSocket.new
|
206
|
+
begin
|
207
|
+
socket.send("Hello #{i}\n", 0, "127.0.0.1", test_port)
|
208
|
+
ensure
|
209
|
+
socket.close
|
210
|
+
end
|
211
|
+
sleep 0.01
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
sleep 0.5
|
216
|
+
|
217
|
+
# The new server shares the sockets and starts listening in parallel with the old one
|
218
|
+
thread_new_server = Thread.new do
|
219
|
+
new_manager_server = SocketManager::Server.share_sockets_with_another_server(server_path)
|
220
|
+
server = manager_client.listen_udp('127.0.0.1', test_port)
|
221
|
+
while server.recv(10)
|
222
|
+
incr_test_state(:count)
|
223
|
+
end
|
224
|
+
ensure
|
225
|
+
new_manager_server&.close
|
226
|
+
server&.close
|
227
|
+
end
|
228
|
+
|
229
|
+
# Stop the old server
|
230
|
+
sleep 0.1
|
231
|
+
thread_server.kill
|
232
|
+
thread_server.join
|
233
|
+
|
234
|
+
thread_client.join
|
235
|
+
wait_for_stop
|
236
|
+
|
237
|
+
# Confirm that server switching was completed without data loss
|
238
|
+
expect(test_state(:count)).to eq(100)
|
239
|
+
ensure
|
240
|
+
manager_server&.close
|
241
|
+
thread_server&.kill
|
242
|
+
thread_new_server&.kill
|
243
|
+
thread_server&.join
|
244
|
+
thread_new_server&.join
|
245
|
+
end
|
246
|
+
end
|
50
247
|
end
|
51
248
|
|
52
249
|
context 'with thread' do
|
data/spec/spec_helper.rb
CHANGED
data/spec/supervisor_spec.rb
CHANGED
@@ -90,7 +90,7 @@ describe ServerEngine::Supervisor do
|
|
90
90
|
context "when using #{sender} as command_sender" do
|
91
91
|
|
92
92
|
it 'start and graceful stop' do
|
93
|
-
|
93
|
+
skip 'not supported on Windows' if ServerEngine.windows? && sender == 'signal'
|
94
94
|
|
95
95
|
sv, t = start_supervisor(command_sender: sender)
|
96
96
|
|
@@ -113,7 +113,7 @@ describe ServerEngine::Supervisor do
|
|
113
113
|
end
|
114
114
|
|
115
115
|
it 'immediate stop' do
|
116
|
-
|
116
|
+
skip 'not supported on Windows' if ServerEngine.windows? && sender == 'signal'
|
117
117
|
|
118
118
|
sv, t = start_supervisor(command_sender: sender)
|
119
119
|
|
@@ -131,7 +131,7 @@ describe ServerEngine::Supervisor do
|
|
131
131
|
end
|
132
132
|
|
133
133
|
it 'graceful restart' do
|
134
|
-
|
134
|
+
skip 'not supported on Windows' if ServerEngine.windows? && sender == 'signal'
|
135
135
|
|
136
136
|
sv, t = start_supervisor(command_sender: sender)
|
137
137
|
|
@@ -155,7 +155,7 @@ describe ServerEngine::Supervisor do
|
|
155
155
|
end
|
156
156
|
|
157
157
|
it 'immediate restart' do
|
158
|
-
|
158
|
+
skip 'not supported on Windows' if ServerEngine.windows? && sender == 'signal'
|
159
159
|
|
160
160
|
sv, t = start_supervisor(command_sender: sender)
|
161
161
|
|
@@ -179,7 +179,7 @@ describe ServerEngine::Supervisor do
|
|
179
179
|
end
|
180
180
|
|
181
181
|
it 'reload' do
|
182
|
-
|
182
|
+
skip 'not supported on Windows' if ServerEngine.windows? && sender == 'signal'
|
183
183
|
|
184
184
|
sv, t = start_supervisor(command_sender: sender)
|
185
185
|
|
@@ -200,7 +200,7 @@ describe ServerEngine::Supervisor do
|
|
200
200
|
# TODO detach
|
201
201
|
|
202
202
|
it 'auto restart in limited ratio' do
|
203
|
-
|
203
|
+
skip 'not supported on Windows' if ServerEngine.windows? && sender == 'signal'
|
204
204
|
|
205
205
|
RR.stub(ServerEngine).dump_uncaught_error
|
206
206
|
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sigdump
|
@@ -24,34 +24,62 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.2.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: base64
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: logger
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.4'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
59
|
- - "~>"
|
32
60
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
61
|
+
version: '13.0'
|
34
62
|
type: :development
|
35
63
|
prerelease: false
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
37
65
|
requirements:
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
68
|
+
version: '13.0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rspec
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
73
|
- - "~>"
|
46
74
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
75
|
+
version: 3.12.0
|
48
76
|
type: :development
|
49
77
|
prerelease: false
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
80
|
- - "~>"
|
53
81
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
82
|
+
version: 3.12.0
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: rake-compiler-dock
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,8 +143,7 @@ executables: []
|
|
115
143
|
extensions: []
|
116
144
|
extra_rdoc_files: []
|
117
145
|
files:
|
118
|
-
- ".github/workflows/
|
119
|
-
- ".github/workflows/windows.yml"
|
146
|
+
- ".github/workflows/test.yml"
|
120
147
|
- ".gitignore"
|
121
148
|
- ".rspec"
|
122
149
|
- Changelog
|
@@ -167,7 +194,7 @@ homepage: https://github.com/fluent/serverengine
|
|
167
194
|
licenses:
|
168
195
|
- Apache 2.0
|
169
196
|
metadata: {}
|
170
|
-
post_install_message:
|
197
|
+
post_install_message:
|
171
198
|
rdoc_options: []
|
172
199
|
require_paths:
|
173
200
|
- lib
|
@@ -175,15 +202,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
175
202
|
requirements:
|
176
203
|
- - ">="
|
177
204
|
- !ruby/object:Gem::Version
|
178
|
-
version: 2.
|
205
|
+
version: 2.3.0
|
179
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
207
|
requirements:
|
181
208
|
- - ">="
|
182
209
|
- !ruby/object:Gem::Version
|
183
210
|
version: '0'
|
184
211
|
requirements: []
|
185
|
-
rubygems_version: 3.
|
186
|
-
signing_key:
|
212
|
+
rubygems_version: 3.5.11
|
213
|
+
signing_key:
|
187
214
|
specification_version: 4
|
188
215
|
summary: ServerEngine - multiprocess server framework
|
189
216
|
test_files:
|
@@ -1,42 +0,0 @@
|
|
1
|
-
name: Testing on Windows
|
2
|
-
on:
|
3
|
-
push:
|
4
|
-
branches: [master]
|
5
|
-
pull_request:
|
6
|
-
branches: [master]
|
7
|
-
|
8
|
-
jobs:
|
9
|
-
test:
|
10
|
-
runs-on: ${{ matrix.os }}
|
11
|
-
strategy:
|
12
|
-
fail-fast: false
|
13
|
-
matrix:
|
14
|
-
ruby: [ '3.1', '2.7' ]
|
15
|
-
os:
|
16
|
-
- windows-latest
|
17
|
-
include:
|
18
|
-
- ruby: '3.0.3'
|
19
|
-
os: windows-latest
|
20
|
-
# On Ruby 3.0, we need to use fiddle 1.0.8 or later to retrieve correct
|
21
|
-
# error code. In addition, we have to specify the path of fiddle by RUBYLIB
|
22
|
-
# because RubyInstaller loads Ruby's bundled fiddle before initializing gem.
|
23
|
-
# See also:
|
24
|
-
# * https://github.com/ruby/fiddle/issues/72
|
25
|
-
# * https://bugs.ruby-lang.org/issues/17813
|
26
|
-
# * https://github.com/oneclick/rubyinstaller2/blob/8225034c22152d8195bc0aabc42a956c79d6c712/lib/ruby_installer/build/dll_directory.rb
|
27
|
-
ruby-lib-opt: RUBYLIB=%RUNNER_TOOL_CACHE%/Ruby/3.0.3/x64/lib/ruby/gems/3.0.0/gems/fiddle-1.1.0/lib
|
28
|
-
|
29
|
-
name: Unit testing with Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
|
30
|
-
steps:
|
31
|
-
- uses: actions/checkout@v2
|
32
|
-
- name: Set up Ruby
|
33
|
-
uses: ruby/setup-ruby@v1
|
34
|
-
with:
|
35
|
-
ruby-version: ${{ matrix.ruby }}
|
36
|
-
- name: Add Fiddle 1.1.0
|
37
|
-
if: ${{ matrix.ruby == '3.0.3' }}
|
38
|
-
run: gem install fiddle --version 1.1.0
|
39
|
-
- name: Install dependencies
|
40
|
-
run: ridk exec bundle install --jobs 4 --retry 3
|
41
|
-
- name: Run tests
|
42
|
-
run: bundle exec rake spec ${{ matrix.ruby-lib-opt }}
|