I'm trying to better understand a design decision made in the network library. Reputable sources mention in a github issue and a mailing list response that network
uses non-blocking sockets. Instead of using the default blocking behavior, they use select to block until the socket is ready to be read. Why is this better? Either way, it ends up blocking, and network
only exposes a blocking API to end users. My guess is that it's bad for an FFI call to block and that there is some kind of GHC magic around select
, but I haven't been able to confirm that.
As a minor aside, I cannot find where select
in called in network
. Grepping the codebase did not turn up anything. I just discovered GHC.Event, which seems to provide functions that would be used instead of calling select
directly, but grepping shows network
doesn't use this either.
In blocking socket mode, a system call event halts the execution until an appropriate reply has been received. In non-blocking sockets, it continues to execute even if the system call has been invoked and deals with its reply appropriately later.
From MSDN, the return value of connect(): On a blocking socket, the return value indicates success or failure of the connection attempt. With a nonblocking socket, the connection attempt cannot be completed immediately. In this case, connect will return SOCKET_ERROR , and WSAGetLastError() will return WSAEWOULDBLOCK.
A socket is in blocking mode when an I/O call waits for an event to complete. If the blocking mode is set for a socket, the calling program is suspended until the expected event completes.
To mark a socket as non-blocking, we use the fcntl system call. Here's an example: int flags = guard(fcntl(socket_fd, F_GETFL), "could not get file flags"); guard(fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK), "could not set file flags"); Here's a complete example.
The non-blocking IO event loop is part of GHC's runtime system (RTS). This interacts very nicely with GHC's green thread system: instead of writing asynchronous code, you can just use lightweight threads and the runtime will take care of waking up the correct one.
All IO in in Haskell is non-blocking by default, so if you've got two threads that are each blocked on a different socket, then the runtime system will internally do select
(or some other platform-specific way to wait on multiple file descriptors like epoll
or kqueue
) to only wake the thread up when the file descriptor becomes ready. See https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/IOManager for more details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With