Add some tracing and increase timeout for flaky test (#2130)
This commit is contained in:
parent
7d205d1093
commit
1678c54291
|
|
@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_connectionKeepAlive(_logger, connectionId, null);
|
_connectionKeepAlive(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionRejected(string connectionId)
|
public virtual void ConnectionRejected(string connectionId)
|
||||||
{
|
{
|
||||||
_connectionRejected(_logger, connectionId, null);
|
_connectionRejected(_logger, connectionId, null);
|
||||||
}
|
}
|
||||||
|
|
@ -129,12 +129,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_connectionHeadResponseBodyWrite(_logger, connectionId, count, null);
|
_connectionHeadResponseBodyWrite(_logger, connectionId, count, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotAllConnectionsClosedGracefully()
|
public virtual void NotAllConnectionsClosedGracefully()
|
||||||
{
|
{
|
||||||
_notAllConnectionsClosedGracefully(_logger, null);
|
_notAllConnectionsClosedGracefully(_logger, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectionBadRequest(string connectionId, BadHttpRequestException ex)
|
public virtual void ConnectionBadRequest(string connectionId, BadHttpRequestException ex)
|
||||||
{
|
{
|
||||||
_connectionBadRequest(_logger, connectionId, ex.Message, ex);
|
_connectionBadRequest(_logger, connectionId, ex.Message, ex);
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +144,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_requestProcessingError(_logger, connectionId, ex);
|
_requestProcessingError(_logger, connectionId, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotAllConnectionsAborted()
|
public virtual void NotAllConnectionsAborted()
|
||||||
{
|
{
|
||||||
_notAllConnectionsAborted(_logger, null);
|
_notAllConnectionsAborted(_logger, null);
|
||||||
}
|
}
|
||||||
|
|
@ -169,27 +169,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_requestBodyDone(_logger, connectionId, traceIdentifier, null);
|
_requestBodyDone(_logger, connectionId, traceIdentifier, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate)
|
public virtual void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate)
|
||||||
{
|
{
|
||||||
_requestBodyMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, rate, null);
|
_requestBodyMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, rate, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier)
|
public virtual void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier)
|
||||||
{
|
{
|
||||||
_responseMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, null);
|
_responseMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex)
|
public virtual void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex)
|
||||||
{
|
{
|
||||||
_http2ConnectionError(_logger, connectionId, ex);
|
_http2ConnectionError(_logger, connectionId, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Http2StreamError(string connectionId, Http2StreamErrorException ex)
|
public virtual void Http2StreamError(string connectionId, Http2StreamErrorException ex)
|
||||||
{
|
{
|
||||||
_http2StreamError(_logger, connectionId, ex);
|
_http2StreamError(_logger, connectionId, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex)
|
public virtual void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex)
|
||||||
{
|
{
|
||||||
_hpackDecodingError(_logger, connectionId, streamId, ex);
|
_hpackDecodingError(_logger, connectionId, streamId, ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
@ -19,23 +20,24 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Sdk;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
{
|
{
|
||||||
public class ResponseTests
|
public class ResponseTests : LoggedTest
|
||||||
{
|
{
|
||||||
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
|
public static TheoryData<ListenOptions> ConnectionAdapterData => new TheoryData<ListenOptions>
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +48,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ResponseTests(ITestOutputHelper outputHelper) : base(outputHelper) { }
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LargeDownload()
|
public async Task LargeDownload()
|
||||||
{
|
{
|
||||||
|
|
@ -2447,73 +2451,94 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
|
public void ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
|
||||||
{
|
{
|
||||||
var chunkSize = 64 * 1024;
|
using (StartLog(out var loggerFactory))
|
||||||
var chunks = 128;
|
|
||||||
var responseSize = chunks * chunkSize;
|
|
||||||
|
|
||||||
var requestAborted = new ManualResetEventSlim();
|
|
||||||
var messageLogged = new ManualResetEventSlim();
|
|
||||||
var mockKestrelTrace = new Mock<IKestrelTrace>();
|
|
||||||
mockKestrelTrace
|
|
||||||
.Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
|
|
||||||
.Callback(() => messageLogged.Set());
|
|
||||||
|
|
||||||
var testContext = new TestServiceContext
|
|
||||||
{
|
{
|
||||||
Log = mockKestrelTrace.Object,
|
var logger = loggerFactory.CreateLogger($"{typeof(ResponseTests).FullName}.{nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}");
|
||||||
SystemClock = new SystemClock(),
|
|
||||||
ServerOptions =
|
var chunkSize = 64 * 1024;
|
||||||
|
var chunks = 128;
|
||||||
|
var responseSize = chunks * chunkSize;
|
||||||
|
|
||||||
|
var requestAborted = new ManualResetEventSlim();
|
||||||
|
var messageLogged = new ManualResetEventSlim();
|
||||||
|
var mockKestrelTrace = new Mock<KestrelTrace>(loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel")) { CallBase = true };
|
||||||
|
mockKestrelTrace
|
||||||
|
.Setup(trace => trace.ResponseMininumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Callback(() => messageLogged.Set());
|
||||||
|
|
||||||
|
var testContext = new TestServiceContext
|
||||||
{
|
{
|
||||||
Limits =
|
LoggerFactory = loggerFactory,
|
||||||
|
Log = mockKestrelTrace.Object,
|
||||||
|
SystemClock = new SystemClock(),
|
||||||
|
ServerOptions =
|
||||||
{
|
{
|
||||||
MinResponseDataRate = new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: TimeSpan.FromSeconds(2))
|
Limits =
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var server = new TestServer(async context =>
|
|
||||||
{
|
|
||||||
context.RequestAborted.Register(() => requestAborted.Set());
|
|
||||||
|
|
||||||
context.Response.ContentLength = responseSize;
|
|
||||||
|
|
||||||
for (var i = 0; i < chunks; i++)
|
|
||||||
{
|
|
||||||
await context.Response.WriteAsync(new string('a', chunkSize), context.RequestAborted);
|
|
||||||
}
|
|
||||||
}, testContext))
|
|
||||||
{
|
|
||||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
|
||||||
{
|
|
||||||
socket.ReceiveBufferSize = 1;
|
|
||||||
|
|
||||||
socket.Connect(new IPEndPoint(IPAddress.Loopback, server.Port));
|
|
||||||
socket.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost: \r\n\r\n"));
|
|
||||||
|
|
||||||
Assert.True(messageLogged.Wait(TimeSpan.FromSeconds(60)), "The expected message was not logged within the timeout period.");
|
|
||||||
Assert.True(requestAborted.Wait(TimeSpan.FromSeconds(60)), "The request was not aborted within the timeout period.");
|
|
||||||
|
|
||||||
var totalReceived = 0;
|
|
||||||
var received = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var buffer = new byte[chunkSize];
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
received = socket.Receive(buffer);
|
MinResponseDataRate = new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: TimeSpan.FromSeconds(2))
|
||||||
totalReceived += received;
|
}
|
||||||
} while (received > 0 && totalReceived < responseSize);
|
|
||||||
}
|
|
||||||
catch (SocketException) { }
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
// Socket.Receive could throw, and that is fine
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Since we expect writes to be cut off by the rate control, we should never see the entire response
|
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
|
||||||
Assert.NotEqual(responseSize, totalReceived);
|
listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(loggerFactory.CreateLogger<LoggingConnectionAdapter>()));
|
||||||
|
|
||||||
|
var appLogger = loggerFactory.CreateLogger("App");
|
||||||
|
async Task App(HttpContext context)
|
||||||
|
{
|
||||||
|
appLogger.LogInformation("Request received");
|
||||||
|
context.RequestAborted.Register(() => requestAborted.Set());
|
||||||
|
|
||||||
|
context.Response.ContentLength = responseSize;
|
||||||
|
|
||||||
|
for (var i = 0; i < chunks; i++)
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync(new string('a', chunkSize), context.RequestAborted);
|
||||||
|
appLogger.LogInformation("Wrote chunk of {chunkSize} bytes", chunkSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var server = new TestServer(App, testContext, listenOptions))
|
||||||
|
{
|
||||||
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
||||||
|
{
|
||||||
|
socket.ReceiveBufferSize = 1;
|
||||||
|
|
||||||
|
socket.Connect(new IPEndPoint(IPAddress.Loopback, server.Port));
|
||||||
|
logger.LogInformation("Sending request");
|
||||||
|
socket.Send(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost: \r\n\r\n"));
|
||||||
|
logger.LogInformation("Sent request");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
logger.LogInformation("Waiting for connection to abort.");
|
||||||
|
Assert.True(messageLogged.Wait(TimeSpan.FromSeconds(120)), "The expected message was not logged within the timeout period.");
|
||||||
|
Assert.True(requestAborted.Wait(TimeSpan.FromSeconds(120)), "The request was not aborted within the timeout period.");
|
||||||
|
sw.Stop();
|
||||||
|
logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
|
||||||
|
|
||||||
|
var totalReceived = 0;
|
||||||
|
var received = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var buffer = new byte[chunkSize];
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
received = socket.Receive(buffer);
|
||||||
|
totalReceived += received;
|
||||||
|
} while (received > 0 && totalReceived < responseSize);
|
||||||
|
}
|
||||||
|
catch (SocketException) { }
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
// Socket.Receive could throw, and that is fine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we expect writes to be cut off by the rate control, we should never see the entire response
|
||||||
|
logger.LogInformation("Received {totalReceived} bytes", totalReceived);
|
||||||
|
Assert.NotEqual(responseSize, totalReceived);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,30 +49,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
Context = context;
|
Context = context;
|
||||||
|
|
||||||
_host = TransportSelector.GetWebHostBuilder()
|
_host = TransportSelector.GetWebHostBuilder()
|
||||||
.UseKestrel(o =>
|
.UseKestrel(o =>
|
||||||
{
|
{
|
||||||
o.ListenOptions.Add(_listenOptions);
|
o.ListenOptions.Add(_listenOptions);
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<IStartup>(this);
|
services.AddSingleton<IStartup>(this);
|
||||||
services.AddSingleton(context.LoggerFactory);
|
services.AddSingleton(context.LoggerFactory);
|
||||||
services.AddSingleton<IServer>(sp =>
|
services.AddSingleton<IServer>(sp =>
|
||||||
{
|
{
|
||||||
// Manually configure options on the TestServiceContext.
|
// Manually configure options on the TestServiceContext.
|
||||||
// We're doing this so we can use the same instance that was passed in
|
// We're doing this so we can use the same instance that was passed in
|
||||||
var configureOptions = sp.GetServices<IConfigureOptions<KestrelServerOptions>>();
|
var configureOptions = sp.GetServices<IConfigureOptions<KestrelServerOptions>>();
|
||||||
foreach (var c in configureOptions)
|
foreach (var c in configureOptions)
|
||||||
{
|
{
|
||||||
c.Configure(context.ServerOptions);
|
c.Configure(context.ServerOptions);
|
||||||
}
|
}
|
||||||
return new KestrelServer(sp.GetRequiredService<ITransportFactory>(), context);
|
return new KestrelServer(sp.GetRequiredService<ITransportFactory>(), context);
|
||||||
});
|
});
|
||||||
|
|
||||||
configureServices(services);
|
configureServices(services);
|
||||||
})
|
})
|
||||||
.UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName)
|
.UseSetting(WebHostDefaults.ApplicationKey, typeof(TestServer).GetTypeInfo().Assembly.FullName)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_host.Start();
|
_host.Start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue