11# frozen_string_literal: false
22
3- require 'socketry'
3+ require 'socket'
4+ require 'openssl'
45require 'uri'
6+ require 'timeout'
57
68module SplitIoClient
79 module SSE
@@ -36,12 +38,15 @@ def initialize(config,
3638
3739 def close ( status = nil )
3840 unless connected?
39- @config . logger . error ( 'SSEClient already disconected.' ) if @config . debug_enabled
41+ @config . logger . debug ( 'SSEClient already disconected.' )
4042 return
4143 end
44+ @config . logger . debug ( "Closing SSEClient socket" )
4245
4346 @connected . make_false
44- @socket &.close
47+ @socket . sync_close = true if @socket . is_a? OpenSSL ::SSL ::SSLSocket
48+ @socket . close
49+ @config . logger . debug ( "SSEClient socket state #{ @socket . state } " ) if @socket . is_a? OpenSSL ::SSL ::SSLSocket
4550 push_status ( status )
4651 rescue StandardError => e
4752 @config . logger . error ( "SSEClient close Error: #{ e . inspect } " )
@@ -55,7 +60,6 @@ def start(url)
5560
5661 @uri = URI ( url )
5762 latch = Concurrent ::CountDownLatch . new ( 1 )
58-
5963 connect_thread ( latch )
6064
6165 return false unless latch . wait ( CONNECT_TIMEOUT )
@@ -74,42 +78,73 @@ def connected?
7478
7579 def connect_thread ( latch )
7680 @config . threads [ :connect_stream ] = Thread . new do
77- @config . logger . info ( 'Starting connect_stream thread ...' ) if @config . debug_enabled
81+ @config . logger . info ( 'Starting connect_stream thread ...' )
7882 new_status = connect_stream ( latch )
7983 push_status ( new_status )
80- @config . logger . info ( 'connect_stream thread finished.' ) if @config . debug_enabled
84+ @config . logger . info ( 'connect_stream thread finished.' )
8185 end
8286 end
8387
8488 def connect_stream ( latch )
8589 return Constants ::PUSH_NONRETRYABLE_ERROR unless socket_write ( latch )
86-
8790 while connected? || @first_event . value
88- begin
89- partial_data = @socket . readpartial ( 10_000 , timeout : @read_timeout )
90-
91- read_first_event ( partial_data , latch )
92-
93- raise 'eof exception' if partial_data == :eof
94- rescue Errno ::EBADF , IOError => e
95- @config . logger . error ( e . inspect ) if @config . debug_enabled
96- return nil
97- rescue StandardError => e
98- return nil if ENV [ 'SPLITCLIENT_ENV' ] == 'test'
99-
100- @config . logger . error ( "Error reading partial data: #{ e . inspect } " ) if @config . debug_enabled
101- return Constants ::PUSH_RETRYABLE_ERROR
91+ begin
92+ if IO . select ( [ @socket ] , nil , nil , @read_timeout )
93+ begin
94+ partial_data = @socket . readpartial ( 10_000 )
95+ read_first_event ( partial_data , latch )
96+
97+ raise 'eof exception' if partial_data == :eof
98+ rescue IO ::WaitReadable => e
99+ @config . logger . debug ( "SSE client IO::WaitReadable transient error: #{ e . inspect } " )
100+ IO . select ( [ @socket ] , nil , nil , @read_timeout )
101+ retry
102+ rescue Errno ::EAGAIN => e
103+ @config . logger . debug ( "SSE client transient error: #{ e . inspect } " )
104+ IO . select ( [ @socket ] , nil , nil , @read_timeout )
105+ retry
106+ rescue Errno ::ETIMEDOUT => e
107+ @config . logger . error ( "SSE read operation timed out!: #{ e . inspect } " )
108+ return Constants ::PUSH_RETRYABLE_ERROR
109+ rescue EOFError => e
110+ puts "SSE read operation EOF Exception!: #{ e . inspect } "
111+ @config . logger . error ( "SSE read operation EOF Exception!: #{ e . inspect } " )
112+ raise 'eof exception'
113+ rescue Errno ::EBADF , IOError => e
114+ @config . logger . error ( "SSE read operation EBADF or IOError: #{ e . inspect } " )
115+ return Constants ::PUSH_RETRYABLE_ERROR
116+ rescue StandardError => e
117+ @config . logger . error ( "SSE read operation StandardError: #{ e . inspect } " )
118+ return nil if ENV [ 'SPLITCLIENT_ENV' ] == 'test'
119+
120+ @config . logger . error ( "Error reading partial data: #{ e . inspect } " )
121+ return Constants ::PUSH_RETRYABLE_ERROR
122+ end
123+ else
124+ @config . logger . error ( "SSE read operation timed out, no data available." )
125+ return Constants ::PUSH_RETRYABLE_ERROR
126+ end
127+ rescue Errno ::EBADF
128+ @config . logger . debug ( "SSE socket is not connected (Errno::EBADF)" )
129+ break
130+ rescue RuntimeError
131+ raise 'eof exception'
132+ rescue Exception => e
133+ @config . logger . debug ( "SSE socket is not connected: #{ e . inspect } " )
134+ break
102135 end
103136
104137 process_data ( partial_data )
105138 end
139+ @config . logger . info ( "SSE read operation exited: #{ connected? } " )
140+
106141 nil
107142 end
108143
109144 def socket_write ( latch )
110145 @first_event . make_true
111146 @socket = socket_connect
112- @socket . write ( build_request ( @uri ) )
147+ @socket . puts ( build_request ( @uri ) )
113148 true
114149 rescue StandardError => e
115150 @config . logger . error ( "Error during connecting to #{ @uri . host } . Error: #{ e . inspect } " )
@@ -130,6 +165,7 @@ def read_first_event(data, latch)
130165
131166 if response_code == OK_CODE && !error_event
132167 @connected . make_true
168+ @config . logger . debug ( "SSE client first event Connected is true" )
133169 @telemetry_runtime_producer . record_streaming_event ( Telemetry ::Domain ::Constants ::SSE_CONNECTION_ESTABLISHED , nil )
134170 push_status ( Constants ::PUSH_CONNECTED )
135171 end
@@ -138,15 +174,37 @@ def read_first_event(data, latch)
138174 end
139175
140176 def socket_connect
141- return Socketry ::SSL ::Socket . connect ( @uri . host , @uri . port ) if @uri . scheme . casecmp ( 'https' ) . zero?
177+ tcp_socket = TCPSocket . new ( @uri . host , @uri . port )
178+ if @uri . scheme . casecmp ( 'https' ) . zero?
179+ begin
180+ ssl_context = OpenSSL ::SSL ::SSLContext . new
181+ ssl_socket = OpenSSL ::SSL ::SSLSocket . new ( tcp_socket , ssl_context )
182+ ssl_socket . hostname = @uri . host
183+
184+ begin
185+ ssl_socket . connect_nonblock
186+ rescue IO ::WaitReadable
187+ IO . select ( [ ssl_socket ] )
188+ retry
189+ rescue IO ::WaitWritable
190+ IO . select ( nil , [ ssl_socket ] )
191+ retry
192+ end
193+ return ssl_socket
194+
195+ rescue Exception => e
196+ @config . logger . error ( "socket connect error: #{ e . inspect } " )
197+ return nil
198+ end
199+ end
142200
143- Socketry :: TCP :: Socket . connect ( @uri . host , @uri . port )
201+ tcp_socket
144202 end
145203
146204 def process_data ( partial_data )
205+ @config . logger . debug ( "Event partial data: #{ partial_data } " )
147206 return if partial_data . nil? || partial_data == KEEP_ALIVE_RESPONSE
148207
149- @config . logger . debug ( "Event partial data: #{ partial_data } " ) if @config . debug_enabled
150208 events = @event_parser . parse ( partial_data )
151209 events . each { |event | process_event ( event ) }
152210 rescue StandardError => e
@@ -162,7 +220,7 @@ def build_request(uri)
162220 req << "SplitSDKMachineName: #{ @config . machine_name } \r \n "
163221 req << "SplitSDKClientKey: #{ @api_key . split ( // ) . last ( 4 ) . join } \r \n " unless @api_key . nil?
164222 req << "Cache-Control: no-cache\r \n \r \n "
165- @config . logger . debug ( "Request info: #{ req } " ) if @config . debug_enabled
223+ @config . logger . debug ( "Request info: #{ req } " )
166224 req
167225 end
168226
0 commit comments