Merge pull request #10261 from barronpm/ihostlifetime
This commit is contained in:
commit
14b06ecbe2
|
@ -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;
|
||||
|
@ -102,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;
|
||||
|
@ -112,7 +112,7 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Class CompositionRoot.
|
||||
/// </summary>
|
||||
public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
|
||||
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The disposable parts.
|
||||
|
@ -127,7 +127,6 @@ namespace Emby.Server.Implementations
|
|||
private readonly IPluginManager _pluginManager;
|
||||
|
||||
private List<Type> _creatingInstances;
|
||||
private ISessionManager _sessionManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets all concrete types.
|
||||
|
@ -172,6 +171,8 @@ namespace Emby.Server.Implementations
|
|||
ConfigurationManager.Configuration,
|
||||
ApplicationPaths.PluginsPath,
|
||||
ApplicationVersion);
|
||||
|
||||
_disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -204,6 +205,9 @@ namespace Emby.Server.Implementations
|
|||
/// <inheritdoc />
|
||||
public bool IsShuttingDown { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ShouldRestart { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
|
@ -406,11 +410,9 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Runs the startup tasks.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns><see cref="Task" />.</returns>
|
||||
public async Task RunStartupTasksAsync(CancellationToken cancellationToken)
|
||||
public async Task RunStartupTasksAsync()
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Logger.LogInformation("Running startup tasks");
|
||||
|
||||
Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
|
||||
|
@ -424,8 +426,6 @@ namespace Emby.Server.Implementations
|
|||
|
||||
var entryPoints = GetExports<IServerEntryPoint>();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
|
||||
|
@ -435,8 +435,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 +631,6 @@ namespace Emby.Server.Implementations
|
|||
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
|
||||
await localizationManager.LoadAll().ConfigureAwait(false);
|
||||
|
||||
_sessionManager = Resolve<ISessionManager>();
|
||||
|
||||
SetStaticProperties();
|
||||
|
||||
FindParts();
|
||||
|
@ -855,37 +851,23 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts this instance.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
});
|
||||
ShouldRestart = true;
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
protected abstract void RestartInternal();
|
||||
/// <inheritdoc />
|
||||
public void Shutdown()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
IsShuttingDown = true;
|
||||
Resolve<IHostApplicationLifetime>().StopApplication();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the composable part assemblies.
|
||||
|
@ -1065,30 +1047,6 @@ namespace Emby.Server.Implementations
|
|||
}.ToString().TrimEnd('/');
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<Assembly> GetApiPluginAssemblies()
|
||||
{
|
||||
var assemblies = _allConcreteTypes
|
||||
|
@ -1152,52 +1110,5 @@ namespace Emby.Server.Implementations
|
|||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisposeAsyncCore().ConfigureAwait(false);
|
||||
Dispose(false);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
|
||||
/// </summary>
|
||||
/// <returns>A ValueTask.</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|||
/// <summary>
|
||||
/// Defines the <see cref="PluginManager" />.
|
||||
/// </summary>
|
||||
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
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnloadAssemblies()
|
||||
{
|
||||
foreach (var assemblyLoadContext in _assemblyLoadContexts)
|
||||
{
|
||||
assemblyLoadContext.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all the plugin instances.
|
||||
/// </summary>
|
||||
|
@ -441,6 +430,15 @@ namespace Emby.Server.Implementations.Plugins
|
|||
return SaveManifest(manifest, path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var assemblyLoadContext in _assemblyLoadContexts)
|
||||
{
|
||||
assemblyLoadContext.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
|
|
@ -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
|
|||
/// <summary>
|
||||
/// Class SessionManager.
|
||||
/// </summary>
|
||||
public class SessionManager : ISessionManager, IDisposable
|
||||
public sealed class SessionManager : ISessionManager, IAsyncDisposable
|
||||
{
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly ILogger<SessionManager> _logger;
|
||||
|
@ -57,11 +58,9 @@ namespace Emby.Server.Implementations.Session
|
|||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
||||
/// <summary>
|
||||
/// The active connections.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly CancellationTokenRegistration _shutdownCallback;
|
||||
private readonly ConcurrentDictionary<string, SessionInfo> _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
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the server shutdown notification.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the server restart notification.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task SendServerRestartNotification(CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
_logger.LogDebug("Beginning SendServerRestartNotification");
|
||||
|
||||
return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the additional user.
|
||||
/// </summary>
|
||||
|
@ -1833,5 +1778,53 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
_activeConnections.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -107,11 +106,7 @@ public class SystemController : BaseJellyfinApiController
|
|||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public ActionResult RestartApplication()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
_appHost.Restart();
|
||||
});
|
||||
_appHost.Restart();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
@ -127,11 +122,7 @@ public class SystemController : BaseJellyfinApiController
|
|||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public ActionResult ShutdownApplication()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
await _appHost.Shutdown().ConfigureAwait(false);
|
||||
});
|
||||
_appHost.Shutdown();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
|
|
@ -102,9 +102,6 @@ namespace Jellyfin.Server
|
|||
base.RegisterServices(serviceCollection);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void RestartInternal() => Program.Restart();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
|
||||
{
|
||||
|
@ -114,8 +111,5 @@ namespace Jellyfin.Server
|
|||
// Jellyfin.Server.Implementations
|
||||
yield return typeof(JellyfinDbContext).Assembly;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ShutdownInternal() => Program.Shutdown();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,36 +63,9 @@ namespace Jellyfin.Server
|
|||
.MapResult(StartApp, ErrorParsingArguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down the application.
|
||||
/// </summary>
|
||||
internal static void Shutdown()
|
||||
{
|
||||
if (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the application.
|
||||
/// </summary>
|
||||
internal static void Restart()
|
||||
{
|
||||
_restartOnShutdown = true;
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
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,38 +83,10 @@ 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");
|
||||
|
||||
// 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));
|
||||
|
@ -173,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);
|
||||
|
@ -186,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,
|
||||
|
@ -196,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))
|
||||
|
@ -210,7 +152,7 @@ namespace Jellyfin.Server
|
|||
|
||||
try
|
||||
{
|
||||
await host.StartAsync(_tokenSource.Token).ConfigureAwait(false);
|
||||
await host.StartAsync().ConfigureAwait(false);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket())
|
||||
{
|
||||
|
@ -219,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)
|
||||
{
|
||||
|
@ -257,7 +195,6 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
|
||||
await appHost.DisposeAsync().ConfigureAwait(false);
|
||||
host?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace MediaBrowser.Common
|
||||
|
@ -47,6 +46,11 @@ namespace MediaBrowser.Common
|
|||
/// <value><c>true</c> if this instance is shutting down; otherwise, <c>false</c>.</value>
|
||||
bool IsShuttingDown { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the application should restart.
|
||||
/// </summary>
|
||||
bool ShouldRestart { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application version.
|
||||
/// </summary>
|
||||
|
@ -126,8 +130,7 @@ namespace MediaBrowser.Common
|
|||
/// <summary>
|
||||
/// Shuts down.
|
||||
/// </summary>
|
||||
/// <returns>A task.</returns>
|
||||
Task Shutdown();
|
||||
void Shutdown();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this instance.
|
||||
|
|
|
@ -29,11 +29,6 @@ namespace MediaBrowser.Common.Plugins
|
|||
/// <returns>An IEnumerable{Assembly}.</returns>
|
||||
IEnumerable<Assembly> LoadAssemblies();
|
||||
|
||||
/// <summary>
|
||||
/// Unloads all of the assemblies.
|
||||
/// </summary>
|
||||
void UnloadAssemblies();
|
||||
|
||||
/// <summary>
|
||||
/// Registers the plugin's services with the DI.
|
||||
/// Note: DI is not yet instantiated yet.
|
||||
|
|
|
@ -232,20 +232,6 @@ namespace MediaBrowser.Controller.Session
|
|||
/// <returns>Task.</returns>
|
||||
Task SendRestartRequiredNotification(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the server shutdown notification.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendServerShutdownNotification(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the server restart notification.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendServerRestartNotification(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the additional user.
|
||||
/// </summary>
|
||||
|
|
|
@ -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<IApplicationHost>();
|
||||
appHost.ServiceProvider = testServer.Services;
|
||||
appHost.InitializeServices().GetAwaiter().GetResult();
|
||||
appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
appHost.RunStartupTasksAsync().GetAwaiter().GetResult();
|
||||
|
||||
return testServer;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user