fix(ha): propagate shared-config and library-visibility changes between replicas #29

Merged
benvin merged 3 commits from benvin/config-propagation into main 2026-09-21 21:44:34 +10:00
Member

Every replica caches the system configuration, each named configuration and each library's options for the life of the process, with no invalidation path, so a change made on one pod is silently absent from the others. Disabling a library is the sharp edge: the stale pods keep serving it to every user until they restart.

  • add a configuration invalidation bus over the existing Redis connection, no-op when unset
  • publish from the configuration and library-option write paths
  • drop and reload the matching cache entry on the instances that did not write
  • mark the apply path, so a consumer that answers an update by writing neither repeats another instance's write nor publishes it back
  • fail open, so an unreachable bus leaves today's behaviour

Closes #15

Every replica caches the system configuration, each named configuration and each library's options for the life of the process, with no invalidation path, so a change made on one pod is silently absent from the others. Disabling a library is the sharp edge: the stale pods keep serving it to every user until they restart. - add a configuration invalidation bus over the existing Redis connection, no-op when unset - publish from the configuration and library-option write paths - drop and reload the matching cache entry on the instances that did not write - mark the apply path, so a consumer that answers an update by writing neither repeats another instance's write nor publishes it back - fail open, so an unreachable bus leaves today's behaviour Closes #15
unkin-agent added 1 commit 2026-09-20 23:47:32 +10:00
propagate shared-config and library-option changes between instances
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
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.
Author
Member

Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs:383 — applying a remote NamedConfiguration invalidation replays the same NamedConfigurationUpdated event a local admin save raises. RecordingsManager.OnNamedConfigurationUpdated (src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs:445) reacts to the "livetv" key by calling CreateRecordingFolders(), which can call AddVirtualFolderCollectionFolder.SaveLibraryOptions (Emby.Server.Implementations/Library/LibraryManager.cs:3782), which publishes a further invalidation → two replicas can independently create the same missing recording folder off one config change and re-publish about it. Give the subscriber a way to apply an invalidation without replaying the events write-path handlers use for their own side effects, or make CreateRecordingFolders/AddVirtualFolder race-safe against a concurrent peer reacting to the same change.

Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs:383 — applying a remote NamedConfiguration invalidation replays the same `NamedConfigurationUpdated` event a local admin save raises. RecordingsManager.OnNamedConfigurationUpdated (src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs:445) reacts to the "livetv" key by calling `CreateRecordingFolders()`, which can call `AddVirtualFolder` → `CollectionFolder.SaveLibraryOptions` (Emby.Server.Implementations/Library/LibraryManager.cs:3782), which publishes a further invalidation → two replicas can independently create the same missing recording folder off one config change and re-publish about it. Give the subscriber a way to apply an invalidation without replaying the events write-path handlers use for their own side effects, or make CreateRecordingFolders/AddVirtualFolder race-safe against a concurrent peer reacting to the same change.
unkin-agent added 1 commit 2026-09-21 00:24:08 +10:00
keep an applied invalidation from inducing a write that publishes back
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
a9d6c749fb
Author
Member
  • Emby.Server.Implementations/ApplicationHost.cs:808 — wrapping the whole if (HttpPort != 0 && HttpsPort != 0 && !IsApplyingRemoteInvalidation) block in the remote-apply guard also skips the port-mismatch check and NotifyPendingRestart() on replicas that did not originate the change. A replica receiving a remote network/port invalidation never compares its own bound HttpPort/HttpsPort against the refreshed config and never flags HasPendingRestart, so it keeps running on the stale port indefinitely — reintroduces the staleness this PR exists to remove, for exactly the consumer named in scope. Scope the guard to only the IsPortAuthorized/SaveConfiguration() write, not the mismatch detection and restart notification.
  • nit: MediaBrowser.Common/Configuration/IConfigurationManager.cs:94 — InvalidateCachedConfiguration is a new member on a public interface in a plugin-facing assembly with no default body; any third-party IConfigurationManager implementer fails to compile against the new contract.
- Emby.Server.Implementations/ApplicationHost.cs:808 — wrapping the whole `if (HttpPort != 0 && HttpsPort != 0 && !IsApplyingRemoteInvalidation)` block in the remote-apply guard also skips the port-mismatch check and `NotifyPendingRestart()` on replicas that did not originate the change. A replica receiving a remote network/port invalidation never compares its own bound `HttpPort`/`HttpsPort` against the refreshed config and never flags `HasPendingRestart`, so it keeps running on the stale port indefinitely — reintroduces the staleness this PR exists to remove, for exactly the consumer named in scope. Scope the guard to only the `IsPortAuthorized`/`SaveConfiguration()` write, not the mismatch detection and restart notification. - nit: MediaBrowser.Common/Configuration/IConfigurationManager.cs:94 — `InvalidateCachedConfiguration` is a new member on a public interface in a plugin-facing assembly with no default body; any third-party `IConfigurationManager` implementer fails to compile against the new contract.
unkin-agent added 1 commit 2026-09-21 00:46:27 +10:00
report a peer's port change locally without rewriting it
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
483c739fb1
Keep IConfigurationManager source-compatible for plugin implementers.
Author
Member

No findings.

No findings.
benvin merged commit 2adb13f50f into main 2026-09-21 21:44:34 +10:00
Sign in to join this conversation.