From 056be1242aabe62119faf4fa1de34fbd7070c1d6 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sun, 16 Aug 2026 22:29:20 +1000 Subject: [PATCH] [client] spice: add fail-fast connection option --- client/CMakeLists.txt | 2 +- .../src/{transport_fallback.c => fallback.c} | 37 ++++++++++++++----- client/src/main.c | 8 ++++ client/src/transport_fallback.h | 2 + client/tests/CMakeLists.txt | 2 +- client/transports/SPICE/session.c | 3 -- client/transports/SPICE/spice.c | 13 ++++++- client/transports/SPICE/spice.h | 1 + repos/PureSpice | 2 +- 9 files changed, 54 insertions(+), 16 deletions(-) rename client/src/{transport_fallback.c => fallback.c} (96%) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d37a3434..30bd6b85 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -189,7 +189,7 @@ set(SOURCES src/render_queue.c src/evdev.c src/transport.c - src/transport_fallback.c + src/fallback.c src/sw_surface.c src/input.c diff --git a/client/src/transport_fallback.c b/client/src/fallback.c similarity index 96% rename from client/src/transport_fallback.c rename to client/src/fallback.c index 82bc5c7f..037c0a90 100644 --- a/client/src/transport_fallback.c +++ b/client/src/fallback.c @@ -381,14 +381,16 @@ static bool publishConnection(LG_TransportFallback * fallback, return published; } -static bool connectFallback(LG_TransportFallback * fallback) +static LG_TransportStatus connectFallback(LG_TransportFallback * fallback, + bool retrying) { LG_TransportInstance transport = { 0 }; if (!lgTransport_create(fallback->transportName, &transport)) { - DEBUG_ERROR("Failed to create fallback transport %s", - fallback->transportName); - return false; + if (!retrying) + DEBUG_ERROR("Failed to create fallback transport %s", + fallback->transportName); + return LG_TRANSPORT_ERROR; } LG_LOCK_EXCLUSIVE(fallback->lock); @@ -401,11 +403,12 @@ static bool connectFallback(LG_TransportFallback * fallback) if (status != LG_TRANSPORT_OK) { - if (!atomic_load_explicit(&fallback->stop, memory_order_acquire)) + if (!retrying && !atomic_load_explicit( + &fallback->stop, memory_order_acquire)) DEBUG_ERROR("Fallback transport %s failed to connect: %d", fallback->transportName, status); cleanupConnection(fallback, false); - return false; + return status; } LG_LOCK_EXCLUSIVE(fallback->lock); @@ -445,7 +448,7 @@ static bool connectFallback(LG_TransportFallback * fallback) if (reportMismatch && fallback->eventOps.endpointMismatch) fallback->eventOps.endpointMismatch( fallback->eventOpaque, primaryUUID, fallbackUUID); - return false; + return LG_TRANSPORT_UNAVAILABLE; } notifyConnected(fallback); @@ -483,18 +486,34 @@ static bool connectFallback(LG_TransportFallback * fallback) fallback->eventOps.endpointMismatch( fallback->eventOpaque, primaryUUID, fallbackUUID); notifyDisconnected(fallback, reportDisconnect); - return true; + return lost ? LG_TRANSPORT_DISCONNECTED : LG_TRANSPORT_OK; } static int fallbackThread(void * opaque) { LG_TransportFallback * fallback = opaque; unsigned int retry = RETRY_INITIAL_MS; + bool retrying = false; while (!atomic_load_explicit(&fallback->stop, memory_order_acquire)) { - if (connectFallback(fallback)) + const LG_TransportStatus status = connectFallback(fallback, retrying); + if (status == LG_TRANSPORT_OK) + { retry = RETRY_INITIAL_MS; + retrying = false; + } + else if (status == LG_TRANSPORT_ERROR) + { + if (!atomic_load_explicit(&fallback->stop, memory_order_acquire) && + fallback->eventOps.connectFailed) + fallback->eventOps.connectFailed(fallback->eventOpaque); + break; + } + else if (status == LG_TRANSPORT_DISCONNECTED) + break; + else + retrying = true; if (atomic_load_explicit(&fallback->stop, memory_order_acquire)) break; diff --git a/client/src/main.c b/client/src/main.c index e26657f7..460399da 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -2610,6 +2610,13 @@ static void fallbackLost(void * opaque) app_setState(APP_STATE_SHUTDOWN); } +static void fallbackConnectFailed(void * opaque) +{ + (void)opaque; + DEBUG_INFO("SPICE fallback server is unavailable"); + app_setState(APP_STATE_SHUTDOWN); +} + static void fallbackEndpointMismatch(void * opaque, const uint8_t primary[16], const uint8_t fallback[16]) { @@ -2624,6 +2631,7 @@ static void fallbackEndpointMismatch(void * opaque, const uint8_t primary[16], static const LG_TransportFallbackEventOps fallbackEvents = { .connected = fallbackConnected, + .connectFailed = fallbackConnectFailed, .lost = fallbackLost, .disconnected = fallbackDisconnected, .videoStateChanged = fallbackVideoStateChanged, diff --git a/client/src/transport_fallback.h b/client/src/transport_fallback.h index 66382964..b924e355 100644 --- a/client/src/transport_fallback.h +++ b/client/src/transport_fallback.h @@ -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 when the fallback transport cannot establish a session. */ + void (*connectFailed)(void * opaque); /* Called only when a usable session is unexpectedly lost. */ void (*lost)(void * opaque); void (*disconnected)(void * opaque); diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 4cde3186..a9198e83 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -577,7 +577,7 @@ endforeach() add_executable(transport-fallback-tests transport_fallback_test.c - ../src/transport_fallback.c + ../src/fallback.c ../src/sw_surface.c ) target_include_directories(transport-fallback-tests PRIVATE diff --git a/client/transports/SPICE/session.c b/client/transports/SPICE/session.c index ce94655a..68c366e4 100644 --- a/client/transports/SPICE/session.c +++ b/client/transports/SPICE/session.c @@ -325,10 +325,7 @@ int spiceSession_thread(void * opaque) PSStatus status = PS_STATUS_SHUTDOWN; if (!purespice_connect(&config)) - { - DEBUG_ERROR("Failed to connect to SPICE server"); goto done; - } transport->connected = true; status = PS_STATUS_RUN; diff --git a/client/transports/SPICE/spice.c b/client/transports/SPICE/spice.c index 4310aae8..4602ea92 100644 --- a/client/transports/SPICE/spice.c +++ b/client/transports/SPICE/spice.c @@ -62,6 +62,13 @@ static void spiceSetup(void) .type = OPTION_TYPE_INT, .value.x_int = 5900 }, + { + .module = "spice", + .name = "waitForServer", + .description = "Wait for the SPICE server to become available", + .type = OPTION_TYPE_BOOL, + .value.x_bool = true + }, { .module = "spice", .name = "input", @@ -127,6 +134,7 @@ static bool spiceCreate(LG_Transport ** result) transport->usbAudioEnabled = option_get_bool("spice", "usbAudio"); transport->audioDebug = option_get_bool("audio", "debug"); + transport->waitForServer = option_get_bool("spice", "waitForServer"); #if !ENABLE_USB_AUDIO if (transport->audioEnabled && transport->usbAudioEnabled) @@ -265,9 +273,12 @@ static LG_TransportStatus spiceConnectCancellable(LG_Transport * transport, if (transport->connectStatus != LG_TRANSPORT_OK) { + const LG_TransportStatus status = transport->connectStatus; lgJoinThread(transport->thread, NULL); transport->thread = NULL; - return transport->connectStatus; + if (status == LG_TRANSPORT_ERROR && transport->waitForServer) + return LG_TRANSPORT_UNAVAILABLE; + return status; } *session = transport->session; diff --git a/client/transports/SPICE/spice.h b/client/transports/SPICE/spice.h index bd26087d..e6d9a4be 100644 --- a/client/transports/SPICE/spice.h +++ b/client/transports/SPICE/spice.h @@ -51,6 +51,7 @@ struct LG_Transport bool playbackEnabled; bool recordEnabled; bool audioDebug; + bool waitForServer; SpiceInput * input; SpiceClipboard * clipboard; diff --git a/repos/PureSpice b/repos/PureSpice index e324456a..13695607 160000 --- a/repos/PureSpice +++ b/repos/PureSpice @@ -1 +1 @@ -Subproject commit e324456ab5937d0b0aedc7ab3247566c8d5711f3 +Subproject commit 13695607dec73aded4f7a17f41911bb30b521f80