Skip to content
Advertisement

Sending to a Datagram socket, without blocking indefinitely

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 mean send may block indefinitely if our listen-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 sends with sendtos, 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.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement