We have some code to send metrics to a SOCK_DGRAM
where another daemon listens and aggregates/proxies these messages. Opening the socket looks like:
sock <- socket (ai :: AddressInfo Inet Datagram UDP):_ <- getAddressInfo (Just "127.0.0.1") Nothing aiNumericHost connect s (socketAddress ai) { port } return sock
And at the moment we write to it like:
send sock payload mempty
I want to ensure the call above doesn’t block for very long (or at the very least doesn’t block indefinitely), but my understanding of unix sockets is not very deep and I’m having trouble understanding how exactly send
blocks, looking at internals here and here.
There is a related question here that was helpful: When a non-blocking send() only transfers partial data, can we assume it would return EWOULDBLOCK the next call?
So my questions specifically are:
- General socket question: I see in this implementation
send
is going to block (after busy-waiting) until there is room in the buffer. How exactly is this buffer related to the consumer? Does this meansend
may block indefinitely if ourlisten
-ing daemon is slow or stalls? - If I would rather abort and never block, do I need to make my own fork of
System.Socket.Unsafe
, or am I missing something?
I’m only concerned with linux here.
EDIT: Also, and what probably got me started with all of this is I find that when the metrics collector is not running, that every other of my send
calls above throws a “connection refused” exception. So why that is, or whether it’s normal is another question I have.
EDIT2: Here’s a complete example illustrating the connection refused issue if anyone would like to help repro:
import Data.Functor import System.Socket import System.Socket.Family.Inet repro :: IO () repro = do let port = 6565 (s :: Socket Inet Datagram UDP) <- socket (ai :: AddressInfo Inet Datagram UDP):_ <- getAddressInfo (Just "127.0.0.1") Nothing aiNumericHost connect s (socketAddress ai) { port } putStrLn "Starting send" void $ send s "FOO" mempty void $ send s "BAR" mempty putStrLn "done"
I’m using socket-0.5.3.0
.
EDIT3: this seems to be due to the connect
call, somehow. (Testing on sockets
latest):
{-# LANGUAGE ScopedTypeVariables, OverloadedStrings, NamedFieldPuns #-} import Data.Functor import System.Socket import System.Socket.Protocol.UDP import System.Socket.Type.Datagram import System.Socket.Family.Inet repro :: IO () repro = do (s :: Socket Inet Datagram UDP) <- socket -- Uncommenting raises eConnectionRefused, everytime: -- connect s (SocketAddressInet inetLoopback 6565 :: SocketAddress Inet) putStrLn "Starting send" void $ sendTo s "FOO" mempty (SocketAddressInet inetLoopback 6565 :: SocketAddress Inet) void $ sendTo s "BAR" mempty (SocketAddressInet inetLoopback 6565 :: SocketAddress Inet) putStrLn "done"
As I understand it we should be able to use connect
(at least the underlying syscall) to set the default send address. I haven’t dug into the implementation of connect
in the library yet.
I’ve opened this: https://github.com/lpeterse/haskell-socket/issues/55
Advertisement
Answer
This isn’t a Haskell issue — it’s expected behavior on Linux when sending two UDP packets to a localhost port with no listening process. The following C program:
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netinet/udp.h> int main() { int s = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in dstaddr = { AF_INET, htons(6565), {htonl(0x7f000001)} }; if (connect(s, (struct sockaddr*) &dstaddr, sizeof(dstaddr))) perror("connect"); if (send(s, "FOO", 3, 0) == -1) perror("first send"); if (send(s, "BAR", 3, 0) == -1) perror("second send"); return 0; }
will print second send: Connection refused
, assuming nothing is listening on localhost port 6565.
If you do any one of the following — (i) send to a non-local host, (ii) drop the connect
call and replace the send
s with sendto
s, or (iii) send packets to a port with a process listening for UDP packets — then you won’t get the error.
The behavior is a little complex and not well documented anywhere, though the manpages for udp(7)
hint at it.
You may find the discussion in this Stack Overflow question helpful.