Skip to content

Commit f932b1d

Browse files
committed
Stabilize Go Live cross-tab session sync
1 parent 1f6992c commit f932b1d

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

src/PrompterOne.Shared/AppShell/Services/GoLiveSessionService.CrossTab.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ namespace PrompterOne.Shared.Services;
22

33
internal sealed partial class GoLiveSessionService
44
{
5+
private static readonly TimeSpan[] StateRequestCatchUpDelays =
6+
[
7+
TimeSpan.FromMilliseconds(250),
8+
TimeSpan.FromMilliseconds(750)
9+
];
10+
511
private readonly SemaphoreSlim _crossTabStartGate = new(1, 1);
612

13+
private CancellationTokenSource? _stateRequestCatchUpCancellationTokenSource;
714
private bool _crossTabReady;
815
private bool _disposed;
916
private bool _stateRequestPublished;
@@ -22,6 +29,21 @@ public async Task StartCrossTabSyncAsync(CancellationToken cancellationToken = d
2229
}
2330

2431
public async Task RequestCrossTabStateAsync(CancellationToken cancellationToken = default)
32+
{
33+
await PublishCrossTabStateRequestAsync(cancellationToken);
34+
ScheduleCrossTabStateCatchUpRequests();
35+
}
36+
37+
public async Task RequestCrossTabStateCatchUpAsync(CancellationToken cancellationToken = default)
38+
{
39+
foreach (var delay in StateRequestCatchUpDelays)
40+
{
41+
await Task.Delay(delay, cancellationToken);
42+
await PublishCrossTabStateRequestAsync(cancellationToken);
43+
}
44+
}
45+
46+
private async Task PublishCrossTabStateRequestAsync(CancellationToken cancellationToken = default)
2547
{
2648
await EnsureCrossTabReadyAsync(cancellationToken);
2749
await _crossTabMessageBus.PublishAsync(
@@ -38,10 +60,38 @@ public void Dispose()
3860
}
3961

4062
_disposed = true;
63+
_stateRequestCatchUpCancellationTokenSource?.Cancel();
64+
_stateRequestCatchUpCancellationTokenSource?.Dispose();
65+
_stateRequestCatchUpCancellationTokenSource = null;
4166
_crossTabMessageBus.MessageReceived -= HandleCrossTabMessageAsync;
4267
_crossTabStartGate.Dispose();
4368
}
4469

70+
private void ScheduleCrossTabStateCatchUpRequests()
71+
{
72+
if (_disposed)
73+
{
74+
return;
75+
}
76+
77+
_stateRequestCatchUpCancellationTokenSource?.Cancel();
78+
_stateRequestCatchUpCancellationTokenSource?.Dispose();
79+
_stateRequestCatchUpCancellationTokenSource = new CancellationTokenSource();
80+
81+
_ = RequestCrossTabStateCatchUpInBackgroundAsync(_stateRequestCatchUpCancellationTokenSource.Token);
82+
}
83+
84+
private async Task RequestCrossTabStateCatchUpInBackgroundAsync(CancellationToken cancellationToken)
85+
{
86+
try
87+
{
88+
await RequestCrossTabStateCatchUpAsync(cancellationToken);
89+
}
90+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
91+
{
92+
}
93+
}
94+
4595
private async Task EnsureCrossTabReadyAsync(CancellationToken cancellationToken)
4696
{
4797
if (_crossTabReady)

tests/PrompterOne.Web.Tests/GoLive/GoLiveCrossTabTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ public async Task GoLiveSessionService_RequestCrossTabStateAsync_PublishesRepeat
9191
Assert.Equal(2, requestMessages.Count);
9292
}
9393

94+
[Test]
95+
public async Task GoLiveSessionService_RequestCrossTabStateCatchUpAsync_RepeatsStateRequests()
96+
{
97+
var harness = TestHarnessFactory.Create(this);
98+
var service = Services.GetRequiredService<GoLiveSessionService>();
99+
100+
var publishCountBeforeCatchUp = harness.JsRuntime.InvocationRecords.Count(record =>
101+
string.Equals(record.Identifier, CrossTabInteropMethodNames.Publish, StringComparison.Ordinal));
102+
103+
await service.RequestCrossTabStateCatchUpAsync();
104+
105+
var catchUpRequests = harness.JsRuntime.InvocationRecords
106+
.Skip(publishCountBeforeCatchUp)
107+
.Where(record => string.Equals(record.Identifier, CrossTabInteropMethodNames.Publish, StringComparison.Ordinal))
108+
.Select(record => Assert.IsType<CrossTabMessageEnvelope>(record.Arguments[1]))
109+
.Where(message => string.Equals(message.MessageType, CrossTabMessageTypes.GoLiveSessionRequested, StringComparison.Ordinal))
110+
.ToList();
111+
112+
Assert.Equal(2, catchUpRequests.Count);
113+
}
114+
94115
[Test]
95116
public async Task MainLayout_UpdatesGoLiveIndicator_WhenAnotherTabChangesGoLiveSessionState()
96117
{

0 commit comments

Comments
 (0)