fail quick connect closed on an unreachable valkey and restore the idempotent exchange
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
2026-09-26 14:28:45 +10:00
parent 6c54a02240
commit e1ca272d6c
17 changed files with 664 additions and 245 deletions
@@ -9,9 +9,9 @@ using MediaBrowser.Model.QuickConnect;
namespace MediaBrowser.Controller.QuickConnect;
/// <summary>
/// A process-local <see cref="IQuickConnectStore"/>. It is the single-instance default, and the
/// fallback a shared store degrades to while its backend is unreachable, so quick connect keeps
/// working for clients whose three legs happen to land on one instance.
/// A process-local <see cref="IQuickConnectStore"/>, and the single-instance default. Nothing it holds
/// is visible to another instance, so a deployment running more than one has to configure a shared
/// store instead.
/// </summary>
public sealed class InMemoryQuickConnectStore : IQuickConnectStore
{
@@ -41,7 +41,11 @@ public sealed class InMemoryQuickConnectStore : IQuickConnectStore
ArgumentNullException.ThrowIfNull(request);
Expire();
_requests[request.Secret] = new Entry<QuickConnectResult>(expiresUtc, request);
if (expiresUtc > DateTime.UtcNow)
{
_requests[request.Secret] = new Entry<QuickConnectResult>(expiresUtc, request);
}
return Task.CompletedTask;
}
@@ -61,20 +65,19 @@ public sealed class InMemoryQuickConnectStore : IQuickConnectStore
public Task SetAuthorizationAsync(string secret, AuthenticationResult authenticationResult, DateTime expiresUtc, CancellationToken cancellationToken = default)
{
Expire();
_authorizations[secret] = new Entry<AuthenticationResult>(expiresUtc, authenticationResult);
if (expiresUtc > DateTime.UtcNow)
{
_authorizations[secret] = new Entry<AuthenticationResult>(expiresUtc, authenticationResult);
}
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<AuthenticationResult?> TryConsumeAuthorizationAsync(string secret, CancellationToken cancellationToken = default)
public Task<AuthenticationResult?> GetAuthorizationAsync(string secret, CancellationToken cancellationToken = default)
{
Expire();
if (!_authorizations.TryRemove(secret, out var entry) || entry.ExpiresUtc <= DateTime.UtcNow)
{
return Task.FromResult<AuthenticationResult?>(null);
}
return Task.FromResult<AuthenticationResult?>(entry.Value);
return Task.FromResult(_authorizations.TryGetValue(secret, out var entry) ? entry.Value : null);
}
private void Expire()