Retry transient provider bootstrap connections
This commit is contained in:
parent
63dfe1f792
commit
002fbdf357
4 changed files with 119 additions and 29 deletions
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GameCult.Caching;
|
||||
|
|
@ -190,16 +191,27 @@ namespace GameCult.Eve.UnityScene
|
|||
if (_bootstrapped)
|
||||
return;
|
||||
|
||||
var elapsed = Stopwatch.StartNew();
|
||||
ResolveAdvertisement(forceRefresh: true);
|
||||
TraceStartup("advertisement", elapsed);
|
||||
RefreshSurface();
|
||||
TraceStartup("surface", elapsed);
|
||||
RefreshAssetCatalog();
|
||||
TraceStartup("asset-catalog-and-bundles", elapsed);
|
||||
EnsureLiveSubscriptions();
|
||||
TraceStartup("subscriptions", elapsed);
|
||||
_bootstrapped = true;
|
||||
try
|
||||
{
|
||||
_snapshot ??= CreateSnapshotSession();
|
||||
var elapsed = Stopwatch.StartNew();
|
||||
ResolveAdvertisement(forceRefresh: true);
|
||||
TraceStartup("advertisement", elapsed);
|
||||
RefreshSurface();
|
||||
TraceStartup("surface", elapsed);
|
||||
RefreshAssetCatalog();
|
||||
TraceStartup("asset-catalog-and-bundles", elapsed);
|
||||
EnsureLiveSubscriptions();
|
||||
TraceStartup("subscriptions", elapsed);
|
||||
_bootstrapped = true;
|
||||
}
|
||||
catch (Exception error) when (error is IOException || error is SocketException || error is TimeoutException)
|
||||
{
|
||||
ResetSnapshotSession();
|
||||
throw new InvalidOperationException(
|
||||
$"CultMesh provider '{_providerId}' bootstrap transport failed; the snapshot lane was reset for retry.",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TraceStartup(string phase, Stopwatch elapsed)
|
||||
|
|
@ -510,7 +522,19 @@ namespace GameCult.Eve.UnityScene
|
|||
})
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
_snapshot = CultMesh.SnapshotSession(
|
||||
_snapshot = CreateSnapshotSession();
|
||||
_meshClient = new CultMeshClient(new CultMeshClientOptions
|
||||
{
|
||||
RendezvousEndpoints = new[] { _endpoint },
|
||||
RealtimeConnectors = new ICultMeshRealtimeTransportConnector[]
|
||||
{
|
||||
new CultMeshNativeQuicRealtimeTransportConnector()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private CultMeshSnapshotSession CreateSnapshotSession() =>
|
||||
CultMesh.SnapshotSession(
|
||||
_endpoint,
|
||||
new CultMeshSnapshotRequestOptions
|
||||
{
|
||||
|
|
@ -523,14 +547,11 @@ namespace GameCult.Eve.UnityScene
|
|||
RudpMaxFragmentBytes = 1024
|
||||
},
|
||||
_networkRegistry);
|
||||
_meshClient = new CultMeshClient(new CultMeshClientOptions
|
||||
{
|
||||
RendezvousEndpoints = new[] { _endpoint },
|
||||
RealtimeConnectors = new ICultMeshRealtimeTransportConnector[]
|
||||
{
|
||||
new CultMeshNativeQuicRealtimeTransportConnector()
|
||||
}
|
||||
});
|
||||
|
||||
private void ResetSnapshotSession()
|
||||
{
|
||||
_snapshot?.Dispose();
|
||||
_snapshot = null;
|
||||
}
|
||||
|
||||
private void ResolveAdvertisement(bool forceRefresh = false)
|
||||
|
|
|
|||
|
|
@ -239,8 +239,18 @@ namespace GameCult.Eve.UnityScene
|
|||
_transport.SurfaceDocumentAvailable += OnSurfaceDocumentAvailable;
|
||||
_transport.AssetManifestDocumentAvailable += OnAssetManifestDocumentAvailable;
|
||||
_transport.CommandReceiptAvailable += OnCommandReceiptAvailable;
|
||||
_transport.Connect();
|
||||
_connected = true;
|
||||
try
|
||||
{
|
||||
_transport.Connect();
|
||||
_connected = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_transport.SurfaceDocumentAvailable -= OnSurfaceDocumentAvailable;
|
||||
_transport.AssetManifestDocumentAvailable -= OnAssetManifestDocumentAvailable;
|
||||
_transport.CommandReceiptAvailable -= OnCommandReceiptAvailable;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GameCult.Caching;
|
||||
|
|
@ -981,6 +982,37 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(transport.RefreshCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LiveProviderBridgeRollsBackSubscriptionsWhenTransportConnectFails()
|
||||
{
|
||||
var transport = new FakeLiveProviderTransport(
|
||||
new EveUnitySceneProviderSurfaceDocument(
|
||||
PlayableArpgDocument(),
|
||||
Advertisement("aetheria.daemon.game"),
|
||||
"cultmesh://aetheria/eve/surfaces/aetheria.daemon.game",
|
||||
1),
|
||||
new EveUnityPlayableWorldAssetManifestDocument(
|
||||
"cultmesh://aetheria/assets/manifest",
|
||||
Array.Empty<EveUnityPlayableWorldAssetManifestDocumentEntry>(),
|
||||
"aetheria"))
|
||||
{
|
||||
FailNextConnect = true
|
||||
};
|
||||
using var bridge = new EveUnitySceneLiveProviderBridge(transport);
|
||||
|
||||
Assert.Throws<IOException>(() => bridge.Connect());
|
||||
Assert.That(transport.SurfaceSubscriberCount, Is.Zero);
|
||||
Assert.That(transport.AssetManifestSubscriberCount, Is.Zero);
|
||||
Assert.That(transport.ReceiptSubscriberCount, Is.Zero);
|
||||
|
||||
bridge.Connect();
|
||||
|
||||
Assert.That(transport.ConnectCount, Is.EqualTo(2));
|
||||
Assert.That(transport.SurfaceSubscriberCount, Is.EqualTo(1));
|
||||
Assert.That(transport.AssetManifestSubscriberCount, Is.EqualTo(1));
|
||||
Assert.That(transport.ReceiptSubscriberCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayableWorldClientHostMountsInterfaceProviderWithoutProviderTypes()
|
||||
{
|
||||
|
|
@ -2817,6 +2849,10 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
|
||||
private sealed class FakeLiveProviderTransport : IEveUnitySceneLiveProviderTransport
|
||||
{
|
||||
private Action<EveUnitySceneProviderSurfaceDocument>? _surfaceDocumentAvailable;
|
||||
private Action<EveUnityPlayableWorldAssetManifestDocument>? _assetManifestDocumentAvailable;
|
||||
private Action<EveUnitySceneCommandReceipt>? _commandReceiptAvailable;
|
||||
|
||||
public FakeLiveProviderTransport(
|
||||
EveUnitySceneProviderSurfaceDocument surfaceDocument,
|
||||
EveUnityPlayableWorldAssetManifestDocument assetManifestDocument)
|
||||
|
|
@ -2843,15 +2879,38 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
|
||||
public int RefreshCount { get; private set; }
|
||||
|
||||
public event Action<EveUnitySceneProviderSurfaceDocument>? SurfaceDocumentAvailable;
|
||||
public bool FailNextConnect { get; set; }
|
||||
|
||||
public event Action<EveUnityPlayableWorldAssetManifestDocument>? AssetManifestDocumentAvailable;
|
||||
public int SurfaceSubscriberCount => _surfaceDocumentAvailable?.GetInvocationList().Length ?? 0;
|
||||
|
||||
public event Action<EveUnitySceneCommandReceipt>? CommandReceiptAvailable;
|
||||
public int AssetManifestSubscriberCount => _assetManifestDocumentAvailable?.GetInvocationList().Length ?? 0;
|
||||
|
||||
public int ReceiptSubscriberCount => _commandReceiptAvailable?.GetInvocationList().Length ?? 0;
|
||||
|
||||
public event Action<EveUnitySceneProviderSurfaceDocument>? SurfaceDocumentAvailable
|
||||
{
|
||||
add => _surfaceDocumentAvailable += value;
|
||||
remove => _surfaceDocumentAvailable -= value;
|
||||
}
|
||||
|
||||
public event Action<EveUnityPlayableWorldAssetManifestDocument>? AssetManifestDocumentAvailable
|
||||
{
|
||||
add => _assetManifestDocumentAvailable += value;
|
||||
remove => _assetManifestDocumentAvailable -= value;
|
||||
}
|
||||
|
||||
public event Action<EveUnitySceneCommandReceipt>? CommandReceiptAvailable
|
||||
{
|
||||
add => _commandReceiptAvailable += value;
|
||||
remove => _commandReceiptAvailable -= value;
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
ConnectCount++;
|
||||
if (!FailNextConnect) return;
|
||||
FailNextConnect = false;
|
||||
throw new IOException("transient transport failure");
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
|
|
@ -2872,18 +2931,18 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
public void PublishSurface(EveUnitySceneProviderSurfaceDocument document)
|
||||
{
|
||||
CurrentSurfaceDocument = document;
|
||||
SurfaceDocumentAvailable?.Invoke(document);
|
||||
_surfaceDocumentAvailable?.Invoke(document);
|
||||
}
|
||||
|
||||
public void PublishAssetManifest(EveUnityPlayableWorldAssetManifestDocument document)
|
||||
{
|
||||
CurrentAssetManifestDocument = document;
|
||||
AssetManifestDocumentAvailable?.Invoke(document);
|
||||
_assetManifestDocumentAvailable?.Invoke(document);
|
||||
}
|
||||
|
||||
public void PublishReceipt(EveUnitySceneCommandReceipt receipt)
|
||||
{
|
||||
CommandReceiptAvailable?.Invoke(receipt);
|
||||
_commandReceiptAvailable?.Invoke(receipt);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.3.97",
|
||||
"version": "0.3.98",
|
||||
"unity": "6000.0",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
"org.gamecult.cultlib": "1.0.36",
|
||||
"org.gamecult.cultlib": "1.0.38",
|
||||
"org.gamecult.eve.plugin-fields": "0.2.3",
|
||||
"org.gamecult.eve.surface": "0.2.4",
|
||||
"com.unity.mathematics": "1.3.2",
|
||||
|
|
|
|||
Reference in a new issue