[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