From 1d8c3e088be75ce02b811afaeea20307df0487be Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 Sep 2023 09:50:29 -0400 Subject: [PATCH 1/5] Don't log unhandled exceptions twice --- Jellyfin.Server/Program.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 6e8b17a737..3a3dd97bd3 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -89,12 +89,6 @@ namespace Jellyfin.Server private static async Task StartApp(StartupOptions options) { _startTimestamp = Stopwatch.GetTimestamp(); - - // Log all uncaught exceptions to std error - static void UnhandledExceptionToConsole(object sender, UnhandledExceptionEventArgs e) => - Console.Error.WriteLine("Unhandled Exception\n" + e.ExceptionObject); - AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionToConsole; - ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options); // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager @@ -112,8 +106,7 @@ namespace Jellyfin.Server StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths); _logger = _loggerFactory.CreateLogger("Main"); - // Log uncaught exceptions to the logging instead of std error - AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole; + // Use the logging framework for uncaught exceptions instead of std error AppDomain.CurrentDomain.UnhandledException += (_, e) => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception"); From 493de3297a415061f8d6a69ff9f62261c3159a2a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 Sep 2023 21:10:49 -0400 Subject: [PATCH 2/5] Use IHostLifetime to handle restarting and shutting down --- .../ApplicationHost.cs | 124 ++---------------- .../Plugins/PluginManager.cs | 22 ++-- .../Session/SessionManager.cs | 117 ++++++++--------- Jellyfin.Api/Controllers/SystemController.cs | 14 +- Jellyfin.Server/CoreAppHost.cs | 6 - Jellyfin.Server/Program.cs | 70 +--------- MediaBrowser.Common/IApplicationHost.cs | 21 +-- MediaBrowser.Common/Plugins/IPluginManager.cs | 5 - .../Session/ISessionManager.cs | 14 -- .../JellyfinApplicationFactory.cs | 3 +- 10 files changed, 98 insertions(+), 298 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8b13ccadab..86721ace61 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -12,7 +12,6 @@ using System.Linq; using System.Net; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; using Emby.Dlna; using Emby.Dlna.Main; @@ -112,7 +111,7 @@ namespace Emby.Server.Implementations /// /// Class CompositionRoot. /// - public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable + public abstract class ApplicationHost : IServerApplicationHost, IDisposable { /// /// The disposable parts. @@ -127,7 +126,6 @@ namespace Emby.Server.Implementations private readonly IPluginManager _pluginManager; private List _creatingInstances; - private ISessionManager _sessionManager; /// /// Gets or sets all concrete types. @@ -172,6 +170,8 @@ namespace Emby.Server.Implementations ConfigurationManager.Configuration, ApplicationPaths.PluginsPath, ApplicationVersion); + + _disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue); } /// @@ -202,7 +202,10 @@ namespace Emby.Server.Implementations public bool HasPendingRestart { get; private set; } /// - public bool IsShuttingDown { get; private set; } + public bool IsShuttingDown { get; set; } + + /// + public bool ShouldRestart { get; set; } /// /// Gets the logger. @@ -406,11 +409,9 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// - /// The cancellation token. /// . - public async Task RunStartupTasksAsync(CancellationToken cancellationToken) + public async Task RunStartupTasksAsync() { - cancellationToken.ThrowIfCancellationRequested(); Logger.LogInformation("Running startup tasks"); Resolve().AddTasks(GetExports(false)); @@ -424,8 +425,6 @@ namespace Emby.Server.Implementations var entryPoints = GetExports(); - cancellationToken.ThrowIfCancellationRequested(); - var stopWatch = new Stopwatch(); stopWatch.Start(); @@ -435,8 +434,6 @@ namespace Emby.Server.Implementations Logger.LogInformation("Core startup complete"); CoreStartupHasCompleted = true; - cancellationToken.ThrowIfCancellationRequested(); - stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); @@ -633,8 +630,6 @@ namespace Emby.Server.Implementations var localizationManager = (LocalizationManager)Resolve(); await localizationManager.LoadAll().ConfigureAwait(false); - _sessionManager = Resolve(); - SetStaticProperties(); FindParts(); @@ -855,38 +850,6 @@ namespace Emby.Server.Implementations } } - /// - /// Restarts this instance. - /// - public void Restart() - { - if (IsShuttingDown) - { - return; - } - - IsShuttingDown = true; - _pluginManager.UnloadAssemblies(); - - Task.Run(async () => - { - try - { - await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error sending server restart notification"); - } - - Logger.LogInformation("Calling RestartInternal"); - - RestartInternal(); - }); - } - - protected abstract void RestartInternal(); - /// /// Gets the composable part assemblies. /// @@ -1065,30 +1028,6 @@ namespace Emby.Server.Implementations }.ToString().TrimEnd('/'); } - /// - public async Task Shutdown() - { - if (IsShuttingDown) - { - return; - } - - IsShuttingDown = true; - - try - { - await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error sending server shutdown notification"); - } - - ShutdownInternal(); - } - - protected abstract void ShutdownInternal(); - public IEnumerable GetApiPluginAssemblies() { var assemblies = _allConcreteTypes @@ -1152,52 +1091,5 @@ namespace Emby.Server.Implementations _disposed = true; } - - public async ValueTask DisposeAsync() - { - await DisposeAsyncCore().ConfigureAwait(false); - Dispose(false); - GC.SuppressFinalize(this); - } - - /// - /// Used to perform asynchronous cleanup of managed resources or for cascading calls to . - /// - /// A ValueTask. - protected virtual async ValueTask DisposeAsyncCore() - { - var type = GetType(); - - Logger.LogInformation("Disposing {Type}", type.Name); - - foreach (var (part, _) in _disposableParts) - { - var partType = part.GetType(); - if (partType == type) - { - continue; - } - - Logger.LogInformation("Disposing {Type}", partType.Name); - - try - { - part.Dispose(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error disposing {Type}", partType.Name); - } - } - - if (_sessionManager is not null) - { - // used for closing websockets - foreach (var session in _sessionManager.Sessions) - { - await session.DisposeAsync().ConfigureAwait(false); - } - } - } } } diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 1303012e1a..d7189ef0ca 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.IO; using System.Linq; @@ -11,7 +10,6 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common; @@ -30,7 +28,7 @@ namespace Emby.Server.Implementations.Plugins /// /// Defines the . /// - public class PluginManager : IPluginManager + public sealed class PluginManager : IPluginManager, IDisposable { private const string MetafileName = "meta.json"; @@ -191,15 +189,6 @@ namespace Emby.Server.Implementations.Plugins } } - /// - public void UnloadAssemblies() - { - foreach (var assemblyLoadContext in _assemblyLoadContexts) - { - assemblyLoadContext.Unload(); - } - } - /// /// Creates all the plugin instances. /// @@ -441,6 +430,15 @@ namespace Emby.Server.Implementations.Plugins return SaveManifest(manifest, path); } + /// + public void Dispose() + { + foreach (var assemblyLoadContext in _assemblyLoadContexts) + { + assemblyLoadContext.Unload(); + } + } + /// /// Reconciles the manifest against any properties that exist locally in a pre-packaged meta.json found at the path. /// If no file is found, no reconciliation occurs. diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b4a622ccf4..902d46a906 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -36,6 +36,7 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; @@ -44,7 +45,7 @@ namespace Emby.Server.Implementations.Session /// /// Class SessionManager. /// - public class SessionManager : ISessionManager, IDisposable + public sealed class SessionManager : ISessionManager, IAsyncDisposable { private readonly IUserDataManager _userDataManager; private readonly ILogger _logger; @@ -57,11 +58,9 @@ namespace Emby.Server.Implementations.Session private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerApplicationHost _appHost; private readonly IDeviceManager _deviceManager; - - /// - /// The active connections. - /// - private readonly ConcurrentDictionary _activeConnections = new(StringComparer.OrdinalIgnoreCase); + private readonly CancellationTokenRegistration _shutdownCallback; + private readonly ConcurrentDictionary _activeConnections + = new(StringComparer.OrdinalIgnoreCase); private Timer _idleTimer; @@ -79,7 +78,8 @@ namespace Emby.Server.Implementations.Session IImageProcessor imageProcessor, IServerApplicationHost appHost, IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager) + IMediaSourceManager mediaSourceManager, + IHostApplicationLifetime hostApplicationLifetime) { _logger = logger; _eventManager = eventManager; @@ -92,6 +92,7 @@ namespace Emby.Server.Implementations.Session _appHost = appHost; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; + _shutdownCallback = hostApplicationLifetime.ApplicationStopping.Register(OnApplicationStopping); _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } @@ -151,36 +152,6 @@ namespace Emby.Server.Implementations.Session } } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _idleTimer?.Dispose(); - } - - _idleTimer = null; - - _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; - - _disposed = true; - } - private void CheckDisposed() { if (_disposed) @@ -1330,32 +1301,6 @@ namespace Emby.Server.Implementations.Session return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken); } - /// - /// Sends the server shutdown notification. - /// - /// The cancellation token. - /// Task. - public Task SendServerShutdownNotification(CancellationToken cancellationToken) - { - CheckDisposed(); - - return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); - } - - /// - /// Sends the server restart notification. - /// - /// The cancellation token. - /// Task. - public Task SendServerRestartNotification(CancellationToken cancellationToken) - { - CheckDisposed(); - - _logger.LogDebug("Beginning SendServerRestartNotification"); - - return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken); - } - /// /// Adds the additional user. /// @@ -1833,5 +1778,51 @@ namespace Emby.Server.Implementations.Session return SendMessageToSessions(sessions, name, data, cancellationToken); } + + /// + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + foreach (var session in _activeConnections.Values) + { + await session.DisposeAsync().ConfigureAwait(false); + } + + if (_idleTimer is not null) + { + await _idleTimer.DisposeAsync().ConfigureAwait(false); + _idleTimer = null; + } + + await _shutdownCallback.DisposeAsync().ConfigureAwait(false); + + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; + _disposed = true; + } + + private async void OnApplicationStopping() + { + _logger.LogInformation("Sending shutdown notifications"); + try + { + var messageType = _appHost.ShouldRestart ? SessionMessageType.ServerRestarting : SessionMessageType.ServerShuttingDown; + + await SendMessageToSessions(Sessions, messageType, string.Empty, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending server shutdown notifications"); + } + + // Close open websockets to allow Kestrel to shut down cleanly + foreach (var session in _activeConnections.Values) + { + await session.DisposeAsync().ConfigureAwait(false); + } + } } } diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index a29790961e..4cc0f0ced4 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers; @@ -32,6 +33,7 @@ public class SystemController : BaseJellyfinApiController private readonly IFileSystem _fileSystem; private readonly INetworkManager _network; private readonly ILogger _logger; + private readonly IHostApplicationLifetime _hostApplicationLifetime; /// /// Initializes a new instance of the class. @@ -41,18 +43,21 @@ public class SystemController : BaseJellyfinApiController /// Instance of interface. /// Instance of interface. /// Instance of interface. + /// Instance of interface. public SystemController( IServerConfigurationManager serverConfigurationManager, IServerApplicationHost appHost, IFileSystem fileSystem, INetworkManager network, - ILogger logger) + ILogger logger, + IHostApplicationLifetime hostApplicationLifetime) { _appPaths = serverConfigurationManager.ApplicationPaths; _appHost = appHost; _fileSystem = fileSystem; _network = network; _logger = logger; + _hostApplicationLifetime = hostApplicationLifetime; } /// @@ -110,7 +115,9 @@ public class SystemController : BaseJellyfinApiController Task.Run(async () => { await Task.Delay(100).ConfigureAwait(false); - _appHost.Restart(); + _appHost.ShouldRestart = true; + _appHost.IsShuttingDown = true; + _hostApplicationLifetime.StopApplication(); }); return NoContent(); } @@ -130,7 +137,8 @@ public class SystemController : BaseJellyfinApiController Task.Run(async () => { await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); + _appHost.IsShuttingDown = true; + _hostApplicationLifetime.StopApplication(); }); return NoContent(); } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 0c6315c667..4c116745b8 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -102,9 +102,6 @@ namespace Jellyfin.Server base.RegisterServices(serviceCollection); } - /// - protected override void RestartInternal() => Program.Restart(); - /// protected override IEnumerable GetAssembliesWithPartsInternal() { @@ -114,8 +111,5 @@ namespace Jellyfin.Server // Jellyfin.Server.Implementations yield return typeof(JellyfinDbContext).Assembly; } - - /// - protected override void ShutdownInternal() => Program.Shutdown(); } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 3a3dd97bd3..f9259d0d92 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; @@ -42,7 +41,6 @@ namespace Jellyfin.Server public const string LoggingConfigFileSystem = "logging.json"; private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); - private static CancellationTokenSource _tokenSource = new(); private static long _startTimestamp; private static ILogger _logger = NullLogger.Instance; private static bool _restartOnShutdown; @@ -65,27 +63,6 @@ namespace Jellyfin.Server .MapResult(StartApp, ErrorParsingArguments); } - /// - /// Shuts down the application. - /// - internal static void Shutdown() - { - if (!_tokenSource.IsCancellationRequested) - { - _tokenSource.Cancel(); - } - } - - /// - /// Restarts the application. - /// - internal static void Restart() - { - _restartOnShutdown = true; - - Shutdown(); - } - private static async Task StartApp(StartupOptions options) { _startTimestamp = Stopwatch.GetTimestamp(); @@ -110,33 +87,6 @@ namespace Jellyfin.Server AppDomain.CurrentDomain.UnhandledException += (_, e) => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception"); - // Intercept Ctrl+C and Ctrl+Break - Console.CancelKeyPress += (_, e) => - { - if (_tokenSource.IsCancellationRequested) - { - return; // Already shutting down - } - - e.Cancel = true; - _logger.LogInformation("Ctrl+C, shutting down"); - Environment.ExitCode = 128 + 2; - Shutdown(); - }; - - // Register a SIGTERM handler - AppDomain.CurrentDomain.ProcessExit += (_, _) => - { - if (_tokenSource.IsCancellationRequested) - { - return; // Already shutting down - } - - _logger.LogInformation("Received a SIGTERM signal, shutting down"); - Environment.ExitCode = 128 + 15; - Shutdown(); - }; - _logger.LogInformation( "Jellyfin version: {Version}", Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3)); @@ -166,12 +116,10 @@ namespace Jellyfin.Server do { - _restartOnShutdown = false; await StartServer(appPaths, options, startupConfig).ConfigureAwait(false); if (_restartOnShutdown) { - _tokenSource = new CancellationTokenSource(); _startTimestamp = Stopwatch.GetTimestamp(); } } while (_restartOnShutdown); @@ -179,7 +127,7 @@ namespace Jellyfin.Server private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration startupConfig) { - var appHost = new CoreAppHost( + using var appHost = new CoreAppHost( appPaths, _loggerFactory, options, @@ -189,6 +137,7 @@ namespace Jellyfin.Server try { host = Host.CreateDefaultBuilder() + .UseConsoleLifetime() .ConfigureServices(services => appHost.Init(services)) .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger)) .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConfig)) @@ -203,7 +152,7 @@ namespace Jellyfin.Server try { - await host.StartAsync(_tokenSource.Token).ConfigureAwait(false); + await host.StartAsync().ConfigureAwait(false); if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket()) { @@ -212,22 +161,18 @@ namespace Jellyfin.Server StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger); } } - catch (Exception ex) when (ex is not TaskCanceledException) + catch (Exception) { _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again"); throw; } - await appHost.RunStartupTasksAsync(_tokenSource.Token).ConfigureAwait(false); + await appHost.RunStartupTasksAsync().ConfigureAwait(false); _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp)); - // Block main thread until shutdown - await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Don't throw on cancellation + await host.WaitForShutdownAsync().ConfigureAwait(false); + _restartOnShutdown = appHost.ShouldRestart; } catch (Exception ex) { @@ -250,7 +195,6 @@ namespace Jellyfin.Server } } - await appHost.DisposeAsync().ConfigureAwait(false); host?.Dispose(); } } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 96ee701b38..c1ea6e87cc 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common @@ -42,10 +41,15 @@ namespace MediaBrowser.Common bool HasPendingRestart { get; } /// - /// Gets a value indicating whether this instance is currently shutting down. + /// Gets or sets a value indicating whether this instance is currently shutting down. /// /// true if this instance is shutting down; otherwise, false. - bool IsShuttingDown { get; } + bool IsShuttingDown { get; set; } + + /// + /// Gets or sets a value indicating whether the application should restart. + /// + bool ShouldRestart { get; set; } /// /// Gets the application version. @@ -87,11 +91,6 @@ namespace MediaBrowser.Common /// void NotifyPendingRestart(); - /// - /// Restarts this instance. - /// - void Restart(); - /// /// Gets the exports. /// @@ -123,12 +122,6 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); - /// - /// Shuts down. - /// - /// A task. - Task Shutdown(); - /// /// Initializes this instance. /// diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 1d73de3c95..0ff9719e98 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -29,11 +29,6 @@ namespace MediaBrowser.Common.Plugins /// An IEnumerable{Assembly}. IEnumerable LoadAssemblies(); - /// - /// Unloads all of the assemblies. - /// - void UnloadAssemblies(); - /// /// Registers the plugin's services with the DI. /// Note: DI is not yet instantiated yet. diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 0c4719a0e5..53df7133b5 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -232,20 +232,6 @@ namespace MediaBrowser.Controller.Session /// Task. Task SendRestartRequiredNotification(CancellationToken cancellationToken); - /// - /// Sends the server shutdown notification. - /// - /// The cancellation token. - /// Task. - Task SendServerShutdownNotification(CancellationToken cancellationToken); - - /// - /// Sends the server restart notification. - /// - /// The cancellation token. - /// Task. - Task SendServerRestartNotification(CancellationToken cancellationToken); - /// /// Adds the additional user. /// diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 55bc43455f..1c87d11f18 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Concurrent; using System.Globalization; using System.IO; -using System.Threading; using Emby.Server.Implementations; using Jellyfin.Server.Extensions; using Jellyfin.Server.Helpers; @@ -105,7 +104,7 @@ namespace Jellyfin.Server.Integration.Tests var appHost = (TestAppHost)testServer.Services.GetRequiredService(); appHost.ServiceProvider = testServer.Services; appHost.InitializeServices().GetAwaiter().GetResult(); - appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult(); + appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); return testServer; } From 59ec06c35c3b958a1778a56334bdf91a2f0ccf3f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 29 Sep 2023 12:43:49 -0400 Subject: [PATCH 3/5] Clear active sessions on application stopping --- Emby.Server.Implementations/Session/SessionManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 902d46a906..50d3e8e46d 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1823,6 +1823,8 @@ namespace Emby.Server.Implementations.Session { await session.DisposeAsync().ConfigureAwait(false); } + + _activeConnections.Clear(); } } } From f746db9a54caef36bb3b32cf22812a78d97c865d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 2 Oct 2023 15:55:26 -0400 Subject: [PATCH 4/5] Re-add shutdown/restart methods --- .../ApplicationHost.cs | 19 +++++++++++++++ Jellyfin.Api/Controllers/SystemController.cs | 23 +++---------------- MediaBrowser.Common/IApplicationHost.cs | 18 +++++++++++---- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 86721ace61..e2d2719e5d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -101,6 +101,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; @@ -850,6 +851,24 @@ namespace Emby.Server.Implementations } } + /// + public void Restart() + { + ShouldRestart = true; + Shutdown(); + } + + /// + public void Shutdown() + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + IsShuttingDown = true; + Resolve().StopApplication(); + }); + } + /// /// Gets the composable part assemblies. /// diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 4cc0f0ced4..42ac4a9b4b 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net.Mime; -using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Common.Configuration; @@ -18,7 +17,6 @@ using MediaBrowser.Model.System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers; @@ -33,7 +31,6 @@ public class SystemController : BaseJellyfinApiController private readonly IFileSystem _fileSystem; private readonly INetworkManager _network; private readonly ILogger _logger; - private readonly IHostApplicationLifetime _hostApplicationLifetime; /// /// Initializes a new instance of the class. @@ -43,21 +40,18 @@ public class SystemController : BaseJellyfinApiController /// Instance of interface. /// Instance of interface. /// Instance of interface. - /// Instance of interface. public SystemController( IServerConfigurationManager serverConfigurationManager, IServerApplicationHost appHost, IFileSystem fileSystem, INetworkManager network, - ILogger logger, - IHostApplicationLifetime hostApplicationLifetime) + ILogger logger) { _appPaths = serverConfigurationManager.ApplicationPaths; _appHost = appHost; _fileSystem = fileSystem; _network = network; _logger = logger; - _hostApplicationLifetime = hostApplicationLifetime; } /// @@ -112,13 +106,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult RestartApplication() { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - _appHost.ShouldRestart = true; - _appHost.IsShuttingDown = true; - _hostApplicationLifetime.StopApplication(); - }); + _appHost.Restart(); return NoContent(); } @@ -134,12 +122,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult ShutdownApplication() { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - _appHost.IsShuttingDown = true; - _hostApplicationLifetime.StopApplication(); - }); + _appHost.Shutdown(); return NoContent(); } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index c1ea6e87cc..5985d3dd82 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -41,15 +41,15 @@ namespace MediaBrowser.Common bool HasPendingRestart { get; } /// - /// Gets or sets a value indicating whether this instance is currently shutting down. + /// Gets a value indicating whether this instance is currently shutting down. /// /// true if this instance is shutting down; otherwise, false. - bool IsShuttingDown { get; set; } + bool IsShuttingDown { get; } /// - /// Gets or sets a value indicating whether the application should restart. + /// Gets a value indicating whether the application should restart. /// - bool ShouldRestart { get; set; } + bool ShouldRestart { get; } /// /// Gets the application version. @@ -91,6 +91,11 @@ namespace MediaBrowser.Common /// void NotifyPendingRestart(); + /// + /// Restarts this instance. + /// + void Restart(); + /// /// Gets the exports. /// @@ -122,6 +127,11 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); + /// + /// Shuts down. + /// + void Shutdown(); + /// /// Initializes this instance. /// From ab0790271aac8846430c9689dff647eb4ce55078 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 2 Oct 2023 16:54:19 -0400 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Claus Vium --- Emby.Server.Implementations/ApplicationHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e2d2719e5d..a8b8e5fb94 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -203,10 +203,10 @@ namespace Emby.Server.Implementations public bool HasPendingRestart { get; private set; } /// - public bool IsShuttingDown { get; set; } + public bool IsShuttingDown { get; private set; } /// - public bool ShouldRestart { get; set; } + public bool ShouldRestart { get; private set; } /// /// Gets the logger.