Async UDP socket usage

We have a problem with async usage of UDP sockets, so I made a MWE to demonstrate it.

We run a simple UDP echo deamon with socat:

socat -v PIPE udp-recvfrom:9809,fork

This listens on port 9809 and echoes back to the sender’s UDP port.

This code works:

using Sockets

sock = UDPSocket()
bind(sock, ip"127.0.0.1", 0)

send(sock, ip"127.0.0.1", 9809, "hello")
@info String(recv(sock))

sleep(1)
close(sock)

and prints the echoed packet back.

This code does not work:

using Sockets

sock = UDPSocket()
bind(sock, ip"127.0.0.1", 0)

@async begin
  @info String(recv(sock))
end
send(sock, ip"127.0.0.1", 9809, "hello")

sleep(1)
close(sock)

in the sense that nothing is received back.

Placing small delays in the code makes it work. For example a sleep(0.001) inside the @async will make the code work.

Depending on the exact timing of when the send() and recv() are executed, either nothing is received (UDP packet is silently dropped) or an exception EOFError: read end of file is thrown. Neither makes sense to me.

So, the question is, should I not expect send() and recv() using the same socket to work in asynchronous code?

There is a high chance you are reading your socket buffer without anything being written to it beforehand. You have to use wait for send(sock, ip"127.0.0.1", 9809, "hello") first.

Another way would be to put your async part in a while loop like this:

@async begin
   while isopen(sock)
        @info String(recv(sock))
   end
end

recv() is meant to be block for data. From the docs:

  recv(socket::UDPSocket)

Read a UDP packet from the specified socket, and return
the bytes received. This call blocks.

I tested the code snippet with the while loop, and it has the same problem.

1 Like

My bad, I forgot about the blocking part. I think pinning the recv function to a separate thread should work as expected.

sock = UDPSocket()
bind(sock, ip"127.0.0.1", 0)

StableTasks.@spawnat 4 while isopen(sock1)
    recv(sock) |> String |> println
end

for i in 1:5
    send(sock, ip"127.0.0.1", 9809, "hello"); sleep(0.1)
end

Just in case if you are not familiar with StableTasks.@spawnat 4, it is starting and pining the task to thread 4, assuming you have started julia with 4 threads in this case.