Skip to content

Commit bd11e95

Browse files
BrennanConroyanalogrelay
authored andcommitted
Fix flaky HubConnectionHandler test (#18388)
1 parent bba3430 commit bd11e95

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs

+82
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.IO;
9+
using System.IO.Pipelines;
910
using System.Linq;
1011
using System.Security.Claims;
1112
using System.Text;
13+
using System.Threading;
1214
using System.Threading.Tasks;
1315
using MessagePack;
1416
using MessagePack.Formatters;
@@ -2797,6 +2799,78 @@ public async Task ReceivingMessagesPreventsConnectionTimeoutFromOccuring()
27972799
}
27982800
}
27992801

2802+
internal class PipeReaderWrapper : PipeReader
2803+
{
2804+
private readonly PipeReader _originalPipeReader;
2805+
private TaskCompletionSource<object> _waitForRead;
2806+
private object _lock = new object();
2807+
2808+
public PipeReaderWrapper(PipeReader pipeReader)
2809+
{
2810+
_originalPipeReader = pipeReader;
2811+
_waitForRead = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
2812+
}
2813+
2814+
public override void AdvanceTo(SequencePosition consumed) =>
2815+
_originalPipeReader.AdvanceTo(consumed);
2816+
2817+
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) =>
2818+
_originalPipeReader.AdvanceTo(consumed, examined);
2819+
2820+
public override void CancelPendingRead() =>
2821+
_originalPipeReader.CancelPendingRead();
2822+
2823+
public override void Complete(Exception exception = null) =>
2824+
_originalPipeReader.Complete(exception);
2825+
2826+
public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default)
2827+
{
2828+
lock (_lock)
2829+
{
2830+
_waitForRead.SetResult(null);
2831+
}
2832+
2833+
try
2834+
{
2835+
return await _originalPipeReader.ReadAsync(cancellationToken);
2836+
}
2837+
finally
2838+
{
2839+
lock (_lock)
2840+
{
2841+
_waitForRead = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
2842+
}
2843+
}
2844+
}
2845+
2846+
public override bool TryRead(out ReadResult result) =>
2847+
_originalPipeReader.TryRead(out result);
2848+
2849+
public Task WaitForReadStart()
2850+
{
2851+
lock (_lock)
2852+
{
2853+
return _waitForRead.Task;
2854+
}
2855+
}
2856+
}
2857+
2858+
internal class CustomDuplex : IDuplexPipe
2859+
{
2860+
private readonly IDuplexPipe _originalDuplexPipe;
2861+
public readonly PipeReaderWrapper WrappedPipeReader;
2862+
2863+
public CustomDuplex(IDuplexPipe duplexPipe)
2864+
{
2865+
_originalDuplexPipe = duplexPipe;
2866+
WrappedPipeReader = new PipeReaderWrapper(_originalDuplexPipe.Input);
2867+
}
2868+
2869+
public PipeReader Input => WrappedPipeReader;
2870+
2871+
public PipeWriter Output => _originalDuplexPipe.Output;
2872+
}
2873+
28002874
[Fact]
28012875
public async Task HubMethodInvokeDoesNotCountTowardsClientTimeout()
28022876
{
@@ -2813,6 +2887,9 @@ public async Task HubMethodInvokeDoesNotCountTowardsClientTimeout()
28132887

28142888
using (var client = new TestClient(new JsonHubProtocol()))
28152889
{
2890+
var customDuplex = new CustomDuplex(client.Connection.Transport);
2891+
client.Connection.Transport = customDuplex;
2892+
28162893
var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
28172894
// This starts the timeout logic
28182895
await client.SendHubMessageAsync(PingMessage.Instance);
@@ -2829,6 +2906,11 @@ public async Task HubMethodInvokeDoesNotCountTowardsClientTimeout()
28292906

28302907
await hubMethodTask.OrTimeout();
28312908

2909+
// There is a small window when the hub method finishes and the timer starts again
2910+
// So we need to delay a little before ticking the heart beat.
2911+
// We do this by waiting until we know the HubConnectionHandler code is in pipe.ReadAsync()
2912+
await customDuplex.WrappedPipeReader.WaitForReadStart().OrTimeout();
2913+
28322914
// Tick heartbeat again now that we're outside of the hub method
28332915
client.TickHeartbeat();
28342916

0 commit comments

Comments
 (0)