Problem with threaded TCPSocket reading

I’m try to write a wrapper around the rust analyzer to work over TCP while working “like normal”, I’m using GNU nc to connect to this wrapper.

What am I doing wrong here:

#!/usr/bin/env ruby

require 'socket'

listener_port = 4444
listener = TCPServer.new(listener_port)

def handle_client(client_socket)
  recv_cnt = ''
  loop do
    begin
      data = client_socket.read_nonblock(4096)
      recv_cnt += data
    rescue IO::WaitReadable
      # No more data available for reading, exit the loop
      break
    rescue IO::EAGAINWaitReadable
      # No more data available for reading, exit the loop (for some platforms)
      break
    rescue EOFError
      # Client closed the connection, exit the loop
      break
    end
  end

  puts "Received content: #{recv_cnt}"

  exec_ra = 'rust-analyzer parse'
  parsed_cnt = `echo '#{recv_cnt}' | #{exec_ra}`

  puts "Parsed content: #{parsed_cnt}"

  client_socket.puts(parsed_cnt)
end

loop do
  client_socket = listener.accept
  Thread.new(client_socket) do |socket|
    client_address = socket.peeraddr(true)[3]
    puts "New connection from: #{client_address}"

    begin
      handle_client(socket)
    rescue StandardError => e
      puts "An error occurred for connection from #{client_address}: #{e.message}"
    ensure
      socket.close
      puts "Connection from #{client_address} handled successfully."
    end
  end
end

?

Hi unbounded voyager,

It looks like your code is fine for the most part, but there’s a possible issue with how you handle reading data from the client_socket. The loop in the handle_client method could potentially hang if there’s no more data and the client doesn’t close the connection.

To avoid this issue, try implementing a timeout mechanism for reading data using the select method. Here’s a modified version of the loop in handle_client:

loop do
  ready = IO.select([client_socket], nil, nil, 1)

  if ready && ready[0].include?(client_socket)
    data = client_socket.recv_nonblock(4096)
    recv_cnt += data
  else
    break
  end
rescue IO::WaitReadable
  break
rescue IO::EAGAINWaitReadable
  break
rescue EOFError
  break
end

Now the script will gracefully exit the loop if there’s no data received within the 1 second timeout. You can adjust the timeout value according to your requirements.

Hope this helps! Let me know if you have any more questions.