b662ffa48f
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.
75 lines
3.4 KiB
C#
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>();
|
|
}
|
|
}
|