From 410871e14882fe8622c4d992658eb30f0c850a3e Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 24 Jul 2022 12:35:46 -0400 Subject: Backport pull request #7732 from jellyfin/release-10.8.z Fix to make web sockets close gracefully on server shutdown Authored-by: luke brown Merged-by: Cody Robibero Original-merge: ee22feb89a34632a4cc3a350733dd57c6be863ec --- Emby.Server.Implementations/ApplicationHost.cs | 46 +++++++++++++++++++++- .../HttpServer/WebSocketConnection.cs | 33 +++++++++++++++- .../Session/WebSocketController.cs | 19 ++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 32289625fe..bc55dc6b48 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. @@ -1232,5 +1232,49 @@ 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); + } + } + + // used for closing websockets + 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 b87f1bc226..818ccbb1b8 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 { /// /// 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,39 @@ 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; + } + + /// + 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); + } + + _socket.Dispose(); } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index d21b6a929c..1f3248f075 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; @@ -99,6 +99,23 @@ namespace Emby.Server.Implementations.Session foreach (var socket in _sockets) { socket.Closed -= OnConnectionClosed; + socket.Dispose(); + } + + _disposed = true; + } + + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + foreach (var socket in _sockets) + { + socket.Closed -= OnConnectionClosed; + await socket.DisposeAsync().ConfigureAwait(false); } _disposed = true; -- cgit v1.2.3 From bffda19bbc9d8fe95c864d7315a0c6f6a05d9d7d Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 24 Jul 2022 12:35:47 -0400 Subject: Backport pull request #8166 from jellyfin/release-10.8.z Bind @userid only when it's in the statement Authored-by: Joseph <1315585+joseph39@users.noreply.github.com> Merged-by: Claus Vium Original-merge: 057e8ef24061a969d4ca31b5b115e14d5ba76fe4 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 4361440d75..1b176e60d7 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -4934,6 +4934,7 @@ SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId) AND Type = @InternalPersonType)"); statement?.TryBind("@IsFavorite", query.IsFavorite.Value); statement?.TryBind("@InternalPersonType", typeof(Person).FullName); + statement?.TryBind("@UserId", query.User.InternalId); } if (!query.ItemId.Equals(default)) @@ -4988,11 +4989,6 @@ AND Type = @InternalPersonType)"); statement?.TryBind("@NameContains", "%" + query.NameContains + "%"); } - if (query.User != null) - { - statement?.TryBind("@UserId", query.User.InternalId); - } - return whereClauses; } -- cgit v1.2.3