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
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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
name: Testing on Ubuntu
|
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', '3.0', '2.7' ]
|
15
|
+
os:
|
16
|
+
- ubuntu-latest
|
17
|
+
name: Unit testing with Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v2
|
20
|
+
- name: Set up Ruby
|
21
|
+
uses: ruby/setup-ruby@v1
|
22
|
+
with:
|
23
|
+
ruby-version: ${{ matrix.ruby }}
|
24
|
+
- name: Install dependencies
|
25
|
+
run: |
|
26
|
+
gem install bundler rake
|
27
|
+
bundle install --jobs 4 --retry 3
|
28
|
+
- name: Run tests
|
29
|
+
env:
|
30
|
+
CI: true
|
31
|
+
run: bundle exec rake spec
|
@@ -0,0 +1,42 @@
|
|
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 }}
|
data/Changelog
CHANGED
@@ -1,3 +1,48 @@
|
|
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
|
+
|
13
|
+
2022-01-13 version 2.2.5:
|
14
|
+
|
15
|
+
* Fix DLL load error on Ruby 3.1 on Windows
|
16
|
+
|
17
|
+
2021-05-24 version 2.2.4:
|
18
|
+
|
19
|
+
* Ensure to get correct Win32 socket error on Ruby 3.0
|
20
|
+
|
21
|
+
2021-02-17 version 2.2.3:
|
22
|
+
|
23
|
+
* Change SocketManager's port assignment strategy on Windows
|
24
|
+
|
25
|
+
2020-11-02 version 2.2.2:
|
26
|
+
|
27
|
+
* Fix incomplete Windows support in spawn based multi worker
|
28
|
+
|
29
|
+
2020-01-24 version 2.2.1:
|
30
|
+
|
31
|
+
* Fix IPv6 dual-stack mode issue for UDP
|
32
|
+
* experimental: Add SERVERENGINE_USE_SOCKET_REUSEPORT envvar to enable SO_REUSEPORT
|
33
|
+
|
34
|
+
2019-11-16 version 2.2.0:
|
35
|
+
|
36
|
+
* Fix IPv6 dual-stack mode issue for TCP
|
37
|
+
|
38
|
+
2019-04-22 version 2.1.1:
|
39
|
+
|
40
|
+
* Fix bug to ignore SIGDUMP_SIGNAL
|
41
|
+
|
42
|
+
2018-11-14 version 2.1.0:
|
43
|
+
|
44
|
+
* Improve socket manager security
|
45
|
+
|
1
46
|
2018-07-09 version 2.0.7:
|
2
47
|
|
3
48
|
* Add disable_sigdump option
|
data/NOTICE
CHANGED
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
|
@@ -120,10 +122,19 @@ module ServerEngine
|
|
120
122
|
end
|
121
123
|
|
122
124
|
def send_reload
|
123
|
-
|
125
|
+
return nil unless @pmon
|
126
|
+
if @pmon.command_sender_pipe
|
127
|
+
send_command("RELOAD\n")
|
128
|
+
else
|
129
|
+
@pmon.send_signal(@reload_signal)
|
130
|
+
end
|
124
131
|
nil
|
125
132
|
end
|
126
133
|
|
134
|
+
def send_command(command)
|
135
|
+
@pmon.send_command(command) if @pmon
|
136
|
+
end
|
137
|
+
|
127
138
|
def join
|
128
139
|
@pmon.join if @pmon
|
129
140
|
nil
|
@@ -133,7 +144,11 @@ module ServerEngine
|
|
133
144
|
return false unless @pmon
|
134
145
|
|
135
146
|
if stat = @pmon.try_join
|
136
|
-
|
147
|
+
if @stop
|
148
|
+
@worker.logger.info "Worker #{@wid} finished with #{ServerEngine.format_join_status(stat)}"
|
149
|
+
else
|
150
|
+
@worker.logger.error "Worker #{@wid} finished unexpectedly with #{ServerEngine.format_join_status(stat)}"
|
151
|
+
end
|
137
152
|
if stat.is_a?(Process::Status) && stat.exited? && @unrecoverable_exit_codes.include?(stat.exitstatus)
|
138
153
|
@unrecoverable_exit = true
|
139
154
|
@exitstatus = stat.exitstatus
|
@@ -46,13 +46,6 @@ module ServerEngine
|
|
46
46
|
@pm.command_sender = @command_sender
|
47
47
|
end
|
48
48
|
|
49
|
-
def stop(stop_graceful)
|
50
|
-
if @command_sender == "pipe"
|
51
|
-
@pm.command_sender_pipe.write(stop_graceful ? "GRACEFUL_STOP\n" : "IMMEDIATE_STOP\n")
|
52
|
-
end
|
53
|
-
super
|
54
|
-
end
|
55
|
-
|
56
49
|
def run
|
57
50
|
super
|
58
51
|
ensure
|
@@ -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
|
|
@@ -16,6 +16,7 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
require 'fcntl'
|
19
|
+
require 'serverengine/socket_manager'
|
19
20
|
|
20
21
|
module ServerEngine
|
21
22
|
|
@@ -70,7 +71,6 @@ module ServerEngine
|
|
70
71
|
attr_reader :enable_heartbeat, :auto_heartbeat
|
71
72
|
|
72
73
|
attr_accessor :command_sender
|
73
|
-
attr_reader :command_sender_pipe
|
74
74
|
|
75
75
|
CONFIG_PARAMS = {
|
76
76
|
heartbeat_interval: 1,
|
@@ -179,18 +179,21 @@ module ServerEngine
|
|
179
179
|
end
|
180
180
|
end
|
181
181
|
|
182
|
+
command_sender_pipe = nil
|
182
183
|
if @command_sender == "pipe"
|
183
|
-
inpipe,
|
184
|
-
|
185
|
-
|
184
|
+
inpipe, command_sender_pipe = IO.pipe
|
185
|
+
command_sender_pipe.sync = true
|
186
|
+
command_sender_pipe.binmode
|
186
187
|
options[:in] = inpipe
|
187
188
|
end
|
189
|
+
env['SERVERENGINE_SOCKETMANAGER_INTERNAL_TOKEN'] = SocketManager::INTERNAL_TOKEN
|
188
190
|
pid = Process.spawn(env, *args, options)
|
189
191
|
if @command_sender == "pipe"
|
190
192
|
inpipe.close
|
191
193
|
end
|
192
194
|
|
193
195
|
m = Monitor.new(pid, monitor_options)
|
196
|
+
m.command_sender_pipe = command_sender_pipe
|
194
197
|
|
195
198
|
@monitors << m
|
196
199
|
|
@@ -305,9 +308,11 @@ module ServerEngine
|
|
305
308
|
@graceful_kill_start_time = nil
|
306
309
|
@immediate_kill_start_time = nil
|
307
310
|
@kill_count = 0
|
311
|
+
|
312
|
+
@command_sender_pipe = nil
|
308
313
|
end
|
309
314
|
|
310
|
-
attr_accessor :last_heartbeat_time
|
315
|
+
attr_accessor :last_heartbeat_time, :command_sender_pipe
|
311
316
|
attr_reader :pid
|
312
317
|
|
313
318
|
def heartbeat_delay
|
@@ -327,6 +332,18 @@ module ServerEngine
|
|
327
332
|
end
|
328
333
|
end
|
329
334
|
|
335
|
+
def send_command(command)
|
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
|
345
|
+
end
|
346
|
+
|
330
347
|
def try_join
|
331
348
|
pid = @pid
|
332
349
|
return true unless pid
|
@@ -364,15 +381,25 @@ module ServerEngine
|
|
364
381
|
end
|
365
382
|
|
366
383
|
def start_graceful_stop!
|
367
|
-
|
368
|
-
|
369
|
-
|
384
|
+
if ServerEngine.windows?
|
385
|
+
# heartbeat isn't supported on Windows
|
386
|
+
send_command("GRACEFUL_STOP\n")
|
387
|
+
else
|
388
|
+
now = Time.now
|
389
|
+
@next_kill_time ||= now
|
390
|
+
@graceful_kill_start_time ||= now
|
391
|
+
end
|
370
392
|
end
|
371
393
|
|
372
394
|
def start_immediate_stop!
|
373
|
-
|
374
|
-
|
375
|
-
|
395
|
+
if ServerEngine.windows?
|
396
|
+
# heartbeat isn't supported on Windows
|
397
|
+
system("taskkill /f /pid #{@pid}")
|
398
|
+
else
|
399
|
+
now = Time.now
|
400
|
+
@next_kill_time ||= now
|
401
|
+
@immediate_kill_start_time ||= now
|
402
|
+
end
|
376
403
|
end
|
377
404
|
|
378
405
|
def tick(now=Time.now)
|
data/lib/serverengine/server.rb
CHANGED
data/lib/serverengine/signals.rb
CHANGED
@@ -18,9 +18,20 @@
|
|
18
18
|
require 'socket'
|
19
19
|
require 'ipaddr'
|
20
20
|
require 'time'
|
21
|
+
require 'securerandom'
|
22
|
+
require 'json'
|
23
|
+
require 'base64'
|
24
|
+
|
25
|
+
require_relative 'utils' # for ServerEngine.windows?
|
21
26
|
|
22
27
|
module ServerEngine
|
23
28
|
module SocketManager
|
29
|
+
# This token is used for communication between peers. If token is mismatched, messages will be discarded
|
30
|
+
INTERNAL_TOKEN = if ENV.has_key?('SERVERENGINE_SOCKETMANAGER_INTERNAL_TOKEN')
|
31
|
+
ENV['SERVERENGINE_SOCKETMANAGER_INTERNAL_TOKEN']
|
32
|
+
else
|
33
|
+
SecureRandom.hex
|
34
|
+
end
|
24
35
|
|
25
36
|
class Client
|
26
37
|
def initialize(path)
|
@@ -63,7 +74,10 @@ module ServerEngine
|
|
63
74
|
class Server
|
64
75
|
def self.generate_path
|
65
76
|
if ServerEngine.windows?
|
66
|
-
|
77
|
+
port = ENV['SERVERENGINE_SOCKETMANAGER_PORT']
|
78
|
+
return port.to_i if port
|
79
|
+
|
80
|
+
for port in get_dynamic_port_range
|
67
81
|
if `netstat -na | findstr "#{port}"`.length == 0
|
68
82
|
return port
|
69
83
|
end
|
@@ -151,10 +165,45 @@ module ServerEngine
|
|
151
165
|
ensure
|
152
166
|
peer.close
|
153
167
|
end
|
168
|
+
|
169
|
+
if ServerEngine.windows?
|
170
|
+
def self.valid_dynamic_port_range(start_port, end_port)
|
171
|
+
return false if start_port < 1025 or start_port > 65535
|
172
|
+
return false if end_port < 1025 or end_port > 65535
|
173
|
+
return false if start_port > end_port
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.get_dynamic_port_range
|
178
|
+
numbers = []
|
179
|
+
# Example output of netsh (actual output is localized):
|
180
|
+
#
|
181
|
+
# Protocol tcp Dynamic Port Range
|
182
|
+
# ---------------------------------
|
183
|
+
# Start Port : 49152
|
184
|
+
# Number of Ports : 16384
|
185
|
+
#
|
186
|
+
str = `netsh int ipv4 show dynamicport tcp`.force_encoding("ASCII-8BIT")
|
187
|
+
str.each_line { |line| numbers << $1.to_i if line.match(/.*: (\d+)/) }
|
188
|
+
|
189
|
+
start_port, n_ports = numbers[0], numbers[1]
|
190
|
+
end_port = start_port + n_ports - 1
|
191
|
+
|
192
|
+
if valid_dynamic_port_range(start_port, end_port)
|
193
|
+
return start_port..end_port
|
194
|
+
else
|
195
|
+
# The default dynamic port range is 49152 - 65535 as of Windows Vista
|
196
|
+
# and Windows Server 2008.
|
197
|
+
# https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/default-dynamic-port-range-tcpip-chang
|
198
|
+
return 49152..65535
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
154
202
|
end
|
155
203
|
|
156
204
|
def self.send_peer(peer, obj)
|
157
|
-
data = Marshal.dump(obj)
|
205
|
+
data = [SocketManager::INTERNAL_TOKEN, Base64.strict_encode64(Marshal.dump(obj))]
|
206
|
+
data = JSON.generate(data)
|
158
207
|
peer.write [data.bytesize].pack('N')
|
159
208
|
peer.write data
|
160
209
|
end
|
@@ -165,7 +214,10 @@ module ServerEngine
|
|
165
214
|
|
166
215
|
len = res.unpack('N').first
|
167
216
|
data = peer.read(len)
|
168
|
-
|
217
|
+
data = JSON.parse(data)
|
218
|
+
return nil if SocketManager::INTERNAL_TOKEN != data.first
|
219
|
+
|
220
|
+
Marshal.load(Base64.strict_decode64(data.last))
|
169
221
|
end
|
170
222
|
|
171
223
|
if ServerEngine.windows?
|