Description
On Windows, when a remote client process is force-killed (Task Manager kill / crash — no Disconnect packet sent), the server's UDP receive path stops permanently:
- All other still-running clients get disconnected shortly after (their reliable pipeline reports transport loss).
- No new client can connect ever again (connect attempts time out with "Failed to connect to server").
- The server process stays alive with zero managed exceptions and zero transport error logs — it fails silently.
- The wedge is permanent: waiting well past the disconnect timeout (80+ s) does not recover it.
A graceful quit of the same client (NetworkManager.Shutdown() then Application.Quit()) does not reproduce the issue — subsequent clients connect fine. A fresh server also accepts multiple simultaneous clients fine, so this is specifically the ungraceful-disconnect path.
We consistently reproduce this with all processes on the same machine (127.0.0.1), and expect the same on same-LAN direct UDP. (Other network topologies not tested.)
Suspected root cause
This matches the classic Windows UDP WSAECONNRESET behavior: after the client dies, the server keeps sending to the dead port for a while (snapshots/heartbeats), Windows returns ICMP Port-Unreachable, and because the underlying socket does not have SIO_UDP_CONNRESET disabled, the shared server socket's receive path is affected.
From reading com.unity.transport sources shipped with the editor (6.5.0, Runtime/UDPNetworkInterface.cs):
- We could not find any place where
SIO_UDP_CONNRESET is disabled for the UDP socket created via Baselib_RegisteredNetwork_Socket_UDP_Create, and there is no NetworkSettings knob for it.
- The socket re-creation safety net in the receive job only triggers when all receive requests fail AND
getsockname() fails. In this wedge the socket handle stays valid (getsockname() succeeds), so recovery never runs — consistent with our logs showing neither "Socket error encountered; attempting recovery" nor "Unrecoverable socket failure".
Reproduction steps (same machine, Windows)
- Build a minimal NGO project (NetworkManager + UnityTransport, default settings, port 7777, a trivial player prefab).
- Start server/host process.
- Start client A and client B (both connect fine; server logs ClientConnected for both).
- Force-kill client A (
taskkill /F /PID <A>).
- Observe: server logs ClientDisconnected for A after the disconnect timeout (~30 s), then client B is disconnected as well; any new client C fails to connect ("Failed to connect to server"). Server stays silent — no exceptions, no socket errors, and it never accepts a connection again.
- Repeat the run but close client A gracefully (Shutdown + quit) instead: client B stays connected and new clients connect normally.
Reproduces 100% for us across many runs (release and development builds, -batchmode -nographics and windowed).
Environment
- Unity 6000.5.2f1 (Windows 11 host, all processes on one machine)
- com.unity.netcode.gameobjects 2.13.0
- com.unity.transport 6.5.0 (editor-bundled)
- UnityTransport with default settings, direct UDP (no Relay), 127.0.0.1:7777
Expected outcome
A force-killed (crashed) client must not take down the server's transport. The server should drop that connection after the timeout and keep serving existing/new clients — which is exactly what happens on the graceful-disconnect path.
Workaround we use
Always terminate clients gracefully in development/smoke tests. This obviously cannot cover real crashes, which is why we're reporting it.
We can provide full server/client logs of both the failing (force-kill) and passing (graceful) runs on request.
Description
On Windows, when a remote client process is force-killed (Task Manager kill / crash — no Disconnect packet sent), the server's UDP receive path stops permanently:
A graceful quit of the same client (NetworkManager.Shutdown() then Application.Quit()) does not reproduce the issue — subsequent clients connect fine. A fresh server also accepts multiple simultaneous clients fine, so this is specifically the ungraceful-disconnect path.
We consistently reproduce this with all processes on the same machine (127.0.0.1), and expect the same on same-LAN direct UDP. (Other network topologies not tested.)
Suspected root cause
This matches the classic Windows UDP
WSAECONNRESETbehavior: after the client dies, the server keeps sending to the dead port for a while (snapshots/heartbeats), Windows returns ICMP Port-Unreachable, and because the underlying socket does not haveSIO_UDP_CONNRESETdisabled, the shared server socket's receive path is affected.From reading
com.unity.transportsources shipped with the editor (6.5.0,Runtime/UDPNetworkInterface.cs):SIO_UDP_CONNRESETis disabled for the UDP socket created viaBaselib_RegisteredNetwork_Socket_UDP_Create, and there is noNetworkSettingsknob for it.getsockname()fails. In this wedge the socket handle stays valid (getsockname()succeeds), so recovery never runs — consistent with our logs showing neither "Socket error encountered; attempting recovery" nor "Unrecoverable socket failure".Reproduction steps (same machine, Windows)
taskkill /F /PID <A>).Reproduces 100% for us across many runs (release and development builds,
-batchmode -nographicsand windowed).Environment
Expected outcome
A force-killed (crashed) client must not take down the server's transport. The server should drop that connection after the timeout and keep serving existing/new clients — which is exactly what happens on the graceful-disconnect path.
Workaround we use
Always terminate clients gracefully in development/smoke tests. This obviously cannot cover real crashes, which is why we're reporting it.
We can provide full server/client logs of both the failing (force-kill) and passing (graceful) runs on request.