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
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?
|