Files
jellyfin-ha-src/Jellyfin.Server/Extensions/ConfigurationInvalidationServiceCollectionExtensions.cs
T
unkin-agent b662ffa48f
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
propagate shared-config and library-option changes between instances
Add a Redis pub/sub invalidation bus, publish from the configuration and library-option write paths, and drop the matching local cache entry on the instances that did not write. No-op without a Redis connection string, and fails open when it is unreachable.
2026-09-20 23:47:21 +10:00

75 lines
3.4 KiB
C#

using System;
using Emby.Server.Implementations.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
namespace Jellyfin.Server.Extensions;
/// <summary>
/// Extensions for registering the shared-configuration invalidation bus.
/// </summary>
public static class ConfigurationInvalidationServiceCollectionExtensions
{
/// <summary>
/// Registers the invalidation bus, Redis-backed when a connection string is configured and no-op
/// otherwise, and the subscriber applying the notices other instances publish.
/// </summary>
/// <remarks>
/// The connection string is only set for a multi-instance deployment, which is the only shape where
/// one instance can write the shared configuration directory behind another's back.
/// </remarks>
/// <param name="serviceCollection">The service collection.</param>
/// <param name="configuration">The configuration to read the Redis connection string from.</param>
/// <param name="logger">The logger to report the selected bus on.</param>
/// <returns>The updated service collection.</returns>
public static IServiceCollection AddConfigurationInvalidationBus(
this IServiceCollection serviceCollection,
IConfiguration configuration,
ILogger logger)
{
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(logger);
if (string.IsNullOrEmpty(configuration[TranscodeStoreOptions.RedisConnectionStringKey]))
{
logger.LogInformation(
"Configuration invalidation bus: {Bus}. Shared-configuration and library-visibility changes stay local to the instance that made them; set {Key} to propagate them.",
nameof(NullConfigurationInvalidationBus),
TranscodeStoreOptions.RedisConnectionStringKey);
return serviceCollection.AddSingleton<IConfigurationInvalidationBus>(NullConfigurationInvalidationBus.Instance);
}
logger.LogInformation(
"Configuration invalidation bus: {Bus}. Shared-configuration and library-visibility changes propagate to every instance.",
nameof(RedisConfigurationInvalidationBus));
serviceCollection.AddSingleton<IConfigurationInvalidationBus>(sp =>
{
try
{
return new RedisConfigurationInvalidationBus(
sp.GetRequiredService<IConnectionMultiplexer>(),
sp.GetRequiredService<ILogger<RedisConfigurationInvalidationBus>>());
}
catch (Exception ex)
{
// Fail open: an unreachable Redis degrades to the single-instance behaviour of every
// instance keeping its own cached configuration, rather than aborting startup.
sp.GetRequiredService<ILogger<CoreAppHost>>().LogError(
ex,
"Redis is configured but unavailable, so shared-configuration changes will not propagate between instances. Check {Key}.",
TranscodeStoreOptions.RedisConnectionStringKey);
return NullConfigurationInvalidationBus.Instance;
}
});
return serviceCollection.AddHostedService<ConfigurationInvalidationSubscriber>();
}
}