[Haskell-cafe] Unexpected behaviour with send and send-buffer setting
Simon Yarde
simonyarde at me.com
Thu Sep 5 16:22:40 CEST 2013
Well I'm always happy to take a public pasting in return for the chance to learn something :) It's fair to say I didn't fully understand the implications of TCPs flow-control mechanisms re buffering capacity, and I've been grateful to be guided here.
> On 4 Sep 2013, at 16:52, Bryan O'Sullivan <bos at serpentine.com> wrote:
> Your question is actually not related to Haskell at all,
Well.. I'll try to protest my inquiry was specifically targeted at Haskell and that it's reasonable to ask: "what is the behaviour of send when there is no buffering capacity?"; and "as a newcomer to Haskell, broadly what are the mechanisms used to implement send on top of the C API?". Thanks to everyone who provided pointers.
Network.Socket has this to say:
> Essentially the entire C socket API is exposed through this module; in general the operations follow the behaviour of the C functions of the same name (consult your favourite Unix networking book).
I'm pretty sure you can't understand the behaviour of Network.Socket from consulting a Unix networking book, otherwise you'd be looking for -1 return values and error-codes, and pondering how you might go about efficiently managing read/write to a whole bunch non-blocking sockets.
I don't know how one would go about providing a little background in the Network.Socket docs but it seems it would be very helpful; there's no clue provided as to the magic that it's taking care of.
Anyway, that's enough hot air from me. I've put my findings below in case anyone else is ever interested.
S
For anyone interested, here's what I learned from asking questions here and digging around:
- The underlying C sockets are in non-blocking mode;
- send uses a threadWaitWrite to block the current thread until the socket is writeable;
- threadWaitWrite relies on either epoll or kqueue to efficiently identify writeable file-descriptors, therefore select is not required and not implemented by Network.Socket, and there is no need to provide API access to setting O_NONBLOCK;
- threadWaitWrite is run inside a throwSocketErrorWaitWrite (Network.Socket.Internal), which uses a throwErrnoIfRetry (Foreign.C.Error); the overall effect is that if the threadWaitWrite/send operation ever errors with EINTR then the whole threadWaitWrite/send is retried; otherwise subsequent sends are managed and called by threadWaitWrite when the send is likely to succeed (which is more efficient than polling using a retry loop alone); all other errors are converted to IOError.
The end result on the behaviour of the Network.Socket.send is:
- send can result in all sorts of socket-related IOErrors being thrown, except for EINT interrupts because the send is efficiently managed and retried;
- send never has a return value of -1, because an exception is raised or the send is retried;
- send blocks the current thread if a socket is not currently writeable.
- It strikes me that a return value of 0 for Network.Socket.send is unlikely, because otherwise the socket file-descriptor would not have reported that it was writeable and the send would not have been called. But I'm unsure if a 0 value could never be returned.
Simon Yarde
simonyarde at me.com
More information about the Haskell-Cafe
mailing list