mirror of
https://github.com/ppy/osu.git
synced 2026-05-13 20:33:35 +08:00
aace7f9523
RFC - Requires https://github.com/ppy/osu-server-spectator/pull/366 to actually function. Resolves https://github.com/ppy/osu/issues/35580 Resolves https://github.com/ppy/osu-server-spectator/issues/193 Resolves https://github.com/ppy/osu/issues/35586 Resolves https://github.com/ppy/osu-server-spectator/issues/362 Resolves https://github.com/ppy/osu/issues/37353 ## Outline This enables stateful reconnect for spectator-server endpoints, allowing ConnectionIds to be preserved for a short period and messages to be replayed on reconnect. In practice, this means short disconnects (<30s) should no longer: - Drop replays - Kick you out of multiplayer rooms - Trigger "user has come online" re-alerts. The following video demonstrates two of the above: https://github.com/user-attachments/assets/b9781f31-a8a1-410e-b0ac-65d3374a33d6 Stateful reconnect appears to kick in as long as the socket doesn't get disconnected, _not_ on subsequent re-connections. We have the timeout period set to SignalR's default of 30sec. I've been using the following to simulate a total link loss: ```sh #!/bin/bash DELAY=${1:-1} echo "Conditioning for $DELAY seconds..." sudo ip link set lo down sudo ss -K dst 127.0.0.2 > /dev/null sleep $DELAY sudo ip link set lo up ```
90 lines
3.5 KiB
C#
90 lines
3.5 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.SignalR.Client;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using osu.Framework;
|
|
using osu.Game.Online.API;
|
|
|
|
namespace osu.Game.Online
|
|
{
|
|
public class HubClientConnector : PersistentEndpointClientConnector, IHubClientConnector
|
|
{
|
|
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
|
|
|
|
public const string VERSION_HASH_HEADER = @"X-Osu-Version-Hash";
|
|
public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID";
|
|
|
|
/// <summary>
|
|
/// Invoked whenever a new hub connection is built, to configure it before it's started.
|
|
/// </summary>
|
|
public Action<HubConnection>? ConfigureConnection { get; set; }
|
|
|
|
private readonly string endpoint;
|
|
private readonly string versionHash;
|
|
|
|
/// <summary>
|
|
/// The current connection opened by this connector.
|
|
/// </summary>
|
|
public new HubConnection? CurrentConnection => ((HubClient?)base.CurrentConnection)?.Connection;
|
|
|
|
/// <summary>
|
|
/// Constructs a new <see cref="HubClientConnector"/>.
|
|
/// </summary>
|
|
/// <param name="clientName">The name of the client this connector connects for, used for logging.</param>
|
|
/// <param name="endpoint">The endpoint to the hub.</param>
|
|
/// <param name="api"> An API provider used to react to connection state changes.</param>
|
|
/// <param name="versionHash">The hash representing the current game version, used for verification purposes.</param>
|
|
public HubClientConnector(string clientName, string endpoint, IAPIProvider api, string versionHash)
|
|
: base(api)
|
|
{
|
|
ClientName = clientName;
|
|
this.endpoint = endpoint;
|
|
this.versionHash = versionHash;
|
|
|
|
// Automatically start these connections.
|
|
Start();
|
|
}
|
|
|
|
protected override Task<PersistentEndpointClient> BuildConnectionAsync(CancellationToken cancellationToken)
|
|
{
|
|
var builder = new HubConnectionBuilder()
|
|
.WithUrl(endpoint, options =>
|
|
{
|
|
// Configuring proxies is not supported on iOS, see https://github.com/xamarin/xamarin-macios/issues/14632.
|
|
if (RuntimeInfo.OS != RuntimeInfo.Platform.iOS)
|
|
options.Proxy = HttpClient.DefaultProxy;
|
|
|
|
options.AccessTokenProvider = () => Task.FromResult<string?>(API.AccessToken);
|
|
options.Headers.Add(VERSION_HASH_HEADER, versionHash);
|
|
options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString());
|
|
});
|
|
|
|
builder.WithStatefulReconnect();
|
|
|
|
builder.AddMessagePackProtocol(options =>
|
|
{
|
|
options.SerializerOptions = SignalRUnionWorkaroundResolver.OPTIONS;
|
|
});
|
|
|
|
var newConnection = builder.Build();
|
|
|
|
ConfigureConnection?.Invoke(newConnection);
|
|
|
|
return Task.FromResult((PersistentEndpointClient)new HubClient(newConnection));
|
|
}
|
|
|
|
async Task IHubClientConnector.Disconnect()
|
|
{
|
|
await Disconnect().ConfigureAwait(false);
|
|
API.Logout();
|
|
}
|
|
|
|
protected override string ClientName { get; }
|
|
}
|
|
}
|