Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

send() / recv() on UDPSocket is not async-safe #57001

Open
ettersi opened this issue Jan 9, 2025 · 2 comments
Open

send() / recv() on UDPSocket is not async-safe #57001

ettersi opened this issue Jan 9, 2025 · 2 comments
Labels

Comments

@ettersi
Copy link
Contributor

ettersi commented Jan 9, 2025

The following code tries to send a UDP packet to socat -v PIPE udp-recvfrom:9809,fork and then receive the echo response, but it consistently fails.

julia> using Sockets

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

       errormonitor(@async println(String(recv(sock))))
       send(sock, ip"127.0.0.1", 9809, "hello")

       sleep(1)
       close(sock)

Unhandled Task ERROR: EOFError: read end of file
Stacktrace:
 [1] try_yieldto(undo::typeof(Base.ensure_rescheduled))
   @ Base ./task.jl:958
 [2] wait()
   @ Base ./task.jl:1022
 [3] wait(c::Base.GenericCondition{Base.Threads.SpinLock}; first::Bool)
   @ Base ./condition.jl:130
 [4] wait
   @ ./condition.jl:125 [inlined]
 [5] recvfrom(sock::UDPSocket)
   @ Sockets ~/.julia/juliaup/julia-1.11.2+0.x64.apple.darwin14/share/julia/stdlib/v1.11/Sockets/src/Sockets.jl:359
 [6] recv
   @ ~/.julia/juliaup/julia-1.11.2+0.x64.apple.darwin14/share/julia/stdlib/v1.11/Sockets/src/Sockets.jl:324 [inlined]
 [7] (::var"#7#8")()
   @ Main ./REPL[6]:4

The problem is that the send() "activates" the UDP socket and yields to the async task, and then the recv() notices that the socket is already "active" and skips registering the libuv callback.

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)))

Original discussion: https://discourse.julialang.org/t/async-udp-socket-usage/

@ettersi
Copy link
Contributor Author

ettersi commented Jan 9, 2025

For what it's worth, the "active" check was introduced a decade(!) ago due to this issue: #6505

@ettersi
Copy link
Contributor Author

ettersi commented Jan 14, 2025

I think I have a fix here: https://github.com/subnero1/Sockets. The key change is that I'm calling uv_udp_recv_start() unconditionally and explicitly ignore an EALREADY return code instead of raising an error, see subnero1/Sockets@fc7c537. This seems to work fine in practice, and I'm currently checking with the libuv folks whether this is a sane way of asking about an ongoing UDP receive, see libuv/libuv#4671.

I'd be happy to raise a PR with this change, but before I go through all the PR boilerplate I'd need a few words of encouragement that the proposed change might indeed get accepted. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants