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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 849803d15dfe8e2b3e551b2849d8eb3d02deeffa271d4fa5f890ac4fb8db9a64
4
- data.tar.gz: 703964e48ed203464b7d98eeaee0a161c88c4fc9597c2c1028e4cc3860b52476
3
+ metadata.gz: b1c09d476bdc08a078882ba14ee74f487ab988a397d2228c3e83f611fff94df1
4
+ data.tar.gz: 33732af3d1c591ec28c67249b1b67b16b142f80048f70c3cf5c769b3e267b76a
5
5
  SHA512:
6
- metadata.gz: 7b372a236fbb4861d6a1143c35a17466483d5e43730e6e6ce3a6dbe9c8a85cd560fbf7cb8a73e84edf7f0a56cecf9a1ec0e596cceed552c174d61d83765f2d49
7
- data.tar.gz: '092f8d447cab9895701e566fe5185f6043ca483ff2cc905c57ae653f5551b80f6b080b9f0b51fb1fc42b8466eae463a5809e0941a27e770323479cf99e5e1f92'
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
@@ -1,3 +1,3 @@
1
1
  ServerEngine
2
- https://github.com/frsyuki/serverengine
2
+ https://github.com/treasure-data/serverengine
3
3
  Copyright (C) 2012-2013 Sadayuki Furuhashi
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 wait time before starting a new worker (default: 0) [dynamic reloadable]
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
- @pmon.send_signal(@reload_signal) if @pmon
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
- @worker.logger.info "Worker #{@wid} finished#{@stop ? '' : ' unexpectedly'} with #{ServerEngine.format_join_status(stat)}"
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
@@ -39,8 +39,11 @@ module ServerEngine
39
39
  def initialize(worker, thread)
40
40
  @worker = worker
41
41
  @thread = thread
42
+ @restart_at = nil
42
43
  end
43
44
 
45
+ attr_accessor :restart_at
46
+
44
47
  def send_stop(stop_graceful)
45
48
  Thread.new do
46
49
  begin
@@ -55,8 +55,8 @@ module ServerEngine
55
55
 
56
56
  def run
57
57
  while true
58
- num_alive = keepalive_workers
59
- break if num_alive == 0
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
- num_alive = 0
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
- num_alive += 1
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
- @monitors[wid] = delayed_start_worker(wid)
120
- num_alive += 1
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 num_alive
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
- now = Time.now.to_f
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, @command_sender_pipe = IO.pipe
184
- @command_sender_pipe.sync = true
185
- @command_sender_pipe.binmode
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
- now = Time.now
368
- @next_kill_time ||= now
369
- @graceful_kill_start_time ||= now
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
- now = Time.now
374
- @next_kill_time ||= now
375
- @immediate_kill_start_time ||= now
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)
@@ -80,7 +80,7 @@ module ServerEngine
80
80
  if @command_pipe
81
81
  Thread.new do
82
82
  until @command_pipe.closed?
83
- case @command_pipe.gets.chomp
83
+ case @command_pipe.gets&.chomp
84
84
  when "GRACEFUL_STOP"
85
85
  s.stop(true)
86
86
  when "IMMEDIATE_STOP"
@@ -67,7 +67,7 @@ module ServerEngine
67
67
 
68
68
  def signal_handler_main(sig)
69
69
  # here always creates new thread to avoid
70
- # complicated race conditin in signal handlers
70
+ # complicated race condition in signal handlers
71
71
  Thread.new do
72
72
  begin
73
73
  enqueue(sig)
@@ -26,6 +26,6 @@ module ServerEngine
26
26
  IMMEDIATE_RESTART = :HUP
27
27
  RELOAD = :USR2
28
28
  DETACH = :INT
29
- DUMP = :CONT
29
+ DUMP = ENV.has_key?('SIGDUMP_SIGNAL') ? ENV['SIGDUMP_SIGNAL'].to_sym : :CONT
30
30
  end
31
31
  end
@@ -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
- for port in 10000..65535
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
- Marshal.load(data)
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?