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