Skip to content

Commit fce37c1

Browse files
authored
Implements shadow copying for ASP.NET Core + IIS (#28357)
1 parent 6db1f6e commit fce37c1

31 files changed

+894
-76
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ class AppOfflineHandler: public REQUEST_HANDLER
1111
public:
1212
AppOfflineHandler(IHttpContext& pContext, const std::string& appOfflineContent)
1313
: REQUEST_HANDLER(pContext),
14-
m_pContext(pContext),
15-
m_strAppOfflineContent(appOfflineContent)
14+
m_pContext(pContext),
15+
m_strAppOfflineContent(appOfflineContent)
1616
{
1717
}
1818

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,17 @@ class ApplicationFactory
3232
HRESULT Execute(
3333
_In_ IHttpServer *pServer,
3434
_In_ IHttpContext *pHttpContext,
35+
_In_ std::wstring& shadowCopyDirectory,
3536
_Outptr_ IAPPLICATION **pApplication) const
3637
{
3738
// m_location.data() is const ptr copy to local to get mutable pointer
3839
auto location = m_location;
39-
std::array<APPLICATION_PARAMETER, 3> parameters {
40+
std::array<APPLICATION_PARAMETER, 4> parameters {
4041
{
4142
{"InProcessExeLocation", location.data()},
4243
{"TraceContext", pHttpContext->GetTraceContext()},
43-
{"Site", pHttpContext->GetSite()}
44+
{"Site", pHttpContext->GetSite()},
45+
{"ShadowCopyDirectory", shadowCopyDirectory.data()}
4446
}
4547
};
4648

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer)
2929

3030
HRESULT
3131
HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication,
32+
const std::filesystem::path& shadowCopyPath,
3233
const ShimOptions& pConfiguration,
3334
std::unique_ptr<ApplicationFactory>& pApplicationFactory,
3435
ErrorContext& errorContext)
@@ -62,7 +63,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
6263
RETURN_IF_FAILED(HostFxrResolutionResult::Create(
6364
L"",
6465
pConfiguration.QueryProcessPath(),
65-
pApplication.GetApplicationPhysicalPath(),
66+
shadowCopyPath.empty() ? pApplication.GetApplicationPhysicalPath() : shadowCopyPath,
6667
pConfiguration.QueryArguments(),
6768
errorContext,
6869
options));
@@ -125,7 +126,7 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
125126
}
126127

127128
HRESULT
128-
HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext)
129+
HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, const std::filesystem::path& shadowCopyPath, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext)
129130
{
130131
SRWExclusiveLock lock(m_requestHandlerLoadLock);
131132
if (m_loadedApplicationHostingModel != HOSTING_UNKNOWN)
@@ -168,7 +169,7 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, std
168169

169170
m_loadedApplicationHostingModel = options.QueryHostingModel();
170171
m_loadedApplicationId = pApplication.GetApplicationId();
171-
RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, options, pApplicationFactory, errorContext));
172+
RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, shadowCopyPath, options, pApplicationFactory, errorContext));
172173

173174
return S_OK;
174175
}
@@ -181,6 +182,13 @@ void HandlerResolver::ResetHostingModel()
181182
m_loadedApplicationId.resize(0);
182183
}
183184

185+
APP_HOSTING_MODEL HandlerResolver::GetHostingModel()
186+
{
187+
SRWExclusiveLock lock(m_requestHandlerLoadLock);
188+
189+
return m_loadedApplicationHostingModel;
190+
}
191+
184192
HRESULT
185193
HandlerResolver::FindNativeAssemblyFromGlobalLocation(
186194
const ShimOptions& pConfiguration,

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ class HandlerResolver
1515
{
1616
public:
1717
HandlerResolver(HMODULE hModule, const IHttpServer &pServer);
18-
HRESULT GetApplicationFactory(const IHttpApplication &pApplication, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext);
18+
HRESULT GetApplicationFactory(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, std::unique_ptr<ApplicationFactory>& pApplicationFactory, const ShimOptions& options, ErrorContext& errorContext);
1919
void ResetHostingModel();
20+
APP_HOSTING_MODEL GetHostingModel();
2021

2122
private:
22-
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory, ErrorContext& errorContext);
23+
HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory, ErrorContext& errorContext);
2324
HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath);
2425
HRESULT FindNativeAssemblyFromHostfxr(
2526
const HostFxrResolutionResult& hostfxrOptions,

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include "Environment.h"
99

1010
#define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion"
11+
#define CS_ASPNETCORE_SHADOW_COPY L"experimentalEnableShadowCopy"
12+
#define CS_ASPNETCORE_SHADOW_COPY_DIRECTORY L"shadowCopyDirectory"
13+
#define CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT L"cleanShadowCopyDirectory"
1114

1215
ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
1316
m_hostingModel(HOSTING_UNKNOWN),
@@ -31,12 +34,22 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) :
3134
"or hostingModel=\"outofprocess\" in the web.config file.", hostingModel.c_str()));
3235
}
3336

37+
const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS);
38+
3439
if (m_hostingModel == HOSTING_OUT_PROCESS)
3540
{
36-
const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS);
3741
m_strHandlerVersion = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_VERSION).value_or(std::wstring());
3842
}
3943

44+
auto experimentalEnableShadowCopyElement = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY).value_or(std::wstring());
45+
m_fexperimentalEnableShadowCopying = equals_ignore_case(L"true", experimentalEnableShadowCopyElement);
46+
47+
auto cleanShadowCopyDirectory = find_element(handlerSettings, CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT).value_or(std::wstring());
48+
m_fCleanShadowCopyDirectory = equals_ignore_case(L"true", cleanShadowCopyDirectory);
49+
50+
m_strShadowCopyingDirectory = find_element(handlerSettings, CS_ASPNETCORE_SHADOW_COPY_DIRECTORY)
51+
.value_or(m_fexperimentalEnableShadowCopying ? L"ShadowCopyDirectory" : std::wstring());
52+
4053
m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH);
4154
m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT);
4255
m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED);

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

+21
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@ class ShimOptions: NonCopyable
6565
return m_fShowDetailedErrors;
6666
}
6767

68+
bool
69+
QueryShadowCopyEnabled() const noexcept
70+
{
71+
return m_fexperimentalEnableShadowCopying;
72+
}
73+
74+
bool
75+
QueryCleanShadowCopyDirectory() const noexcept
76+
{
77+
return m_fCleanShadowCopyDirectory;
78+
}
79+
80+
const std::wstring&
81+
QueryShadowCopyDirectory() const noexcept
82+
{
83+
return m_strShadowCopyingDirectory;
84+
}
85+
6886
ShimOptions(const ConfigurationSource &configurationSource);
6987

7088
private:
@@ -76,4 +94,7 @@ class ShimOptions: NonCopyable
7694
bool m_fStdoutLogEnabled;
7795
bool m_fDisableStartupPage;
7896
bool m_fShowDetailedErrors;
97+
bool m_fexperimentalEnableShadowCopying;
98+
bool m_fCleanShadowCopyDirectory;
99+
std::wstring m_strShadowCopyingDirectory;
79100
};

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

+98-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "file_utility.h"
1919

2020
extern HINSTANCE g_hServerModule;
21+
extern BOOL g_fInAppOfflineShutdown;
2122

2223
HRESULT
2324
APPLICATION_INFO::CreateHandler(
@@ -49,7 +50,6 @@ APPLICATION_INFO::CreateHandler(
4950
while (hr != S_OK)
5051
{
5152
// At this point application is either null or shutdown and is returning S_FALSE
52-
5353
if (m_pApplication != nullptr)
5454
{
5555
LOG_INFO(L"Application went offline");
@@ -80,11 +80,25 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext)
8080

8181
return S_OK;
8282
}
83+
8384
try
8485
{
8586
const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pHttpApplication);
8687
ShimOptions options(configurationSource);
8788

89+
if (g_fInAppOfflineShutdown)
90+
{
91+
m_pApplication = make_application<ServerErrorApplication>(
92+
pHttpApplication,
93+
E_FAIL,
94+
options.QueryDisableStartupPage() /* disableStartupPage */,
95+
"" /* responseContent */,
96+
503i16 /* statusCode */,
97+
0i16 /* subStatusCode */,
98+
"Application Shutting Down");
99+
return S_OK;
100+
}
101+
88102
ErrorContext errorContext;
89103
errorContext.statusCode = 500i16;
90104
errorContext.subStatusCode = 0i16;
@@ -130,6 +144,7 @@ APPLICATION_INFO::CreateApplication(IHttpContext& pHttpContext)
130144
}
131145
catch (...)
132146
{
147+
OBSERVE_CAUGHT_EXCEPTION();
133148
EventLog::Error(
134149
ASPNETCORE_CONFIGURATION_LOAD_ERROR,
135150
ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG,
@@ -175,13 +190,17 @@ APPLICATION_INFO::TryCreateApplication(IHttpContext& pHttpContext, const ShimOpt
175190
}
176191
}
177192

178-
RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), m_pApplicationFactory, options, error));
193+
auto shadowCopyPath = HandleShadowCopy(options, pHttpContext);
194+
195+
RETURN_IF_FAILED(m_handlerResolver.GetApplicationFactory(*pHttpContext.GetApplication(), shadowCopyPath, m_pApplicationFactory, options, error));
179196
LOG_INFO(L"Creating handler application");
180197

181198
IAPPLICATION * newApplication;
199+
std::wstring shadowCopyWstring = shadowCopyPath.wstring();
182200
RETURN_IF_FAILED(m_pApplicationFactory->Execute(
183201
&m_pServer,
184202
&pHttpContext,
203+
shadowCopyWstring,
185204
&newApplication));
186205

187206
m_pApplication.reset(newApplication);
@@ -206,19 +225,91 @@ APPLICATION_INFO::TryCreateHandler(
206225
return S_OK;
207226
}
208227
}
228+
209229
return S_FALSE;
210230
}
211231

212232
VOID
213233
APPLICATION_INFO::ShutDownApplication(const bool fServerInitiated)
214234
{
235+
IAPPLICATION* app = nullptr;
236+
{
237+
SRWExclusiveLock lock(m_applicationLock);
238+
if (!m_pApplication)
239+
{
240+
return;
241+
}
242+
app = m_pApplication.get();
243+
}
244+
245+
LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str());
246+
app->Stop(fServerInitiated);
247+
215248
SRWExclusiveLock lock(m_applicationLock);
216249

217-
if (m_pApplication)
250+
m_pApplication = nullptr;
251+
m_pApplicationFactory = nullptr;
252+
}
253+
254+
std::filesystem::path
255+
APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext)
256+
{
257+
std::filesystem::path shadowCopyPath;
258+
259+
// Only support shadow copying for IIS.
260+
if (options.QueryShadowCopyEnabled() && !m_pServer.IsCommandLineLaunch())
218261
{
219-
LOG_INFOF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str());
220-
m_pApplication->Stop(fServerInitiated);
221-
m_pApplication = nullptr;
222-
m_pApplicationFactory = nullptr;
262+
shadowCopyPath = options.QueryShadowCopyDirectory();
263+
std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath();
264+
265+
// Make shadow copy path absolute.
266+
if (!shadowCopyPath.is_absolute())
267+
{
268+
shadowCopyPath = std::filesystem::absolute(std::filesystem::path(physicalPath) / shadowCopyPath);
269+
}
270+
271+
// The shadow copy directory itself isn't copied to directly.
272+
// Instead subdirectories with numerically increasing names are created.
273+
// This is because on shutdown, the app itself will still have all dlls loaded,
274+
// meaning we can't copy to the same subdirectory. Therefore, on shutdown,
275+
// we create a directory that is one larger than the previous largest directory number.
276+
auto directoryName = 0;
277+
std::string directoryNameStr = "0";
278+
auto shadowCopyBaseDirectory = std::filesystem::directory_entry(shadowCopyPath);
279+
if (!shadowCopyBaseDirectory.exists())
280+
{
281+
CreateDirectory(shadowCopyBaseDirectory.path().wstring().c_str(), NULL);
282+
}
283+
284+
for (auto& entry : std::filesystem::directory_iterator(shadowCopyPath))
285+
{
286+
if (entry.is_directory())
287+
{
288+
try
289+
{
290+
auto tempDirName = entry.path().filename().string();
291+
int intFileName = std::stoi(tempDirName);
292+
if (intFileName > directoryName)
293+
{
294+
directoryName = intFileName;
295+
directoryNameStr = tempDirName;
296+
}
297+
}
298+
catch (...)
299+
{
300+
OBSERVE_CAUGHT_EXCEPTION();
301+
// Ignore any folders that can't be converted to an int.
302+
}
303+
}
304+
}
305+
306+
shadowCopyPath = shadowCopyPath / directoryNameStr;
307+
HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), std::filesystem::canonical(shadowCopyBaseDirectory.path()));
308+
if (hr != S_OK)
309+
{
310+
return std::wstring();
311+
}
223312
}
313+
314+
return shadowCopyPath;
224315
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class APPLICATION_INFO: NonCopyable
7979
HRESULT
8080
TryCreateApplication(IHttpContext& pHttpContext, const ShimOptions& options, ErrorContext& error);
8181

82+
std::filesystem::path
83+
HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext);
84+
8285
IHttpServer &m_pServer;
8386
HandlerResolver &m_handlerResolver;
8487

0 commit comments

Comments
 (0)