Skip to content

Commit 0b8930b

Browse files
[release/8.0] Change how ANCM recycles app (#55288)
* Change how ANCM recycles app * app init and single join * don't recycle again * fix iisexpress * fallback * testing * cleanup * env var * fb * fb * compile * Apply suggestions from code review * Apply suggestions from code review --------- Co-authored-by: Brennan <[email protected]>
1 parent 1cb85b6 commit 0b8930b

File tree

15 files changed

+342
-72
lines changed

15 files changed

+342
-72
lines changed

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"
2222
HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer)
2323
: m_hModule(hModule),
2424
m_pServer(pServer),
25-
m_loadedApplicationHostingModel(HOSTING_UNKNOWN)
25+
m_loadedApplicationHostingModel(HOSTING_UNKNOWN),
26+
m_shutdownDelay()
2627
{
2728
m_disallowRotationOnConfigChange = false;
2829
InitializeSRWLock(&m_requestHandlerLoadLock);
@@ -171,6 +172,7 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, con
171172
m_loadedApplicationHostingModel = options.QueryHostingModel();
172173
m_loadedApplicationId = pApplication.GetApplicationId();
173174
m_disallowRotationOnConfigChange = options.QueryDisallowRotationOnConfigChange();
175+
m_shutdownDelay = options.QueryShutdownDelay();
174176

175177
RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, shadowCopyPath, options, pApplicationFactory, errorContext));
176178

@@ -197,6 +199,11 @@ bool HandlerResolver::GetDisallowRotationOnConfigChange()
197199
return m_disallowRotationOnConfigChange;
198200
}
199201

202+
std::chrono::milliseconds HandlerResolver::GetShutdownDelay() const
203+
{
204+
return m_shutdownDelay;
205+
}
206+
200207
HRESULT
201208
HandlerResolver::FindNativeAssemblyFromGlobalLocation(
202209
const ShimOptions& pConfiguration,

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class HandlerResolver
1919
void ResetHostingModel();
2020
APP_HOSTING_MODEL GetHostingModel();
2121
bool GetDisallowRotationOnConfigChange();
22+
std::chrono::milliseconds GetShutdownDelay() const;
2223

2324
private:
2425
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory, ErrorContext& errorContext);
@@ -40,6 +41,7 @@ class HandlerResolver
4041
APP_HOSTING_MODEL m_loadedApplicationHostingModel;
4142
HostFxr m_hHostFxrDll;
4243
bool m_disallowRotationOnConfigChange;
44+
std::chrono::milliseconds m_shutdownDelay;
4345

4446
static const PCWSTR s_pwzAspnetcoreInProcessRequestHandlerName;
4547
static const PCWSTR s_pwzAspnetcoreOutOfProcessRequestHandlerName;

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#define CS_ASPNETCORE_SHADOW_COPY_DIRECTORY L"shadowCopyDirectory"
1313
#define CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT L"cleanShadowCopyDirectory"
1414
#define CS_ASPNETCORE_DISALLOW_ROTATE_CONFIG L"disallowRotationOnConfigChange"
15+
#define CS_ASPNETCORE_SHUTDOWN_DELAY L"shutdownDelay"
16+
#define CS_ASPNETCORE_SHUTDOWN_DELAY_ENV L"ANCM_shutdownDelay"
1517

1618
ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
1719
m_hostingModel(HOSTING_UNKNOWN),
@@ -53,7 +55,7 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
5355

5456
auto disallowRotationOnConfigChange = find_element(handlerSettings, CS_ASPNETCORE_DISALLOW_ROTATE_CONFIG).value_or(std::wstring());
5557
m_fDisallowRotationOnConfigChange = equals_ignore_case(L"true", disallowRotationOnConfigChange);
56-
58+
5759
m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH);
5860
m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT);
5961
m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED);
@@ -82,4 +84,38 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
8284
auto dotnetEnvironmentEnabled = equals_ignore_case(L"Development", dotnetEnvironment);
8385

8486
m_fShowDetailedErrors = detailedErrorsEnabled || aspnetCoreEnvironmentEnabled || dotnetEnvironmentEnabled;
87+
88+
// Specifies how long to delay (in milliseconds) after IIS tells us to stop before starting the application shutdown.
89+
// See StartShutdown in globalmodule to see how it's used.
90+
auto shutdownDelay = find_element(handlerSettings, CS_ASPNETCORE_SHUTDOWN_DELAY).value_or(std::wstring());
91+
if (shutdownDelay.empty())
92+
{
93+
// Fallback to environment variable if process specific config wasn't set
94+
shutdownDelay = Environment::GetEnvironmentVariableValue(CS_ASPNETCORE_SHUTDOWN_DELAY_ENV)
95+
.value_or(environmentVariables[CS_ASPNETCORE_SHUTDOWN_DELAY_ENV]);
96+
if (shutdownDelay.empty())
97+
{
98+
// Default if neither process specific config or environment variable aren't set
99+
m_fShutdownDelay = std::chrono::seconds(0);
100+
}
101+
else
102+
{
103+
SetShutdownDelay(shutdownDelay);
104+
}
105+
}
106+
else
107+
{
108+
SetShutdownDelay(shutdownDelay);
109+
}
110+
}
111+
112+
void ShimOptions::SetShutdownDelay(const std::wstring& shutdownDelay)
113+
{
114+
auto millsecondsValue = std::stoi(shutdownDelay);
115+
if (millsecondsValue < 0)
116+
{
117+
throw ConfigurationLoadException(format(
118+
L"'shutdownDelay' in web.config or '%s' environment variable is less than 0.", CS_ASPNETCORE_SHUTDOWN_DELAY_ENV));
119+
}
120+
m_fShutdownDelay = std::chrono::milliseconds(millsecondsValue);
85121
}

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ class ShimOptions: NonCopyable
8989
return m_fDisallowRotationOnConfigChange;
9090
}
9191

92+
std::chrono::milliseconds
93+
QueryShutdownDelay() const noexcept
94+
{
95+
return m_fShutdownDelay;
96+
}
97+
9298
ShimOptions(const ConfigurationSource &configurationSource);
9399

94100
private:
@@ -104,4 +110,7 @@ class ShimOptions: NonCopyable
104110
bool m_fCleanShadowCopyDirectory;
105111
bool m_fDisallowRotationOnConfigChange;
106112
std::wstring m_strShadowCopyingDirectory;
113+
std::chrono::milliseconds m_fShutdownDelay;
114+
115+
void SetShutdownDelay(const std::wstring& shutdownDelay);
107116
};

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -143,22 +143,26 @@ APPLICATION_MANAGER::RecycleApplicationFromManager(
143143
}
144144
}
145145

146-
// If we receive a request at this point.
147-
// OutOfProcess: we will create a new application with new configuration
148-
// InProcess: the request would have to be rejected, as we are about to call g_HttpServer->RecycleProcess
149-
// on the worker process
150-
151146
if (!applicationsToRecycle.empty())
152147
{
153148
for (auto& application : applicationsToRecycle)
154149
{
155150
try
156151
{
157-
application->ShutDownApplication(/* fServerInitiated */ false);
152+
if (UseLegacyShutdown())
153+
{
154+
application->ShutDownApplication(/* fServerInitiated */ false);
155+
}
156+
else
157+
{
158+
// Recycle the process to trigger OnGlobalStopListening
159+
// which will shutdown the server and stop listening for new requests for this app
160+
m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand");
161+
}
158162
}
159163
catch (...)
160164
{
161-
LOG_ERRORF(L"Failed to stop application '%ls'", application->QueryApplicationInfoKey().c_str());
165+
LOG_ERRORF(L"Failed to recycle application '%ls'", application->QueryApplicationInfoKey().c_str());
162166
OBSERVE_CAUGHT_EXCEPTION()
163167

164168
// Failed to recycle an application. Log an event
@@ -176,28 +180,31 @@ APPLICATION_MANAGER::RecycleApplicationFromManager(
176180
}
177181
}
178182

179-
// Remove apps after calling shutdown on each of them
180-
// This is exclusive to in-process, as the shutdown of an in-process app recycles
181-
// the entire worker process.
182-
if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS)
183+
if (UseLegacyShutdown())
183184
{
184-
SRWExclusiveLock lock(m_srwLock);
185-
const std::wstring configurationPath = pszApplicationId;
186-
187-
auto itr = m_pApplicationInfoHash.begin();
188-
while (itr != m_pApplicationInfoHash.end())
185+
// Remove apps after calling shutdown on each of them
186+
// This is exclusive to in-process, as the shutdown of an in-process app recycles
187+
// the entire worker process.
188+
if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS)
189189
{
190-
if (itr->second != nullptr && itr->second->ConfigurationPathApplies(configurationPath)
191-
&& std::find(applicationsToRecycle.begin(), applicationsToRecycle.end(), itr->second) != applicationsToRecycle.end())
192-
{
193-
itr = m_pApplicationInfoHash.erase(itr);
194-
}
195-
else
190+
SRWExclusiveLock lock(m_srwLock);
191+
const std::wstring configurationPath = pszApplicationId;
192+
193+
auto itr = m_pApplicationInfoHash.begin();
194+
while (itr != m_pApplicationInfoHash.end())
196195
{
197-
++itr;
196+
if (itr->second != nullptr && itr->second->ConfigurationPathApplies(configurationPath)
197+
&& std::find(applicationsToRecycle.begin(), applicationsToRecycle.end(), itr->second) != applicationsToRecycle.end())
198+
{
199+
itr = m_pApplicationInfoHash.erase(itr);
200+
}
201+
else
202+
{
203+
++itr;
204+
}
198205
}
199-
}
200-
} // Release Exclusive m_srwLock
206+
} // Release Exclusive m_srwLock
207+
}
201208
}
202209
CATCH_RETURN()
203210

@@ -211,14 +218,19 @@ APPLICATION_MANAGER::RecycleApplicationFromManager(
211218
VOID
212219
APPLICATION_MANAGER::ShutDown()
213220
{
221+
// During shutdown we lock until we delete the application
222+
SRWExclusiveLock lock(m_srwLock);
223+
214224
// We are guaranteed to only have one outstanding OnGlobalStopListening event at a time
215225
// However, it is possible to receive multiple OnGlobalStopListening events
216226
// Protect against this by checking if we already shut down.
227+
if (g_fInShutdown)
228+
{
229+
return;
230+
}
231+
217232
g_fInShutdown = TRUE;
218233
g_fInAppOfflineShutdown = true;
219-
220-
// During shutdown we lock until we delete the application
221-
SRWExclusiveLock lock(m_srwLock);
222234
for (auto & [str, applicationInfo] : m_pApplicationInfoHash)
223235
{
224236
applicationInfo->ShutDownApplication(/* fServerInitiated */ true);

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ class APPLICATION_MANAGER
4747
return !m_handlerResolver.GetDisallowRotationOnConfigChange();
4848
}
4949

50+
std::chrono::milliseconds GetShutdownDelay() const
51+
{
52+
return m_handlerResolver.GetShutdownDelay();
53+
}
54+
55+
bool UseLegacyShutdown() const
56+
{
57+
return m_handlerResolver.GetShutdownDelay() == std::chrono::milliseconds::zero();
58+
}
59+
5060
private:
5161

5262
std::unordered_map<std::wstring, std::shared_ptr<APPLICATION_INFO>> m_pApplicationInfoHash;

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,14 @@ HRESULT
125125
moduleFactory.release(),
126126
RQ_EXECUTE_REQUEST_HANDLER,
127127
0));
128-
;
128+
129129
auto pGlobalModule = std::make_unique<ASPNET_CORE_GLOBAL_MODULE>(std::move(applicationManager));
130130

131131
RETURN_IF_FAILED(pModuleInfo->SetGlobalNotifications(
132-
pGlobalModule.release(),
133-
GL_CONFIGURATION_CHANGE | // Configuration change triggers IIS application stop
134-
GL_STOP_LISTENING)); // worker process stop or recycle
132+
pGlobalModule.release(),
133+
GL_CONFIGURATION_CHANGE | // Configuration change triggers IIS application stop
134+
GL_STOP_LISTENING | // worker process will stop listening for http requests
135+
GL_APPLICATION_STOP)); // app pool recycle or stop
135136

136137
return S_OK;
137138
}

src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
extern BOOL g_fInShutdown;
77

88
ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE(std::shared_ptr<APPLICATION_MANAGER> pApplicationManager) noexcept
9-
:m_pApplicationManager(std::move(pApplicationManager))
9+
: m_pApplicationManager(std::move(pApplicationManager))
1010
{
1111
}
1212

@@ -16,26 +16,52 @@ ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE(std::shared_ptr<APPLICATION
1616
//
1717
GLOBAL_NOTIFICATION_STATUS
1818
ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening(
19-
_In_ IGlobalStopListeningProvider * pProvider
19+
_In_ IGlobalStopListeningProvider* pProvider
2020
)
2121
{
2222
UNREFERENCED_PARAMETER(pProvider);
2323

2424
LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening");
2525

26-
if (g_fInShutdown)
26+
if (g_fInShutdown || m_shutdown.joinable())
2727
{
2828
// Avoid receiving two shutdown notifications.
2929
return GL_NOTIFICATION_CONTINUE;
3030
}
3131

32-
m_pApplicationManager->ShutDown();
33-
m_pApplicationManager = nullptr;
32+
StartShutdown();
3433

3534
// Return processing to the pipeline.
3635
return GL_NOTIFICATION_CONTINUE;
3736
}
3837

38+
GLOBAL_NOTIFICATION_STATUS
39+
ASPNET_CORE_GLOBAL_MODULE::OnGlobalApplicationStop(
40+
IN IHttpApplicationStopProvider* pProvider
41+
)
42+
{
43+
UNREFERENCED_PARAMETER(pProvider);
44+
45+
// If we're already cleaned up just return.
46+
// If user has opted out of the new shutdown behavior ignore this call as we never registered for it before
47+
if (!m_pApplicationManager || m_pApplicationManager->UseLegacyShutdown())
48+
{
49+
return GL_NOTIFICATION_CONTINUE;
50+
}
51+
52+
LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalApplicationStop");
53+
54+
if (!g_fInShutdown && !m_shutdown.joinable())
55+
{
56+
// Apps with preload + always running that don't receive a request before recycle/shutdown will never call OnGlobalStopListening
57+
// IISExpress can also close without calling OnGlobalStopListening which is where we usually would trigger shutdown
58+
// so we should make sure to shutdown the server in those cases
59+
StartShutdown();
60+
}
61+
62+
return GL_NOTIFICATION_CONTINUE;
63+
}
64+
3965
//
4066
// Is called when configuration changed
4167
// Recycled the corresponding core app if its configuration changed

0 commit comments

Comments
 (0)