From 20fd05b05081ad387e94128b4f26d907808c8f0c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 17 Feb 2023 19:27:36 +0100 Subject: Consistently write IP in upercase --- Jellyfin.Api/Controllers/SystemController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Api/Controllers/SystemController.cs') diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 4ab705f40..91901518f 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -179,7 +179,7 @@ public class SystemController : BaseJellyfinApiController return new EndPointInfo { IsLocal = HttpContext.IsLocal(), - IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp()) + IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) }; } -- cgit v1.2.3 From 493de3297a415061f8d6a69ff9f62261c3159a2a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 Sep 2023 21:10:49 -0400 Subject: Use IHostLifetime to handle restarting and shutting down --- Emby.Server.Implementations/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 - MediaBrowser.Controller/Session/ISessionManager.cs | 14 --- .../JellyfinApplicationFactory.cs | 3 +- 10 files changed, 98 insertions(+), 298 deletions(-) (limited to 'Jellyfin.Api/Controllers/SystemController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8b13ccada..86721ace6 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 1303012e1..d7189ef0c 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 b4a622ccf..902d46a90 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 a29790961..4cc0f0ced 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 0c6315c66..4c116745b 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 3a3dd97bd..f9259d0d9 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 96ee701b3..c1ea6e87c 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 1d73de3c9..0ff9719e9 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 0c4719a0e..53df7133b 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 55bc43455..1c87d11f1 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; } -- cgit v1.2.3 From f746db9a54caef36bb3b32cf22812a78d97c865d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 2 Oct 2023 15:55:26 -0400 Subject: Re-add shutdown/restart methods --- Emby.Server.Implementations/ApplicationHost.cs | 19 +++++++++++++++++++ Jellyfin.Api/Controllers/SystemController.cs | 23 +++-------------------- MediaBrowser.Common/IApplicationHost.cs | 18 ++++++++++++++---- 3 files changed, 36 insertions(+), 24 deletions(-) (limited to 'Jellyfin.Api/Controllers/SystemController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 86721ace6..e2d2719e5 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 4cc0f0ced..42ac4a9b4 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 c1ea6e87c..5985d3dd8 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. /// -- cgit v1.2.3 From 6f7413812fb4654d1b1ca065a177a1bb19408386 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 4 Oct 2023 14:34:53 -0400 Subject: Add SystemManager service --- Emby.Server.Implementations/ApplicationHost.cs | 80 +--------------- Emby.Server.Implementations/SystemManager.cs | 108 ++++++++++++++++++++++ Jellyfin.Api/Controllers/SystemController.cs | 47 +++++----- MediaBrowser.Common/IApplicationHost.cs | 24 +---- MediaBrowser.Controller/IServerApplicationHost.cs | 12 --- MediaBrowser.Controller/ISystemManager.cs | 34 +++++++ 6 files changed, 173 insertions(+), 132 deletions(-) create mode 100644 Emby.Server.Implementations/SystemManager.cs create mode 100644 MediaBrowser.Controller/ISystemManager.cs (limited to 'Jellyfin.Api/Controllers/SystemController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8518b1352..3253ea026 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -101,7 +101,6 @@ 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; @@ -133,7 +132,7 @@ namespace Emby.Server.Implementations /// All concrete types. private Type[] _allConcreteTypes; - private bool _disposed = false; + private bool _disposed; /// /// Initializes a new instance of the class. @@ -184,26 +183,16 @@ namespace Emby.Server.Implementations public bool CoreStartupHasCompleted { get; private set; } - public virtual bool CanLaunchWebBrowser => Environment.UserInteractive - && !_startupOptions.IsService - && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()); - /// /// Gets the singleton instance. /// public INetworkManager NetManager { get; private set; } - /// - /// Gets a value indicating whether this instance has changes that require the entire application to restart. - /// - /// true if this instance has pending application restart; otherwise, false. - public bool HasPendingRestart { get; private set; } - /// - public bool IsShuttingDown { get; private set; } + public bool HasPendingRestart { get; private set; } /// - public bool ShouldRestart { get; private set; } + public bool ShouldRestart { get; set; } /// /// Gets the logger. @@ -507,6 +496,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddScoped(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(NetManager); @@ -850,24 +841,6 @@ 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. /// @@ -923,49 +896,6 @@ namespace Emby.Server.Implementations protected abstract IEnumerable GetAssembliesWithPartsInternal(); - /// - /// Gets the system status. - /// - /// Where this request originated. - /// SystemInfo. - public SystemInfo GetSystemInfo(HttpRequest request) - { - return new SystemInfo - { - HasPendingRestart = HasPendingRestart, - IsShuttingDown = IsShuttingDown, - Version = ApplicationVersionString, - WebSocketPortNumber = HttpPort, - CompletedInstallations = Resolve().CompletedInstallations.ToArray(), - Id = SystemId, - ProgramDataPath = ApplicationPaths.ProgramDataPath, - WebPath = ApplicationPaths.WebPath, - LogPath = ApplicationPaths.LogDirectoryPath, - ItemsByNamePath = ApplicationPaths.InternalMetadataPath, - InternalMetadataPath = ApplicationPaths.InternalMetadataPath, - CachePath = ApplicationPaths.CachePath, - CanLaunchWebBrowser = CanLaunchWebBrowser, - TranscodingTempPath = ConfigurationManager.GetTranscodePath(), - ServerName = FriendlyName, - LocalAddress = GetSmartApiUrl(request), - SupportsLibraryMonitor = true, - PackageName = _startupOptions.PackageName - }; - } - - public PublicSystemInfo GetPublicSystemInfo(HttpRequest request) - { - return new PublicSystemInfo - { - Version = ApplicationVersionString, - ProductName = ApplicationProductName, - Id = SystemId, - ServerName = FriendlyName, - LocalAddress = GetSmartApiUrl(request), - StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted - }; - } - /// public string GetSmartApiUrl(IPAddress remoteAddr) { diff --git a/Emby.Server.Implementations/SystemManager.cs b/Emby.Server.Implementations/SystemManager.cs new file mode 100644 index 000000000..5e9c424e9 --- /dev/null +++ b/Emby.Server.Implementations/SystemManager.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; + +namespace Emby.Server.Implementations; + +/// +public class SystemManager : ISystemManager +{ + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly IServerApplicationHost _applicationHost; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IServerConfigurationManager _configurationManager; + private readonly IStartupOptions _startupOptions; + private readonly IInstallationManager _installationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + /// Instance of . + public SystemManager( + IHostApplicationLifetime applicationLifetime, + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IServerConfigurationManager configurationManager, + IStartupOptions startupOptions, + IInstallationManager installationManager) + { + _applicationLifetime = applicationLifetime; + _applicationHost = applicationHost; + _applicationPaths = applicationPaths; + _configurationManager = configurationManager; + _startupOptions = startupOptions; + _installationManager = installationManager; + } + + private bool CanLaunchWebBrowser => Environment.UserInteractive + && !_startupOptions.IsService + && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()); + + /// + public SystemInfo GetSystemInfo(HttpRequest request) + { + return new SystemInfo + { + HasPendingRestart = _applicationHost.HasPendingRestart, + IsShuttingDown = _applicationLifetime.ApplicationStopping.IsCancellationRequested, + Version = _applicationHost.ApplicationVersionString, + WebSocketPortNumber = _applicationHost.HttpPort, + CompletedInstallations = _installationManager.CompletedInstallations.ToArray(), + Id = _applicationHost.SystemId, + ProgramDataPath = _applicationPaths.ProgramDataPath, + WebPath = _applicationPaths.WebPath, + LogPath = _applicationPaths.LogDirectoryPath, + ItemsByNamePath = _applicationPaths.InternalMetadataPath, + InternalMetadataPath = _applicationPaths.InternalMetadataPath, + CachePath = _applicationPaths.CachePath, + CanLaunchWebBrowser = CanLaunchWebBrowser, + TranscodingTempPath = _configurationManager.GetTranscodePath(), + ServerName = _applicationHost.FriendlyName, + LocalAddress = _applicationHost.GetSmartApiUrl(request), + SupportsLibraryMonitor = true, + PackageName = _startupOptions.PackageName + }; + } + + /// + public PublicSystemInfo GetPublicSystemInfo(HttpRequest request) + { + return new PublicSystemInfo + { + Version = _applicationHost.ApplicationVersionString, + ProductName = _applicationHost.Name, + Id = _applicationHost.SystemId, + ServerName = _applicationHost.FriendlyName, + LocalAddress = _applicationHost.GetSmartApiUrl(request), + StartupWizardCompleted = _configurationManager.CommonConfiguration.IsStartupWizardCompleted + }; + } + + /// + public void Restart() => ShutdownInternal(true); + + /// + public void Shutdown() => ShutdownInternal(false); + + private void ShutdownInternal(bool restart) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + _applicationHost.ShouldRestart = restart; + _applicationLifetime.StopApplication(); + }); + } +} diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 42ac4a9b4..11095a97f 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -10,7 +10,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; @@ -26,32 +25,36 @@ namespace Jellyfin.Api.Controllers; /// public class SystemController : BaseJellyfinApiController { + private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly INetworkManager _network; - private readonly ILogger _logger; + private readonly INetworkManager _networkManager; + private readonly ISystemManager _systemManager; /// /// Initializes a new instance of the class. /// - /// Instance of interface. + /// Instance of interface. + /// Instance of interface. /// Instance of interface. /// Instance of interface. - /// Instance of interface. - /// Instance of interface. + /// Instance of interface. + /// Instance of interface. public SystemController( - IServerConfigurationManager serverConfigurationManager, + ILogger logger, IServerApplicationHost appHost, + IServerApplicationPaths appPaths, IFileSystem fileSystem, - INetworkManager network, - ILogger logger) + INetworkManager networkManager, + ISystemManager systemManager) { - _appPaths = serverConfigurationManager.ApplicationPaths; + _logger = logger; _appHost = appHost; + _appPaths = appPaths; _fileSystem = fileSystem; - _network = network; - _logger = logger; + _networkManager = networkManager; + _systemManager = systemManager; } /// @@ -65,9 +68,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult GetSystemInfo() - { - return _appHost.GetSystemInfo(Request); - } + => _systemManager.GetSystemInfo(Request); /// /// Gets public information about the server. @@ -77,9 +78,7 @@ public class SystemController : BaseJellyfinApiController [HttpGet("Info/Public")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetPublicSystemInfo() - { - return _appHost.GetPublicSystemInfo(Request); - } + => _systemManager.GetPublicSystemInfo(Request); /// /// Pings the system. @@ -90,9 +89,7 @@ public class SystemController : BaseJellyfinApiController [HttpPost("Ping", Name = "PostPingSystem")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult PingSystem() - { - return _appHost.Name; - } + => _appHost.Name; /// /// Restarts the application. @@ -106,7 +103,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult RestartApplication() { - _appHost.Restart(); + _systemManager.Restart(); return NoContent(); } @@ -122,7 +119,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status403Forbidden)] public ActionResult ShutdownApplication() { - _appHost.Shutdown(); + _systemManager.Shutdown(); return NoContent(); } @@ -180,7 +177,7 @@ public class SystemController : BaseJellyfinApiController return new EndPointInfo { IsLocal = HttpContext.IsLocal(), - IsInNetwork = _network.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) + IsInNetwork = _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()) }; } @@ -218,7 +215,7 @@ public class SystemController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetWakeOnLanInfo() { - var result = _network.GetMacAddresses() + var result = _networkManager.GetMacAddresses() .Select(i => new WakeOnLanInfo(i)); return Ok(result); } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 5985d3dd8..23795c6be 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -35,21 +35,15 @@ namespace MediaBrowser.Common string SystemId { get; } /// - /// Gets a value indicating whether this instance has pending kernel reload. + /// Gets a value indicating whether this instance has pending changes requiring a restart. /// - /// true if this instance has pending kernel reload; otherwise, false. + /// true if this instance has a pending restart; otherwise, false. bool HasPendingRestart { get; } /// - /// Gets a value indicating whether this instance is currently shutting down. + /// Gets or sets a value indicating whether the application should restart. /// - /// true if this instance is shutting down; otherwise, false. - bool IsShuttingDown { get; } - - /// - /// Gets a value indicating whether the application should restart. - /// - bool ShouldRestart { get; } + bool ShouldRestart { get; set; } /// /// Gets the application version. @@ -91,11 +85,6 @@ namespace MediaBrowser.Common /// void NotifyPendingRestart(); - /// - /// Restarts this instance. - /// - void Restart(); - /// /// Gets the exports. /// @@ -127,11 +116,6 @@ namespace MediaBrowser.Common /// ``0. T Resolve(); - /// - /// Shuts down. - /// - void Shutdown(); - /// /// Initializes this instance. /// diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 45ac5c3a8..e9c4d9e19 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -4,7 +4,6 @@ using System.Net; using MediaBrowser.Common; -using MediaBrowser.Model.System; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller @@ -16,8 +15,6 @@ namespace MediaBrowser.Controller { bool CoreStartupHasCompleted { get; } - bool CanLaunchWebBrowser { get; } - /// /// Gets the HTTP server port. /// @@ -41,15 +38,6 @@ namespace MediaBrowser.Controller /// The name of the friendly. string FriendlyName { get; } - /// - /// Gets the system info. - /// - /// The HTTP request. - /// SystemInfo. - SystemInfo GetSystemInfo(HttpRequest request); - - PublicSystemInfo GetPublicSystemInfo(HttpRequest request); - /// /// Gets a URL specific for the request. /// diff --git a/MediaBrowser.Controller/ISystemManager.cs b/MediaBrowser.Controller/ISystemManager.cs new file mode 100644 index 000000000..ef3034d2f --- /dev/null +++ b/MediaBrowser.Controller/ISystemManager.cs @@ -0,0 +1,34 @@ +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Controller; + +/// +/// A service for managing the application instance. +/// +public interface ISystemManager +{ + /// + /// Gets the system info. + /// + /// The HTTP request. + /// The . + SystemInfo GetSystemInfo(HttpRequest request); + + /// + /// Gets the public system info. + /// + /// The HTTP request. + /// The . + PublicSystemInfo GetPublicSystemInfo(HttpRequest request); + + /// + /// Starts the application restart process. + /// + void Restart(); + + /// + /// Starts the application shutdown process. + /// + void Shutdown(); +} -- cgit v1.2.3