From 4b563f4d7ec1de7bea6c25937e63a736a1defa3e Mon Sep 17 00:00:00 2001 From: luke brown Date: Mon, 9 May 2022 20:21:13 -0500 Subject: [PATCH 01/13] Fix web sockets closing ungracefully --- Emby.Dlna/PlayTo/PlayToController.cs | 5 +++++ .../HttpServer/WebSocketConnection.cs | 9 +++++++++ .../Session/SessionManager.cs | 17 +++++++++++++++++ .../Session/WebSocketController.cs | 12 ++++++++++++ .../Net/IWebSocketConnection.cs | 6 ++++++ .../Session/ISessionController.cs | 6 ++++++ 6 files changed, 55 insertions(+) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index e27a8975b7..3d36a50454 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -889,6 +889,11 @@ namespace Emby.Dlna.PlayTo return Task.CompletedTask; } + public void CloseAllWebSockets(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + private class StreamParams { private MediaSourceInfo _mediaSource; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index b87f1bc226..2c5ec81ada 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -231,6 +231,15 @@ namespace Emby.Server.Implementations.HttpServer CancellationToken.None); } + /// + /// Gracefully closes the socket. + /// + /// The cancellation token. + public void CloseSocket(CancellationToken cancellationToken) + { + _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", cancellationToken); + } + /// public void Dispose() { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e2fa93a380..0d2d0e5176 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1363,9 +1363,26 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); + CloseAllWebSockets(cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); } + /// + /// Gracefully closes all web sockets in all sessions. + /// + /// The cancellation token. + private void CloseAllWebSockets(CancellationToken cancellationToken) + { + foreach (var session in Sessions) + { + foreach (var sessionController in session.SessionControllers) + { + sessionController.CloseAllWebSockets(cancellationToken); + } + } + } + /// /// Sends the server restart notification. /// diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 9fa92a53a1..16312ffb81 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -88,6 +88,18 @@ namespace Emby.Server.Implementations.Session cancellationToken); } + /// + /// Gracefully closes all web sockets. + /// + /// The cancellation token. + public void CloseAllWebSockets(CancellationToken cancellationToken) + { + foreach (var socket in _sockets) + { + socket.CloseSocket(cancellationToken); + } + } + /// public void Dispose() { diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 2c6483ae28..c53199de6b 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -58,5 +58,11 @@ namespace MediaBrowser.Controller.Net Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken); Task ProcessAsync(CancellationToken cancellationToken = default); + + /// + /// Gracefully closes the socket. + /// + /// The cancellation token. + void CloseSocket(CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index b38ee11462..3db79076da 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -33,5 +33,11 @@ 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. + void CloseAllWebSockets(CancellationToken cancellationToken); } } From 99df9c8fcd337ff0d3b237e9ee505d5a1008700b Mon Sep 17 00:00:00 2001 From: luke92brown Date: Mon, 9 May 2022 22:00:30 -0500 Subject: [PATCH 02/13] Replace redundant xml comments with inheritdoc. Make async changes to CloseSocket. --- Emby.Dlna/PlayTo/PlayToController.cs | 4 ++-- .../HttpServer/WebSocketConnection.cs | 9 +++------ Emby.Server.Implementations/Session/SessionManager.cs | 6 +++--- .../Session/WebSocketController.cs | 9 +++------ MediaBrowser.Controller/Net/IWebSocketConnection.cs | 3 ++- MediaBrowser.Controller/Session/ISessionController.cs | 3 ++- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 3d36a50454..bb2eedfb4f 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -889,9 +889,9 @@ namespace Emby.Dlna.PlayTo return Task.CompletedTask; } - public void CloseAllWebSockets(CancellationToken cancellationToken) + /// + public async Task CloseAllWebSockets(CancellationToken cancellationToken) { - throw new NotImplementedException(); } private class StreamParams diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2c5ec81ada..331b5aa37a 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -231,13 +231,10 @@ namespace Emby.Server.Implementations.HttpServer CancellationToken.None); } - /// - /// Gracefully closes the socket. - /// - /// The cancellation token. - public void CloseSocket(CancellationToken cancellationToken) + /// + public async Task CloseSocket(CancellationToken cancellationToken) { - _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", cancellationToken); + await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", cancellationToken).ConfigureAwait(false); } /// diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 0d2d0e5176..be0d14ea2c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1363,7 +1363,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - CloseAllWebSockets(cancellationToken); + CloseAllWebSockets(cancellationToken).ConfigureAwait(false); return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); } @@ -1372,13 +1372,13 @@ namespace Emby.Server.Implementations.Session /// Gracefully closes all web sockets in all sessions. /// /// The cancellation token. - private void CloseAllWebSockets(CancellationToken cancellationToken) + private async Task CloseAllWebSockets(CancellationToken cancellationToken) { foreach (var session in Sessions) { foreach (var sessionController in session.SessionControllers) { - sessionController.CloseAllWebSockets(cancellationToken); + await sessionController.CloseAllWebSockets(cancellationToken).ConfigureAwait(false); } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 16312ffb81..f9757a0af5 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -88,15 +88,12 @@ namespace Emby.Server.Implementations.Session cancellationToken); } - /// - /// Gracefully closes all web sockets. - /// - /// The cancellation token. - public void CloseAllWebSockets(CancellationToken cancellationToken) + /// + public async Task CloseAllWebSockets(CancellationToken cancellationToken) { foreach (var socket in _sockets) { - socket.CloseSocket(cancellationToken); + await socket.CloseSocket(cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index c53199de6b..cea2049fd7 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -62,7 +62,8 @@ namespace MediaBrowser.Controller.Net /// /// Gracefully closes the socket. /// + /// Task. /// The cancellation token. - void CloseSocket(CancellationToken cancellationToken); + Task CloseSocket(CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 3db79076da..3acfe19a27 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Session /// Gracefully closes all web sockets. /// /// The cancellation token. - void CloseAllWebSockets(CancellationToken cancellationToken); + /// A task. + Task CloseAllWebSockets(CancellationToken cancellationToken); } } From c5f09ab650829f7f91898807a8bde435e3aeda36 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Mon, 9 May 2022 23:59:31 -0500 Subject: [PATCH 03/13] Fix broken CI by adding await --- Emby.Dlna/PlayTo/PlayToController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index bb2eedfb4f..75f31ccc27 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -892,6 +892,7 @@ namespace Emby.Dlna.PlayTo /// public async Task CloseAllWebSockets(CancellationToken cancellationToken) { + await Task.CompletedTask; } private class StreamParams From 8bb4cd017c74533599a7e8e743b65d8f833d678f Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Tue, 10 May 2022 08:28:01 -0500 Subject: [PATCH 04/13] applied reviewer suggestions: removing unnecessary async and adding necessary async --- Emby.Dlna/PlayTo/PlayToController.cs | 4 ++-- Emby.Server.Implementations/Session/SessionManager.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 75f31ccc27..5169f65adb 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -890,9 +890,9 @@ namespace Emby.Dlna.PlayTo } /// - public async Task CloseAllWebSockets(CancellationToken cancellationToken) + public Task CloseAllWebSockets(CancellationToken cancellationToken) { - await Task.CompletedTask; + return Task.CompletedTask; } private class StreamParams diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index be0d14ea2c..9c00e9e9f4 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1359,13 +1359,13 @@ namespace Emby.Server.Implementations.Session /// /// The cancellation token. /// Task. - public Task SendServerShutdownNotification(CancellationToken cancellationToken) + public async Task SendServerShutdownNotification(CancellationToken cancellationToken) { CheckDisposed(); - CloseAllWebSockets(cancellationToken).ConfigureAwait(false); + await CloseAllWebSockets(cancellationToken).ConfigureAwait(false); - return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); + await SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken).ConfigureAwait(false); } /// From a64eebe79f86a3016848c36f73b6c0a045205e1b Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Mon, 16 May 2022 22:09:41 -0500 Subject: [PATCH 05/13] changes to use dispose --- Emby.Dlna/PlayTo/PlayToController.cs | 6 ---- .../ApplicationHost.cs | 21 +++++++++++++- .../HttpServer/WebSocketConnection.cs | 28 ++++++++++++++----- .../Session/SessionManager.cs | 17 ----------- .../Session/WebSocketController.cs | 21 ++++++++++---- Jellyfin.Server/Program.cs | 2 +- .../Net/IWebSocketConnection.cs | 7 ----- .../Session/ISessionController.cs | 7 ----- .../Session/SessionInfo.cs | 23 +++++++++++++-- 9 files changed, 79 insertions(+), 53 deletions(-) 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; @@ -89,16 +89,22 @@ namespace Emby.Server.Implementations.Session } /// - public async Task CloseAllWebSockets(CancellationToken cancellationToken) + public void Dispose() { + if (_disposed) + { + return; + } + foreach (var socket in _sockets) { - await socket.CloseSocket(cancellationToken).ConfigureAwait(false); + socket.Closed -= OnConnectionClosed; } + + _disposed = true; } - /// - public void Dispose() + public async ValueTask DisposeAsync() { if (_disposed) { @@ -108,6 +114,11 @@ namespace Emby.Server.Implementations.Session 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); + } + } + } } } From 8f761a64f55b761e091f74970f274a5672206cb2 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Mon, 16 May 2022 22:33:04 -0500 Subject: [PATCH 06/13] revert changes to SessionManager --- Emby.Server.Implementations/Session/SessionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 278e71ef3c..e2fa93a380 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1359,11 +1359,11 @@ namespace Emby.Server.Implementations.Session /// /// The cancellation token. /// Task. - public async Task SendServerShutdownNotification(CancellationToken cancellationToken) + public Task SendServerShutdownNotification(CancellationToken cancellationToken) { CheckDisposed(); - await SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken).ConfigureAwait(false); + return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); } /// From 8d0024ec49b065071af49ec758e0df0f78bfbf42 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Mon, 16 May 2022 22:35:45 -0500 Subject: [PATCH 07/13] change logging back to debug --- MediaBrowser.Controller/Session/SessionInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 7a2993a10e..659f50e263 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -381,7 +381,7 @@ namespace MediaBrowser.Controller.Session { if (controller is IDisposable disposable) { - _logger.LogInformation("Disposing session controller synchronously {0}", disposable.GetType().Name); + _logger.LogDebug("Disposing session controller synchronously {0}", disposable.GetType().Name); disposable.Dispose(); } } @@ -399,7 +399,7 @@ namespace MediaBrowser.Controller.Session { if (controller is IAsyncDisposable disposableAsync) { - _logger.LogInformation("Disposing session controller asynchronously {0}", disposableAsync.GetType().Name); + _logger.LogDebug("Disposing session controller asynchronously {0}", disposableAsync.GetType().Name); await disposableAsync.DisposeAsync().ConfigureAwait(false); } } From abfbd04782246150b135802f12ab674d07642e5d Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Wed, 18 May 2022 21:04:22 -0500 Subject: [PATCH 08/13] Moved IAsyncDisposable and IDisposable from WebSocketConnection to it's interface. Added private variable and guard statement to avoid disposing of WebSocketConnection unnecessarily. --- .../HttpServer/WebSocketConnection.cs | 11 ++++++++++- .../Session/WebSocketController.cs | 7 ++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 74d9e91450..d6dc49ff0e 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, IAsyncDisposable, IDisposable + public class WebSocketConnection : IWebSocketConnection { /// /// The logger. @@ -36,6 +36,8 @@ namespace Emby.Server.Implementations.HttpServer /// private readonly WebSocket _socket; + private bool _disposed = false; + /// /// Initializes a new instance of the class. /// @@ -244,10 +246,17 @@ namespace Emby.Server.Implementations.HttpServer /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { _socket.Dispose(); } + + _disposed = true; } /// diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 373f04f625..47c01435b7 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -99,6 +99,7 @@ namespace Emby.Server.Implementations.Session foreach (var socket in _sockets) { socket.Closed -= OnConnectionClosed; + socket.Dispose(); } _disposed = true; @@ -114,11 +115,7 @@ namespace Emby.Server.Implementations.Session foreach (var socket in _sockets) { socket.Closed -= OnConnectionClosed; - - if (socket is IAsyncDisposable disposableAsync) - { - await disposableAsync.DisposeAsync().ConfigureAwait(false); - } + await socket.DisposeAsync().ConfigureAwait(false); } _disposed = true; From 044ff0542ba545a67cebf6b5d4738299e9585fdb Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Thu, 19 May 2022 16:40:48 -0500 Subject: [PATCH 09/13] adding interface changes --- MediaBrowser.Controller/Net/IWebSocketConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 2c6483ae28..43c7ce3708 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { - public interface IWebSocketConnection + public interface IWebSocketConnection : IAsyncDisposable, IDisposable { /// /// Occurs when [closed]. From 3e8fe1ce116572e089472142ffa5cb9c1679c8e3 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Tue, 31 May 2022 21:49:01 -0500 Subject: [PATCH 10/13] change placeholder text in LogDebug message --- MediaBrowser.Controller/Session/SessionInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 659f50e263..b4520ae48f 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -381,7 +381,7 @@ namespace MediaBrowser.Controller.Session { if (controller is IDisposable disposable) { - _logger.LogDebug("Disposing session controller synchronously {0}", disposable.GetType().Name); + _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name); disposable.Dispose(); } } @@ -399,7 +399,7 @@ namespace MediaBrowser.Controller.Session { if (controller is IAsyncDisposable disposableAsync) { - _logger.LogDebug("Disposing session controller asynchronously {0}", disposableAsync.GetType().Name); + _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name); await disposableAsync.DisposeAsync().ConfigureAwait(false); } } From 0f7ba42987105c79acafc3770465a4fe5e3f3ee9 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Mon, 6 Jun 2022 23:59:09 -0500 Subject: [PATCH 11/13] move the Dispose logic into DisposeAsyncCore --- .../ApplicationHost.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4f74136337..899f5e30a8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1234,7 +1234,7 @@ namespace Emby.Server.Implementations public async ValueTask DisposeAsync() { await DisposeAsyncCore().ConfigureAwait(false); - Dispose(true); + Dispose(false); GC.SuppressFinalize(this); } @@ -1244,6 +1244,31 @@ namespace Emby.Server.Implementations /// 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); + } + } + + // used for closing websockets foreach (var session in _sessionManager.Sessions) { await session.DisposeAsync().ConfigureAwait(false); From f088ca5555cefc80c1ece96f7c412aa4694f5ae4 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Tue, 19 Jul 2022 17:31:00 -0500 Subject: [PATCH 12/13] add Dispose in DisposeAsyncCore --- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index d6dc49ff0e..d054d2e3be 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -276,6 +276,7 @@ namespace Emby.Server.Implementations.HttpServer if (_socket.State == WebSocketState.Open) { await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false); + _socket.Dispose(); } } } From 9e31d5a73f31ff810173eeab82efbe4dfccccc91 Mon Sep 17 00:00:00 2001 From: Luke Brown Date: Tue, 19 Jul 2022 21:49:17 -0500 Subject: [PATCH 13/13] relocated Dispose --- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index d054d2e3be..818ccbb1b8 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -276,8 +276,9 @@ namespace Emby.Server.Implementations.HttpServer if (_socket.State == WebSocketState.Open) { await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false); - _socket.Dispose(); } + + _socket.Dispose(); } } }