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;
}