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/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;
}
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;
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 2bda8d2905..a6f0b705dc 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/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 91acca0a75..15afde601d 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -4970,7 +4970,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.InputProtocol == MediaProtocol.Rtsp)
{
- inputModifier += " -rtsp_transport tcp -rtsp_transport udp -rtsp_flags prefer_tcp";
+ inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
}
if (!string.IsNullOrEmpty(state.InputAudioSync))
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].
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index c2ca233868..b4520ae48f 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.LogDebug("Disposing session controller synchronously {TypeName}", 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.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
+ await disposableAsync.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+ }
}
}