mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 14:28:17 +03:00
inodes: prepopulate the InodeMap on mount
Summary: On Windows, the working copy doesn't go away on unmount, instead placeholders and full files[0] are still present on disk. For this reason, EdenFS needs to either overly invalidate files and directories at update times, or need to remember what is present on disk so the state can be recovered. For now, this diff simply focus on reloading all the inodes that are present on disk in the InodeMap. [0]: https://docs.microsoft.com/en-us/windows/win32/projfs/cache-state Reviewed By: chadaustin Differential Revision: D28889082 fbshipit-source-id: 90170c1291da563bea455c8032dc8282a093c9b3
This commit is contained in:
parent
b0a08b6a4f
commit
d1ad84db19
@ -264,6 +264,8 @@ FOLLY_NODISCARD folly::Future<folly::Unit> EdenMount::initialize(
|
||||
.thenValue([this, takeover](TreeInodePtr initTreeNode) {
|
||||
if (takeover) {
|
||||
inodeMap_->initializeFromTakeover(std::move(initTreeNode), *takeover);
|
||||
} else if (isWorkingCopyPersistent()) {
|
||||
inodeMap_->initializeFromOverlay(std::move(initTreeNode), *overlay_);
|
||||
} else {
|
||||
inodeMap_->initialize(std::move(initTreeNode));
|
||||
}
|
||||
|
@ -279,6 +279,17 @@ class EdenMount {
|
||||
Nfsd3* FOLLY_NULLABLE getNfsdChannel() const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Test if the working copy persist on disk after this mount will be
|
||||
* destroyed.
|
||||
*
|
||||
* This is only true on Windows when using ProjectedFS as files are left
|
||||
* around in the working copy after the mount is unmounted.
|
||||
*/
|
||||
constexpr bool isWorkingCopyPersistent() const {
|
||||
return folly::kIsWindows;
|
||||
}
|
||||
|
||||
ProcessAccessLog& getProcessAccessLog() const;
|
||||
|
||||
/**
|
||||
|
@ -122,20 +122,9 @@ inline void InodeMap::insertLoadedInode(
|
||||
}
|
||||
}
|
||||
|
||||
void InodeMap::initialize(TreeInodePtr root) {
|
||||
auto data = data_.wlock();
|
||||
XCHECK(!root_);
|
||||
root_ = std::move(root);
|
||||
insertLoadedInode(data, root_.get());
|
||||
XDCHECK_EQ(1u, data->numTreeInodes_);
|
||||
XDCHECK_EQ(0u, data->numFileInodes_);
|
||||
}
|
||||
|
||||
void InodeMap::initializeFromTakeover(
|
||||
FOLLY_MAYBE_UNUSED TreeInodePtr root,
|
||||
FOLLY_MAYBE_UNUSED const SerializedInodeMap& takeover) {
|
||||
auto data = data_.wlock();
|
||||
|
||||
void InodeMap::initializeRoot(
|
||||
const folly::Synchronized<Members>::LockedPtr& data,
|
||||
TreeInodePtr root) {
|
||||
XCHECK_EQ(data->loadedInodes_.size(), 0ul)
|
||||
<< "cannot load InodeMap data over a populated instance";
|
||||
XCHECK_EQ(data->unloadedInodes_.size(), 0ul)
|
||||
@ -146,6 +135,35 @@ void InodeMap::initializeFromTakeover(
|
||||
insertLoadedInode(data, root_.get());
|
||||
XDCHECK_EQ(1ul, data->numTreeInodes_);
|
||||
XDCHECK_EQ(0ul, data->numFileInodes_);
|
||||
}
|
||||
|
||||
void InodeMap::initialize(TreeInodePtr root) {
|
||||
initializeRoot(data_.wlock(), std::move(root));
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void InodeMap::initializeUnloadedInode(
|
||||
const folly::Synchronized<Members>::LockedPtr& data,
|
||||
InodeNumber parentIno,
|
||||
InodeNumber ino,
|
||||
Args&&... args) {
|
||||
auto unloadedEntry = UnloadedInode(parentIno, std::forward<Args>(args)...);
|
||||
auto result = data->unloadedInodes_.emplace(ino, std::move(unloadedEntry));
|
||||
if (!result.second) {
|
||||
auto message = fmt::format(
|
||||
"failed to emplace inode number {}; is it already present in the InodeMap?",
|
||||
ino);
|
||||
XLOG(ERR) << message;
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
void InodeMap::initializeFromTakeover(
|
||||
TreeInodePtr root,
|
||||
const SerializedInodeMap& takeover) {
|
||||
auto data = data_.wlock();
|
||||
initializeRoot(data, std::move(root));
|
||||
|
||||
for (const auto& entry : *takeover.unloadedInodes_ref()) {
|
||||
if (*entry.numFsReferences_ref() < 0) {
|
||||
auto message = folly::to<std::string>(
|
||||
@ -156,8 +174,10 @@ void InodeMap::initializeFromTakeover(
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
auto unloadedEntry = UnloadedInode(
|
||||
initializeUnloadedInode(
|
||||
data,
|
||||
InodeNumber::fromThrift(*entry.parentInode_ref()),
|
||||
InodeNumber::fromThrift(*entry.inodeNumber_ref()),
|
||||
PathComponentPiece{*entry.name_ref()},
|
||||
*entry.isUnlinked_ref(),
|
||||
*entry.mode_ref(),
|
||||
@ -165,18 +185,6 @@ void InodeMap::initializeFromTakeover(
|
||||
? std::nullopt
|
||||
: std::optional<Hash>{hashFromThrift(*entry.hash_ref())},
|
||||
folly::to<uint32_t>(*entry.numFsReferences_ref()));
|
||||
|
||||
auto result = data->unloadedInodes_.emplace(
|
||||
InodeNumber::fromThrift(*entry.inodeNumber_ref()),
|
||||
std::move(unloadedEntry));
|
||||
if (!result.second) {
|
||||
auto message = folly::to<std::string>(
|
||||
"failed to emplace inode number ",
|
||||
*entry.inodeNumber_ref(),
|
||||
"; is it already present in the InodeMap?");
|
||||
XLOG(ERR) << message;
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
XLOG(DBG2) << "InodeMap initialized mount " << mount_->getPath()
|
||||
@ -184,6 +192,81 @@ void InodeMap::initializeFromTakeover(
|
||||
<< " inodes registered";
|
||||
}
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Test if a file is present in the working copy.
|
||||
*
|
||||
* This needs to be called before the working copy is fully initialized to
|
||||
* avoid the risk of recursively calling into EdenFS. Thus this will only true
|
||||
* if the file is a placeholder/full file.
|
||||
*
|
||||
* See eden/fs/inodes/treeoverlay/TreeOverlayWindowsFsck.h for a description of
|
||||
* placeholder/full file.
|
||||
*/
|
||||
bool isFileInWorkingCopy(AbsolutePathPiece path) {
|
||||
PRJ_FILE_STATE state;
|
||||
auto widePath = path.wide();
|
||||
auto result = PrjGetOnDiskFileState(widePath.c_str(), &state);
|
||||
return !FAILED(result);
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* Test if a file is present in the backing overlay.
|
||||
*
|
||||
* On Linux and macOS, the working copy doesn't exist prior to mounting it, thus
|
||||
* this always returns false.
|
||||
*/
|
||||
bool isFileInWorkingCopy(AbsolutePathPiece /*path*/) {
|
||||
EDEN_BUG()
|
||||
<< "Linux and macOS do not have a persistent working copy across mounts";
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
void InodeMap::initializeFromOverlay(TreeInodePtr root, Overlay& overlay) {
|
||||
XCHECK(mount_->isWorkingCopyPersistent());
|
||||
|
||||
auto data = data_.wlock();
|
||||
initializeRoot(data, std::move(root));
|
||||
|
||||
std::vector<std::tuple<AbsolutePath, InodeNumber>> pending;
|
||||
pending.emplace_back(mount_->getPath(), root_->getNodeId());
|
||||
|
||||
while (!pending.empty()) {
|
||||
auto [path, dirInode] = std::move(pending.back());
|
||||
pending.pop_back();
|
||||
|
||||
auto dirEntries = overlay.loadOverlayDir(dirInode);
|
||||
for (const auto& [name, dirent] : dirEntries) {
|
||||
auto entryPath = path + name;
|
||||
|
||||
if (!isFileInWorkingCopy(entryPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ino = dirent.getInodeNumber();
|
||||
if (dirent.isDirectory()) {
|
||||
pending.emplace_back(std::move(entryPath), dirent.getInodeNumber());
|
||||
}
|
||||
|
||||
initializeUnloadedInode(
|
||||
data,
|
||||
dirInode,
|
||||
ino,
|
||||
name,
|
||||
false,
|
||||
dirent.getInitialMode(),
|
||||
dirent.getOptionalHash(),
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
XLOG(DBG2) << "InodeMap initialized mount " << mount_->getPath()
|
||||
<< " from overlay, " << data->unloadedInodes_.size()
|
||||
<< " inodes registered";
|
||||
}
|
||||
|
||||
ImmediateFuture<InodePtr> InodeMap::lookupInode(InodeNumber number) {
|
||||
// Lock the data.
|
||||
// We hold it while doing most of our work below, but explicitly unlock it
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include "eden/fs/inodes/InodeNumber.h"
|
||||
#include "eden/fs/inodes/InodePtr.h"
|
||||
#include "eden/fs/inodes/Overlay.h"
|
||||
#include "eden/fs/model/Hash.h"
|
||||
#include "eden/fs/takeover/gen-cpp2/takeover_types.h"
|
||||
#include "eden/fs/utils/ImmediateFuture.h"
|
||||
@ -134,6 +135,18 @@ class InodeMap {
|
||||
TreeInodePtr root,
|
||||
const SerializedInodeMap& takeover);
|
||||
|
||||
/**
|
||||
* Initialize the InodeMap from the content of the overlay.
|
||||
*
|
||||
* This should be called on platforms where the working copy is persistent in
|
||||
* between restarts so that the InodeMap is populated with all the inodes
|
||||
* that are already present on disk. This needs to be called before the
|
||||
* FsChannel is initialized for the mount.
|
||||
*
|
||||
* The only platform where this applies is Windows.
|
||||
*/
|
||||
void initializeFromOverlay(TreeInodePtr root, Overlay& overlay);
|
||||
|
||||
/**
|
||||
* Get the root inode.
|
||||
*/
|
||||
@ -608,6 +621,29 @@ class InodeMap {
|
||||
const folly::Synchronized<Members>::LockedPtr& data,
|
||||
InodeBase* inode);
|
||||
|
||||
/**
|
||||
* Verify the InodeMap precondition and initialize the root_ member.
|
||||
*/
|
||||
void initializeRoot(
|
||||
const folly::Synchronized<Members>::LockedPtr& data,
|
||||
TreeInodePtr root);
|
||||
|
||||
/**
|
||||
* Construct an UnloadedInode and insert it onto the unloadedInodes_ map.
|
||||
*
|
||||
* Will throw a std::runtime_error if the passed in InodeNumber is already
|
||||
* known by the the InodeMap.
|
||||
*
|
||||
* The argument list will be directly passed in to the UnloadedInode
|
||||
* constructor.
|
||||
*/
|
||||
template <class... Args>
|
||||
void initializeUnloadedInode(
|
||||
const folly::Synchronized<Members>::LockedPtr& data,
|
||||
InodeNumber parentIno,
|
||||
InodeNumber ino,
|
||||
Args&&... args);
|
||||
|
||||
/**
|
||||
* The EdenMount that owns this InodeMap.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user