[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.


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