sapling/eden/fs/inodes/test/UnloadTest.cpp
Wez Furlong 3bee7ea2cc fixup root cause of ESTALE
Summary:
Thanks to some bpf tracing by strager, we traced the ESTALE response to
`d_splice_alias` and noted this comment above the implementation in the kernel:

> If a non-IS_ROOT directory is found, the filesystem is corrupt, and
> we should error out: directories can't have multiple aliases.

Well, our magic `.eden` directory is a directory with aliases and we were
seeing the error trigger on that dir.  So, this diff replaces hardlinking
directories into each tree with a hardlink to a symlink in each tree!

At mount time we create `.eden/this-dir` as a symlink to `/abs/path/to/mount/.eden`
so that `readlink("/abs/path/to/mount/sub/dir/.eden/socket")` still
resolves as it did prior to this diff.

Reviewed By: strager

Differential Revision: D12954819

fbshipit-source-id: 7f3b1b53f2bd5b9c51e64055fc34110657a19110
2018-11-07 15:20:58 -08:00

137 lines
4.5 KiB
C++

/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <folly/test/TestUtils.h>
#include <gtest/gtest.h>
#include "eden/fs/inodes/InodeMap.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/testharness/FakeTreeBuilder.h"
#include "eden/fs/testharness/InodeUnloader.h"
#include "eden/fs/testharness/TestMount.h"
using namespace std::chrono_literals;
using namespace facebook::eden;
namespace {
template <typename Unloader>
struct UnloadTest : ::testing::Test {
Unloader unloader;
};
} // namespace
TYPED_TEST_CASE(UnloadTest, InodeUnloaderTypes);
TYPED_TEST(UnloadTest, inodesAreUnloaded) {
FakeTreeBuilder builder;
builder.mkdir("docs");
builder.setFile("docs/README.md", "readme");
builder.setFile("docs/WholeFish", "sea bass");
builder.mkdir("src");
builder.setFile("src/code.c", "main() {}");
builder.mkdir("test");
builder.setFile("test/test.c", "TEST()");
TestMount testMount{builder};
const auto* edenMount = testMount.getEdenMount().get();
auto inodeMap = edenMount->getInodeMap();
std::vector<InodeNumber> loadedInodeNumbers;
auto load = [&](RelativePathPiece relpath) -> InodeNumber {
auto inode = edenMount->getInodeBlocking(relpath);
inode->incFuseRefcount();
loadedInodeNumbers.push_back(inode->getNodeId());
return inode->getNodeId();
};
// Load every file, increment the FUSE refcount, and remember its InodeNumber.
auto readme_ino = load("docs/README.md"_relpath);
auto wholefish_ino = load("docs/WholeFish"_relpath);
auto code_ino = load("src/code.c"_relpath);
auto test_ino = load("test/test.c"_relpath);
EXPECT_TRUE(inodeMap->lookupInode(readme_ino).get());
EXPECT_TRUE(inodeMap->lookupInode(wholefish_ino).get());
EXPECT_TRUE(inodeMap->lookupInode(code_ino).get());
EXPECT_TRUE(inodeMap->lookupInode(test_ino).get());
// Now decrement the FUSE refcounts.
inodeMap->decFuseRefcount(readme_ino, 1);
inodeMap->decFuseRefcount(wholefish_ino, 1);
inodeMap->decFuseRefcount(code_ino, 1);
inodeMap->decFuseRefcount(test_ino, 1);
// At this point, every file and tree should be loaded, plus the root and
// .eden.
// 4 files + 3 subdirectories + 1 root + 1 .eden + 4 .eden entries
EXPECT_EQ(13, inodeMap->getLoadedInodeCount());
EXPECT_EQ(0, inodeMap->getUnloadedInodeCount());
// Count includes files only, and the root's refcount will never go to zero
// while the mount is up.
EXPECT_EQ(12, this->unloader.unload(*edenMount->getRootInode()));
EXPECT_EQ(1, inodeMap->getLoadedInodeCount());
EXPECT_EQ(0, inodeMap->getUnloadedInodeCount());
}
TYPED_TEST(UnloadTest, inodesCanBeUnloadedDuringLoad) {
auto builder = FakeTreeBuilder{};
builder.setFile("src/sub/file.txt", "this is a test file");
TestMount testMount{builder, false};
// Look up the "src" tree inode by name, which starts the load.
// The future should only be fulfilled when after we make the tree ready
auto rootInode = testMount.getEdenMount()->getRootInode();
auto srcFuture = rootInode->getOrLoadChild("src"_pc);
EXPECT_FALSE(srcFuture.isReady());
rootInode->unloadChildrenNow();
builder.setReady("src");
ASSERT_TRUE(srcFuture.isReady());
auto srcTree = std::move(srcFuture).get(1s).asTreePtr();
EXPECT_NE(kRootNodeId, srcTree->getNodeId());
auto subFuture = srcTree->getOrLoadChild("sub"_pc);
srcTree.reset();
EXPECT_FALSE(subFuture.isReady());
rootInode->unloadChildrenNow();
builder.setReady("src/sub");
ASSERT_TRUE(subFuture.isReady());
auto sub = std::move(subFuture).get(1s);
EXPECT_NE(kRootNodeId, sub->getNodeId());
}
TEST(UnloadUnreferencedByFuse, inodesReferencedByFuseAreNotUnloaded) {
FakeTreeBuilder builder;
builder.mkdir("src");
builder.setFile("src/file.txt", "contents");
TestMount testMount{builder};
const auto* edenMount = testMount.getEdenMount().get();
auto inodeMap = edenMount->getInodeMap();
auto inode = edenMount->getInodeBlocking("src/file.txt"_relpath);
inode->incFuseRefcount();
inode.reset();
// 1 file + 1 subdirectory + 1 root + 1 .eden + 4 .eden entries
EXPECT_EQ(8, inodeMap->getLoadedInodeCount());
EXPECT_EQ(0, inodeMap->getUnloadedInodeCount());
EXPECT_EQ(5, edenMount->getRootInode()->unloadChildrenUnreferencedByFuse());
// root + src + file.txt
EXPECT_EQ(3, inodeMap->getLoadedInodeCount());
EXPECT_EQ(0, inodeMap->getUnloadedInodeCount());
}