9e66708d87
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.
77 lines
2.7 KiB
C#
77 lines
2.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Stands in for the websocket of a session another instance holds: messages are forwarded to that
|
|
/// instance, which writes them to the connection it owns.
|
|
/// </summary>
|
|
public sealed class RemoteSessionController : ISessionController
|
|
{
|
|
private readonly IPodMessageBus _bus;
|
|
private readonly ILogger _logger;
|
|
private readonly string _ownerPod;
|
|
private readonly string _sessionId;
|
|
|
|
/// <summary>
|
|
/// 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="supportsMediaControl">Whether the owner reported the session as controllable.</param>
|
|
public RemoteSessionController(IPodMessageBus bus, ILogger logger, string ownerPod, string sessionId, bool supportsMediaControl)
|
|
{
|
|
_bus = bus;
|
|
_logger = logger;
|
|
_ownerPod = ownerPod;
|
|
_sessionId = sessionId;
|
|
SupportsMediaControl = supportsMediaControl;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool IsSessionActive => true;
|
|
|
|
/// <inheritdoc />
|
|
public bool SupportsMediaControl { get; }
|
|
|
|
/// <inheritdoc />
|
|
public async Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken)
|
|
{
|
|
var routed = new RoutedSessionMessage
|
|
{
|
|
SessionId = _sessionId,
|
|
MessageType = name,
|
|
MessageId = messageId,
|
|
Data = JsonSerializer.Serialize(data, JsonDefaults.Options)
|
|
};
|
|
|
|
var delivered = await _bus.PublishAsync(
|
|
_ownerPod,
|
|
new PodMessage
|
|
{
|
|
Kind = RoutedSessionMessage.Kind,
|
|
Payload = JsonSerializer.Serialize(routed, JsonDefaults.Options)
|
|
},
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|