lib/jrpc/transport/socket_tcp.rb in jrpc-1.1.7 vs lib/jrpc/transport/socket_tcp.rb in jrpc-1.1.8

- old
+ new

@@ -6,10 +6,11 @@ received = '' length_to_read = length while length_to_read > 0 io_read, = IO.select([socket], [], [], timeout) raise ReadTimeoutError unless io_read + check_fin_signal chunk = io_read[0].read_nonblock(length_to_read) received += chunk length_to_read -= chunk.bytesize end received @@ -23,10 +24,11 @@ length_written = 0 data_to_write = data while data_to_write.bytesize > 0 _, io_write, = IO.select([], [socket], [], timeout) raise WriteTimeoutError unless io_write + check_fin_signal chunk_length = io_write[0].write_nonblock(data_to_write) length_written += chunk_length data_to_write = data.byteslice(length_written, data.length) end length_written @@ -39,18 +41,50 @@ def close return if @socket.nil? socket.close end + # Socket implementation allows client to send data to server after FIN, + # but server will never receive this data. + # So we consider socket closed when it have FIN event + # and close it correctly from client side. def closed? - @socket.nil? || socket.closed? + return true if @socket.nil? || socket.closed? + + if fin_signal? + close + return true + end + + false end + # Socket implementation allows client to send data to server after FIN, + # but server will never receive this data. + # We correctly close socket from client side when FIN event received. + # Should be checked before send data to socket or recv data from socket. + def check_fin_signal + close if socket && !socket.closed? && fin_signal? + end + def socket @socket ||= build_socket end private + + # when recv_nonblock(1) responds with empty string means that FIN event was received. + # in other cases it will return 1 byte string or raise EAGAINWaitReadable. + # MSG_PEEK means we do not move pointer when reading data. + # see https://apidock.com/ruby/BasicSocket/recv_nonblock + def fin_signal? + begin + resp = socket.recv_nonblock(1, Socket::MSG_PEEK) + rescue IO::EAGAINWaitReadable => _ + resp = nil + end + resp == '' + end def clear_socket! return if @socket.nil? @socket.close unless @socket.closed? @socket = nil