Skip to content
This repository was archived by the owner on Nov 1, 2018. It is now read-only.

Commit f808bdc

Browse files
authored
Disconnect the disconnect handler when request processing ends (#1413)
1 parent 14aebeb commit f808bdc

File tree

9 files changed

+126
-20
lines changed

9 files changed

+126
-20
lines changed

src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
#include "DisconnectHandler.h"
55
#include "exceptions.h"
66
#include "proxymodule.h"
7+
#include "SRWExclusiveLock.h"
78

89
void DisconnectHandler::NotifyDisconnect()
910
{
1011
try
1112
{
12-
const auto module = m_pModule.exchange(nullptr);
13+
std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> module;
14+
{
15+
SRWExclusiveLock lock(m_handlerLock);
16+
m_pHandler.swap(module);
17+
}
18+
1319
if (module != nullptr)
1420
{
15-
module ->NotifyDisconnect();
21+
module->NotifyDisconnect();
1622
}
1723
}
1824
catch (...)
@@ -27,7 +33,8 @@ void DisconnectHandler::CleanupStoredContext() noexcept
2733
delete this;
2834
}
2935

30-
void DisconnectHandler::SetHandler(ASPNET_CORE_PROXY_MODULE * module) noexcept
36+
void DisconnectHandler::SetHandler(std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> handler) noexcept
3137
{
32-
m_pModule = module;
38+
SRWExclusiveLock lock(m_handlerLock);
39+
handler.swap(m_pHandler);
3340
}

src/AspNetCoreModuleV2/AspNetCore/DisconnectHandler.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
#pragma once
5-
#include <atomic>
5+
6+
#include <memory>
7+
#include "irequesthandler.h"
68

79
class ASPNET_CORE_PROXY_MODULE;
810

911
class DisconnectHandler final: public IHttpConnectionStoredContext
1012
{
1113
public:
1214
DisconnectHandler()
13-
: m_pModule(nullptr)
15+
: m_pHandler(nullptr)
1416
{
17+
InitializeSRWLock(&m_handlerLock);
1518
}
1619

1720
virtual
@@ -27,9 +30,10 @@ class DisconnectHandler final: public IHttpConnectionStoredContext
2730
CleanupStoredContext() noexcept override;
2831

2932
void
30-
SetHandler(ASPNET_CORE_PROXY_MODULE * module) noexcept;
33+
SetHandler(std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> handler) noexcept;
3134

3235
private:
33-
std::atomic<ASPNET_CORE_PROXY_MODULE*> m_pModule;
36+
SRWLOCK m_handlerLock {};
37+
std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> m_pHandler;
3438
};
3539

src/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ ASPNET_CORE_PROXY_MODULE::ASPNET_CORE_PROXY_MODULE(HTTP_MODULE_ID moduleId, std:
7171

7272
ASPNET_CORE_PROXY_MODULE::~ASPNET_CORE_PROXY_MODULE()
7373
{
74+
// At this point m_pDisconnectHandler should be disconnected in
75+
// HandleNotificationStatus
76+
assert(m_pDisconnectHandler == nullptr);
77+
7478
if (m_pDisconnectHandler != nullptr)
7579
{
7680
m_pDisconnectHandler->SetHandler(nullptr);
@@ -112,15 +116,15 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
112116
FINISHED_IF_FAILED(moduleContainer->SetConnectionModuleContext(static_cast<IHttpConnectionStoredContext*>(disconnectHandler.release()), m_moduleId));
113117
}
114118

115-
m_pDisconnectHandler->SetHandler(this);
116-
117119
FINISHED_IF_FAILED(m_pApplicationManager->GetOrCreateApplicationInfo(
118120
*pHttpContext,
119121
m_pApplicationInfo));
120122

121123
FINISHED_IF_FAILED(m_pApplicationInfo->CreateHandler(*pHttpContext, m_pHandler));
122124

123125
retVal = m_pHandler->OnExecuteRequestHandler();
126+
127+
m_pDisconnectHandler->SetHandler(::ReferenceRequestHandler(m_pHandler.get()));
124128
}
125129
catch (...)
126130
{
@@ -141,7 +145,7 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler(
141145
}
142146
}
143147

144-
return retVal;
148+
return HandleNotificationStatus(retVal);
145149
}
146150

147151
__override
@@ -156,18 +160,27 @@ ASPNET_CORE_PROXY_MODULE::OnAsyncCompletion(
156160
{
157161
try
158162
{
159-
return m_pHandler->OnAsyncCompletion(
163+
return HandleNotificationStatus(m_pHandler->OnAsyncCompletion(
160164
pCompletionInfo->GetCompletionBytes(),
161-
pCompletionInfo->GetCompletionStatus());
165+
pCompletionInfo->GetCompletionStatus()));
162166
}
163167
catch (...)
164168
{
165169
OBSERVE_CAUGHT_EXCEPTION();
166-
return RQ_NOTIFICATION_FINISH_REQUEST;
170+
return HandleNotificationStatus(RQ_NOTIFICATION_FINISH_REQUEST);
167171
}
168172
}
169173

170-
void ASPNET_CORE_PROXY_MODULE::NotifyDisconnect() const
174+
REQUEST_NOTIFICATION_STATUS ASPNET_CORE_PROXY_MODULE::HandleNotificationStatus(REQUEST_NOTIFICATION_STATUS status) noexcept
171175
{
172-
m_pHandler->NotifyDisconnect();
176+
if (status != RQ_NOTIFICATION_PENDING)
177+
{
178+
if (m_pDisconnectHandler != nullptr)
179+
{
180+
m_pDisconnectHandler->SetHandler(nullptr);
181+
m_pDisconnectHandler = nullptr;
182+
}
183+
}
184+
185+
return status;
173186
}

src/AspNetCoreModuleV2/AspNetCore/proxymodule.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ class ASPNET_CORE_PROXY_MODULE : NonCopyable, public CHttpModule
4747
IHttpCompletionInfo * pCompletionInfo
4848
) override;
4949

50-
void
51-
NotifyDisconnect() const;
50+
REQUEST_NOTIFICATION_STATUS
51+
HandleNotificationStatus(REQUEST_NOTIFICATION_STATUS status) noexcept;
5252

5353
private:
5454
std::shared_ptr<APPLICATION_MANAGER> m_pApplicationManager;

src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#include "SRWExclusiveLock.h"
55

6-
SRWExclusiveLock::SRWExclusiveLock(const SRWLOCK& lock)
6+
SRWExclusiveLock::SRWExclusiveLock(const SRWLOCK& lock) noexcept
77
: m_lock(lock)
88
{
99
AcquireSRWLockExclusive(const_cast<SRWLOCK*>(&m_lock));

src/AspNetCoreModuleV2/CommonLib/SRWExclusiveLock.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
class SRWExclusiveLock
99
{
1010
public:
11-
SRWExclusiveLock(const SRWLOCK& lock);
11+
SRWExclusiveLock(const SRWLOCK& lock) noexcept;
1212
~SRWExclusiveLock();
1313
private:
1414
const SRWLOCK& m_lock;

src/AspNetCoreModuleV2/CommonLib/irequesthandler.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,9 @@ struct IREQUEST_HANDLER_DELETER
5454
application->DereferenceRequestHandler();
5555
}
5656
};
57+
58+
inline std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER> ReferenceRequestHandler(IREQUEST_HANDLER* handler)
59+
{
60+
handler->ReferenceRequestHandler();
61+
return std::unique_ptr<IREQUEST_HANDLER, IREQUEST_HANDLER_DELETER>(handler);
62+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Net.Http;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Server.IntegrationTesting;
9+
using Microsoft.AspNetCore.Testing.xunit;
10+
using Xunit;
11+
12+
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
13+
{
14+
[Collection(PublishedSitesCollection.Name)]
15+
public class ClientDisconnectStressTests: FunctionalTestsBase
16+
{
17+
private readonly PublishedSitesFixture _fixture;
18+
19+
public ClientDisconnectStressTests(PublishedSitesFixture fixture)
20+
{
21+
_fixture = fixture;
22+
}
23+
24+
[ConditionalTheory]
25+
[InlineData(HostingModel.InProcess)]
26+
[InlineData(HostingModel.OutOfProcess)]
27+
public async Task ClientDisconnectStress(HostingModel hostingModel)
28+
{
29+
var site = await StartAsync(_fixture.GetBaseDeploymentParameters(hostingModel));
30+
var maxRequestSize = 1000;
31+
var blockSize = 40;
32+
var random = new Random();
33+
async Task RunRequests()
34+
{
35+
using (var connection = new TestConnection(site.HttpClient.BaseAddress.Port))
36+
{
37+
await connection.Send(
38+
"POST /ReadAndFlushEcho HTTP/1.1",
39+
$"Content-Length: {maxRequestSize}",
40+
"Host: localhost",
41+
"Connection: close",
42+
"",
43+
"");
44+
45+
var disconnectAfter = random.Next(maxRequestSize);
46+
var data = new byte[blockSize];
47+
for (int i = 0; i < disconnectAfter / blockSize; i++)
48+
{
49+
await connection.Stream.WriteAsync(data);
50+
}
51+
}
52+
}
53+
54+
List<Task> tasks = new List<Task>();
55+
for (int i = 0; i < 100; i++)
56+
{
57+
tasks.Add(Task.Run(RunRequests));
58+
}
59+
60+
await Task.WhenAll(tasks);
61+
62+
StopServer();
63+
}
64+
}
65+
}

test/WebSites/InProcessWebSite/Startup.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,17 @@ private async Task ReadAndWriteEcho(HttpContext ctx)
324324
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
325325
}
326326
}
327+
private async Task ReadAndFlushEcho(HttpContext ctx)
328+
{
329+
var readBuffer = new byte[4096];
330+
var result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
331+
while (result != 0)
332+
{
333+
await ctx.Response.WriteAsync(Encoding.UTF8.GetString(readBuffer, 0, result));
334+
await ctx.Response.Body.FlushAsync();
335+
result = await ctx.Request.Body.ReadAsync(readBuffer, 0, readBuffer.Length);
336+
}
337+
}
327338

328339
private async Task ReadAndWriteEchoLines(HttpContext ctx)
329340
{

0 commit comments

Comments
 (0)