Skip to content

Commit 676418d

Browse files
Merged PR 34318: Fix canceled requests in IIS (#52056)
Canceled requests can end up hanging a thread due to the `std::conditional_variable` never being triggered. This is because the previous assumption, that disconnect would be called once there was an http context set in the native layer, was wrong. Because `NotifyDisconnect` is called by IIS and `SetManagedHttpContext` is called by managed code, they can race and end up with `m_disconnectFired` as true, but `m_pManagedHttpContext` as false. The fix is fairly simple, just don't gate unblocking the `std::conditional_variable` on whether the http context has been set. Added comments as well to make it a little easier to understand some of the code, hopefully we'll slowly improve the maintainability by doing this with future changes as well. Co-authored-by: Brennan Conroy <brecon@microsoft.com>
1 parent 7de7876 commit 676418d

File tree

1 file changed

+20
-8
lines changed

1 file changed

+20
-8
lines changed

src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ REQUEST_NOTIFICATION_STATUS IN_PROCESS_HANDLER::ServerShutdownMessage() const
8989
return ShuttingDownHandler::ServerShutdownMessage(m_pW3Context);
9090
}
9191

92+
// Called from native IIS
9293
VOID
9394
IN_PROCESS_HANDLER::NotifyDisconnect()
9495
{
@@ -112,20 +113,28 @@ IN_PROCESS_HANDLER::NotifyDisconnect()
112113
m_disconnectFired = true;
113114
}
114115

116+
// This could be null if the request is completed before the http context is set
117+
// for example this can happen when the client cancels the request very quickly after making it
115118
if (pManagedHttpContext != nullptr)
116119
{
117120
m_pDisconnectHandler(pManagedHttpContext);
118-
{
119-
// lock before notifying, this prevents the condition where m_queueNotified is already checked but
120-
// the condition_variable isn't waiting yet, which would cause notify_all to NOOP and block
121-
// IndicateManagedRequestComplete until a spurious wakeup
122-
std::lock_guard<std::mutex> lock(m_lockQueue);
123-
m_queueNotified = true;
124-
}
125-
m_queueCheck.notify_all();
126121
}
122+
123+
// Make sure we unblock any potential current or future m_queueCheck.wait(...) calls
124+
// We could make this conditional, but it would need to be duplicated in SetManagedHttpContext
125+
// to avoid a race condition where the http context is null but we called disconnect which could make IndicateManagedRequestComplete hang
126+
// It's more future proof to just always do this even if nothing will be waiting on the conditional_variable
127+
{
128+
// lock before notifying, this prevents the condition where m_queueNotified is already checked but
129+
// the condition_variable isn't waiting yet, which would cause notify_all to NOOP and block
130+
// IndicateManagedRequestComplete until a spurious wakeup
131+
std::lock_guard<std::mutex> lock(m_lockQueue);
132+
m_queueNotified = true;
133+
}
134+
m_queueCheck.notify_all();
127135
}
128136

137+
// Called from managed server
129138
VOID
130139
IN_PROCESS_HANDLER::IndicateManagedRequestComplete(
131140
VOID
@@ -164,6 +173,7 @@ IN_PROCESS_HANDLER::SetAsyncCompletionStatus(
164173
m_requestNotificationStatus = requestNotificationStatus;
165174
}
166175

176+
// Called from managed server
167177
VOID
168178
IN_PROCESS_HANDLER::SetManagedHttpContext(
169179
PVOID pManagedHttpContext
@@ -179,6 +189,8 @@ IN_PROCESS_HANDLER::SetManagedHttpContext(
179189

180190
if (disconnectFired && pManagedHttpContext != nullptr)
181191
{
192+
// Safe to call, managed code is waiting on SetManagedHttpContext in the process request loop and doesn't dispose
193+
// the GCHandle until after the request loop completes
182194
m_pDisconnectHandler(pManagedHttpContext);
183195
}
184196
}

0 commit comments

Comments
 (0)