Skip to content

net: never block the main thread on udp sends - #3293

Open
Vandomas wants to merge 7 commits into
beyond-all-reason:masterfrom
Vandomas:pr/nonblocking-udp-only
Open

net: never block the main thread on udp sends#3293
Vandomas wants to merge 7 commits into
beyond-all-reason:masterfrom
Vandomas:pr/nonblocking-udp-only

Conversation

@Vandomas

Copy link
Copy Markdown
Contributor

A synchronous send_to blocks until the send buffer drains, which freezes the whole client when the link chokes. Hit this on macOS mid game, with the VPN on

The socket is non-blocking now. A full buffer surfaces as try_again, which CheckErrorCode already treats as harmless, so the packet gets 5ms to drain and is dropped after that, same as a packet lost on the wire. Drops are counted in the connection statistics

A full socket send buffer wedges a synchronous send_to until it drains, which on a stalling link means the whole client freezes: no rendering, no input, watchdog stack traces, and on macOS behind a VPN tunnel it froze at connect and mid game.

Put the socket in non-blocking mode and, when the buffer is full, wait up to 5ms for it to drain and retry once before giving up on the packet. A drop there is what a lost packet on the wire looks like, the chunk layer resends unacked chunks anyway, and try_again was already treated as harmless in CheckErrorCode. Dropped sends are counted and printed in the connection statistics.
Comment thread rts/System/Net/UDPConnection.cpp
Comment thread rts/System/Net/UDPConnection.cpp Outdated
Vandomas and others added 2 commits August 29, 2026 17:38
Tooling that parses the existing lines keeps working.
Co-authored-by: sprunk <spr.ng@o2.pl>

@sprunk sprunk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but it would be good if somebody more competent at low-level stuff looked too.

@sprunk

sprunk commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Added a changelog snippet, please check if it's accurate (from a PoV of a game developer using the engine and treating the networking bits as a blackbox).

@Vandomas

Copy link
Copy Markdown
Contributor Author

Fair enough, is there someone in particular I should ping for a review, or does that usually happen on its own?

@sprunk

sprunk commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

@lostsquirrel1 I guess.

@Vandomas

Copy link
Copy Markdown
Contributor Author

Added a changelog snippet, please check if it's accurate (from a PoV of a game developer using the engine and treating the networking bits as a blackbox).

Looks accurate, that captures it well

Comment thread rts/System/Net/UDPConnection.cpp Outdated
Co-authored-by: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com>
@bruno-dasilva

bruno-dasilva commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Now I'm curious about the underlying mechanism of UDP sockets. Under what circumstances do they drain/not drain under different network issues, and do they behave similarly or differently to TCP sockets in those circumstances?

My original intuition was because UDP sockets are fire and forget, I don't quite understand how the buffer can fill unless you're sending a metric ton of packets

edit: maybe the VPN, acting as the fake NIC, is holding onto packets when the VPN has a network issue? I'd think normal NICs would just put them on the wire and if they were dropped by the router then oh well

@Vandomas

Copy link
Copy Markdown
Contributor Author

sendto doesn’t put the packet on the wire, it hands it to the interface’s output queue, and that queue has a limit. With a real NIC it drains at line rate and you never notice. A VPN is just a process, when that process lags, the queue stops draining and the socket buffer backs up

On top of that, macOS returns ENOBUFS or blocks in that situation, whereas Linux, if I remember right, tends to drop silently. That’s why the same code freezes here and looks fine there

@bruno-dasilva

Copy link
Copy Markdown
Collaborator

neat, thanks!

Comment thread rts/System/Net/UDPConnection.cpp Outdated

@bruno-dasilva bruno-dasilva left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approving, with some minor comment feedback, but still should have TK (or marek?) look at this as he probably has a better intuition of recoil netcode.

Comment thread rts/System/Net/UDPConnection.cpp Outdated
Comment on lines +1081 to +1082
/* Balance between too many resends (lower)
* and main thread stalls (higher) */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious @lostsquirrel1 what you think re: if we actually have to care about spinlock duration here (and in general)

Suggested change
/* Balance between too many resends (lower)
* and main thread stalls (higher) */
// Balance between too many resends (lower) and main thread stalls (higher).
// Note that parent code holds holds a spinlock over this, so we definitely don't
// want to block for too long.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe wait for feedback from TK before applything this one

Co-authored-by: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants