diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 5169f65adb..e27a8975b7 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -889,12 +889,6 @@ namespace Emby.Dlna.PlayTo
return Task.CompletedTask;
}
- ///
- public Task CloseAllWebSockets(CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
-
private class StreamParams
{
private MediaSourceInfo _mediaSource;
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 82294644b8..4f74136337 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations
///
/// Class CompositionRoot.
///
- public abstract class ApplicationHost : IServerApplicationHost, IDisposable
+ public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
{
///
/// The environment variable prefixes to log at server startup.
@@ -1230,5 +1230,24 @@ namespace Emby.Server.Implementations
_disposed = true;
}
+
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore().ConfigureAwait(false);
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Used to perform asynchronous cleanup of managed resources or for cascading calls to .
+ ///
+ /// A ValueTask.
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ foreach (var session in _sessionManager.Sessions)
+ {
+ await session.DisposeAsync().ConfigureAwait(false);
+ }
+ }
}
}
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 331b5aa37a..74d9e91450 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
///
/// Class WebSocketConnection.
///
- public class WebSocketConnection : IWebSocketConnection, IDisposable
+ public class WebSocketConnection : IWebSocketConnection, IAsyncDisposable, IDisposable
{
///
/// The logger.
@@ -231,12 +231,6 @@ namespace Emby.Server.Implementations.HttpServer
CancellationToken.None);
}
- ///
- public async Task CloseSocket(CancellationToken cancellationToken)
- {
- await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", cancellationToken).ConfigureAwait(false);
- }
-
///
public void Dispose()
{
@@ -255,5 +249,25 @@ namespace Emby.Server.Implementations.HttpServer
_socket.Dispose();
}
}
+
+ ///
+ 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()
+ {
+ if (_socket.State == WebSocketState.Open)
+ {
+ await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false);
+ }
+ }
}
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 9c00e9e9f4..278e71ef3c 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1363,26 +1363,9 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
- await CloseAllWebSockets(cancellationToken).ConfigureAwait(false);
-
await SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken).ConfigureAwait(false);
}
- ///
- /// Gracefully closes all web sockets in all sessions.
- ///
- /// The cancellation token.
- private async Task CloseAllWebSockets(CancellationToken cancellationToken)
- {
- foreach (var session in Sessions)
- {
- foreach (var sessionController in session.SessionControllers)
- {
- await sessionController.CloseAllWebSockets(cancellationToken).ConfigureAwait(false);
- }
- }
- }
-
///
/// Sends the server restart notification.
///
diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs
index f9757a0af5..373f04f625 100644
--- a/Emby.Server.Implementations/Session/WebSocketController.cs
+++ b/Emby.Server.Implementations/Session/WebSocketController.cs
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
- public sealed class WebSocketController : ISessionController, IDisposable
+ public sealed class WebSocketController : ISessionController, IAsyncDisposable, IDisposable
{
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
@@ -88,15 +88,6 @@ namespace Emby.Server.Implementations.Session
cancellationToken);
}
- ///
- public async Task CloseAllWebSockets(CancellationToken cancellationToken)
- {
- foreach (var socket in _sockets)
- {
- await socket.CloseSocket(cancellationToken).ConfigureAwait(false);
- }
- }
-
///
public void Dispose()
{
@@ -112,5 +103,25 @@ namespace Emby.Server.Implementations.Session
_disposed = true;
}
+
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ foreach (var socket in _sockets)
+ {
+ socket.Closed -= OnConnectionClosed;
+
+ if (socket is IAsyncDisposable disposableAsync)
+ {
+ await disposableAsync.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+
+ _disposed = true;
+ }
}
}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 1323d8a480..40685cae84 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -243,7 +243,7 @@ namespace Jellyfin.Server
}
}
- appHost.Dispose();
+ await appHost.DisposeAsync().ConfigureAwait(false);
}
if (_restartOnShutdown)
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index cea2049fd7..2c6483ae28 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -58,12 +58,5 @@ namespace MediaBrowser.Controller.Net
Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken);
Task ProcessAsync(CancellationToken cancellationToken = default);
-
- ///
- /// Gracefully closes the socket.
- ///
- /// Task.
- /// The cancellation token.
- Task CloseSocket(CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs
index 3acfe19a27..b38ee11462 100644
--- a/MediaBrowser.Controller/Session/ISessionController.cs
+++ b/MediaBrowser.Controller/Session/ISessionController.cs
@@ -33,12 +33,5 @@ namespace MediaBrowser.Controller.Session
/// CancellationToken for operation.
/// A task.
Task SendMessage(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken);
-
- ///
- /// Gracefully closes all web sockets.
- ///
- /// The cancellation token.
- /// A task.
- Task CloseAllWebSockets(CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index c2ca233868..7a2993a10e 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
+using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Session
///
/// Class SessionInfo.
///
- public sealed class SessionInfo : IDisposable
+ public sealed class SessionInfo : IAsyncDisposable, IDisposable
{
// 1 second
private const long ProgressIncrement = 10000000;
@@ -380,10 +381,28 @@ namespace MediaBrowser.Controller.Session
{
if (controller is IDisposable disposable)
{
- _logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
+ _logger.LogInformation("Disposing session controller synchronously {0}", disposable.GetType().Name);
disposable.Dispose();
}
}
}
+
+ public async ValueTask DisposeAsync()
+ {
+ _disposed = true;
+
+ StopAutomaticProgress();
+
+ var controllers = SessionControllers.ToList();
+
+ foreach (var controller in controllers)
+ {
+ if (controller is IAsyncDisposable disposableAsync)
+ {
+ _logger.LogInformation("Disposing session controller asynchronously {0}", disposableAsync.GetType().Name);
+ await disposableAsync.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+ }
}
}