Skip to content

Commit bba2e5d

Browse files
authored
Fix race condition when finishing streaming and getting trailers (#876)
1 parent f99787e commit bba2e5d

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

src/Grpc.Net.Client/Internal/GrpcCall.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ private void FinishResponseAndCleanUp(Status status)
210210
/// <param name="status">The completed response status code.</param>
211211
public void ResponseStreamEnded(Status status)
212212
{
213+
// Set response finished immediately rather than set it in logic resumed
214+
// from the callTcs to avoid race condition.
215+
// e.g. response stream finished and then immediately call GetTrailers().
216+
ResponseFinished = true;
217+
213218
_callTcs.TrySetResult(status);
214219
}
215220

@@ -546,7 +551,7 @@ private async ValueTask RunCall(HttpRequestMessage request, TimeSpan? timeout)
546551
status = await CallTask.ConfigureAwait(false);
547552

548553
finished = FinishCall(request, diagnosticSourceEnabled, activity, status.Value);
549-
FinishResponseAndCleanUp(status.Value);
554+
Cleanup(status.Value);
550555
}
551556
}
552557
}

test/FunctionalTests/Client/StreamingTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using System;
2020
using System.Diagnostics;
2121
using System.IO;
22+
using System.Linq;
2223
using System.Threading;
2324
using System.Threading.Tasks;
2425
using Google.Protobuf;
@@ -424,6 +425,39 @@ async Task UnaryDeadlineExceeded(IAsyncStreamReader<DataMessage> requestStream,
424425
Assert.IsFalse(await call2.ResponseStream.MoveNext().DefaultTimeout());
425426
}
426427

428+
[Test]
429+
public async Task ServerStreaming_GetTrailersAndStatus_Success()
430+
{
431+
async Task ServerStreamingWithTrailers(DataMessage request, IServerStreamWriter<DataMessage> responseStream, ServerCallContext context)
432+
{
433+
await responseStream.WriteAsync(new DataMessage());
434+
context.ResponseTrailers.Add("my-trailer", "value");
435+
}
436+
437+
// Arrange
438+
var method = Fixture.DynamicGrpc.AddServerStreamingMethod<DataMessage, DataMessage>(ServerStreamingWithTrailers);
439+
440+
var channel = CreateChannel();
441+
442+
var client = TestClientFactory.Create(channel, method);
443+
444+
// Act
445+
var call = client.ServerStreamingCall(new DataMessage());
446+
447+
// Assert
448+
Assert.IsTrue(await call.ResponseStream.MoveNext().DefaultTimeout());
449+
450+
Assert.AreEqual(0, call.ResponseStream.Current.Data.Length);
451+
452+
Assert.IsFalse(await call.ResponseStream.MoveNext().DefaultTimeout());
453+
454+
var trailers = call.GetTrailers();
455+
Assert.AreEqual(1, trailers.Count);
456+
Assert.AreEqual("value", trailers.First(e => e.Key == "my-trailer").Value);
457+
458+
Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
459+
}
460+
427461
private static byte[] CreateTestData(int size)
428462
{
429463
var data = new byte[size];

0 commit comments

Comments
 (0)