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