2012-08-17 03:22:31 +00:00
require 'em-websocket'
2014-01-25 23:48:17 +00:00
require 'bugsnag'
2012-08-17 03:22:31 +00:00
module JamWebsockets
2020-12-27 23:58:31 +00:00
2012-08-17 03:22:31 +00:00
class Server
def initialize ( options = { } )
2014-04-29 01:45:06 +00:00
EM :: WebSocket . close_timeout = 10 # the default of 60 is pretty intense
2012-08-17 03:22:31 +00:00
@log = Logging . logger [ self ]
@count = 0
2012-10-23 03:16:14 +00:00
@router = Router . new
2014-04-29 01:45:06 +00:00
@ar_base_logger = :: Logging :: Repository . instance [ ActiveRecord :: Base ]
2015-09-22 20:25:48 +00:00
@last_conn_check = nil
2012-08-17 03:22:31 +00:00
end
def run ( options = { } )
host = " 0.0.0.0 "
port = options [ :port ]
2014-05-19 13:46:03 +00:00
trust_port = port + 1
2014-04-30 03:01:28 +00:00
connect_time_stale_client = options [ :connect_time_stale_client ] . to_i
connect_time_expire_client = options [ :connect_time_expire_client ] . to_i
connect_time_stale_browser = options [ :connect_time_stale_browser ] . to_i
connect_time_expire_browser = options [ :connect_time_expire_browser ] . to_i
2014-08-14 16:35:17 +00:00
max_connections_per_user = options [ :max_connections_per_user ] . to_i
2016-01-19 00:41:53 +00:00
chat_enabled = options [ :chat_enabled ] . nil? ? true : options [ :chat_enabled ]
2016-02-08 12:56:54 +00:00
chat_blast = options [ :chat_blast ] . nil? ? true : options [ :chat_blast ]
2014-09-24 19:27:56 +00:00
gateway_name = options [ :gateway_name ]
2014-03-04 21:51:49 +00:00
rabbitmq_host = options [ :rabbitmq_host ]
rabbitmq_port = options [ :rabbitmq_port ] . to_i
2015-01-23 22:51:59 +00:00
allow_dynamic_registration = options [ :allow_dynamic_registration ] . nil? ? true : options [ :allow_dynamic_registration ]
2014-12-30 23:10:16 +00:00
2015-09-22 20:25:48 +00:00
2014-12-30 23:10:16 +00:00
Stats :: init ( options )
2014-04-09 17:25:52 +00:00
calling_thread = options [ :calling_thread ]
2014-05-19 13:46:03 +00:00
trust_check = TrustCheck . new ( trust_port , options [ :cidr ] )
2012-08-17 03:22:31 +00:00
2014-09-26 01:40:31 +00:00
@log . info " starting server #{ host } : #{ port } staleness_time= #{ connect_time_stale_client } ; reconnect time = #{ connect_time_expire_client } , rabbitmq= #{ rabbitmq_host } : #{ rabbitmq_port } gateway_name= #{ gateway_name } "
2012-08-24 02:46:58 +00:00
2013-09-21 20:43:40 +00:00
EventMachine . error_handler { | e |
2015-01-04 15:00:50 +00:00
puts " unhandled error #{ e } "
2020-12-27 23:58:31 +00:00
puts " unhandled error #{ e . backtrace } "
2013-11-05 02:25:03 +00:00
@log . error " unhandled error #{ e } "
2020-12-27 23:58:31 +00:00
@log . error " unhandled error #{ e . backtrace } "
2021-02-03 16:57:44 +00:00
# SHOULD WE JUST DIE HERE? This was seen in production, and the gateway stopped after this!!!
begin
Bugsnag . notify ( e )
rescue = > bugsnag_e
puts " unable to report to bugsnag #{ bugsnag_e } "
@log . error " unable to report to bugsnag #{ bugsnag_e } "
end
2013-09-21 20:43:40 +00:00
}
2012-10-23 03:16:14 +00:00
EventMachine . run do
2016-02-08 12:56:54 +00:00
@router . start ( connect_time_stale_client , connect_time_expire_client , connect_time_stale_browser , connect_time_expire_browser , host : rabbitmq_host , port : rabbitmq_port , max_connections_per_user : max_connections_per_user , gateway : gateway_name , allow_dynamic_registration : allow_dynamic_registration , chat_enabled : chat_enabled , chat_blast : chat_blast ) do
2020-12-27 23:58:31 +00:00
2021-01-04 13:40:49 +00:00
@router . init
2021-01-05 00:38:15 +00:00
2020-12-27 23:58:31 +00:00
#start_connection_expiration
#start_client_expiration
#start_connection_flagger
2014-09-24 19:27:56 +00:00
start_stats_dump
2014-05-19 13:46:03 +00:00
start_websocket_listener ( host , port , trust_port , trust_check , options [ :emwebsocket_debug ] )
2014-04-09 17:25:52 +00:00
calling_thread . wakeup if calling_thread
2013-07-24 03:01:51 +00:00
end
2012-08-24 02:46:58 +00:00
2012-10-23 03:16:14 +00:00
# if you don't do this, the app won't exit unless you kill -9
at_exit do
@log . info " cleaning up server "
2020-03-26 15:24:54 +00:00
@log . info " Exit error: #{ $ERROR_INFO } " if $ERROR_INFO
@log . info " Exit position: #{ $ERROR_POSITION } " if $ERROR_POSITION
2012-10-23 03:16:14 +00:00
@router . cleanup
end
2012-10-23 11:42:28 +00:00
end
end
2013-10-21 16:13:00 +00:00
def stop
EventMachine :: stop_event_loop
end
2015-09-22 20:25:48 +00:00
def check_for_em_drift ( timer )
# if our timer check is a full second off, say what's up
2015-09-23 14:18:00 +00:00
drift = Time . now - @last_conn_check
@router . highest_drift = drift if drift > @router . highest_drift
2015-09-22 20:25:48 +00:00
@last_conn_check = Time . now
end
2014-05-19 13:46:03 +00:00
def start_websocket_listener ( listen_ip , port , trust_port , trust_check , emwebsocket_debug )
2014-01-11 04:57:07 +00:00
EventMachine :: WebSocket . run ( :host = > listen_ip , :port = > port , :debug = > emwebsocket_debug ) do | ws |
2014-09-24 19:27:56 +00:00
#@log.info "new client #{ws}"
2014-05-19 13:46:03 +00:00
@router . new_client ( ws , false )
end
EventMachine :: WebSocket . run ( :host = > listen_ip , :port = > trust_port , :debug = > emwebsocket_debug ) do | ws |
@log . info " new latency_tester client #{ ws } "
# verify this connection came in from a valid subnet, if specified
ip = extract_ip ( ws )
if trust_check . trusted? ( ip , trust_port )
@router . new_client ( ws , true )
else
@log . warn ( " untrusted client attempted to connect to #{ listen_ip } : #{ trust_port } from #{ ip } " )
ws . close
end
2012-10-23 11:42:28 +00:00
end
2014-04-09 17:25:52 +00:00
@log . debug ( " started websocket " )
2012-10-23 11:42:28 +00:00
end
2014-05-01 19:09:33 +00:00
def start_connection_expiration
2012-10-23 11:42:28 +00:00
# one cleanup on startup
2020-12-27 23:58:31 +00:00
@router . wipe_all_connections
#@router.periodical_check_connections
2012-10-23 11:42:28 +00:00
2015-09-22 20:25:48 +00:00
@last_conn_check = Time . now
timer = 2
EventMachine :: PeriodicTimer . new ( timer ) do
check_for_em_drift ( timer )
time_it ( 'conn_expire' ) { safety_net { sane_logging { @router . periodical_check_connections } } }
2012-10-23 11:42:28 +00:00
end
end
2014-09-24 19:27:56 +00:00
def start_client_expiration
# one cleanup on startup
@router . periodical_check_clients
EventMachine :: PeriodicTimer . new ( 30 ) do
2015-09-22 20:25:48 +00:00
time_it ( 'client_expire' ) { safety_net { sane_logging { @router . periodical_check_clients } } }
2012-10-23 03:16:14 +00:00
end
2012-08-17 03:22:31 +00:00
end
2013-02-06 13:43:26 +00:00
2014-05-01 19:09:33 +00:00
def start_connection_flagger
2013-02-06 13:43:26 +00:00
# one cleanup on startup
2014-09-24 19:27:56 +00:00
@router . periodical_flag_connections
2013-02-06 13:43:26 +00:00
2014-05-01 19:09:33 +00:00
EventMachine :: PeriodicTimer . new ( 2 ) do
2015-09-22 20:25:48 +00:00
time_it ( 'conn_flagger' ) { safety_net { sane_logging { @router . periodical_flag_connections } } }
2013-02-06 13:43:26 +00:00
end
end
2014-09-24 19:27:56 +00:00
def start_stats_dump
2020-12-28 04:35:07 +00:00
EventMachine :: PeriodicTimer . new ( 60 ) do
2015-09-22 20:25:48 +00:00
time_it ( 'stats_dump' ) { safety_net { @router . periodical_stats_dump } }
2013-02-06 13:43:26 +00:00
end
end
2014-10-29 02:16:54 +00:00
# this was added for this reason: https://jamkazam.atlassian.net/browse/VRFS-2425
# if an unhandled exception occurs in PeriodicTimer, it just kills all future timers; doesn't kill the app.
# not really what you want.
# so, we signal to Bugsnag, so we know really bad stuff is happening, but we also move
def safety_net ( & blk )
begin
blk . call
rescue = > e
2018-04-21 16:33:18 +00:00
#Bugsnag.notify(e)
2014-10-29 02:16:54 +00:00
@log . error ( " unhandled exception in EM Timer #{ e } " )
2015-09-23 14:18:00 +00:00
puts " Error during processing: #{ $! } "
puts " Backtrace: \n \t #{ e . backtrace . join ( " \n \t " ) } "
2014-10-29 02:16:54 +00:00
end
end
2015-09-22 20:25:48 +00:00
def time_it ( cat , & blk )
start = Time . now
blk . call
time = Time . now - start
2015-09-23 01:57:01 +00:00
@router . time_it_sums [ cat ] = ( @router . time_it_sums [ cat ] || 0 ) + time
2015-09-22 20:25:48 +00:00
@log . warn ( " LONG TIME #{ cat } : #{ time } " ) if time > 1
end
2014-04-29 01:45:06 +00:00
def sane_logging ( & blk )
# used around repeated transactions that cause too much ActiveRecord::Base logging
2014-05-01 19:09:33 +00:00
# example is handling heartbeats
2014-04-29 01:45:06 +00:00
begin
2014-05-01 19:09:33 +00:00
original_level = @ar_base_logger . level if @ar_base_logger
2014-04-29 01:45:06 +00:00
@ar_base_logger . level = :info if @ar_base_logger
blk . call
ensure
@ar_base_logger . level = original_level if @ar_base_logger
end
end
2014-05-19 13:46:03 +00:00
private
def extract_ip ( client )
Socket . unpack_sockaddr_in ( client . get_peername ) [ 1 ]
end
2012-08-17 03:22:31 +00:00
end
end