[client] transport: stop after guest sessions end

Track unexpected loss of an admitted fallback independently from normal
stop and UUID revocation. Terminate once both primary and fallback
sessions have disappeared, regardless of notification order.

Make shutdown terminal so concurrent disconnect handling cannot replace
it with restart or running state.
This commit is contained in:
Geoffrey McRae
2026-08-12 12:13:48 +10:00
parent 2d96ec401c
commit 8fb3c4d9f6
7 changed files with 141 additions and 33 deletions

View File

@@ -30,6 +30,13 @@
#include "interface/displayserver.h"
#include "interface/overlay.h"
enum RunState
{
APP_STATE_RUNNING,
APP_STATE_RESTART,
APP_STATE_SHUTDOWN
};
typedef enum LG_MsgAlert
{
LG_ALERT_INFO ,
@@ -53,6 +60,9 @@ typedef struct LG_MouseState
LG_MouseState;
bool app_isRunning(void);
enum RunState app_getState(void);
void app_setState(enum RunState state);
bool app_transitionState(enum RunState from, enum RunState to);
bool app_inputEnabled(void);
bool app_isCaptureMode(void);
bool app_isCaptureOnlyMode(void);

View File

@@ -57,6 +57,37 @@
} \
while (0)
extern _Atomic(enum RunState) p_appState;
enum RunState app_getState(void)
{
return atomic_load_explicit(&p_appState, memory_order_acquire);
}
void app_setState(enum RunState state)
{
if (state == APP_STATE_SHUTDOWN)
{
atomic_store_explicit(&p_appState, state, memory_order_release);
return;
}
enum RunState current = atomic_load_explicit(
&p_appState, memory_order_acquire);
while (current != APP_STATE_SHUTDOWN &&
!atomic_compare_exchange_weak_explicit(&p_appState, &current, state,
memory_order_acq_rel, memory_order_acquire));
}
bool app_transitionState(enum RunState from, enum RunState to)
{
if (from == APP_STATE_SHUTDOWN)
return false;
return atomic_compare_exchange_strong_explicit(
&p_appState, &from, to, memory_order_acq_rel, memory_order_acquire);
}
bool app_isRunning(void)
{
const enum RunState state = app_getState();

View File

@@ -81,12 +81,18 @@ _Static_assert((int)LG_CAPTURE_RGBA32F == (int)LG_TEST_CAPTURE_RGBA32F,
"capture format mismatch");
#endif
#define TRANSPORT_LOST_PRIMARY (1U << 0)
#define TRANSPORT_LOST_FALLBACK (1U << 1)
#define TRANSPORT_LOST_ALL \
(TRANSPORT_LOST_PRIMARY | TRANSPORT_LOST_FALLBACK)
// forwards
static int renderThread(void * unused);
static RenderQueueSource renderQueueSource(LG_VideoSource source);
static bool videoSourceInvalidate(LG_VideoSource source);
static void videoSourceShowSplashIfNeeded(void);
static bool fallbackRequestVideo(void);
static void primaryLost(void);
static LGEvent *e_startup = NULL;
static LGEvent *e_cursorRepaint = NULL;
@@ -1148,11 +1154,13 @@ int main_cursorThread(void * unused)
lgInput_dropTransport();
lgAudio_dropTransport();
lgClipboard_dropTransport();
primaryLost();
}
app_setState(status == LG_TRANSPORT_DISCONNECTED ?
APP_STATE_RESTART : APP_STATE_SHUTDOWN);
if (status != LG_TRANSPORT_DISCONNECTED)
else
{
DEBUG_ERROR("Pointer transport failed with status %d", status);
app_setState(APP_STATE_SHUTDOWN);
}
break;
}
@@ -1334,7 +1342,7 @@ int main_frameThread(void * unused)
lgInput_dropTransport();
lgAudio_dropTransport();
lgClipboard_dropTransport();
app_setState(APP_STATE_RESTART);
primaryLost();
}
else if (status == LG_TRANSPORT_END)
app_setState(APP_STATE_SHUTDOWN);
@@ -2196,6 +2204,8 @@ static void fallbackConnected(void * opaque,
const LG_TransportSession * session)
{
(void)opaque;
atomic_fetch_and_explicit(&g_state.transportLost,
~TRANSPORT_LOST_FALLBACK, memory_order_acq_rel);
if (app_getState() == APP_STATE_SHUTDOWN)
return;
@@ -2219,6 +2229,19 @@ static void fallbackDisconnected(void * opaque)
videoSourceShowSplashIfNeeded();
}
static void fallbackLost(void * opaque)
{
(void)opaque;
const unsigned int lost = atomic_fetch_or_explicit(&g_state.transportLost,
TRANSPORT_LOST_FALLBACK, memory_order_acq_rel) |
TRANSPORT_LOST_FALLBACK;
if ((lost & TRANSPORT_LOST_ALL) == TRANSPORT_LOST_ALL)
{
DEBUG_INFO("Primary and fallback transport sessions disconnected");
app_setState(APP_STATE_SHUTDOWN);
}
}
static void fallbackUUIDMismatch(void * opaque, const uint8_t primary[16],
const uint8_t fallback[16])
{
@@ -2233,10 +2256,40 @@ static void fallbackUUIDMismatch(void * opaque, const uint8_t primary[16],
static const LG_TransportFallbackEventOps fallbackEvents =
{
.connected = fallbackConnected,
.lost = fallbackLost,
.disconnected = fallbackDisconnected,
.uuidMismatch = fallbackUUIDMismatch,
};
static void primaryLost(void)
{
atomic_store_explicit(
&g_state.lgHostConnected, false, memory_order_release);
unsigned int lost = atomic_load_explicit(
&g_state.transportLost, memory_order_acquire);
for (;;)
{
unsigned int next = lost | TRANSPORT_LOST_PRIMARY;
if (lgTransportFallback_ready(g_state.fallback))
next &= ~TRANSPORT_LOST_FALLBACK;
if (atomic_compare_exchange_weak_explicit(&g_state.transportLost,
&lost, next, memory_order_acq_rel, memory_order_acquire))
{
lost = next;
break;
}
}
if ((lost & TRANSPORT_LOST_ALL) == TRANSPORT_LOST_ALL)
{
DEBUG_INFO("Primary and fallback transport sessions disconnected");
app_setState(APP_STATE_SHUTDOWN);
return;
}
DEBUG_INFO("Waiting for the host to restart...");
app_setState(APP_STATE_RESTART);
}
static bool fallbackStart(const uint8_t primaryUUID[16])
{
if (!option_get_bool("spice", "enable") ||
@@ -3268,6 +3321,8 @@ restart:
if (probe.status == LG_TRANSPORT_OK)
{
session = probe.session;
atomic_fetch_and_explicit(&g_state.transportLost,
~TRANSPORT_LOST_PRIMARY, memory_order_acq_rel);
initialFallbackEnable = 0;
break;
}
@@ -3454,10 +3509,7 @@ restart:
lgInput_dropTransport();
lgAudio_dropTransport();
lgClipboard_dropTransport();
atomic_store_explicit(
&g_state.lgHostConnected, false, memory_order_release);
DEBUG_INFO("Waiting for the host to restart...");
app_setState(APP_STATE_RESTART);
primaryLost();
break;
}
lgMessage_process();
@@ -3491,10 +3543,12 @@ restart:
lgClipboard_dropTransport();
g_state.transport.ops->disconnect(g_state.transport.handle);
app_setState(APP_STATE_RUNNING);
if (app_transitionState(APP_STATE_RESTART, APP_STATE_RUNNING))
{
lgInit();
goto restart;
}
}
return recoveryExit(&recoveryPrompt, 0);
}

View File

@@ -34,16 +34,10 @@
#include "common/ll.h"
#include "cimgui.h"
#include "app.h"
#include "interface/transport.h"
#include "transport_fallback.h"
enum RunState
{
APP_STATE_RUNNING,
APP_STATE_RESTART,
APP_STATE_SHUTDOWN
};
enum MicDefaultState {
MIC_DEFAULT_PROMPT,
MIC_DEFAULT_ALLOW,
@@ -112,6 +106,7 @@ struct AppState
bool videoGeometryDirty;
atomic_bool fallbackUUIDMismatch;
atomic_uint transportLost;
uint8_t guestUUID[16];
bool guestUUIDValid;
@@ -359,18 +354,6 @@ extern struct AppState g_state;
extern struct CursorState g_cursor;
extern struct AppParams g_params;
static inline enum RunState app_getState(void)
{
extern _Atomic(enum RunState) p_appState;
return atomic_load_explicit(&p_appState, memory_order_acquire);
}
static inline void app_setState(enum RunState state)
{
extern _Atomic(enum RunState) p_appState;
atomic_store_explicit(&p_appState, state, memory_order_release);
}
int main_cursorThread(void * unused);
int main_frameThread(void * unused);

View File

@@ -164,6 +164,16 @@ static void notifyDisconnected(
LG_UNLOCK(fallback->eventLock);
}
static void notifyLost(LG_TransportFallback * fallback, bool requested)
{
if (!requested || !fallback->eventOps.lost)
return;
LG_LOCK(fallback->eventLock);
fallback->eventOps.lost(fallback->eventOpaque);
LG_UNLOCK(fallback->eventLock);
}
static void unpublishProviders(LG_TransportFallback * fallback, bool live)
{
LG_LOCK(fallback->providerLock);
@@ -420,9 +430,12 @@ static bool connectFallback(LG_TransportFallback * fallback)
notifyConnected(fallback);
bool lost = false;
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
{
lgWaitEvent(fallback->wakeEvent, SESSION_POLL_MS);
if (atomic_load_explicit(&fallback->stop, memory_order_acquire))
break;
applyVideoRequest(fallback);
LG_LOCK_EXCLUSIVE(fallback->lock);
@@ -436,16 +449,21 @@ static bool connectFallback(LG_TransportFallback * fallback)
if (reject || !admitted || closing)
break;
if (!sessionLive(fallback))
{
lost = !atomic_load_explicit(
&fallback->stop, memory_order_acquire);
break;
}
}
const bool knownDead = !atomic_load_explicit(
&fallback->stop, memory_order_acquire) && !sessionLive(fallback);
const bool reportDisconnect = cleanupConnection(fallback, knownDead);
const bool reportDisconnect = cleanupConnection(fallback, lost);
if (reportMismatch && fallback->eventOps.uuidMismatch)
fallback->eventOps.uuidMismatch(
fallback->eventOpaque, primaryUUID, fallbackUUID);
const bool reportLost = lost && reportDisconnect && !atomic_load_explicit(
&fallback->stop, memory_order_acquire);
notifyLost(fallback, reportLost);
notifyDisconnected(fallback, reportDisconnect);
return true;
}

View File

@@ -32,6 +32,8 @@ typedef struct LG_TransportFallbackEventOps
{
/* The session is valid only for the duration of this callback. */
void (*connected)(void * opaque, const LG_TransportSession * session);
/* Called only when an admitted session is unexpectedly lost. */
void (*lost)(void * opaque);
void (*disconnected)(void * opaque);
void (*uuidMismatch)(void * opaque, const uint8_t primary[16],
const uint8_t fallback[16]);

View File

@@ -81,6 +81,16 @@ struct CursorState g_cursor;
struct AppParams g_params;
_Atomic(enum RunState) p_appState;
enum RunState app_getState(void)
{
return atomic_load(&p_appState);
}
void app_setState(enum RunState state)
{
atomic_store(&p_appState, state);
}
static const int fKeys[] =
{
KEY_F1 , KEY_F2 , KEY_F3 , KEY_F4,