Steam transport: accepted connections survive listener close
Round-three diagnosis from the three-machine logs: the console channel connected first try on SDR both ways - then died seconds later with end reason 5010 (PeerSentNoConnection). Cause: the arcade engine closes its console LISTENER the moment the console connects (correct under TCP, where accepted sockets outlive the listener), but Steam''s CloseListenSocket closes all accepted connections ungracefully. The members silently killed their console connection at accept; the owner''s next packet got no-connection back, no eggs were ever fed, and all three pods sat waiting. The transport now bridges the semantics: an engine close only marks the listener (new callers are rejected, pending queue dropped); the Steam socket is destroyed in Cleanup at mission teardown. Re-listening on the same engine port reopens the marked listener. The loopback self-test now covers exactly this: accept, close the listener, then push data both ways over the accepted connection - PASSED (ping 1, survives listener close 1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,11 @@ namespace
|
||||
unsigned short enginePort; // host order
|
||||
HSteamNetConnection pending[maxPending];
|
||||
int pendingCount;
|
||||
// The engine closes listeners with TCP semantics (accepted
|
||||
// connections survive). Steam's CloseListenSocket kills the
|
||||
// accepted connections ungracefully - so an engine close only
|
||||
// marks the listener here; the real close waits for Cleanup.
|
||||
Logical closed;
|
||||
};
|
||||
|
||||
struct ConnectionRecord
|
||||
@@ -171,6 +176,18 @@ namespace
|
||||
case k_ESteamNetworkingConnectionState_Connecting:
|
||||
if (status->m_info.m_hListenSocket != k_HSteamListenSocket_Invalid)
|
||||
{
|
||||
// no more callers once the engine closed the listener
|
||||
ListenerRecord *listener = FindListener(status->m_info.m_hListenSocket);
|
||||
if (listener == NULL || listener->closed)
|
||||
{
|
||||
DEBUG_STREAM << "SteamNetTransport: incoming ["
|
||||
<< status->m_info.m_szConnectionDescription
|
||||
<< "] on a closed listener - rejecting\n" << std::flush;
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
status->m_hConn, 0, "listener closed", false);
|
||||
break;
|
||||
}
|
||||
|
||||
// incoming: accept immediately, queue it when Connected
|
||||
DEBUG_STREAM << "SteamNetTransport: incoming ["
|
||||
<< status->m_info.m_szConnectionDescription
|
||||
@@ -411,6 +428,20 @@ namespace
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
// a logically-closed listener for this engine port reopens
|
||||
// (the Steam socket outlives engine closes; see Close)
|
||||
for (int existing = 0; existing < gListenerCount; ++existing)
|
||||
{
|
||||
if (gListeners[existing].enginePort == (unsigned short) local_port)
|
||||
{
|
||||
gListeners[existing].closed = False;
|
||||
gListeners[existing].pendingCount = 0;
|
||||
DEBUG_STREAM << "SteamNetTransport: reopened listener on engine port "
|
||||
<< local_port << "\n" << std::flush;
|
||||
return (Connection) gListeners[existing].handle;
|
||||
}
|
||||
}
|
||||
|
||||
// engine port -> fake port index, in order of appearance:
|
||||
// the console channel always listens first, the mesh second
|
||||
int index = -1;
|
||||
@@ -457,7 +488,7 @@ namespace
|
||||
SteamAPI_RunCallbacks();
|
||||
|
||||
ListenerRecord *listener = FindListener((HSteamListenSocket) listener_handle);
|
||||
if (listener == NULL || listener->pendingCount == 0)
|
||||
if (listener == NULL || listener->closed || listener->pendingCount == 0)
|
||||
{
|
||||
return InvalidConnection;
|
||||
}
|
||||
@@ -493,9 +524,19 @@ namespace
|
||||
ListenerRecord *listener = FindListener((HSteamListenSocket) connection);
|
||||
if (listener != NULL)
|
||||
{
|
||||
SteamNetworkingSockets()->CloseListenSocket(listener->handle);
|
||||
*listener = gListeners[gListenerCount - 1];
|
||||
--gListenerCount;
|
||||
//
|
||||
// TCP semantics: accepted connections must survive a
|
||||
// listener close. Steam's CloseListenSocket kills them
|
||||
// ungracefully, so only mark it closed (rejecting any
|
||||
// new callers) and destroy it for real in Cleanup.
|
||||
//
|
||||
listener->closed = True;
|
||||
for (int i = 0; i < listener->pendingCount; ++i)
|
||||
{
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
listener->pending[i], 0, "listener closed", false);
|
||||
}
|
||||
listener->pendingCount = 0;
|
||||
return;
|
||||
}
|
||||
SteamNetworkingSockets()->CloseConnection(
|
||||
@@ -713,6 +754,7 @@ Logical
|
||||
}
|
||||
|
||||
Logical ok = False;
|
||||
Logical survives_close = False;
|
||||
if (outgoing != NetTransport::InvalidConnection &&
|
||||
incoming != NetTransport::InvalidConnection)
|
||||
{
|
||||
@@ -728,11 +770,32 @@ Logical
|
||||
}
|
||||
Sleep(25);
|
||||
}
|
||||
|
||||
//
|
||||
// The arcade engine closes a listener right after
|
||||
// accepting - the accepted connection MUST survive
|
||||
// (Steam kills children on CloseListenSocket; the
|
||||
// transport defers the real close to Cleanup)
|
||||
//
|
||||
transport->Close(listener);
|
||||
transport->Send(incoming, "PONG", 4);
|
||||
recv_deadline = GetTickCount() + 5 * 1000;
|
||||
while ((LONG)(GetTickCount() - recv_deadline) < 0)
|
||||
{
|
||||
if (transport->Receive(outgoing, buffer, sizeof(buffer)) == 4)
|
||||
{
|
||||
survives_close = (memcmp(buffer, "PONG", 4) == 0);
|
||||
break;
|
||||
}
|
||||
Sleep(25);
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << "SteamNetTransport: SELF TEST "
|
||||
<< (ok ? "PASSED" : "FAILED")
|
||||
<< ((ok && survives_close) ? "PASSED" : "FAILED")
|
||||
<< " (out " << (outgoing != NetTransport::InvalidConnection)
|
||||
<< ", in " << (incoming != NetTransport::InvalidConnection)
|
||||
<< ", ping " << ok
|
||||
<< ", survives listener close " << survives_close
|
||||
<< ")\n" << std::flush;
|
||||
|
||||
// leave the session pristine for the real race
|
||||
|
||||
Reference in New Issue
Block a user