retry building the quick connect store in the startup probe, not just reading it
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

This commit is contained in:
2026-09-26 22:56:28 +10:00
parent 6c7f76fd26
commit 33a5fbce9b
4 changed files with 76 additions and 16 deletions
+33 -10
View File
@@ -52,6 +52,7 @@ using Jellyfin.Server.Implementations.SystemBackupService;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -670,29 +671,45 @@ namespace Emby.Server.Implementations
}
/// <summary>
/// Reads the quick connect store here so a store that cannot be reached stops startup, rather than
/// being discovered on the first request that needs it. A read rather than a resolve because a
/// shared store built with <c>abortConnect=false</c> constructs without touching the network, and
/// retried until <see cref="_quickConnectProbeDeadline"/> so a starting instance rides out the blip
/// a running one already tolerates.
/// Builds and reads the quick connect store here so a store that cannot be reached stops startup,
/// rather than being discovered on the first request that needs it. Both halves sit inside the
/// retry: a store built with <c>abortConnect=false</c> constructs without touching the network and
/// only the read settles it, while one built without that option connects eagerly and fails at the
/// resolve. Retried until <see cref="_quickConnectProbeDeadline"/> so a starting instance rides out
/// the blip a running one already tolerates.
/// </summary>
private async Task ProbeQuickConnectStoreAsync()
{
var store = Resolve<IQuickConnectStore>();
var startTimestamp = Stopwatch.GetTimestamp();
var reported = false;
while (true)
{
try
{
// A throwing singleton factory is not cached, so the resolve is retried along with the read.
var store = Resolve<IQuickConnectStore>();
await store.GetRequestBySecretAsync(StartupProbeSecret).ConfigureAwait(false);
return;
}
catch (Exception ex)
{
if (Stopwatch.GetElapsedTime(startTimestamp) + _quickConnectProbeRetryDelay < _quickConnectProbeDeadline)
var elapsed = Stopwatch.GetElapsedTime(startTimestamp);
if (elapsed + _quickConnectProbeRetryDelay < _quickConnectProbeDeadline)
{
Logger.LogWarning(ex, "Quick connect store is not reachable yet, retrying.");
if (!reported)
{
reported = true;
Logger.LogWarning(
ex,
"Quick connect store is not reachable yet, retrying for up to {Seconds}s.",
(int)_quickConnectProbeDeadline.TotalSeconds);
}
else
{
Logger.LogDebug(ex, "Quick connect store is still not reachable, retrying.");
}
await Task.Delay(_quickConnectProbeRetryDelay).ConfigureAwait(false);
continue;
}
@@ -701,8 +718,14 @@ namespace Emby.Server.Implementations
ex,
"Quick connect is configured against the shared valkey/Redis store at {Key} and it is UNREACHABLE after {Seconds}s, so the server will not start. Bring valkey up, or clear that setting to keep quick connect state on this instance alone.",
TranscodeStoreOptions.RedisConnectionStringKey,
(int)_quickConnectProbeDeadline.TotalSeconds);
throw;
(int)elapsed.TotalSeconds);
if (ex is ServiceUnavailableException)
{
throw;
}
throw new ServiceUnavailableException("Quick connect store is unreachable.", ex);
}
}
}