General cleanup for client disconnect handling. (#1446)

This commit is contained in:
Justin Kotalik 2018-10-11 08:44:03 -07:00 committed by GitHub
parent aa4684f0f5
commit 0e04527fb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 123 additions and 29 deletions

View File

@ -10,15 +10,16 @@ void DisconnectHandler::NotifyDisconnect()
{ {
try try
{ {
std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> module; std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> pHandler;
{ {
SRWExclusiveLock lock(m_handlerLock); SRWExclusiveLock lock(m_handlerLock);
m_pHandler.swap(module); m_pHandler.swap(pHandler);
m_disconnectFired = true;
} }
if (module != nullptr) if (pHandler != nullptr)
{ {
module->NotifyDisconnect(); pHandler->NotifyDisconnect();
} }
} }
catch (...) catch (...)
@ -29,12 +30,28 @@ void DisconnectHandler::NotifyDisconnect()
void DisconnectHandler::CleanupStoredContext() noexcept void DisconnectHandler::CleanupStoredContext() noexcept
{ {
SetHandler(nullptr);
delete this; delete this;
} }
void DisconnectHandler::SetHandler(std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> handler) noexcept void DisconnectHandler::SetHandler(std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> handler)
{ {
SRWExclusiveLock lock(m_handlerLock); IREQUEST_HANDLER* pHandler = nullptr;
handler.swap(m_pHandler); {
SRWExclusiveLock lock(m_handlerLock);
handler.swap(m_pHandler);
pHandler = m_pHandler.get();
}
assert(pHandler != nullptr);
if (pHandler != nullptr && (m_disconnectFired || m_pHttpConnection != nullptr && !m_pHttpConnection->IsConnected()))
{
pHandler->NotifyDisconnect();
}
}
void DisconnectHandler::RemoveHandler() noexcept
{
m_pHandler = nullptr;
} }

View File

@ -11,8 +11,8 @@ class ASPNET_CORE_PROXY_MODULE;
class DisconnectHandler final: public IHttpConnectionStoredContext class DisconnectHandler final: public IHttpConnectionStoredContext
{ {
public: public:
DisconnectHandler() DisconnectHandler(IHttpConnection* pHttpConnection)
: m_pHandler(nullptr) : m_pHandler(nullptr), m_pHttpConnection(pHttpConnection), m_disconnectFired(false)
{ {
InitializeSRWLock(&m_handlerLock); InitializeSRWLock(&m_handlerLock);
} }
@ -20,7 +20,7 @@ public:
virtual virtual
~DisconnectHandler() ~DisconnectHandler()
{ {
SetHandler(nullptr); RemoveHandler();
} }
void void
@ -30,10 +30,14 @@ public:
CleanupStoredContext() noexcept override; CleanupStoredContext() noexcept override;
void void
SetHandler(std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> handler) noexcept; SetHandler(std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> handler);
void RemoveHandler() noexcept;
private: private:
SRWLOCK m_handlerLock {}; SRWLOCK m_handlerLock {};
std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> m_pHandler; std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> m_pHandler;
IHttpConnection* m_pHttpConnection;
bool m_disconnectFired;
}; };

View File

@ -96,7 +96,6 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS));
} }
FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo( FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo(
*pHttpContext, *pHttpContext,
m_pApplicationInfo)); m_pApplicationInfo));
@ -185,7 +184,7 @@ void ASPNET_CORE_PROXY_MODULE::SetupDisconnectHandler(IHttpContext * pHttpContex
if (pDisconnectHandler == nullptr) if (pDisconnectHandler == nullptr)
{ {
auto newHandler = std::make_unique<DisconnectHandler>(); auto newHandler = std::make_unique<DisconnectHandler>(pHttpContext->GetConnection());
pDisconnectHandler = newHandler.get(); pDisconnectHandler = newHandler.get();
// ModuleContextContainer takes ownership of disconnectHandler // ModuleContextContainer takes ownership of disconnectHandler
// we are trusting that it would not release it before deleting the context // we are trusting that it would not release it before deleting the context
@ -207,6 +206,6 @@ void ASPNET_CORE_PROXY_MODULE::RemoveDisconnectHandler() noexcept
if (handler != nullptr) if (handler != nullptr)
{ {
handler->SetHandler(nullptr); handler->RemoveHandler();
} }
} }

View File

@ -26,8 +26,10 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
m_pRequestHandlerContext(pRequestHandlerContext), m_pRequestHandlerContext(pRequestHandlerContext),
m_pAsyncCompletionHandler(pAsyncCompletion), m_pAsyncCompletionHandler(pAsyncCompletion),
m_pDisconnectHandler(pDisconnectHandler), m_pDisconnectHandler(pDisconnectHandler),
m_moduleTracer(pW3Context->GetTraceContext()) m_moduleTracer(pW3Context->GetTraceContext()),
m_disconnectFired(false)
{ {
InitializeSRWLock(&m_srwDisconnectLock);
} }
__override __override
@ -92,16 +94,29 @@ REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage()
VOID VOID
IN_PROCESS_HANDLER::NotifyDisconnect() IN_PROCESS_HANDLER::NotifyDisconnect()
{ {
m_moduleTracer.RequestDisconnect();
if (m_pApplication->QueryBlockCallbacksIntoManaged() || if (m_pApplication->QueryBlockCallbacksIntoManaged() ||
m_fManagedRequestComplete) m_fManagedRequestComplete)
{ {
return; return;
} }
m_moduleTracer.RequestDisconnect(); // NotifyDisconnect can be called before the m_pManagedHttpContext is set,
// so save that in a bool.
// Don't lock when calling m_pDisconnect to avoid the potential deadlock between this
// and SetManagedHttpContext
void* pManagedHttpContext = nullptr;
{
SRWExclusiveLock lock(m_srwDisconnectLock);
pManagedHttpContext = m_pManagedHttpContext;
m_disconnectFired = true;
}
assert(m_pManagedHttpContext != nullptr); if (pManagedHttpContext != nullptr)
m_pDisconnectHandler(m_pManagedHttpContext); {
m_pDisconnectHandler(m_pManagedHttpContext);
}
} }
VOID VOID
@ -127,7 +142,15 @@ IN_PROCESS_HANDLER::SetManagedHttpContext(
PVOID pManagedHttpContext PVOID pManagedHttpContext
) )
{ {
m_pManagedHttpContext = pManagedHttpContext; {
SRWExclusiveLock lock(m_srwDisconnectLock);
m_pManagedHttpContext = pManagedHttpContext;
}
if (m_disconnectFired && m_pManagedHttpContext != nullptr)
{
m_pDisconnectHandler(m_pManagedHttpContext);
}
} }
// static // static

View File

@ -85,4 +85,6 @@ private:
PFN_DISCONNECT_HANDLER m_pDisconnectHandler; PFN_DISCONNECT_HANDLER m_pDisconnectHandler;
static ALLOC_CACHE_HANDLER * sm_pAlloc; static ALLOC_CACHE_HANDLER * sm_pAlloc;
ModuleTracer m_moduleTracer; ModuleTracer m_moduleTracer;
bool m_disconnectFired;
SRWLOCK m_srwDisconnectLock;
}; };

View File

@ -190,13 +190,20 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_bodyOutput.Dispose(); _bodyOutput.Dispose();
try var cts = _abortedCts;
if (cts != null)
{ {
_abortedCts?.Cancel(); ThreadPool.QueueUserWorkItem(t =>
} {
catch (Exception) try
{ {
// ignore cts.Cancel();
}
catch (Exception)
{
// ignore
}
});
} }
} }

View File

@ -109,8 +109,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
_thisHandle = GCHandle.Alloc(this); _thisHandle = GCHandle.Alloc(this);
NativeMethods.HttpSetManagedContext(_pInProcessHandler, (IntPtr)_thisHandle);
Method = GetVerb(); Method = GetVerb();
RawTarget = GetRawUrl(); RawTarget = GetRawUrl();
@ -170,6 +168,8 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
resumeWriterThreshold: ResumeWriterTheshold, resumeWriterThreshold: ResumeWriterTheshold,
minimumSegmentSize: MinAllocBufferSize)); minimumSegmentSize: MinAllocBufferSize));
_bodyOutput = new OutputProducer(pipe); _bodyOutput = new OutputProducer(pipe);
NativeMethods.HttpSetManagedContext(_pInProcessHandler, (IntPtr)_thisHandle);
} }
private string GetOriginalPath() private string GetOriginalPath()

View File

@ -213,7 +213,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{ {
context?.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(OnDisconnect)}."); context?.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(OnDisconnect)}.");
} }
} }
private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPtr pvManagedHttpContext, int hr, int bytes) private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPtr pvManagedHttpContext, int hr, int bytes)

View File

@ -1,8 +1,12 @@
// 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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
@ -61,5 +65,44 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "0"); await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "0");
} }
[ConditionalFact]
public async Task ClientDisconnectCallbackStress()
{
// Fixture initialization fails if inside of the Task.Run, so send an
// initial request to initialize the fixture.
var response = await _fixture.Client.GetAsync("HelloWorld");
var numTotalRequests = 0;
for (var j = 0; j < 20; j++)
{
// Windows has a max connection limit of 10 for the IIS server,
// so setting limit fairly low.
const int numRequests = 5;
async Task RunRequests()
{
using (var connection = _fixture.CreateTestConnection())
{
await connection.Send(
"GET /WaitForAbort HTTP/1.1",
"Host: localhost",
"Connection: close",
"",
"");
await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() != "0");
Interlocked.Increment(ref numTotalRequests);
}
}
List<Task> tasks = new List<Task>();
for (int i = 0; i < numRequests; i++)
{
tasks.Add(Task.Run(RunRequests));
}
await Task.WhenAll(tasks);
await _fixture.Client.RetryRequestAsync("/WaitingRequestCount", async message => await message.Content.ReadAsStringAsync() == "0");
}
}
} }
} }