fix(ha): stop startup when the quick connect store is unreachable #37
Reference in New Issue
Block a user
Delete Branch "benvin/quickconnect-startup-fail-closed"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Makes quick connect's fail-closed guarantee real, and visible to the orchestrator.
InitializeServicesbuilds and reads the store before anything is served, both inside the retry:abortConnect=falsefails on the read, its default on the build.Criticalwith the real elapsed and stops startup.Error, notCompleted.DOTNET_RUNNING_IN_CONTAINERis set.docs/FORK-DIFF.mdrecords the precedence between the components sharing the connection.Tests (
RequiresDocker): down past the deadline stops startup on either connect shape; back inside it starts; the process exits 1.Emby.Server.Implementations/ApplicationHost.cs:653— one-shot read: a single momentary valkey blip at exactly this instant kills the pod, where an already-running pod shrugs the same blip off at the cost of quick connect only. During a rollout that turns a sub-second hiccup into every replica failing to come up -> retry the read a few times over a bounded window before giving up.Jellyfin.Server/Program.cs:255-268— the failure path this PR now routes into logs critical, sleeps 10 minutes serving 503, then returns without settingEnvironment.ExitCode, so the container exits 0 and k8s reportsCompletedrather than a crash. "The outage is visible where the server is started" does not hold for the orchestrator -> set a non-zero exit code and skip the 10-minute wait for this cause.Emby.Server.Implementations/MediaEncoding/TranscodeStoreConnectivityProbe.cs:37,47— "connecting must not be able to abort startup" and the swallow-and-continue branch are now unreachable: it is anIHostedService, so it first runs atProgram.cs:228, afterApplicationHost.cs:653has already aborted on the same multiplexer and the same connection string.RedisScanLeaderLease.cs:75-78fails open on the same dependency too -> reconcile the three policies or drop the claims that are no longer true.tests/Jellyfin.Server.Tests/QuickConnect/QuickConnectStartupTests.cs:188-200— whenInitializeServicesthrows, the host built at :188 is never returned and never disposed, leaving a liveConnectionMultiplexerreconnecting for the rest of the run; that is the pathUnreachableStore_StopsStartuptakes -> dispose it on the throwing path.tests/Jellyfin.Server.Tests/QuickConnect/QuickConnectStartupTests.cs:150—JELLYFIN_FFMPEG__NOVALIDATIONis set process-wide and never cleared, so it leaks into every later test in the assembly -> clear it inDisposeAsyncalongside the connection string.Emby.Server.Implementations/ApplicationHost.cs:681—Resolve<IQuickConnectStore>()is outside the retried region and resolving it eagerly runsConnectionMultiplexer.Connect, so a connection string that omitsabortConnect=falsethrows here: no retry, and the Critical message that names the key and the remedy never fires.deploy/helm/jellyfin-ha/values.yaml:39-40documents exactly that shape (redis:6379,redis:6379,password=secret), andUnreachableRedisAtStartup_FailsClosedpins the eager throw -> move the resolve inside the loop’stryso both the retry and the Critical message cover it.Emby.Server.Implementations/ApplicationHost.cs:702—{Seconds}is the configured deadline constant, so the message always reads "after 30s" no matter how long the loop ran; the final attempt’s connect/sync timeout sits on top of it -> logStopwatch.GetElapsedTime(startTimestamp).Jellyfin.Server/Program.cs:270—options.StartupMode is null or Configuration.StartupMode.MediaServer && !IsRunningInContainer()reads as though&&were part of the pattern -> parenthesise theisexpression.Emby.Server.Implementations/ApplicationHost.cs:695— each attempt also logs an Error fromRedisQuickConnectStore.CallAsync, so a dead store emits ~30 Error plus ~29 Warning lines before the one line that matters -> drop the per-attempt warning or move it to Debug.