public async Task Unary_AttemptsGreaterThanDefaultClientLimit_LimitedAttemptsMade(int hedgingDelay) { var callCount = 0; Task <DataMessage> UnaryFailure(DataMessage request, ServerCallContext context) { Interlocked.Increment(ref callCount); return(Task.FromException <DataMessage>(new RpcException(new Status(StatusCode.Unavailable, "")))); } using var httpEventListener = new HttpEventSourceListener(LoggerFactory); // Arrange var method = Fixture.DynamicGrpc.AddUnaryMethod <DataMessage, DataMessage>(UnaryFailure); var channel = CreateChannel(serviceConfig: ServiceConfigHelpers.CreateRetryServiceConfig(maxAttempts: 10, initialBackoff: TimeSpan.FromMilliseconds(hedgingDelay))); var client = TestClientFactory.Create(channel, method); // Act var call = client.UnaryCall(new DataMessage()); // Assert var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.ResponseAsync).DefaultTimeout(); Assert.AreEqual(StatusCode.Unavailable, ex.StatusCode); Assert.AreEqual(StatusCode.Unavailable, call.GetStatus().StatusCode); Assert.AreEqual(5, callCount); AssertHasLog(LogLevel.Debug, "MaxAttemptsLimited", "The method has 10 attempts specified in the service config. The number of attempts has been limited by channel configuration to 5."); AssertHasLog(LogLevel.Debug, "CallCommited", "Call commited. Reason: ExceededAttemptCount"); }
public async Task SendUnauthenticatedRequest_UnauthenticatedErrorResponse() { using var httpEventListener = new HttpEventSourceListener(LoggerFactory); SetExpectedErrorsFilter(writeContext => { // This error can happen if the server returns an unauthorized response // before the client has finished sending the request content. if (writeContext.LoggerName == "Grpc.Net.Client.Internal.GrpcCall" && writeContext.EventId.Name == "ErrorSendingMessage" && writeContext.Exception is OperationCanceledException) { return(true); } return(false); }); // Arrage var channel = CreateGrpcWebChannel(); var client = new AuthorizedGreeter.AuthorizedGreeterClient(channel); // Act var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => client.SayHelloAsync(new HelloRequest { Name = "test" }).ResponseAsync).DefaultTimeout(); // Assert Assert.AreEqual(StatusCode.Unauthenticated, ex.StatusCode); AssertHasLog(LogLevel.Information, "GrpcStatusError", "Call failed with gRPC error status. Status code: 'Unauthenticated', Message: 'Bad gRPC response. HTTP status code: 401'."); }
public static async Task <int> Main(string[] args) { var rootCommand = new RootCommand(); rootCommand.AddOption(new Option <string>(new string[] { "--client_type", nameof(ClientOptions.ClientType) }, () => "httpclient")); rootCommand.AddOption(new Option <string>(new string[] { "--server_host", nameof(ClientOptions.ServerHost) }) { Required = true }); rootCommand.AddOption(new Option <string>(new string[] { "--server_host_override", nameof(ClientOptions.ServerHostOverride) })); rootCommand.AddOption(new Option <int>(new string[] { "--server_port", nameof(ClientOptions.ServerPort) }) { Required = true }); rootCommand.AddOption(new Option <string>(new string[] { "--test_case", nameof(ClientOptions.TestCase) }) { Required = true }); rootCommand.AddOption(new Option <bool>(new string[] { "--use_tls", nameof(ClientOptions.UseTls) })); rootCommand.AddOption(new Option <bool>(new string[] { "--use_test_ca", nameof(ClientOptions.UseTestCa) })); rootCommand.AddOption(new Option <string>(new string[] { "--default_service_account", nameof(ClientOptions.DefaultServiceAccount) })); rootCommand.AddOption(new Option <string>(new string[] { "--oauth_scope", nameof(ClientOptions.OAuthScope) })); rootCommand.AddOption(new Option <string>(new string[] { "--service_account_key_file", nameof(ClientOptions.ServiceAccountKeyFile) })); rootCommand.AddOption(new Option <string>(new string[] { "--grpc_web_mode", nameof(ClientOptions.GrpcWebMode) })); rootCommand.AddOption(new Option <bool>(new string[] { "--use_winhttp", nameof(ClientOptions.UseWinHttp) })); rootCommand.AddOption(new Option <bool>(new string[] { "--use_http3", nameof(ClientOptions.UseHttp3) })); rootCommand.Handler = CommandHandler.Create <ClientOptions>(async(options) => { var runtimeVersion = typeof(object).Assembly.GetCustomAttribute <AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown"; Console.WriteLine("Runtime: " + runtimeVersion); Console.WriteLine("Use TLS: " + options.UseTls); Console.WriteLine("Use WinHttp: " + options.UseWinHttp); Console.WriteLine("Use HTTP/3: " + options.UseHttp3); Console.WriteLine("Use GrpcWebMode: " + options.GrpcWebMode); Console.WriteLine("Use Test CA: " + options.UseTestCa); Console.WriteLine("Client type: " + options.ClientType); Console.WriteLine("Server host: " + options.ServerHost); Console.WriteLine("Server port: " + options.ServerPort); var services = new ServiceCollection(); services.AddLogging(configure => { configure.SetMinimumLevel(LogLevel.Trace); configure.AddConsole(loggerOptions => loggerOptions.IncludeScopes = true); }); using var serviceProvider = services.BuildServiceProvider(); var loggerFactory = serviceProvider.GetRequiredService <ILoggerFactory>(); using var httpEventListener = new HttpEventSourceListener(loggerFactory); var interopClient = new InteropClient(options, loggerFactory); await interopClient.Run(); }); return(await rootCommand.InvokeAsync(args)); }
public async Task UseUrls_HelloWorld_ClientSuccess() { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseKestrel(o => { o.ConfigureEndpointDefaults(listenOptions => { listenOptions.Protocols = Core.HttpProtocols.Http3; }); }) .UseUrls("https://127.0.0.1:0") .Configure(app => { app.Run(async context => { await context.Response.WriteAsync("hello, world"); }); }); }) .ConfigureServices(AddTestLogging); using (var host = builder.Build()) using (var client = CreateClient()) { await host.StartAsync(); var request = new HttpRequestMessage(HttpMethod.Get, $"https://127.0.0.1:{host.GetPort()}/"); request.Version = HttpVersion.Version30; request.VersionPolicy = HttpVersionPolicy.RequestVersionExact; // Act var response = await client.SendAsync(request); // Assert response.EnsureSuccessStatusCode(); Assert.Equal(HttpVersion.Version30, response.Version); var responseText = await response.Content.ReadAsStringAsync(); Assert.Equal("hello, world", responseText); await host.StopAsync(); } }
private InteropClient(ClientOptions options) { this.options = options; var services = new ServiceCollection(); services.AddLogging(configure => { configure.SetMinimumLevel(LogLevel.Trace); configure.AddConsole(loggerOptions => { #pragma warning disable CS0618 // Type or member is obsolete loggerOptions.IncludeScopes = true; loggerOptions.DisableColors = true; #pragma warning restore CS0618 // Type or member is obsolete }); }); serviceProvider = services.BuildServiceProvider(); loggerFactory = serviceProvider.GetRequiredService <ILoggerFactory>(); httpEventSource = new HttpEventSourceListener(loggerFactory); }
public async Task ClientStream_HttpClientWithTimeout_Success() { SetExpectedErrorsFilter(writeContext => { if (writeContext.LoggerName == "Grpc.Net.Client.Internal.GrpcCall" && writeContext.Exception is TaskCanceledException) { return(true); } if (writeContext.LoggerName == "Grpc.Net.Client.Internal.GrpcCall" && writeContext.EventId.Name == "WriteMessageError" && writeContext.Exception is InvalidOperationException && writeContext.Exception.Message == "Can't write the message because the call is complete.") { return(true); } if (writeContext.LoggerName == TestConstants.ServerCallHandlerTestName) { return(true); } return(false); }); using var httpEventListener = new HttpEventSourceListener(LoggerFactory); var tcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously); async Task <DataComplete> ClientStreamedData(IAsyncStreamReader <DataMessage> requestStream, ServerCallContext context) { Logger.LogInformation("Server started"); context.CancellationToken.Register(() => { Logger.LogInformation("Server completed TCS"); tcs.SetResult(null); }); var total = 0L; await foreach (var message in requestStream.ReadAllAsync()) { total += message.Data.Length; if (message.ServerDelayMilliseconds > 0) { await Task.Delay(message.ServerDelayMilliseconds); } } return(new DataComplete { Size = total }); } // Arrange var data = CreateTestData(1024); // 1 KB var method = Fixture.DynamicGrpc.AddClientStreamingMethod <DataMessage, DataComplete>(ClientStreamedData, "ClientStreamedDataTimeout"); var httpClient = Fixture.CreateClient(); httpClient.Timeout = TimeSpan.FromSeconds(0.5); var channel = GrpcChannel.ForAddress(httpClient.BaseAddress !, new GrpcChannelOptions { HttpClient = httpClient, LoggerFactory = LoggerFactory }); var client = TestClientFactory.Create(channel, method); var dataMessage = new DataMessage { Data = ByteString.CopyFrom(data) }; // Act var call = client.ClientStreamingCall(); Logger.LogInformation("Client writing message"); await call.RequestStream.WriteAsync(dataMessage).DefaultTimeout(); Logger.LogInformation("Client waiting for TCS to complete"); await tcs.Task.DefaultTimeout(); var ex = await ExceptionAssert.ThrowsAsync <RpcException>(() => call.RequestStream.WriteAsync(dataMessage)).DefaultTimeout(); // Assert Assert.AreEqual(StatusCode.Cancelled, ex.StatusCode); Assert.AreEqual(StatusCode.Cancelled, call.GetStatus().StatusCode); AssertHasLog(LogLevel.Information, "GrpcStatusError", "Call failed with gRPC error status. Status code: 'Cancelled', Message: ''."); await TestHelpers.AssertIsTrueRetryAsync( () => HasLog(LogLevel.Error, "ErrorExecutingServiceMethod", "Error when executing service method 'ClientStreamedDataTimeout'."), "Wait for server error so it doesn't impact other tests.").DefaultTimeout(); }
public async Task MultipleMessagesFromOneClient_SuccessResponses() { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var ms = new MemoryStream(); MessageHelpers.WriteMessage(ms, new ChatMessage { Name = "John", Message = "Hello Jill" }); var streamingContent = new StreamingContent(); var httpRequest = GrpcHttpHelper.Create("Chat.Chatter/Chat"); httpRequest.Content = streamingContent; // Act var responseTask = Fixture.Client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead); // Assert Assert.IsFalse(responseTask.IsCompleted, "Server should wait for first message from client"); var requestStream = await streamingContent.GetRequestStreamAsync().DefaultTimeout(); Logger.LogInformation("Client sending message"); await requestStream.WriteAsync(ms.ToArray()).AsTask().DefaultTimeout(); await requestStream.FlushAsync().DefaultTimeout(); Logger.LogInformation("Client waiting for response"); var response = await responseTask.DefaultTimeout(); response.AssertIsSuccessfulGrpcRequest(); var responseStream = await response.Content.ReadAsStreamAsync().DefaultTimeout(); var pipeReader = PipeReader.Create(responseStream); Logger.LogInformation("Client reading message"); var message1Task = MessageHelpers.AssertReadStreamMessageAsync <ChatMessage>(pipeReader); var message1 = await message1Task.DefaultTimeout(); Assert.AreEqual("John", message1 !.Name); Assert.AreEqual("Hello Jill", message1.Message); Logger.LogInformation("Client starting reading message"); var message2Task = MessageHelpers.AssertReadStreamMessageAsync <ChatMessage>(pipeReader); Assert.IsFalse(message2Task.IsCompleted, "Server is waiting for messages from client"); ms = new MemoryStream(); MessageHelpers.WriteMessage(ms, new ChatMessage { Name = "Jill", Message = "Hello John" }); Logger.LogInformation("Client sending message"); await requestStream.WriteAsync(ms.ToArray()).AsTask().DefaultTimeout(); await requestStream.FlushAsync().DefaultTimeout(); Logger.LogInformation("Client waiting for reading message"); var message2 = await message2Task.DefaultTimeout(); Assert.AreEqual("Jill", message2 !.Name); Assert.AreEqual("Hello John", message2.Message); var finishedTask = MessageHelpers.AssertReadStreamMessageAsync <ChatMessage>(pipeReader); Assert.IsFalse(finishedTask.IsCompleted, "Server is waiting for client to end streaming"); // Complete request stream streamingContent.Complete(); await finishedTask.DefaultTimeout(); response.AssertTrailerStatus(); }
public async Task UnaryMethod_SuccessfulCall_PollingCountersUpdatedCorrectly() { using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var tcs = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); async Task <HelloReply> UnarySuccess(HelloRequest request, ServerCallContext context) { await tcs.Task.DefaultTimeout(); return(new HelloReply()); } // Arrange var clientEventListener = CreateEnableListener(Grpc.Net.Client.Internal.GrpcEventSource.Log); var serverEventListener = CreateEnableListener(Grpc.AspNetCore.Server.Internal.GrpcEventSource.Log); // Act - Start call var method = Fixture.DynamicGrpc.AddUnaryMethod <HelloRequest, HelloReply>(UnarySuccess); var client = TestClientFactory.Create(Channel, method); var call = client.UnaryCall(new HelloRequest()); // Assert - Call in progress await AssertCounters("Server call in progress", serverEventListener, new Dictionary <string, long> { ["total-calls"] = 1, ["current-calls"] = 1, ["messages-sent"] = 0, ["messages-received"] = 1, }).DefaultTimeout(); await AssertCounters("Client call in progress", clientEventListener, new Dictionary <string, long> { ["total-calls"] = 1, ["current-calls"] = 1, ["messages-sent"] = 1, ["messages-received"] = 0, }).DefaultTimeout(); // Act - Complete call tcs.SetResult(true); await call.ResponseAsync.DefaultTimeout(); // Assert - Call complete await AssertCounters("Server call in complete", serverEventListener, new Dictionary <string, long> { ["total-calls"] = 1, ["current-calls"] = 0, ["messages-sent"] = 1, ["messages-received"] = 1, }).DefaultTimeout(); await AssertCounters("Client call complete", clientEventListener, new Dictionary <string, long> { ["total-calls"] = 1, ["current-calls"] = 0, ["messages-sent"] = 1, ["messages-received"] = 1, }).DefaultTimeout(); }
public async Task ClientStreaming_MultipleWritesAndRetries_Failure() { var nextFailure = 1; async Task <DataMessage> ClientStreamingWithReadFailures(IAsyncStreamReader <DataMessage> requestStream, ServerCallContext context) { List <byte> bytes = new List <byte>(); await foreach (var message in requestStream.ReadAllAsync()) { if (bytes.Count >= nextFailure) { nextFailure = nextFailure * 2; Logger.LogInformation($"Server failing at {bytes.Count}. Next failure at {nextFailure}."); throw new RpcException(new Status(StatusCode.Unavailable, "")); } Logger.LogInformation($"Server received {bytes.Count}."); bytes.Add(message.Data[0]); } Logger.LogInformation("Server returning response."); return(new DataMessage { Data = ByteString.CopyFrom(bytes.ToArray()) }); } SetExpectedErrorsFilter(writeContext => { return(true); }); using var httpEventListener = new HttpEventSourceListener(LoggerFactory); // Arrange var method = Fixture.DynamicGrpc.AddClientStreamingMethod <DataMessage, DataMessage>(ClientStreamingWithReadFailures); var channel = CreateChannel(serviceConfig: ServiceConfigHelpers.CreateRetryServiceConfig(maxAttempts: 10), maxRetryAttempts: 10); var client = TestClientFactory.Create(channel, method); var sentData = new List <byte>(); // Act var call = client.ClientStreamingCall(); for (var i = 0; i < 15; i++) { sentData.Add((byte)i); Logger.LogInformation($"Client writing message {i}."); await call.RequestStream.WriteAsync(new DataMessage { Data = ByteString.CopyFrom(new byte[] { (byte)i }) }).DefaultTimeout(); await Task.Delay(1); } Logger.LogInformation("Client completing request stream."); await call.RequestStream.CompleteAsync().DefaultTimeout(); Logger.LogInformation("Client waiting for response."); var result = await call.ResponseAsync.DefaultTimeout(); // Assert Assert.IsTrue(result.Data.Span.SequenceEqual(sentData.ToArray())); }
public async Task GET_RequestReturnsLargeData_GracefulShutdownDuringRequest_RequestGracefullyCompletes(bool hasTrailers) { // Enable client logging. // Test failure on CI could be from HttpClient bug. using var httpEventSource = new HttpEventSourceListener(LoggerFactory); // Arrange const int DataLength = 500_000; var randomBytes = Enumerable.Range(1, DataLength).Select(i => (byte)((i % 10) + 48)).ToArray(); var syncPoint = new SyncPoint(); ILogger logger = null; var builder = CreateHostBuilder( async c => { await syncPoint.WaitToContinue(); var memory = c.Response.BodyWriter.GetMemory(randomBytes.Length); logger.LogInformation($"Server writing {randomBytes.Length} bytes response"); randomBytes.CopyTo(memory); // It's important for this test that the large write is the last data written to // the response and it's not awaited by the request delegate. logger.LogInformation($"Server advancing {randomBytes.Length} bytes response"); c.Response.BodyWriter.Advance(randomBytes.Length); if (hasTrailers) { c.Response.AppendTrailer("test-trailer", "value!"); } }, protocol: HttpProtocols.Http2, plaintext: true); using var host = builder.Build(); logger = host.Services.GetRequiredService <ILoggerFactory>().CreateLogger("Test"); var client = HttpHelpers.CreateClient(); // Act await host.StartAsync().DefaultTimeout(); var longRunningTask = StartLongRunningRequestAsync(logger, host, client); logger.LogInformation("Waiting for request on server"); await syncPoint.WaitForSyncPoint().DefaultTimeout(); logger.LogInformation("Stopping server"); var stopTask = host.StopAsync(); syncPoint.Continue(); var(readData, trailers) = await longRunningTask.DefaultTimeout(); await stopTask.DefaultTimeout(); // Assert Assert.Equal(randomBytes, readData); if (hasTrailers) { Assert.Equal("value!", trailers.GetValues("test-trailer").Single()); } }
public void Configure(IApplicationBuilder app, IHostApplicationLifetime applicationLifetime) { // Required to notify performance infrastructure that it can begin benchmarks applicationLifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started.")); var loggerFactory = app.ApplicationServices.GetRequiredService <ILoggerFactory>(); if (loggerFactory.CreateLogger <Startup>().IsEnabled(LogLevel.Trace)) { _ = new HttpEventSourceListener(loggerFactory); } app.UseRouting(); #if NET5_0_OR_GREATER bool.TryParse(_config["enableCertAuth"], out var enableCertAuth); if (enableCertAuth) { app.UseAuthentication(); app.UseAuthorization(); } #endif #if GRPC_WEB bool.TryParse(_config["enableGrpcWeb"], out var enableGrpcWeb); if (enableGrpcWeb) { app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); } #endif app.UseMiddleware <ServiceProvidersMiddleware>(); app.UseEndpoints(endpoints => { ConfigureAuthorization(endpoints.MapGrpcService <BenchmarkServiceImpl>()); ConfigureAuthorization(endpoints.MapControllers()); ConfigureAuthorization(endpoints.MapGet("/", context => { return(context.Response.WriteAsync("Benchmark Server")); })); ConfigureAuthorization(endpoints.MapPost("/unary", async context => { MemoryStream ms = new MemoryStream(); await context.Request.Body.CopyToAsync(ms); ms.Seek(0, SeekOrigin.Begin); JsonSerializer serializer = new JsonSerializer(); var message = serializer.Deserialize <SimpleRequest>(new JsonTextReader(new StreamReader(ms))) !; ms.Seek(0, SeekOrigin.Begin); using (var writer = new JsonTextWriter(new StreamWriter(ms, Encoding.UTF8, 1024, true))) { serializer.Serialize(writer, BenchmarkServiceImpl.CreateResponse(message)); } context.Response.StatusCode = StatusCodes.Status200OK; ms.Seek(0, SeekOrigin.Begin); await ms.CopyToAsync(context.Response.Body); })); }); }
public static void Main(string[] args) { var hostBuilder = new HostBuilder() .ConfigureLogging((_, factory) => { factory.SetMinimumLevel(LogLevel.Trace); factory.AddSimpleConsole(o => o.TimestampFormat = "[HH:mm:ss.fff] "); }) .ConfigureWebHost(webHost => { webHost.UseKestrel() .ConfigureKestrel((context, options) => { var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); options.ConfigureHttpsDefaults(httpsOptions => { httpsOptions.ServerCertificate = cert; // httpsOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate; // httpsOptions.AllowAnyClientCertificate(); }); options.ListenAnyIP(5000, listenOptions => { listenOptions.UseConnectionLogging(); listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); options.ListenAnyIP(5001, listenOptions => { listenOptions.UseHttps(); listenOptions.UseConnectionLogging(); listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); options.ListenAnyIP(5002, listenOptions => { listenOptions.UseHttps(StoreName.My, "localhost"); listenOptions.UseConnectionLogging(); listenOptions.Protocols = HttpProtocols.Http3; }); options.ListenAnyIP(5003, listenOptions => { listenOptions.UseHttps(httpsOptions => { // ConnectionContext is null httpsOptions.ServerCertificateSelector = (context, host) => cert; }); listenOptions.UseConnectionLogging(); listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); // No SslServerAuthenticationOptions callback is currently supported by QuicListener options.ListenAnyIP(5004, listenOptions => { listenOptions.UseHttps(httpsOptions => { httpsOptions.OnAuthenticate = (_, sslOptions) => sslOptions.ServerCertificate = cert; }); listenOptions.UseConnectionLogging(); listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); // ServerOptionsSelectionCallback isn't currently supported by QuicListener options.ListenAnyIP(5005, listenOptions => { ServerOptionsSelectionCallback callback = (SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken) => { var options = new SslServerAuthenticationOptions() { ServerCertificate = cert, }; return(new ValueTask <SslServerAuthenticationOptions>(options)); }; listenOptions.UseHttps(callback, state: null); listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); // TlsHandshakeCallbackOptions (ServerOptionsSelectionCallback) isn't currently supported by QuicListener options.ListenAnyIP(5006, listenOptions => { listenOptions.UseHttps(new TlsHandshakeCallbackOptions() { OnConnection = context => { var options = new SslServerAuthenticationOptions() { ServerCertificate = cert, }; return(new ValueTask <SslServerAuthenticationOptions>(options)); }, }); listenOptions.UseConnectionLogging(); listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); }) .UseStartup <Startup>(); }); var host = hostBuilder.Build(); // Listener needs to be configured before host (and HTTP/3 endpoints) start up. using var httpEventSource = new HttpEventSourceListener(host.Services.GetRequiredService <ILoggerFactory>()); host.Run(); }
public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var now = new DateTimeOffset(2021, 7, 6, 12, 0, 0, TimeSpan.Zero); var testSystemClock = new TestSystemClock { UtcNow = now }; await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testSystemClock); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); var testHeartbeatFeature = new TestHeartbeatFeature(); serverConnection.Features.Set <IConnectionHeartbeatFeature>(testHeartbeatFeature); // Act & Assert var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); Assert.Equal(0, quicConnectionContext.StreamPool.Count); var stream1 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection, Logger); Assert.Equal(1, quicConnectionContext.StreamPool.Count); QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); now = now.AddMilliseconds(100); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); var stream2 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection, Logger); Assert.Equal(1, quicConnectionContext.StreamPool.Count); pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); Assert.Same(stream1, stream2); now = now.AddTicks(QuicConnectionContext.StreamPoolExpiryTicks); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); now = now.AddTicks(1); testSystemClock.UtcNow = now; testHeartbeatFeature.RaiseHeartbeat(); // Removed. Assert.Equal(0, quicConnectionContext.StreamPool.Count); }
public async Task BidirectionalStream_ServerWritesDataAndDisposes_ClientReadsData(int dataLength) { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var testData = new byte[dataLength]; for (int i = 0; i < dataLength; i++) { testData[i] = (byte)i; } await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); // Act Logger.LogInformation("Client starting stream."); var clientStream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); await clientStream.WriteAsync(TestData, completeWrites : true).DefaultTimeout(); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); Logger.LogInformation("Server accepted stream."); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); // Input should be completed. readResult = await serverStream.Transport.Input.ReadAsync().DefaultTimeout(); Assert.True(readResult.IsCompleted); Logger.LogInformation("Client starting to read."); var readingTask = clientStream.ReadUntilEndAsync(); Logger.LogInformation("Server sending data."); await serverStream.Transport.Output.WriteAsync(testData).DefaultTimeout(); Logger.LogInformation("Server completing pipes."); await serverStream.Transport.Input.CompleteAsync().DefaultTimeout(); await serverStream.Transport.Output.CompleteAsync().DefaultTimeout(); Logger.LogInformation("Client reading until end of stream."); var data = await readingTask.DefaultTimeout(); Assert.Equal(testData.Length, data.Length); Assert.Equal(testData, data); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); Logger.LogInformation("Server waiting for send and receiving loops to complete."); await quicStreamContext._processingTask.DefaultTimeout(); Assert.True(quicStreamContext.CanWrite); Assert.True(quicStreamContext.CanRead); Logger.LogInformation("Server disposing stream."); await quicStreamContext.DisposeAsync().DefaultTimeout(); quicStreamContext.Dispose(); var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); Assert.Equal(1, quicConnectionContext.StreamPool.Count); }
public async Task BidirectionalStream_ClientAbortedAfterDisposeCalled_NotPooled() { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); // Act Logger.LogInformation("Client starting stream."); var clientStream = await clientConnection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); await clientStream.WriteAsync(TestData).DefaultTimeout(); var readTask = clientStream.ReadUntilEndAsync(); Logger.LogInformation("Server accepted stream."); var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); // Server sends a large response that will make it wait to complete sends. Logger.LogInformation("Server writing a large response."); await serverStream.Transport.Output.WriteAsync(new byte[1024 * 1024 * 32]).DefaultTimeout(); // Complete reading and writing. Logger.LogInformation("Server complete reading and writing."); await serverStream.Transport.Input.CompleteAsync(); await serverStream.Transport.Output.CompleteAsync(); Logger.LogInformation("Client wait to finish reading."); await readTask.DefaultTimeout(); var quicStreamContext = Assert.IsType <QuicStreamContext>(serverStream); // Server starts disposing Logger.LogInformation("Server starts disposing."); var disposeTask = quicStreamContext.DisposeAsync(); // Client aborts while server is draining clientStream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.RequestCancelled); clientStream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.RequestCancelled); // Server finishes disposing Logger.LogInformation("Wait for server finish disposing."); await disposeTask.DefaultTimeout(); quicStreamContext.Dispose(); var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection); // Assert Assert.Equal(0, quicConnectionContext.StreamPool.Count); }
public async Task NoBuffering_SuccessResponsesStreamed() { using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var methodWrapper = new MethodWrapper { Logger = Logger, SyncPoint = new SyncPoint(runContinuationsAsynchronously: true) }; async Task SayHellos(HelloRequest request, IServerStreamWriter <HelloReply> responseStream, ServerCallContext context) { // Explicitly send the response headers before any streamed content Metadata responseHeaders = new Metadata(); responseHeaders.Add("test-response-header", "value"); await context.WriteResponseHeadersAsync(responseHeaders); await methodWrapper.SayHellosAsync(request, responseStream); } // Arrange var method = Fixture.DynamicGrpc.AddServerStreamingMethod <HelloRequest, HelloReply>(SayHellos); var requestMessage = new HelloRequest { Name = "World" }; var requestStream = new MemoryStream(); MessageHelpers.WriteMessage(requestStream, requestMessage); var httpRequest = GrpcHttpHelper.Create(method.FullName); httpRequest.Content = new GrpcStreamContent(requestStream); // Act var response = await Fixture.Client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead).DefaultTimeout(); // Assert response.AssertIsSuccessfulGrpcRequest(); var responseStream = await response.Content.ReadAsStreamAsync().DefaultTimeout(); var pipeReader = PipeReader.Create(responseStream); for (var i = 0; i < 3; i++) { var greetingTask = MessageHelpers.AssertReadStreamMessageAsync <HelloReply>(pipeReader); Assert.IsFalse(greetingTask.IsCompleted); await methodWrapper.SyncPoint.WaitForSyncPoint().DefaultTimeout(); var currentSyncPoint = methodWrapper.SyncPoint; methodWrapper.SyncPoint = new SyncPoint(runContinuationsAsynchronously: true); currentSyncPoint.Continue(); var greeting = (await greetingTask.DefaultTimeout()) !; Assert.AreEqual($"How are you World? {i}", greeting.Message); } var goodbyeTask = MessageHelpers.AssertReadStreamMessageAsync <HelloReply>(pipeReader); Assert.False(goodbyeTask.IsCompleted); await methodWrapper.SyncPoint.WaitForSyncPoint().DefaultTimeout(); methodWrapper.SyncPoint.Continue(); Assert.AreEqual("Goodbye World!", (await goodbyeTask.DefaultTimeout()) !.Message); var finishedTask = MessageHelpers.AssertReadStreamMessageAsync <HelloReply>(pipeReader); Assert.IsNull(await finishedTask.DefaultTimeout()); }