diff --git a/build/testsite.props b/build/testsite.props
index ad7204aab4..861d44a563 100644
--- a/build/testsite.props
+++ b/build/testsite.props
@@ -36,8 +36,8 @@
/config:"$(IISExpressAppHostConfig)" /systray:false
-h "$(IISAppHostConfig)"
- $(NativePlatform)\aspnetcore.dll
- $(NativePlatform)\aspnetcorev2.dll
+ $(AspNetCoreModuleV1ShimDll)
+ $(AspNetCoreModuleV2ShimDll)
aspnetcorev2_inprocess.dll
$(userprofile)\.dotnet\$(NativePlatform)\dotnet.exe
diff --git a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj
index b71ab41c83..bd0129be53 100644
--- a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj
+++ b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj
@@ -231,6 +231,7 @@
+
@@ -246,6 +247,7 @@
+
diff --git a/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp b/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp
new file mode 100644
index 0000000000..b921994850
--- /dev/null
+++ b/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp
@@ -0,0 +1,33 @@
+// 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.
+
+#include "DisconnectHandler.h"
+#include "exceptions.h"
+#include "proxymodule.h"
+
+void DisconnectHandler::NotifyDisconnect()
+{
+ try
+ {
+ const auto module = m_pModule.exchange(nullptr);
+ if (module != nullptr)
+ {
+ module ->NotifyDisconnect();
+ }
+ }
+ catch (...)
+ {
+ OBSERVE_CAUGHT_EXCEPTION();
+ }
+}
+
+void DisconnectHandler::CleanupStoredContext() noexcept
+{
+ SetHandler(nullptr);
+ delete this;
+}
+
+void DisconnectHandler::SetHandler(ASPNET_CORE_PROXY_MODULE * module) noexcept
+{
+ m_pModule = module;
+}
diff --git a/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h b/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h
new file mode 100644
index 0000000000..34c8a24a63
--- /dev/null
+++ b/src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h
@@ -0,0 +1,35 @@
+// 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.
+
+#pragma once
+#include
+
+class ASPNET_CORE_PROXY_MODULE;
+
+class DisconnectHandler final: public IHttpConnectionStoredContext
+{
+public:
+ DisconnectHandler()
+ : m_pModule(nullptr)
+ {
+ }
+
+ virtual
+ ~DisconnectHandler()
+ {
+ SetHandler(nullptr);
+ }
+
+ void
+ NotifyDisconnect() override;
+
+ void
+ CleanupStoredContext() noexcept override;
+
+ void
+ SetHandler(ASPNET_CORE_PROXY_MODULE * module) noexcept;
+
+private:
+ std::atomic m_pModule;
+};
+
diff --git a/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp b/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp
index 4ec0d774dd..3fce85afdd 100644
--- a/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp
+++ b/src/AspNetCoreModuleV2/AspNetCore/dllmain.cpp
@@ -41,7 +41,7 @@ BOOL WINAPI DllMain(HMODULE hModule,
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
-
+
ALLOC_CACHE_HANDLER::StaticInitialize();
g_hServerModule = hModule;
DisableThreadLibraryCalls(hModule);
@@ -101,7 +101,7 @@ HRESULT
{
g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER);
}
-
+
// check whether the feature is disabled due to security reason
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters",
@@ -144,9 +144,9 @@ HRESULT
// The ASPNET_CORE_PROXY_MODULE_FACTORY::Terminate method will clean any
// static object initialized.
//
-
+
auto applicationManager = std::make_shared(g_hServerModule, *pHttpServer);
- auto moduleFactory = std::make_unique(applicationManager);
+ auto moduleFactory = std::make_unique(pModuleInfo->GetId(), applicationManager);
RETURN_IF_FAILED(pModuleInfo->SetRequestNotifications(
moduleFactory.release(),
@@ -159,7 +159,7 @@ HRESULT
pGlobalModule.release(),
GL_CONFIGURATION_CHANGE | // Configuration change trigers IIS application stop
GL_STOP_LISTENING)); // worker process stop or recycle
-
+
return S_OK;
}
CATCH_RETURN()
diff --git a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp
index 8fafe13c1a..d3d71e8b4b 100644
--- a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp
+++ b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp
@@ -6,13 +6,15 @@
#include "applicationmanager.h"
#include "applicationinfo.h"
#include "exceptions.h"
+#include "DisconnectHandler.h"
extern BOOL g_fInShutdown;
__override
-ASPNET_CORE_PROXY_MODULE_FACTORY::ASPNET_CORE_PROXY_MODULE_FACTORY(std::shared_ptr applicationManager) noexcept
- :m_pApplicationManager(std::move(applicationManager))
+ASPNET_CORE_PROXY_MODULE_FACTORY::ASPNET_CORE_PROXY_MODULE_FACTORY(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept
+ : m_pApplicationManager(std::move(applicationManager)),
+ m_moduleId(moduleId)
{
}
@@ -22,10 +24,10 @@ ASPNET_CORE_PROXY_MODULE_FACTORY::GetHttpModule(
IModuleAllocator * pAllocator
)
{
-
+
#pragma warning( push )
#pragma warning ( disable : 26409 ) // Disable "Avoid using new"
- *ppModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(m_pApplicationManager);
+ *ppModule = new (pAllocator) ASPNET_CORE_PROXY_MODULE(m_moduleId, m_pApplicationManager);
#pragma warning( push )
if (*ppModule == nullptr)
{
@@ -58,13 +60,23 @@ Return value:
delete this;
}
-ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE(std::shared_ptr applicationManager) noexcept
+ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept
: m_pApplicationManager(std::move(applicationManager)),
m_pApplicationInfo(nullptr),
- m_pHandler(nullptr)
+ m_pHandler(nullptr),
+ m_moduleId(moduleId),
+ m_pDisconnectHandler(nullptr)
{
}
+ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE()
+{
+ if (m_pDisconnectHandler != nullptr)
+ {
+ m_pDisconnectHandler->SetHandler(nullptr);
+ }
+}
+
__override
REQUEST_NOTIFICATION_STATUS
ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
@@ -77,12 +89,31 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
try
{
-
if (g_fInShutdown)
{
FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS));
}
+ auto moduleContainer = pHttpContext
+ ->GetConnection()
+ ->GetModuleContextContainer();
+
+ #pragma warning( push )
+ #pragma warning ( disable : 26466 ) // Disable "Don't use static_cast downcasts". We build without RTTI support so dynamic_cast is not available
+ m_pDisconnectHandler = static_cast(moduleContainer->GetConnectionModuleContext(m_moduleId));
+ #pragma warning( push )
+
+ if (m_pDisconnectHandler == nullptr)
+ {
+ auto disconnectHandler = std::make_unique();
+ m_pDisconnectHandler = disconnectHandler.get();
+ // ModuleContextContainer takes ownership of disconnectHandler
+ // we are trusting that it would not release it before deleting the context
+ FINISHED_IF_FAILED(moduleContainer->SetConnectionModuleContext(static_cast(disconnectHandler.release()), m_moduleId));
+ }
+
+ m_pDisconnectHandler->SetHandler(this);
+
FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo(
*pHttpContext,
m_pApplicationInfo));
@@ -135,3 +166,8 @@ ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion(
return RQ_NOTIFICATION_FINISH_REQUEST;
}
}
+
+void ASPNET_CORE_PROXY_MODULE::NotifyDisconnect() const
+{
+ m_pHandler->NotifyDisconnect();
+}
diff --git a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h
index 7c67712bab..97c126b0f9 100644
--- a/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h
+++ b/src/AspNetCoreModuleV2/AspNetCore/proxymodule.h
@@ -7,6 +7,7 @@
#include "applicationinfo.h"
#include "irequesthandler.h"
#include "applicationmanager.h"
+#include "DisconnectHandler.h"
extern HTTP_MODULE_ID g_pModuleId;
@@ -14,9 +15,9 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
{
public:
- ASPNET_CORE_PROXY_MODULE(std::shared_ptr applicationManager) noexcept;
+ ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept;
- ~ASPNET_CORE_PROXY_MODULE() = default;
+ ~ASPNET_CORE_PROXY_MODULE();
void * operator new(size_t size, IModuleAllocator * pPlacement)
{
@@ -46,16 +47,21 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
IHttpCompletionInfo * pCompletionInfo
) override;
+ void
+ NotifyDisconnect() const;
+
private:
std::shared_ptr m_pApplicationManager;
std::shared_ptr m_pApplicationInfo;
std::unique_ptr m_pHandler;
+ HTTP_MODULE_ID m_moduleId;
+ DisconnectHandler * m_pDisconnectHandler;
};
class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory
{
public:
- ASPNET_CORE_PROXY_MODULE_FACTORY(std::shared_ptr applicationManager) noexcept;
+ ASPNET_CORE_PROXY_MODULE_FACTORY(HTTP_MODULE_ID moduleId, std::shared_ptr applicationManager) noexcept;
virtual ~ASPNET_CORE_PROXY_MODULE_FACTORY() = default;
HRESULT
@@ -66,7 +72,8 @@ class ASPNET_CORE_PROXY_MODULE_FACTORY : NonCopyable, public IHttpModuleFactory
VOID
Terminate() noexcept override;
-
+
private:
std::shared_ptr m_pApplicationManager;
+ HTTP_MODULE_ID m_moduleId;
};
diff --git a/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h b/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h
index 5f9b415794..8bd2076072 100644
--- a/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h
+++ b/src/AspNetCoreModuleV2/CommonLib/irequesthandler.h
@@ -26,9 +26,7 @@ public:
virtual
VOID
- TerminateRequest(
- bool fClientInitiated
- ) = 0;
+ NotifyDisconnect() noexcept(false) = 0;
virtual
~IREQUEST_HANDLER(
diff --git a/src/AspNetCoreModuleV2/CommonLib/requesthandler.h b/src/AspNetCoreModuleV2/CommonLib/requesthandler.h
index 7de54e4c77..a5e16bf022 100644
--- a/src/AspNetCoreModuleV2/CommonLib/requesthandler.h
+++ b/src/AspNetCoreModuleV2/CommonLib/requesthandler.h
@@ -14,7 +14,7 @@ class REQUEST_HANDLER: public virtual IREQUEST_HANDLER
public:
VOID
- ReferenceRequestHandler() noexcept override
+ ReferenceRequestHandler() noexcept override
{
InterlockedIncrement(&m_cRefs);
}
@@ -39,9 +39,11 @@ public:
return RQ_NOTIFICATION_FINISH_REQUEST;
}
- VOID TerminateRequest(bool fClientInitiated) override
+ #pragma warning( push )
+ #pragma warning ( disable : 26440 ) // Disable "Can be marked with noexcept"
+ VOID NotifyDisconnect() override
+ #pragma warning( pop )
{
- UNREFERENCED_PARAMETER(fClientInitiated);
}
private:
diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp
index 6f37a1a3b9..7fd934ae87 100644
--- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp
+++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp
@@ -44,14 +44,14 @@ IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
VOID
IN_PROCESS_APPLICATION::StopInternal(bool fServerInitiated)
-{
+{
StopClr();
InProcessApplicationBase::StopInternal(fServerInitiated);
}
VOID
IN_PROCESS_APPLICATION::StopClr()
-{
+{
LOG_INFO(L"Stopping CLR");
if (!m_blockManagedCallbacks)
@@ -86,6 +86,7 @@ VOID
IN_PROCESS_APPLICATION::SetCallbackHandles(
_In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
+ _In_ PFN_DISCONNECT_HANDLER disconnect_callback,
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
@@ -95,6 +96,7 @@ IN_PROCESS_APPLICATION::SetCallbackHandles(
m_RequestHandler = request_handler;
m_RequestHandlerContext = pvRequstHandlerContext;
+ m_DisconnectHandler = disconnect_callback;
m_ShutdownHandler = shutdown_handler;
m_ShutdownHandlerContext = pvShutdownHandlerContext;
m_AsyncCompletionHandler = async_completion_handler;
@@ -135,13 +137,13 @@ IN_PROCESS_APPLICATION::LoadManagedApplication()
}, ::ReferenceApplication(this));
LOG_INFO(L"Waiting for initialization");
-
+
const HANDLE waitHandles[2] = { m_pInitializeEvent, m_workerThread.native_handle() };
-
+
// Wait for shutdown request
const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, m_pConfig->QueryStartupTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
-
+
if (waitResult == WAIT_TIMEOUT)
{
// If server wasn't initialized in time shut application down without waiting for CLR thread to exit
@@ -168,9 +170,9 @@ void
IN_PROCESS_APPLICATION::ExecuteApplication()
{
try
- {
+ {
std::unique_ptr hostFxrOptions;
-
+
auto context = std::make_shared();
auto pProc = s_fMainCallback;
@@ -184,7 +186,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
// Get the entry point for main
pProc = reinterpret_cast(GetProcAddress(hModule, "hostfxr_main"));
THROW_LAST_ERROR_IF_NULL(pProc);
-
+
THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
m_dotnetExeKnownLocation,
m_pConfig->QueryProcessPath(),
@@ -217,7 +219,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
// We set a static so that managed code can call back into this instance and
// set the callbacks
s_Application = this;
-
+
//Start CLR thread
m_clrThread = std::thread(ClrThreadEntryPoint, context);
@@ -237,7 +239,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
{
const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
-
+
clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
}
@@ -274,7 +276,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
// in case when it was not initialized we need to keep server running to serve 502 page
if (m_Initialized)
{
- QueueStop();
+ QueueStop();
}
}
}
@@ -298,7 +300,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
QueryApplicationId().c_str(),
QueryApplicationPhysicalPath().c_str(),
GetUnexpectedExceptionMessage(ex).c_str());
-
+
OBSERVE_CAUGHT_EXCEPTION();
}
}
@@ -500,9 +502,9 @@ IN_PROCESS_APPLICATION::CreateHandler(
{
try
{
- *pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_AsyncCompletionHandler);
+ *pRequestHandler = new IN_PROCESS_HANDLER(::ReferenceApplication(this), pHttpContext, m_RequestHandler, m_RequestHandlerContext, m_DisconnectHandler, m_AsyncCompletionHandler);
}
CATCH_RETURN();
-
+
return S_OK;
}
diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h
index e2488d71e7..caf16193d6 100644
--- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h
+++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h
@@ -10,6 +10,7 @@
class IN_PROCESS_HANDLER;
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext);
+typedef VOID(WINAPI * PFN_DISCONNECT_HANDLER) (void *pvManagedHttpContext);
typedef BOOL(WINAPI * PFN_SHUTDOWN_HANDLER) (void* pvShutdownHandlerContext);
typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_ASYNC_COMPLETION_HANDLER)(void *pvManagedHttpContext, HRESULT hrCompletionStatus, DWORD cbCompletion);
@@ -33,6 +34,7 @@ public:
SetCallbackHandles(
_In_ PFN_REQUEST_HANDLER request_callback,
_In_ PFN_SHUTDOWN_HANDLER shutdown_callback,
+ _In_ PFN_DISCONNECT_HANDLER disconnect_callback,
_In_ PFN_ASYNC_COMPLETION_HANDLER managed_context_callback,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
@@ -48,14 +50,14 @@ public:
// Executes the .NET Core process
void
ExecuteApplication();
-
+
HRESULT
LoadManagedApplication();
void
QueueStop();
-
+
void
StopIncomingRequests()
{
@@ -110,7 +112,7 @@ public:
private:
struct ExecuteClrContext: std::enable_shared_from_this
{
- ExecuteClrContext():
+ ExecuteClrContext():
m_argc(0),
m_pProc(nullptr),
m_exitCode(0),
@@ -121,11 +123,11 @@ private:
DWORD m_argc;
std::unique_ptr m_argv;
hostfxr_main_fn m_pProc;
-
+
int m_exitCode;
int m_exceptionCode;
};
-
+
// Thread executing the .NET Core process this might be abandoned in timeout cases
std::thread m_clrThread;
// Thread tracking the CLR thread, this one is always joined on shutdown
@@ -144,6 +146,7 @@ private:
VOID* m_ShutdownHandlerContext;
PFN_ASYNC_COMPLETION_HANDLER m_AsyncCompletionHandler;
+ PFN_DISCONNECT_HANDLER m_DisconnectHandler;
std::wstring m_dotnetExeKnownLocation;
@@ -164,7 +167,7 @@ private:
HRESULT
SetEnvironmentVariablesOnWorkerProcess();
-
+
void
StopClr();
@@ -175,7 +178,7 @@ private:
static
void
ExecuteClr(const std::shared_ptr &context);
-
+
// Allows to override call to hostfxr_main with custom callback
// used in testing
inline static hostfxr_main_fn s_fMainCallback = nullptr;
diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp
index 9e7e84d7f1..c8fc89956e 100644
--- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp
+++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp
@@ -6,6 +6,7 @@
#include "aspnetcore_event.h"
#include "IOutputManager.h"
#include "ShuttingDownApplication.h"
+#include "ntassert.h"
ALLOC_CACHE_HANDLER * IN_PROCESS_HANDLER::sm_pAlloc = NULL;
@@ -14,6 +15,7 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
_In_ IHttpContext *pW3Context,
_In_ PFN_REQUEST_HANDLER pRequestHandler,
_In_ void * pRequestHandlerContext,
+ _In_ PFN_DISCONNECT_HANDLER pDisconnectHandler,
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion
): m_pManagedHttpContext(nullptr),
m_requestNotificationStatus(RQ_NOTIFICATION_PENDING),
@@ -22,7 +24,8 @@ IN_PROCESS_HANDLER::IN_PROCESS_HANDLER(
m_pApplication(std::move(pApplication)),
m_pRequestHandler(pRequestHandler),
m_pRequestHandlerContext(pRequestHandlerContext),
- m_pAsyncCompletionHandler(pAsyncCompletion)
+ m_pAsyncCompletionHandler(pAsyncCompletion),
+ m_pDisconnectHandler(pDisconnectHandler)
{
}
@@ -63,7 +66,7 @@ IN_PROCESS_HANDLER::OnExecuteRequestHandler()
{
return ServerShutdownMessage();
}
-
+
return m_pRequestHandler(this, m_pRequestHandlerContext);
}
@@ -87,6 +90,7 @@ IN_PROCESS_HANDLER::OnAsyncCompletion(
return ServerShutdownMessage();
}
+ assert(m_pManagedHttpContext != nullptr);
// Call the managed handler for async completion.
return m_pAsyncCompletionHandler(m_pManagedHttpContext, hrCompletionStatus, cbCompletion);
}
@@ -97,11 +101,16 @@ REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage() const
}
VOID
-IN_PROCESS_HANDLER::TerminateRequest(
- bool fClientInitiated
-)
+IN_PROCESS_HANDLER::NotifyDisconnect()
{
- UNREFERENCED_PARAMETER(fClientInitiated);
+ if (m_pApplication->QueryBlockCallbacksIntoManaged() ||
+ m_fManagedRequestComplete)
+ {
+ return;
+ }
+
+ assert(m_pManagedHttpContext != nullptr);
+ m_pDisconnectHandler(m_pManagedHttpContext);
}
VOID
@@ -110,6 +119,7 @@ IN_PROCESS_HANDLER::IndicateManagedRequestComplete(
)
{
m_fManagedRequestComplete = TRUE;
+ m_pManagedHttpContext = nullptr;
}
VOID
diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h
index 6089adc3ca..c1fae5dfc7 100644
--- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h
+++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.h
@@ -18,6 +18,7 @@ public:
_In_ IHttpContext *pW3Context,
_In_ PFN_REQUEST_HANDLER pRequestHandler,
_In_ void * pRequestHandlerContext,
+ _In_ PFN_DISCONNECT_HANDLER m_DisconnectHandler,
_In_ PFN_ASYNC_COMPLETION_HANDLER pAsyncCompletion);
~IN_PROCESS_HANDLER() override = default;
@@ -35,10 +36,8 @@ public:
__override
VOID
- TerminateRequest(
- bool fClientInitiated
- ) override;
-
+ NotifyDisconnect() override;
+
IHttpContext*
QueryHttpContext() const
{
@@ -82,6 +81,6 @@ private:
PFN_REQUEST_HANDLER m_pRequestHandler;
void* m_pRequestHandlerContext;
PFN_ASYNC_COMPLETION_HANDLER m_pAsyncCompletionHandler;
-
+ PFN_DISCONNECT_HANDLER m_pDisconnectHandler;
static ALLOC_CACHE_HANDLER * sm_pAlloc;
};
diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp
index 27a41da909..2185f975a4 100644
--- a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp
+++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp
@@ -16,6 +16,7 @@ register_callbacks(
_In_ IN_PROCESS_APPLICATION* pInProcessApplication,
_In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
+ _In_ PFN_DISCONNECT_HANDLER disconnect_handler,
_In_ PFN_ASYNC_COMPLETION_HANDLER async_completion_handler,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
@@ -29,6 +30,7 @@ register_callbacks(
pInProcessApplication->SetCallbackHandles(
request_handler,
shutdown_handler,
+ disconnect_handler,
async_completion_handler,
pvRequstHandlerContext,
pvShutdownHandlerContext
@@ -424,6 +426,16 @@ http_disable_buffering(
return S_OK;
}
+EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
+HRESULT
+http_close_connection(
+ _In_ IN_PROCESS_HANDLER* pInProcessHandler
+)
+{
+ pInProcessHandler->QueryHttpContext()->GetResponse()->ResetConnection();
+ return S_OK;
+}
+
EXTERN_C __MIDL_DECLSPEC_DLLEXPORT
HRESULT
http_response_set_unknown_header(
diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj
index df9c00ebb1..3c4ee3b264 100644
--- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj
+++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/OutOfProcessRequestHandler.vcxproj
@@ -224,7 +224,6 @@
-
diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/disconnectcontext.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/disconnectcontext.h
deleted file mode 100644
index e421fe11e3..0000000000
--- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/disconnectcontext.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-
-#pragma once
-
-class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext
-{
-public:
- ASYNC_DISCONNECT_CONTEXT()
- {
- m_pHandler = NULL;
- }
-
- VOID
- CleanupStoredContext()
- {
- DBG_ASSERT(m_pHandler == NULL);
- delete this;
- }
-
- VOID
- NotifyDisconnect()
- {
- IREQUEST_HANDLER *pInitialValue = (IREQUEST_HANDLER*)
- InterlockedExchangePointer((PVOID*)&m_pHandler, NULL);
-
- if (pInitialValue != NULL)
- {
- pInitialValue->TerminateRequest(TRUE);
- pInitialValue->DereferenceRequestHandler();
- }
- }
-
- VOID
- SetHandler(
- IREQUEST_HANDLER *pHandler
- )
- {
- //
- // Take a reference on the forwarding handler.
- // This reference will be released on either of two conditions:
- //
- // 1. When the request processing ends, in which case a ResetHandler()
- // is called.
- //
- // 2. When a disconnect notification arrives.
- //
- // We need to make sure that only one of them ends up dereferencing
- // the object.
- //
-
- DBG_ASSERT(pHandler != NULL);
- DBG_ASSERT(m_pHandler == NULL);
-
- pHandler->ReferenceRequestHandler();
- InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler);
- }
-
- VOID
- ResetHandler(
- VOID
- )
- {
- IREQUEST_HANDLER *pInitialValue = (IREQUEST_HANDLER*)
- InterlockedExchangePointer((PVOID*)&m_pHandler, NULL);
-
- if (pInitialValue != NULL)
- {
- pInitialValue->DereferenceRequestHandler();
- }
- }
-
-private:
- ~ASYNC_DISCONNECT_CONTEXT()
- {}
-
- IREQUEST_HANDLER * m_pHandler;
-};
\ No newline at end of file
diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp
index 6dd018783b..c43241495d 100644
--- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp
+++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.cpp
@@ -46,7 +46,8 @@ FORWARDING_HANDLER::FORWARDING_HANDLER(
m_fServerResetConn(FALSE),
m_cRefs(1),
m_pW3Context(pW3Context),
- m_pApplication(std::move(pApplication))
+ m_pApplication(std::move(pApplication)),
+ m_fReactToDisconnect(FALSE)
{
LOG_TRACE(L"FORWARDING_HANDLER::FORWARDING_HANDLER");
@@ -73,7 +74,7 @@ FORWARDING_HANDLER::~FORWARDING_HANDLER(
// The m_pServer cleanup would happen afterwards, since there may be a
// call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders()
//
- DBG_ASSERT(m_pDisconnect == NULL);
+ DBG_ASSERT(!m_fReactToDisconnect);
RemoveRequest();
@@ -93,7 +94,6 @@ FORWARDING_HANDLER::OnExecuteRequestHandler()
REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE;
HRESULT hr = S_OK;
BOOL fRequestLocked = FALSE;
- BOOL fHandleSet = FALSE;
BOOL fFailedToStartKestrel = FALSE;
BOOL fSecure = FALSE;
HINTERNET hConnect = NULL;
@@ -199,31 +199,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler()
goto Failure;
}
- // Set client disconnect callback contract with IIS
- m_pDisconnect = static_cast(
- pClientConnection->GetModuleContextContainer()->
- GetConnectionModuleContext(m_pModuleId));
- if (m_pDisconnect == NULL)
- {
- m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT();
- if (m_pDisconnect == NULL)
- {
- hr = E_OUTOFMEMORY;
- goto Failure;
- }
-
- hr = pClientConnection->GetModuleContextContainer()->
- SetConnectionModuleContext(m_pDisconnect,
- m_pModuleId);
- DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED));
- if (FAILED_LOG(hr))
- {
- goto Failure;
- }
- }
-
- m_pDisconnect->SetHandler(this);
- fHandleSet = TRUE;
+ m_fReactToDisconnect = TRUE;
// require lock as client disconnect callback may happen
AcquireSRWLockShared(&m_RequestLock);
@@ -2705,21 +2681,16 @@ FORWARDING_HANDLER::RemoveRequest(
VOID
)
{
- ASYNC_DISCONNECT_CONTEXT * pDisconnect;
- pDisconnect = (ASYNC_DISCONNECT_CONTEXT *)InterlockedExchangePointer((PVOID*)&m_pDisconnect, NULL);
- if (pDisconnect != NULL)
- {
- pDisconnect->ResetHandler();
- pDisconnect = NULL;
- }
+ m_fReactToDisconnect = FALSE;
}
VOID
-FORWARDING_HANDLER::TerminateRequest(
- bool fClientInitiated
-)
+FORWARDING_HANDLER::NotifyDisconnect()
{
- UNREFERENCED_PARAMETER(fClientInitiated);
+ if (!m_fReactToDisconnect)
+ {
+ return;
+ }
BOOL fLocked = FALSE;
if (TlsGetValue(g_dwTlsIndex) != this)
@@ -2740,7 +2711,7 @@ FORWARDING_HANDLER::TerminateRequest(
if (!m_fHttpHandleInClose)
{
- m_fClientDisconnected = fClientInitiated;
+ m_fClientDisconnected = true;
}
if (fLocked)
diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h
index a41b86fe50..cb9255e16d 100644
--- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h
+++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/forwardinghandler.h
@@ -68,9 +68,7 @@ public:
StaticTerminate();
VOID
- TerminateRequest(
- bool fClientInitiated
- );
+ NotifyDisconnect() override;
static void * operator new(size_t size);
@@ -220,7 +218,6 @@ private:
DWORD m_cMinBufferLimit;
ULONGLONG m_cContentLength;
WEBSOCKET_HANDLER * m_pWebSocket;
- ASYNC_DISCONNECT_CONTEXT * m_pDisconnect;
BYTE * m_pEntityBuffer;
static const SIZE_T INLINE_ENTITY_BUFFERS = 8;
@@ -239,5 +236,5 @@ private:
mutable LONG m_cRefs;
IHttpContext* m_pW3Context;
std::unique_ptr m_pApplication;
- HTTP_MODULE_ID m_pModuleId;
+ bool m_fReactToDisconnect;
};
diff --git a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h
index 83498f3057..0134ac111e 100644
--- a/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h
+++ b/src/AspNetCoreModuleV2/OutOfProcessRequestHandler/stdafx.h
@@ -59,7 +59,6 @@
#include "resources.h"
#include "aspnetcore_event.h"
#include "aspnetcore_msg.h"
-#include "disconnectcontext.h"
#include "requesthandler_config.h"
#include "sttimer.h"
diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.cs
index 46cda59046..8ff01c778f 100644
--- a/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.cs
+++ b/src/Microsoft.AspNetCore.Server.IIS/Core/DuplexStream.cs
@@ -8,7 +8,6 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
- // TODO redudant file, remove
// See https://github.com/aspnet/IISIntegration/issues/426
internal class DuplexStream : Stream
{
diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/EmptyStream.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/EmptyStream.cs
new file mode 100644
index 0000000000..dfc804df42
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IIS/Core/EmptyStream.cs
@@ -0,0 +1,94 @@
+// 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.
+
+using System;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Server.IIS.Core
+{
+ internal class EmptyStream : ReadOnlyStream
+ {
+ private readonly IHttpBodyControlFeature _bodyControl;
+ private HttpStreamState _state;
+ private Exception _error;
+
+ public EmptyStream(IHttpBodyControlFeature bodyControl)
+ {
+ _bodyControl = bodyControl;
+ _state = HttpStreamState.Open;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (!_bodyControl.AllowSynchronousIO)
+ {
+ throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
+ }
+
+ return 0;
+ }
+
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ ValidateState(cancellationToken);
+
+ return Task.FromResult(0);
+ }
+
+ public void StopAcceptingReads()
+ {
+ // Can't use dispose (or close) as can be disposed too early by user code
+ // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
+ _state = HttpStreamState.Closed;
+ }
+
+ public void Abort(Exception error = null)
+ {
+ // We don't want to throw an ODE until the app func actually completes.
+ // If the request is aborted, we throw a TaskCanceledException instead,
+ // unless error is not null, in which case we throw it.
+ if (_state != HttpStreamState.Closed)
+ {
+ _state = HttpStreamState.Aborted;
+ _error = error;
+ }
+ }
+
+ private void ValidateState(CancellationToken cancellationToken)
+ {
+ switch (_state)
+ {
+ case HttpStreamState.Open:
+ if (cancellationToken.IsCancellationRequested)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ break;
+ case HttpStreamState.Closed:
+ throw new ObjectDisposedException(nameof(HttpRequestStream));
+ case HttpStreamState.Aborted:
+ if (_error != null)
+ {
+ ExceptionDispatchInfo.Capture(_error).Throw();
+ }
+ else
+ {
+ throw new TaskCanceledException();
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/HttpRequestStream.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/HttpRequestStream.cs
new file mode 100644
index 0000000000..8a9d37b146
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IIS/Core/HttpRequestStream.cs
@@ -0,0 +1,160 @@
+// 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.
+
+using System;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Server.IIS.Core
+{
+ internal class HttpRequestStream : ReadOnlyStream
+ {
+ private readonly IHttpBodyControlFeature _bodyControl;
+ private IISHttpContext _body;
+ private HttpStreamState _state;
+ private Exception _error;
+
+ public HttpRequestStream(IHttpBodyControlFeature bodyControl)
+ {
+ _bodyControl = bodyControl;
+ _state = HttpStreamState.Closed;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (!_bodyControl.AllowSynchronousIO)
+ {
+ throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
+ }
+
+ return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
+ }
+
+ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ var task = ReadAsync(buffer, offset, count, default(CancellationToken), state);
+ if (callback != null)
+ {
+ task.ContinueWith(t => callback.Invoke(t));
+ }
+ return task;
+ }
+
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ return ((Task)asyncResult).GetAwaiter().GetResult();
+ }
+
+ private Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
+ {
+ var tcs = new TaskCompletionSource(state);
+ var task = ReadAsync(buffer, offset, count, cancellationToken);
+ task.ContinueWith((task2, state2) =>
+ {
+ var tcs2 = (TaskCompletionSource)state2;
+ if (task2.IsCanceled)
+ {
+ tcs2.SetCanceled();
+ }
+ else if (task2.IsFaulted)
+ {
+ tcs2.SetException(task2.Exception);
+ }
+ else
+ {
+ tcs2.SetResult(task2.Result);
+ }
+ }, tcs, cancellationToken);
+ return tcs.Task;
+ }
+
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ ValidateState(cancellationToken);
+
+ return ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask();
+ }
+
+#if NETCOREAPP2_1
+ public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default)
+ {
+ ValidateState(cancellationToken);
+
+ return ReadAsyncInternal(destination, cancellationToken);
+ }
+#elif NETSTANDARD2_0
+#else
+#error TFMs need to be updated
+#endif
+
+ private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return await _body.ReadAsync(buffer, cancellationToken);
+ }
+ catch (ConnectionAbortedException ex)
+ {
+ throw new TaskCanceledException("The request was aborted", ex);
+ }
+ }
+
+ public void StartAcceptingReads(IISHttpContext body)
+ {
+ // Only start if not aborted
+ if (_state == HttpStreamState.Closed)
+ {
+ _state = HttpStreamState.Open;
+ _body = body;
+ }
+ }
+
+ public void StopAcceptingReads()
+ {
+ // Can't use dispose (or close) as can be disposed too early by user code
+ // As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
+ _state = HttpStreamState.Closed;
+ _body = null;
+ }
+
+ public void Abort(Exception error = null)
+ {
+ // We don't want to throw an ODE until the app func actually completes.
+ // If the request is aborted, we throw a TaskCanceledException instead,
+ // unless error is not null, in which case we throw it.
+ if (_state != HttpStreamState.Closed)
+ {
+ _state = HttpStreamState.Aborted;
+ _error = error;
+ }
+ }
+
+ private void ValidateState(CancellationToken cancellationToken)
+ {
+ switch (_state)
+ {
+ case HttpStreamState.Open:
+ if (cancellationToken.IsCancellationRequested)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ break;
+ case HttpStreamState.Closed:
+ throw new ObjectDisposedException(nameof(HttpRequestStream));
+ case HttpStreamState.Aborted:
+ if (_error != null)
+ {
+ ExceptionDispatchInfo.Capture(_error).Throw();
+ }
+ else
+ {
+ throw new TaskCanceledException();
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.IIS/Core/HttpResponseStream.cs b/src/Microsoft.AspNetCore.Server.IIS/Core/HttpResponseStream.cs
new file mode 100644
index 0000000000..1a04535448
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IIS/Core/HttpResponseStream.cs
@@ -0,0 +1,151 @@
+// 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.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Server.IIS.Core
+{
+ internal class HttpResponseStream : WriteOnlyStream
+ {
+ private readonly IHttpBodyControlFeature _bodyControl;
+ private readonly IISHttpContext _context;
+ private HttpStreamState _state;
+
+ public HttpResponseStream(IHttpBodyControlFeature bodyControl, IISHttpContext context)
+ {
+ _bodyControl = bodyControl;
+ _context = context;
+ _state = HttpStreamState.Closed;
+ }
+
+ public override void Flush()
+ {
+ FlushAsync(default(CancellationToken)).GetAwaiter().GetResult();
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ ValidateState(cancellationToken);
+
+ return _context.FlushAsync(cancellationToken);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (!_bodyControl.AllowSynchronousIO)
+ {
+ throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
+ }
+
+ WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
+ }
+
+ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ var task = WriteAsync(buffer, offset, count, default(CancellationToken), state);
+ if (callback != null)
+ {
+ task.ContinueWith(t => callback.Invoke(t));
+ }
+ return task;
+ }
+
+ public override void EndWrite(IAsyncResult asyncResult)
+ {
+ ((Task