mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
2bfdf6f481
Summary: A while back, we saw that concurrent directory creation would lead to EdenFS being confused and failing to record some of the created directories. This then caused EdenFS to no longer being in sync with what was on disk. To handle this case, we've had to manually creating these directories recursively. What I didn't realize at the time was that these concurrent notifications could also happen on removal this time, and if a directory removal notification wins the race against the removal of its last children, that directory wouldn't be removed and EdenFS would once again be confused about the state of the repository. Fixing this is a bit trickier than directory creation as it's more racier. Consider a directory that is being removed, and then immediately recreated with a file in it in a different process. The naive approach of simply force removing all of the children of a directory when handling the removal notification would clash with the file creation. We could argue that nobody should be doing this, but there would be an unhandled race, and thus a bug where data would potentially be lost[0]. We can however fix this bug slightly differently. For file/directory removal, we can actually hook onto the pre-callback, ie: one that happens before the file/directory is no longer visible on disk. This inherently eliminate the race altogether as the callback will be guaranteed to run when none of its children are present, and if a race happens with a file creation in it, we can simply fail the removal properly. The only tricky bit is for the renaming logic, as renaming a file is logically a removal followed by a creation. For that reason, I've moved part of the renaming bits to the pre-callback too. In theory, this change may negatively affect workloads that do concurrent directory removal as the duration during which a file/directory is visible ondisk now includes the EdenFS callback while it didn't before. Such workflows should be fairly rare and/or redirected to avoid EdenFS altogether if performance matters. [0]: This left-over file that EdenFS wouldn't be aware of would also later cause the checkout code to fail due to invalidation failures triggered when trying to invalidate that directory. This would be fairly hard to debug. Reviewed By: fanzeyi Differential Revision: D25112381 fbshipit-source-id: 9300499ce872ad93d0a687f0e61b7e2a9caf9556
103 lines
2.9 KiB
C++
103 lines
2.9 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This software may be used and distributed according to the terms of the
|
|
* GNU General Public License version 2.
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
#include "eden/fs/utils/WinError.h"
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <sstream>
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
std::string win32ErrorToString(uint32_t error) {
|
|
struct LocalDeleter {
|
|
void operator()(HLOCAL p) noexcept {
|
|
::LocalFree(p);
|
|
}
|
|
};
|
|
|
|
LPSTR messageBufferRaw = nullptr;
|
|
// By default, FormatMessageA will terminate the string with "\r\n",
|
|
// and the mis-named (and mis-documented) FORMAT_MESSAGE_MAX_WIDTH_MASK flag
|
|
// will remove these (but add a whitespace instead).
|
|
size_t size = FormatMessageA(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
|
|
nullptr,
|
|
error,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPSTR)&messageBufferRaw,
|
|
0,
|
|
nullptr);
|
|
// Get a unique_ptr to the raw buffer, so it's released in case of an
|
|
// exception.
|
|
std::unique_ptr<char, LocalDeleter> messageBuffer{messageBufferRaw};
|
|
|
|
std::stringstream stream;
|
|
if ((size > 0) && (messageBuffer)) {
|
|
stream << "Error (0x" << std::hex << error << ") " << messageBuffer.get();
|
|
} else {
|
|
stream << "Error (0x" << std::hex << error << ") Unknown Error";
|
|
}
|
|
return stream.str();
|
|
}
|
|
|
|
const char* Win32ErrorCategory::name() const noexcept {
|
|
return "Win32 Error";
|
|
}
|
|
|
|
std::string Win32ErrorCategory::message(int error) const {
|
|
return win32ErrorToString(error);
|
|
}
|
|
|
|
const std::error_category& Win32ErrorCategory::get() noexcept {
|
|
static class Win32ErrorCategory cat;
|
|
return cat;
|
|
}
|
|
|
|
const char* HResultErrorCategory::name() const noexcept {
|
|
return "HRESULT Error";
|
|
}
|
|
|
|
std::string HResultErrorCategory::message(int error) const {
|
|
return win32ErrorToString(error);
|
|
}
|
|
|
|
const std::error_category& HResultErrorCategory::get() noexcept {
|
|
static class HResultErrorCategory cat;
|
|
return cat;
|
|
}
|
|
|
|
HRESULT exceptionToHResult(const std::exception& ex) noexcept {
|
|
if (auto e = dynamic_cast<const std::system_error*>(&ex)) {
|
|
auto code = e->code();
|
|
XLOG(ERR) << e->what() << ": " << code;
|
|
if (code.category() == HResultErrorCategory::get()) {
|
|
return code.value();
|
|
}
|
|
if (code.category() == Win32ErrorCategory::get()) {
|
|
return HRESULT_FROM_WIN32(code.value());
|
|
}
|
|
if (code.category() == std::generic_category()) {
|
|
switch (code.value()) {
|
|
case ENOTEMPTY:
|
|
return HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY);
|
|
}
|
|
}
|
|
return HRESULT_FROM_WIN32(ERROR_ERRORS_ENCOUNTERED);
|
|
} else if (auto e = dynamic_cast<const std::bad_alloc*>(&ex)) {
|
|
return E_OUTOFMEMORY;
|
|
} else {
|
|
XLOG(ERR) << ex.what();
|
|
return HRESULT_FROM_WIN32(ERROR_ERRORS_ENCOUNTERED);
|
|
}
|
|
}
|
|
|
|
} // namespace eden
|
|
} // namespace facebook
|
|
#endif
|