-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Hi,
I got many issues with shadow copy lately, mainly with old dlls not being deleted in the shadow copy folder and decided to analyse the source code.
I found this code chunk:
aspnetcore/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp
Lines 257 to 337 in 1eda538
/* Overview of shadow copy logic when enabled. See https://github.com/dotnet/aspnetcore/pull/28357 for more context | |
* On first request, ANCM goes through its startup sequence, starting dotnet and sending the request into managed code. During this sequence, | |
* ANCM will copy the contents of the app directory to another directory which is user specified. The path to this directory can be absolute or relative. | |
* Logs and log files will be written to the app directory rather than the shadow copy directory. app_offline will also only be watched in the app directory. | |
* The current directory will be set to the app directory as well as the AppContext.BaseDirectory. | |
* On publish of new content to the app directory, ANCM will start debouncing file change notifications for dlls, waiting for a steady state. | |
* This is done by resetting a timer each time a dll is changed, eventually triggering the timer once there are no dll changes. Afterwards, shutdown is started, | |
* causing the process to recycle. | |
* Subfolders are created under the user specified shadowCopyDirectory, where the highest int value directory name will be used each time. | |
* It will start at subdirectory with name '0' and increment from there. On shutdown, because dlls are still locked by the running process, | |
* we need to copy dlls to a different directory than what is currently running in the app. So in the case where the directory name is '0', | |
* we will create a directory name '1' and write the contents there. Then on app start, it will pick the directory name '1' as it's the highest value. | |
* Other directories in the shadow copy directory will be cleaned up as well. Following the example, after '1' has been selected as the directory to use, | |
* we will start a thread that deletes all other folders in that directory. | |
*/ | |
std::filesystem::path | |
APPLICATION_INFO::HandleShadowCopy(const ShimOptions& options, IHttpContext& pHttpContext) | |
{ | |
std::filesystem::path shadowCopyPath; | |
// Only support shadow copying for IIS. | |
if (options.QueryShadowCopyEnabled() && !m_pServer.IsCommandLineLaunch()) | |
{ | |
shadowCopyPath = options.QueryShadowCopyDirectory(); | |
std::wstring physicalPath = pHttpContext.GetApplication()->GetApplicationPhysicalPath(); | |
// Make shadow copy path absolute. | |
if (!shadowCopyPath.is_absolute()) | |
{ | |
shadowCopyPath = std::filesystem::absolute(std::filesystem::path(physicalPath) / shadowCopyPath); | |
} | |
// The shadow copy directory itself isn't copied to directly. | |
// Instead subdirectories with numerically increasing names are created. | |
// This is because on shutdown, the app itself will still have all dlls loaded, | |
// meaning we can't copy to the same subdirectory. Therefore, on shutdown, | |
// we create a directory that is one larger than the previous largest directory number. | |
auto directoryName = 0; | |
std::string directoryNameStr = "0"; | |
auto shadowCopyBaseDirectory = std::filesystem::directory_entry(shadowCopyPath); | |
if (!shadowCopyBaseDirectory.exists()) | |
{ | |
CreateDirectory(shadowCopyBaseDirectory.path().wstring().c_str(), nullptr); | |
} | |
for (auto& entry : std::filesystem::directory_iterator(shadowCopyPath)) | |
{ | |
if (entry.is_directory()) | |
{ | |
try | |
{ | |
auto tempDirName = entry.path().filename().string(); | |
int intFileName = std::stoi(tempDirName); | |
if (intFileName > directoryName) | |
{ | |
directoryName = intFileName; | |
directoryNameStr = tempDirName; | |
} | |
} | |
catch (...) | |
{ | |
OBSERVE_CAUGHT_EXCEPTION(); | |
// Ignore any folders that can't be converted to an int. | |
} | |
} | |
} | |
int copiedFileCount = 0; | |
shadowCopyPath = shadowCopyPath / directoryNameStr; | |
LOG_INFOF(L"Copying to shadow copy directory %ls.", shadowCopyPath.c_str()); | |
// Avoid using canonical for shadowCopyBaseDirectory | |
// It could expand to a network drive, or an expanded link folder path | |
// We already made it an absolute path relative to the physicalPath above | |
HRESULT hr = Environment::CopyToDirectory(physicalPath, shadowCopyPath, options.QueryCleanShadowCopyDirectory(), shadowCopyBaseDirectory.path(), copiedFileCount); |
It seems like it looks for the last existing numbered folder, but instead of incrementing the int value to create a new folder, it reuses the int value of the found folder.
I don't quite grasp the difference of responsibility between both those files :
https://github.com/dotnet/aspnetcore/blob/1eda5387220d8c1ee5f3be433d867cae817df4ed/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp
and
https://github.com/dotnet/aspnetcore/blob/main/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp
but in the later, where the shadow copy logic is replicated, the increment is done as expected :
directoryNameInt++; |
Can you explain the difference between both the logics, and is what I spotted actually a bug ?