Detecting when TCP connection fails to open or is terminated

Viewed 111

I am reading and writing to TCP socket via a duplex handle h.

In the client:

sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
connect sock $ addrAddress addr
h <- getSocketHandle conn

In the server:

sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
forever $ do
  (conn, _) <- accept sock
  h <- getSocketHandle conn
  forkFinally (server h) (const $ hClose h)

where:

getSocketHandle :: Socket -> IO Handle
getSocketHandle conn = do
  h <- socketToHandle conn ReadWriteMode
  hSetBinaryMode h True
  hSetNewlineMode h NewlineMode {inputNL = CRLF, outputNL = CRLF}
  hSetBuffering h LineBuffering
  return h

When both the client and the server are available, it all works fine, but when the TCP connection terminates, e.g. because the client or the server exit, nothing happens - hGetLine/hPutStrLn just block the thread.

How can I make them throw exception?

Similarly, if the client fails to connect (e.g. because of the server not responding), connect also just blocks.

I suspect I should somehow set the timeouts on the socket, but experimenting with socket options didn't change much - I tried SO_LINGER, SO_KEEPALIVE, SO_USER_TIMEOUT doesn't seem to be supported by the system (it blocks when I try to get it), SO_RCVTIMEO / SO_SNDTIMEO don't seem to be supported by Network.Socket - at least that's what the docs say...

For connect/hPutStrLn I could use timeout from System.Timeout (it seems a hack, but it will work), but for hGetLine/hGet it won't work - it can just wait for user input, so it should only throw exception when the connection terminates.

I should probably just read the network programming - I was hoping to figure it out quickly... Any help is really appreciated.

EDIT: It seems like I should be using lower level send/sendAll and recv, do buffering and cutting to lines in my code (or read byte by byte - probably it would be buffered anyway as set in listen) and treat 0 length returns by recv as disconnection. Isn't there a way to make it work via handle with higher level functions?

1 Answers

Thanks to the suggestion of K. A. Buhr, I figured out that the issue was not related to handle/socket, and the exceptions are indeed thrown correctly.

Minimal client/server setup that is working: https://gist.github.com/epoberezkin/d6063c0d8f6e95d9e606652565113413

It's based on the example in Network.Socket package, but handles multiple resolved addresses and uses handles to read/write socket.

The real issue was due to incorrectly handling exceptions in some cases, specifically instead of something like:

race (send h) (receive h) `finally` disconnected

I did

race (send h) (receive h) >> disconnected
Related