Perform a graceful disconnection if the termination was locally initiated

This commit is contained in:
Cameron Gutman 2021-05-14 22:06:07 -05:00
parent 1b3c14a792
commit 387ff48a65
4 changed files with 70 additions and 9 deletions

View File

@ -24,6 +24,7 @@ OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
int OriginalVideoBitrate; int OriginalVideoBitrate;
int AudioPacketDuration; int AudioPacketDuration;
bool AudioEncryptionEnabled; bool AudioEncryptionEnabled;
bool UserRequestedTermination;
// Connection stages // Connection stages
static const char* stageNames[STAGE_MAX] = { static const char* stageNames[STAGE_MAX] = {
@ -55,6 +56,12 @@ void LiInterruptConnection(void) {
// Stop the connection by undoing the step at the current stage and those before it // Stop the connection by undoing the step at the current stage and those before it
void LiStopConnection(void) { void LiStopConnection(void) {
// If this was a fully complete connection and we haven't started any termination
// logic prior to this point, this termination is user requested.
if (stage == STAGE_MAX - 1 && !alreadyTerminated) {
UserRequestedTermination = true;
}
// Disable termination callbacks now // Disable termination callbacks now
alreadyTerminated = true; alreadyTerminated = true;
@ -190,6 +197,10 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig)); memcpy(&StreamConfig, streamConfig, sizeof(StreamConfig));
OriginalVideoBitrate = streamConfig->bitrate; OriginalVideoBitrate = streamConfig->bitrate;
RemoteAddrString = strdup(serverInfo->address); RemoteAddrString = strdup(serverInfo->address);
alreadyTerminated = false;
ConnectionInterrupted = false;
UserRequestedTermination = false;
// Validate the audio configuration // Validate the audio configuration
if (MAGIC_BYTE_FROM_AUDIO_CONFIG(StreamConfig.audioConfiguration) != 0xCA || if (MAGIC_BYTE_FROM_AUDIO_CONFIG(StreamConfig.audioConfiguration) != 0xCA ||
@ -245,9 +256,6 @@ int LiStartConnection(PSERVER_INFORMATION serverInfo, PSTREAM_CONFIGURATION stre
goto Cleanup; goto Cleanup;
} }
alreadyTerminated = false;
ConnectionInterrupted = false;
Limelog("Initializing platform..."); Limelog("Initializing platform...");
ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT); ListenerCallbacks.stageStarting(STAGE_PLATFORM_INIT);
err = initializePlatform(); err = initializePlatform();

View File

@ -75,6 +75,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx;
#define IDX_TERMINATION 7 #define IDX_TERMINATION 7
#define CONTROL_STREAM_TIMEOUT_SEC 10 #define CONTROL_STREAM_TIMEOUT_SEC 10
#define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2
static const short packetTypesGen3[] = { static const short packetTypesGen3[] = {
0x1407, // Request IDR frame 0x1407, // Request IDR frame
@ -1053,9 +1054,19 @@ int stopControlStream(void) {
} }
if (peer != NULL) { if (peer != NULL) {
// We use enet_peer_disconnect_now() so the host knows immediately if (UserRequestedTermination) {
// of our termination and can cleanup properly for reconnection. // Gracefully disconnect to ensure the remote host receives all of our final
enet_peer_disconnect_now(peer, 0); // outbound traffic, including any key up events that might be sent.
gracefullyDisconnectEnetPeer(client, peer, CONTROL_STREAM_LINGER_TIMEOUT_SEC * 1000);
enet_peer_reset(peer);
}
else {
// This termination was requested by the remote host or caused due to a
// connection problem, so just do a quick abortive disconnect. The peer
// may be gone by this point.
enet_peer_disconnect_now(peer, 0);
}
peer = NULL; peer = NULL;
} }
if (client != NULL) { if (client != NULL) {

View File

@ -31,6 +31,7 @@ extern OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
extern int OriginalVideoBitrate; extern int OriginalVideoBitrate;
extern int AudioPacketDuration; extern int AudioPacketDuration;
extern bool AudioEncryptionEnabled; extern bool AudioEncryptionEnabled;
extern bool UserRequestedTermination;
#ifndef UINT24_MAX #ifndef UINT24_MAX
#define UINT24_MAX 0xFFFFFF #define UINT24_MAX 0xFFFFFF
@ -63,6 +64,7 @@ extern bool AudioEncryptionEnabled;
#define MAGIC_BYTE_FROM_AUDIO_CONFIG(x) ((x) & 0xFF) #define MAGIC_BYTE_FROM_AUDIO_CONFIG(x) ((x) & 0xFF)
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs); int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs);
int gracefullyDisconnectEnetPeer(ENetHost* host, ENetPeer* peer, enet_uint32 lingerTimeoutMs);
int extractVersionQuadFromString(const char* string, int* quad); int extractVersionQuadFromString(const char* string, int* quad);
bool isReferenceFrameInvalidationEnabled(void); bool isReferenceFrameInvalidationEnabled(void);
void* extendBuffer(void* ptr, size_t newSize); void* extendBuffer(void* ptr, size_t newSize);

View File

@ -6,7 +6,7 @@
// multiple times for retransmissions to work correctly. It is meant to be a drop-in // multiple times for retransmissions to work correctly. It is meant to be a drop-in
// replacement for enet_host_service(). It also handles cancellation of the connection // replacement for enet_host_service(). It also handles cancellation of the connection
// attempt during the wait. // attempt during the wait.
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) { static int serviceEnetHostInternal(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs, bool ignoreInterrupts) {
int ret; int ret;
// We need to call enet_host_service() multiple times to make sure retransmissions happen // We need to call enet_host_service() multiple times to make sure retransmissions happen
@ -14,7 +14,7 @@ int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
int selectedTimeout = timeoutMs < ENET_INTERNAL_TIMEOUT_MS ? timeoutMs : ENET_INTERNAL_TIMEOUT_MS; int selectedTimeout = timeoutMs < ENET_INTERNAL_TIMEOUT_MS ? timeoutMs : ENET_INTERNAL_TIMEOUT_MS;
// We want to report an interrupt event if we are able to read data // We want to report an interrupt event if we are able to read data
if (ConnectionInterrupted) { if (!ignoreInterrupts && ConnectionInterrupted) {
Limelog("ENet wait interrupted\n"); Limelog("ENet wait interrupted\n");
ret = -1; ret = -1;
break; break;
@ -27,10 +27,50 @@ int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
timeoutMs -= selectedTimeout; timeoutMs -= selectedTimeout;
} }
return ret; return ret;
} }
int serviceEnetHost(ENetHost* client, ENetEvent* event, enet_uint32 timeoutMs) {
return serviceEnetHostInternal(client, event, timeoutMs, false);
}
// This function performs a graceful disconnect, including lingering until outbound
// traffic is acked (up until the linger timeout elapses).
int gracefullyDisconnectEnetPeer(ENetHost* host, ENetPeer* peer, enet_uint32 lingerTimeoutMs) {
ENetEvent event;
int err;
// Begin the disconnection process. If this peer is already a zombie, we'll get an
// immediate disconnect event that will complete the process. If it's still alive,
// we'll get the disconnect event after all outgoing messages have been acked.
enet_peer_disconnect_later(peer, 0);
// We must use the internal function which lets us ignore pending interrupts.
while ((err = serviceEnetHostInternal(host, &event, lingerTimeoutMs, true)) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE:
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
Limelog("ENet peer disconnection is complete\n");
return 0;
default:
LC_ASSERT(false);
break;
}
}
if (err == 0) {
Limelog("Timed out waiting for ENet peer to acknowledge disconnection\n");
}
else {
Limelog("Failed to receive ENet peer disconnection acknowledgement\n");
}
return -1;
}
int extractVersionQuadFromString(const char* string, int* quad) { int extractVersionQuadFromString(const char* string, int* quad) {
char versionString[128]; char versionString[128];
char* nextDot; char* nextDot;