From cd4dc311ae27bc36534d8a176675b699de25bc93 Mon Sep 17 00:00:00 2001 From: Muhammad Nuzaihan Date: Fri, 16 Mar 2018 02:21:04 +0800 Subject: [PATCH] Refactor and test IPv6 support in websocket server --- script/websocket-server.rb | 36 ++++++++++++------------- spec/scripts/websocket_server_spec.rb | 39 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 spec/scripts/websocket_server_spec.rb diff --git a/script/websocket-server.rb b/script/websocket-server.rb index e701ec528..cdee2cb15 100755 --- a/script/websocket-server.rb +++ b/script/websocket-server.rb @@ -5,7 +5,7 @@ $LOAD_PATH << './lib' require 'rubygems' # load rails env -dir = File.expand_path(File.join(File.dirname(__FILE__), '..')) +dir = File.expand_path('..', __dir__) Dir.chdir dir RAILS_ENV = ENV['RAILS_ENV'] || 'development' @@ -38,8 +38,8 @@ def after_fork(dir) file.sync = true end - $stdout.reopen( "#{dir}/log/websocket-server_out.log", 'w') - $stderr.reopen( "#{dir}/log/websocket-server_err.log", 'w') + $stdout.reopen( "#{dir}/log/websocket-server_out.log", 'w').sync = true + $stderr.reopen( "#{dir}/log/websocket-server_err.log", 'w').sync = true end before_fork @@ -70,7 +70,7 @@ OptionParser.new do |opts| @options[:p] = p end opts.on('-b', '--bind [OPT]', 'bind address') do |b| - @options[:b] = b + @options[:b] = IPAddr.new(b).to_s end opts.on('-s', '--secure', 'enable secure connections') do |s| @options[:s] = s @@ -92,29 +92,29 @@ if ARGV[0] != 'start' && ARGV[0] != 'stop' end if ARGV[0] == 'stop' - puts "Stopping websocket server (pid:#{@options[:i]})" + pid = File.read(@options[:i]).to_i + puts "Stopping websocket server (pid: #{pid})" - # read pid - pid = File.open( @options[:i].to_s ).read - pid.gsub!(/\r|\n/, '') + # IMPORTANT: Use SIGTERM (15), not SIGKILL (9) + # Daemons.rb cleans up the PID file automatically on termination; + # SIGKILL ends the process immediately and bypasses cleanup. + # See https://major.io/2010/03/18/sigterm-vs-sigkill/ for more. + Process.kill(:SIGTERM, pid) - # kill - Process.kill( 9, pid.to_i ) exit end if ARGV[0] == 'start' && @options[:d] - puts "Starting websocket server on #{@options[:b]}:#{@options[:p]} (secure:#{@options[:s]},pid:#{@options[:i]})" + puts "Starting websocket server on #{@options[:b]}:#{@options[:p]} (secure: #{@options[:s]}, pidfile: #{@options[:i]})" - Daemons.daemonize + # Use Daemons.rb's built-in facility for generating PID files + Daemons.daemonize( + app_name: File.basename(@options[:i], '.pid'), + dir_mode: :normal, + dir: File.dirname(@options[:i]) + ) after_fork(dir) - - # create pid file - daemon_pid = File.new(@options[:i].to_s, 'w') - daemon_pid.sync = true - daemon_pid.puts(Process.pid.to_s) - daemon_pid.close end @clients = {} diff --git a/spec/scripts/websocket_server_spec.rb b/spec/scripts/websocket_server_spec.rb new file mode 100644 index 000000000..557fcab70 --- /dev/null +++ b/spec/scripts/websocket_server_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +require 'timeout' + +describe 'websocket-server' do + # Why not Rails.root.join here? + # Because it's not avaialable in this spec (no 'rails_helper' = faster start-up) + let(:app_root) { File.expand_path('../..', __dir__) } + let(:ws_server) { File.expand_path('script/websocket-server.rb', app_root) } + let(:pidfile) { File.expand_path('tmp/pids/websocket.pid', app_root) } + let(:output_log) { File.expand_path('log/websocket-server_out.log', app_root) } + let(:error_log) { File.expand_path('log/websocket-server_err.log', app_root) } + + context 'with IPv6 bind address (via -b option)' do + # This error is raised for invalid bind addresses + let(:error_msg) { "`start_tcp_server': no acceptor" } + let(:ipv6_addr) { '::1/128' } + + # Flush logs + before do + File.write(output_log, '') + File.write(error_log, '') + end + + it 'starts up successfully' do + begin + system("#{ws_server} start -db #{ipv6_addr} >/dev/null 2>&1") + + # Wait for daemon to start + Timeout.timeout(20, Timeout::Error, 'WebSocket Server startup timed out') do + loop { break if File.size(output_log) + File.size(error_log) > 0 } + end + + expect(File.read(error_log)).not_to include(error_msg) + ensure + system("#{ws_server} stop >/dev/null 2>&1") if File.exist?(pidfile) + end + end + end +end