Async UDP socket usage

Hah, I think I know what’s happening! The problem is that recv() first checks whether the UDP socket is “active” and only if it’s found to be not active then it registers a callback for when a matching UDP datagram arrives. From Sockets.jl:

if ccall(:uv_is_active, Cint, (Ptr{Cvoid},), sock.handle) == 0
    err = ccall(:uv_udp_recv_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
                sock,
                @cfunction(Base.uv_alloc_buf, Cvoid, (Ptr{Cvoid}, Csize_t, Ptr{Cvoid})),
                @cfunction(uv_recvcb, Cvoid, (Ptr{Cvoid}, Cssize_t, Ptr{Cvoid}, Ptr{Cvoid}, Cuint)))

The trouble is that sending a datagram also counts as active; therefore, if the recv() is executed during the send() then the callback is never registered and therefore the recv() never resolves.

julia> sock = UDPSocket()
       bind(sock, ip"127.0.0.1", 0)
       println("Before send: ", is_active(sock))

       @async begin
           println("In async before yield: ", is_active(sock))
           println("In async after yield: ", is_active(sock))
       end
       send(sock, ip"127.0.0.1", 9809, "hello")
       println("After send: ", is_active(sock))

       close(sock)
Before send: 0
In async before yield: 1
In async after yield: 0
After send: 0

This also explains why delaying the recv() even further by putting a sleep() before it fixes the problem: the extra sleep means the task yields once more, and that allows the send() to finish before the recv() starts.

5 Likes