fix(session): tie directory ownership to the live connection
Ownership is claimed with a Lua check-and-set keyed on the instance holding the websocket, routing prefers a live controller over a local copy, the session list deduplicates by owner, removal is ownership-checked, undelivered routed messages surface, single-session lookups stop scanning the keyspace and directory writes leave the request path bounded by a timeout.
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Session;
|
||||
|
||||
@@ -15,6 +18,7 @@ namespace Emby.Server.Implementations.Session;
|
||||
public sealed class RemoteSessionController : ISessionController
|
||||
{
|
||||
private readonly IPodMessageBus _bus;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _ownerPod;
|
||||
private readonly string _sessionId;
|
||||
|
||||
@@ -22,27 +26,27 @@ public sealed class RemoteSessionController : ISessionController
|
||||
/// Initializes a new instance of the <see cref="RemoteSessionController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bus">The cross-instance bus.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="ownerPod">The instance holding the connection.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="isSessionActive">Whether the owner reported the session as active.</param>
|
||||
/// <param name="supportsMediaControl">Whether the owner reported the session as controllable.</param>
|
||||
public RemoteSessionController(IPodMessageBus bus, string ownerPod, string sessionId, bool isSessionActive, bool supportsMediaControl)
|
||||
public RemoteSessionController(IPodMessageBus bus, ILogger logger, string ownerPod, string sessionId, bool supportsMediaControl)
|
||||
{
|
||||
_bus = bus;
|
||||
_logger = logger;
|
||||
_ownerPod = ownerPod;
|
||||
_sessionId = sessionId;
|
||||
IsSessionActive = isSessionActive;
|
||||
SupportsMediaControl = supportsMediaControl;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSessionActive { get; }
|
||||
public bool IsSessionActive => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsMediaControl { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken)
|
||||
public async Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
var routed = new RoutedSessionMessage
|
||||
{
|
||||
@@ -52,14 +56,21 @@ public sealed class RemoteSessionController : ISessionController
|
||||
Data = JsonSerializer.Serialize(data, JsonDefaults.Options)
|
||||
};
|
||||
|
||||
_bus.Publish(
|
||||
var delivered = await _bus.PublishAsync(
|
||||
_ownerPod,
|
||||
new PodMessage
|
||||
{
|
||||
Kind = RoutedSessionMessage.Kind,
|
||||
Payload = JsonSerializer.Serialize(routed, JsonDefaults.Options)
|
||||
});
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Task.CompletedTask;
|
||||
if (delivered == 0)
|
||||
{
|
||||
_logger.LogWarning("Instance {OwnerPod} holds session {SessionId} but is not listening; the {MessageType} message was not delivered.", _ownerPod, _sessionId, name);
|
||||
|
||||
throw new ResourceNotFoundException(
|
||||
string.Format(CultureInfo.InvariantCulture, "The instance holding session {0} is unreachable.", _sessionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user