Fix AppOfflineDroppedWhileSiteStarting_SiteShutsDown_InProcess (#8419)
This commit is contained in:
parent
d329cca17d
commit
1679ba7844
|
|
@ -155,8 +155,8 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt
|
||||||
}
|
}
|
||||||
LOG_LAST_ERROR_IF(WaitForSingleObject(eventHandle, INFINITE) != WAIT_OBJECT_0);
|
LOG_LAST_ERROR_IF(WaitForSingleObject(eventHandle, INFINITE) != WAIT_OBJECT_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), m_pApplicationFactory, options));
|
RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), m_pApplicationFactory, options));
|
||||||
LOG_INFO(L"Creating handler application");
|
LOG_INFO(L"Creating handler application");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "SRWExclusiveLock.h"
|
#include "SRWExclusiveLock.h"
|
||||||
#include "SRWSharedLock.h"
|
#include "SRWSharedLock.h"
|
||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
|
#include "HandleWrapper.h"
|
||||||
|
|
||||||
class APPLICATION : public IAPPLICATION
|
class APPLICATION : public IAPPLICATION
|
||||||
{
|
{
|
||||||
|
|
@ -24,8 +25,10 @@ public:
|
||||||
{
|
{
|
||||||
*pRequestHandler = nullptr;
|
*pRequestHandler = nullptr;
|
||||||
|
|
||||||
SRWSharedLock stopLock(m_stateLock);
|
SRWSharedLock stopLock(m_stopLock);
|
||||||
|
|
||||||
|
// If we have acquired the stopLock, we don't need to acquire the data lock
|
||||||
|
// as m_fStoppedCalled is only set by Stop.
|
||||||
if (m_fStopCalled)
|
if (m_fStopCalled)
|
||||||
{
|
{
|
||||||
return S_FALSE;
|
return S_FALSE;
|
||||||
|
|
@ -49,22 +52,25 @@ public:
|
||||||
m_applicationConfigPath(pHttpApplication.GetAppConfigPath()),
|
m_applicationConfigPath(pHttpApplication.GetAppConfigPath()),
|
||||||
m_applicationId(pHttpApplication.GetApplicationId())
|
m_applicationId(pHttpApplication.GetApplicationId())
|
||||||
{
|
{
|
||||||
InitializeSRWLock(&m_stateLock);
|
InitializeSRWLock(&m_stopLock);
|
||||||
|
InitializeSRWLock(&m_dataLock);
|
||||||
m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath);
|
m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
Stop(bool fServerInitiated) override
|
Stop(bool fServerInitiated) override
|
||||||
{
|
{
|
||||||
SRWExclusiveLock stopLock(m_stateLock);
|
SRWExclusiveLock stopLock(m_stopLock);
|
||||||
|
|
||||||
if (m_fStopCalled)
|
|
||||||
{
|
{
|
||||||
return;
|
SRWExclusiveLock dataLock(m_dataLock);
|
||||||
}
|
if (m_fStopCalled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_fStopCalled = true;
|
m_fStopCalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
StopInternal(fServerInitiated);
|
StopInternal(fServerInitiated);
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +126,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SRWLOCK m_stateLock {};
|
SRWLOCK m_stopLock{};
|
||||||
|
SRWLOCK m_dataLock {};
|
||||||
bool m_fStopCalled;
|
bool m_fStopCalled;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
|
||||||
IHttpServer& pHttpServer,
|
IHttpServer& pHttpServer,
|
||||||
IHttpApplication& pApplication,
|
IHttpApplication& pApplication,
|
||||||
std::unique_ptr<InProcessOptions> pConfig,
|
std::unique_ptr<InProcessOptions> pConfig,
|
||||||
APPLICATION_PARAMETER *pParameters,
|
APPLICATION_PARAMETER* pParameters,
|
||||||
DWORD nParameters) :
|
DWORD nParameters) :
|
||||||
InProcessApplicationBase(pHttpServer, pApplication),
|
InProcessApplicationBase(pHttpServer, pApplication),
|
||||||
m_Initialized(false),
|
m_Initialized(false),
|
||||||
|
|
@ -55,6 +55,7 @@ IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
|
||||||
VOID
|
VOID
|
||||||
IN_PROCESS_APPLICATION::StopClr()
|
IN_PROCESS_APPLICATION::StopClr()
|
||||||
{
|
{
|
||||||
|
// This has the state lock around it.
|
||||||
LOG_INFO(L"Stopping CLR");
|
LOG_INFO(L"Stopping CLR");
|
||||||
|
|
||||||
if (!m_blockManagedCallbacks)
|
if (!m_blockManagedCallbacks)
|
||||||
|
|
@ -69,11 +70,13 @@ IN_PROCESS_APPLICATION::StopClr()
|
||||||
shutdownHandler(m_ShutdownHandlerContext);
|
shutdownHandler(m_ShutdownHandlerContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SRWSharedLock dataLock(m_dataLock);
|
||||||
|
|
||||||
auto requestCount = m_requestCount.load();
|
auto requestCount = m_requestCount.load();
|
||||||
|
|
||||||
if (requestCount == 0)
|
if (requestCount == 0)
|
||||||
{
|
{
|
||||||
LOG_INFO(L"Drained all requests, notifying managed.");
|
CallRequestsDrained();
|
||||||
m_RequestsDrainedHandler(m_ShutdownHandlerContext);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,12 +144,6 @@ IN_PROCESS_APPLICATION::LoadManagedApplication()
|
||||||
FALSE, // not set
|
FALSE, // not set
|
||||||
nullptr)); // name
|
nullptr)); // name
|
||||||
|
|
||||||
THROW_LAST_ERROR_IF_NULL(m_pRequestDrainEvent = CreateEvent(
|
|
||||||
nullptr, // default security attributes
|
|
||||||
TRUE, // manual reset event
|
|
||||||
FALSE, // not set
|
|
||||||
nullptr)); // name
|
|
||||||
|
|
||||||
LOG_INFO(L"Waiting for initialization");
|
LOG_INFO(L"Waiting for initialization");
|
||||||
|
|
||||||
m_workerThread = std::thread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
|
m_workerThread = std::thread([](std::unique_ptr<IN_PROCESS_APPLICATION, IAPPLICATION_DELETER> application)
|
||||||
|
|
@ -532,8 +529,13 @@ IN_PROCESS_APPLICATION::CreateHandler(
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
SRWSharedLock dataLock(m_dataLock);
|
||||||
|
|
||||||
DBG_ASSERT(!m_fStopCalled);
|
DBG_ASSERT(!m_fStopCalled);
|
||||||
m_requestCount++;
|
m_requestCount++;
|
||||||
|
|
||||||
|
LOG_TRACEF(L"Adding request. Total Request Count %d", m_requestCount.load());
|
||||||
|
|
||||||
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler);
|
*pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler);
|
||||||
}
|
}
|
||||||
CATCH_RETURN();
|
CATCH_RETURN();
|
||||||
|
|
@ -544,11 +546,24 @@ IN_PROCESS_APPLICATION::CreateHandler(
|
||||||
void
|
void
|
||||||
IN_PROCESS_APPLICATION::HandleRequestCompletion()
|
IN_PROCESS_APPLICATION::HandleRequestCompletion()
|
||||||
{
|
{
|
||||||
SRWSharedLock lock(m_stateLock);
|
SRWSharedLock dataLock(m_dataLock);
|
||||||
auto requestCount = m_requestCount--;
|
|
||||||
if (m_fStopCalled && requestCount == 0)
|
auto requestCount = --m_requestCount;
|
||||||
|
|
||||||
|
LOG_TRACEF(L"Removing Request %d", requestCount);
|
||||||
|
|
||||||
|
if (m_fStopCalled && requestCount == 0 && !m_blockManagedCallbacks)
|
||||||
|
{
|
||||||
|
CallRequestsDrained();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IN_PROCESS_APPLICATION::CallRequestsDrained()
|
||||||
|
{
|
||||||
|
if (m_RequestsDrainedHandler != nullptr)
|
||||||
{
|
{
|
||||||
LOG_INFO(L"Drained all requests, notifying managed.");
|
LOG_INFO(L"Drained all requests, notifying managed.");
|
||||||
m_RequestsDrainedHandler(m_ShutdownHandlerContext);
|
m_RequestsDrainedHandler(m_ShutdownHandlerContext);
|
||||||
|
m_RequestsDrainedHandler = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,8 +150,6 @@ private:
|
||||||
// The event that gets triggered when worker thread should exit
|
// The event that gets triggered when worker thread should exit
|
||||||
HandleWrapper<NullHandleTraits> m_pShutdownEvent;
|
HandleWrapper<NullHandleTraits> m_pShutdownEvent;
|
||||||
|
|
||||||
HandleWrapper<NullHandleTraits> m_pRequestDrainEvent;
|
|
||||||
|
|
||||||
// The request handler callback from managed code
|
// The request handler callback from managed code
|
||||||
PFN_REQUEST_HANDLER m_RequestHandler;
|
PFN_REQUEST_HANDLER m_RequestHandler;
|
||||||
VOID* m_RequestHandlerContext;
|
VOID* m_RequestHandlerContext;
|
||||||
|
|
@ -169,6 +167,7 @@ private:
|
||||||
std::atomic_bool m_blockManagedCallbacks;
|
std::atomic_bool m_blockManagedCallbacks;
|
||||||
bool m_Initialized;
|
bool m_Initialized;
|
||||||
bool m_waitForShutdown;
|
bool m_waitForShutdown;
|
||||||
|
|
||||||
std::atomic<int> m_requestCount;
|
std::atomic<int> m_requestCount;
|
||||||
|
|
||||||
std::unique_ptr<InProcessOptions> m_pConfig;
|
std::unique_ptr<InProcessOptions> m_pConfig;
|
||||||
|
|
@ -188,6 +187,9 @@ private:
|
||||||
void
|
void
|
||||||
StopClr();
|
StopClr();
|
||||||
|
|
||||||
|
void
|
||||||
|
CallRequestsDrained();
|
||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context);
|
ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ OUT_OF_PROCESS_APPLICATION::OUT_OF_PROCESS_APPLICATION(
|
||||||
|
|
||||||
OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION()
|
OUT_OF_PROCESS_APPLICATION::~OUT_OF_PROCESS_APPLICATION()
|
||||||
{
|
{
|
||||||
SRWExclusiveLock lock(m_stateLock);
|
SRWExclusiveLock lock(m_stopLock);
|
||||||
if (m_pProcessManager != NULL)
|
if (m_pProcessManager != NULL)
|
||||||
{
|
{
|
||||||
m_pProcessManager->Shutdown();
|
m_pProcessManager->Shutdown();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Testing.xunit;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
||||||
{
|
{
|
||||||
|
|
@ -98,8 +99,8 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
||||||
await deploymentResult.AssertRecycledAsync(() => AssertAppOffline(deploymentResult));
|
await deploymentResult.AssertRecycledAsync(() => AssertAppOffline(deploymentResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConditionalFact(Skip = "https://github.com/aspnet/AspNetCore/issues/6555")]
|
[ConditionalFact]
|
||||||
[RequiresIIS(IISCapability.ShutdownToken)]
|
[RequiresNewHandler]
|
||||||
public async Task AppOfflineDroppedWhileSiteStarting_SiteShutsDown_InProcess()
|
public async Task AppOfflineDroppedWhileSiteStarting_SiteShutsDown_InProcess()
|
||||||
{
|
{
|
||||||
// This test often hits a race between debug logging and stdout redirection closing the handle
|
// This test often hits a race between debug logging and stdout redirection closing the handle
|
||||||
|
|
@ -131,19 +132,100 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
||||||
// if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app
|
// if AssertAppOffline succeeded ANCM have picked up app_offline before starting the app
|
||||||
// try again
|
// try again
|
||||||
RemoveAppOffline(deploymentResult.ContentRoot);
|
RemoveAppOffline(deploymentResult.ContentRoot);
|
||||||
|
|
||||||
|
if (deploymentResult.DeploymentParameters.ServerType == ServerType.IIS)
|
||||||
|
{
|
||||||
|
deploymentResult.AssertWorkerProcessStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// For IISExpress, we need to catch the exception because IISExpress will not restart a process if it crashed.
|
||||||
|
// RemoveAppOffline will fail due to a bad request exception as the server is down.
|
||||||
|
Assert.Contains(TestSink.Writes, context => context.Message.Contains("Drained all requests, notifying managed."));
|
||||||
deploymentResult.AssertWorkerProcessStop();
|
deploymentResult.AssertWorkerProcessStop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.True(false);
|
Assert.True(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task GracefulShutdownWorksWithMultipleRequestsInFlight_InProcess()
|
||||||
|
{
|
||||||
|
// The goal of this test is to have multiple requests currently in progress
|
||||||
|
// and for app offline to be dropped. We expect that all requests are eventually drained
|
||||||
|
// and graceful shutdown occurs.
|
||||||
|
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite);
|
||||||
|
deploymentParameters.TransformArguments((a, _) => $"{a} IncreaseShutdownLimit");
|
||||||
|
|
||||||
|
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||||
|
|
||||||
|
var result = await deploymentResult.HttpClient.GetAsync("/HelloWorld");
|
||||||
|
|
||||||
|
// Send two requests that will hang until data is sent from the client.
|
||||||
|
var connectionList = new List<TestConnection>();
|
||||||
|
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var connection = new TestConnection(deploymentResult.HttpClient.BaseAddress.Port);
|
||||||
|
await connection.Send(
|
||||||
|
"POST /ReadAndCountRequestBody HTTP/1.1",
|
||||||
|
"Content-Length: 1",
|
||||||
|
"Host: localhost",
|
||||||
|
"Connection: close",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK", "");
|
||||||
|
await connection.ReceiveHeaders();
|
||||||
|
await connection.Receive("1", $"{i + 1}");
|
||||||
|
connectionList.Add(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a request that will end once app lifetime is triggered (ApplicationStopping cts).
|
||||||
|
var statusConnection = new TestConnection(deploymentResult.HttpClient.BaseAddress.Port);
|
||||||
|
|
||||||
|
await statusConnection.Send(
|
||||||
|
"GET /WaitForAppToStartShuttingDown HTTP/1.1",
|
||||||
|
"Host: localhost",
|
||||||
|
"Connection: close",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await statusConnection.Receive("HTTP/1.1 200 OK",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await statusConnection.ReceiveHeaders();
|
||||||
|
|
||||||
|
// Receiving some data means we are currently waiting for IHostApplicationLifetime.
|
||||||
|
await statusConnection.Receive("5",
|
||||||
|
"test1",
|
||||||
|
"");
|
||||||
|
|
||||||
|
AddAppOffline(deploymentResult.ContentRoot);
|
||||||
|
|
||||||
|
// Receive the rest of all open connections.
|
||||||
|
await statusConnection.Receive("5", "test2", "");
|
||||||
|
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
await connectionList[i].Send("a", "");
|
||||||
|
await connectionList[i].Receive("", "4", "done");
|
||||||
|
connectionList[i].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
deploymentResult.AssertWorkerProcessStop();
|
||||||
|
|
||||||
|
// Shutdown should be graceful here!
|
||||||
|
EventLogHelpers.VerifyEventLogEvent(deploymentResult,
|
||||||
|
EventLogHelpers.InProcessShutdown());
|
||||||
|
}
|
||||||
|
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_InProcess()
|
public async Task AppOfflineDroppedWhileSiteRunning_SiteShutsDown_InProcess()
|
||||||
{
|
{
|
||||||
|
|
@ -282,6 +364,5 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string InProcessShutdown()
|
||||||
|
{
|
||||||
|
return "Application 'MACHINE/WEBROOT/APPHOST/.*?' has shutdown.";
|
||||||
|
}
|
||||||
|
|
||||||
public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason)
|
public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason)
|
||||||
{
|
{
|
||||||
return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/.*?'.";
|
return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/.*?'.";
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
Websockets = 1,
|
Websockets = 1,
|
||||||
WindowsAuthentication = 2,
|
WindowsAuthentication = 2,
|
||||||
PoolEnvironmentVariables = 4,
|
PoolEnvironmentVariables = 4,
|
||||||
ShutdownToken = 8,
|
DynamicCompression = 8,
|
||||||
DynamicCompression = 16,
|
ApplicationInitialization = 16,
|
||||||
ApplicationInitialization = 32,
|
TracingModule = 32,
|
||||||
TracingModule = 64,
|
FailedRequestTracingModule = 64,
|
||||||
FailedRequestTracingModule = 128,
|
BasicAuthentication = 128
|
||||||
BasicAuthentication = 256
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,12 +124,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (capabilities.HasFlag(IISCapability.ShutdownToken))
|
|
||||||
{
|
|
||||||
IsMet = false;
|
|
||||||
SkipReason += "https://github.com/aspnet/IISIntegration/issues/1074";
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var module in Modules)
|
foreach (var module in Modules)
|
||||||
{
|
{
|
||||||
if (capabilities.HasFlag(module.Capability))
|
if (capabilities.HasFlag(module.Capability))
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,18 @@ namespace TestSite
|
||||||
|
|
||||||
Thread.Sleep(Timeout.Infinite);
|
Thread.Sleep(Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case "IncreaseShutdownLimit":
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.UseIIS()
|
||||||
|
.UseShutdownTimeout(TimeSpan.FromSeconds(120))
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "CheckConsoleFunctions":
|
case "CheckConsoleFunctions":
|
||||||
// Call a bunch of console functions and make sure none return invalid handle.
|
// Call a bunch of console functions and make sure none return invalid handle.
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,28 @@ namespace TestSite
|
||||||
{
|
{
|
||||||
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
|
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _requestsInFlight = 0;
|
||||||
|
private async Task ReadAndCountRequestBody(HttpContext ctx)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _requestsInFlight);
|
||||||
|
await ctx.Response.WriteAsync(_requestsInFlight.ToString());
|
||||||
|
|
||||||
|
var readBuffer = new byte[1];
|
||||||
|
await ctx.Request.Body.ReadAsync(readBuffer, 0, 1);
|
||||||
|
|
||||||
|
await ctx.Response.WriteAsync("done");
|
||||||
|
Interlocked.Decrement(ref _requestsInFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitForAppToStartShuttingDown(HttpContext ctx)
|
||||||
|
{
|
||||||
|
await ctx.Response.WriteAsync("test1");
|
||||||
|
var lifetime = ctx.RequestServices.GetService<IHostApplicationLifetime>();
|
||||||
|
lifetime.ApplicationStopping.WaitHandle.WaitOne();
|
||||||
|
await ctx.Response.WriteAsync("test2");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReadFullBody(HttpContext ctx)
|
private async Task ReadFullBody(HttpContext ctx)
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
||||||
private const string DetailedErrorsEnvironmentVariable = "ASPNETCORE_DETAILEDERRORS";
|
private const string DetailedErrorsEnvironmentVariable = "ASPNETCORE_DETAILEDERRORS";
|
||||||
|
|
||||||
private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(60);
|
private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(60);
|
||||||
private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200);
|
private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(100);
|
||||||
|
|
||||||
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
|
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
|
||||||
|
|
||||||
private string _configPath;
|
private string _configPath;
|
||||||
private string _debugLogFile;
|
private string _debugLogFile;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public Process HostProcess { get; set; }
|
public Process HostProcess { get; set; }
|
||||||
|
|
||||||
|
|
@ -45,6 +46,12 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
Dispose(gracefulShutdown: false);
|
Dispose(gracefulShutdown: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,6 +427,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
||||||
List<Exception> exceptions = null;
|
List<Exception> exceptions = null;
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
|
var delay = _retryDelay;
|
||||||
|
|
||||||
while (sw.Elapsed < _timeout)
|
while (sw.Elapsed < _timeout)
|
||||||
{
|
{
|
||||||
|
|
@ -443,7 +451,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
|
||||||
}
|
}
|
||||||
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
Thread.Sleep(_retryDelay);
|
Thread.Sleep(delay);
|
||||||
|
delay *= 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new AggregateException($"Operation did not succeed after {retryCount} retries", exceptions.ToArray());
|
throw new AggregateException($"Operation did not succeed after {retryCount} retries", exceptions.ToArray());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue