sapling/eden/fs/inodes/EdenMount.cpp

605 lines
22 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2016-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 "EdenMount.h"
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <folly/ExceptionWrapper.h>
#include <folly/FBString.h>
#include <folly/File.h>
#include <folly/Subprocess.h>
#include <folly/ThreadName.h>
#include <folly/experimental/logging/Logger.h>
#include <folly/experimental/logging/xlog.h>
#include <folly/futures/Future.h>
#include "eden/fs/config/ClientConfig.h"
#include "eden/fs/fuse/FuseChannel.h"
#include "eden/fs/fuse/privhelper/PrivHelper.h"
#include "eden/fs/inodes/CheckoutContext.h"
#include "eden/fs/inodes/DiffContext.h"
#include "eden/fs/inodes/Dirstate.h"
#include "eden/fs/inodes/EdenDispatcher.h"
#include "eden/fs/inodes/FileInode.h"
#include "eden/fs/inodes/InodeError.h"
introduce a new InodeMap class Summary: This diff starts adding a new InodeMap class. This class will eventually consolidate the functionality of InodeNameMap plus the inode map stored in EdenDispatcher. This new class will bring several new benefits: - All inode mapping logic consolidated into a single class, with a single lock protecting the maps. - A well-defined model for loaded vs unloaded inodes. InodeMap explicitly tracks inodes that have InodeBase objects created for them vs inodes that have an inode number allocated (for FUSE) but do not have an InodeBase object in memory. This will make it possible to unload Inode objects on demand to reduce memory usage. - Tracking of pending loads, and de-duplicating load requests. This ensures that only one Inode object ever exists for a given inode number / path. If a second request to load an Inode arrives when a previous load request is still in progress, InodeMap deals with this situation properly. - Better support for using Inode objects without FUSE. With the old code, attempts to interact with Inode objects without going through the FUSE dispatch (say, when processing a thrift call) could result in inconsistent state. New inodes created would not be put into the EdenDispatcher map, which could result in problems. - More convenient child inode access from TreeInode. With this change, the TreeInode class can easily tell which of its children are loaded. This makes it easier to do tasks which only need to operate on existing loaded inode state (for instance, dirstate computation). - Support for saving and loading state, to implement graceful restart.. InodeMap provides a central place to write out inode state on shutdown and restoring it on startup. Saved inodes can easily be restored to an "unloaded" state on startup. This code is not implemented yet as part of this diff, but it should be straightforward to add in future diffs. Reviewed By: bolinfest Differential Revision: D4318060 fbshipit-source-id: d9b16430fc8367e3516e788d1e991e5163aa6997
2016-12-23 02:34:54 +03:00
#include "eden/fs/inodes/InodeMap.h"
Flip Dirstate -> EdenMount dependency. Summary: Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes pointers to a `MountPoint` and an `ObjectStore` because it does not need the entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create the `Dirstate` itself, but that will be done in a follow-up commit. Fortunately, it was pretty easy to remove the references to `edenMount_` in `Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`. The one thing that I also decided to move was `getModifiedDirectoriesForMount()` because I already needed to create an `EdenMounts` file (admittedly not a great name) to collect some utility functions that use members of an `EdenMount` while not having access to the `EdenMount` itself. As part of this change, all of the code in `eden/fs/model/hg` has been moved to `eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change the `Dirstate` from an Hg-specific concept to a more general concept. `LocalDirstatePersistence` is no longer one of two implementations of `DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is just one concrete implementation called `DirstatePersistence` that takes its implementation from `LocalDirstatePersistence`. Because there is no longer a `FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that uses a `TemporaryFile`. Because `TestMount` now takes responsibility for creating the `Dirstate`, it must also give callers the ability to specify the user directives. To that end, `TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a `getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated as part of this revision, but `DirstateTest` needed quite a few updates (which were generally mechanical). Reviewed By: simpkins Differential Revision: D4230154 fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
#include "eden/fs/inodes/Overlay.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/model/Hash.h"
#include "eden/fs/model/Tree.h"
#include "eden/fs/model/git/GitIgnoreStack.h"
#include "eden/fs/store/ObjectStore.h"
fix EdenServer::unmount() to fully wait for mount point cleanup Summary: This fixes EdenServer::unmount() to actually wait for all EdenMount cleanup to complete, and fixes unmountAll() to return a Future that correctly waits for all mount points to be cleaned up. Previously `unmount()` waited for the mount point to be unmounted from the kernel, but did not wait for EdenMount shutdown to complete. Previously EdenMount shutdown was not triggered until the last reference to the shared_ptr<EdenMount> was released. This often happened in the FUSE channel thread that triggered the mountFinished() call--it would still hold a reference to this pointer, and would not release it until after mountFinished() returns. As a result, when the main thread was shutting down, `main()` would call `unmountAll()`, and then return soon after it completed. Some FUSE channel threads may still be running at this point, still performing `EdenMount` shutdown while the main thread was exiting. This could result in crashes and deadlocks as shutdown tried to access objects already destroyed by the main thread. With this change `EdenMount::shutdown()` is triggered explicitly during `mountFinished()`, and `unmount()` will not complete until this finishes. The `EdenMount` object may still exist at this point, and could still be deleted by the FUSE channel thread, but the deletion now only requires freeing the memory and does not require accessing other data that may have been cleaned up by the main thread. We should still clean up the FUSE channel thread handling in the future, to make sure these threads are joined before the main thread exits. However, that cleanup can wait until a separate diff. Ideally I would like to move more of the mount and unmount logic from EdenServer and EdenServiceHandler and put that code in EdenMount instead. Reviewed By: bolinfest Differential Revision: D5541318 fbshipit-source-id: 470332478357a85c314bc40458373cb0f827f62b
2017-08-03 02:52:18 +03:00
#include "eden/fs/utils/Bug.h"
using folly::Future;
using folly::StringPiece;
using folly::Unit;
using folly::Unit;
using folly::makeFuture;
using folly::setThreadName;
using folly::to;
using std::make_shared;
using std::make_unique;
using std::shared_ptr;
using std::unique_ptr;
using std::vector;
DEFINE_int32(fuseNumThreads, 16, "how many fuse dispatcher threads to spawn");
namespace facebook {
namespace eden {
static constexpr folly::StringPiece kEdenStracePrefix = "eden.strace.";
// We compute this when the process is initialized, but stash a copy
// in each EdenMount. We may in the future manage to propagate enough
// state across upgrades or restarts that we can preserve this, but
// as implemented today, a process restart will invalidate any cached
// mountGeneration that a client may be holding on to.
// We take the bottom 16-bits of the pid and 32-bits of the current
// time and shift them up, leaving 16 bits for a mount point generation
// number.
static const uint64_t globalProcessGeneration =
(uint64_t(getpid()) << 48) | (uint64_t(time(nullptr)) << 16);
// Each time we create an EdenMount we bump this up and OR it together
// with the globalProcessGeneration to come up with a generation number
// for a given mount instance.
static std::atomic<uint16_t> mountGeneration{0};
folly::Future<std::shared_ptr<EdenMount>> EdenMount::create(
std::unique_ptr<ClientConfig> config,
std::unique_ptr<ObjectStore> objectStore,
AbsolutePathPiece socketPath,
fusell::ThreadLocalEdenStats* globalStats,
std::chrono::system_clock::time_point lastCheckoutTime) {
auto mount = std::shared_ptr<EdenMount>{new EdenMount{std::move(config),
std::move(objectStore),
socketPath,
globalStats,
lastCheckoutTime},
EdenMountDeleter{}};
return mount->initialize().then([mount] { return mount; });
}
EdenMount::EdenMount(
std::unique_ptr<ClientConfig> config,
std::unique_ptr<ObjectStore> objectStore,
AbsolutePathPiece socketPath,
fusell::ThreadLocalEdenStats* globalStats,
std::chrono::system_clock::time_point lastCheckOutTime)
: globalEdenStats_(globalStats),
config_(std::move(config)),
inodeMap_{new InodeMap(this)},
dispatcher_{new EdenDispatcher(this)},
objectStore_(std::move(objectStore)),
overlay_(std::make_shared<Overlay>(config_->getOverlayPath())),
dirstate_(std::make_unique<Dirstate>(this)),
bindMounts_(config_->getBindMounts()),
mountGeneration_(globalProcessGeneration | ++mountGeneration),
socketPath_(socketPath),
straceLogger_{kEdenStracePrefix.str() +
config_->getMountPath().value().toStdString()},
lastCheckoutTime_(lastCheckOutTime),
path_(config_->getMountPath()),
uid_(getuid()),
gid_(getgid()) {}
folly::Future<folly::Unit> EdenMount::initialize() {
auto parents = std::make_shared<ParentCommits>(config_->getParentCommits());
parentInfo_.wlock()->parents.setParents(*parents);
return createRootInode(*parents).then(
[this, parents](TreeInodePtr initTreeNode) {
auto maxInodeNumber = overlay_->getMaxRecordedInode();
inodeMap_->initialize(std::move(initTreeNode), maxInodeNumber);
XLOG(DBG2) << "Initializing eden mount " << getPath()
<< "; max existing inode number is " << maxInodeNumber;
// Record the transition from no snapshot to the current snapshot in
// the journal. This also sets things up so that we can carry the
// snapshot id forward through subsequent journal entries.
auto delta = std::make_unique<JournalDelta>();
delta->toHash = parents->parent1();
journal_.wlock()->addDelta(std::move(delta));
return setupDotEden(getRootInode());
});
}
folly::Future<TreeInodePtr> EdenMount::createRootInode(
const ParentCommits& parentCommits) {
// Load the overlay, if present.
auto rootOverlayDir = overlay_->loadOverlayDir(FUSE_ROOT_ID);
if (rootOverlayDir) {
return folly::makeFuture<TreeInodePtr>(
TreeInodePtr::makeNew(this, std::move(rootOverlayDir.value())));
}
return objectStore_->getTreeForCommit(parentCommits.parent1())
.then([this](std::shared_ptr<const Tree> tree) {
return TreeInodePtr::makeNew(this, std::move(tree));
});
}
folly::Future<folly::Unit> EdenMount::setupDotEden(TreeInodePtr root) {
// Set up the magic .eden dir
return root->getOrLoadChildTree(PathComponentPiece{kDotEdenName})
.then([=](TreeInodePtr dotEdenInode) {
// We could perhaps do something here to ensure that it reflects the
// current state of the world, but for the moment we trust that it
// still reflects how things were when we set it up.
dotEdenInodeNumber_ = dotEdenInode->getNodeId();
})
.onError([=](const InodeError& /*err*/) {
auto dotEdenInode =
getRootInode()->mkdir(PathComponentPiece{kDotEdenName}, 0744);
dotEdenInodeNumber_ = dotEdenInode->getNodeId();
dotEdenInode->symlink(
PathComponentPiece{"root"}, config_->getMountPath().stringPiece());
dotEdenInode->symlink(
PathComponentPiece{"socket"}, getSocketPath().stringPiece());
dotEdenInode->symlink(
PathComponentPiece{"client"},
config_->getClientDirectory().stringPiece());
});
}
EdenMount::~EdenMount() {}
void EdenMount::performBindMounts() {
for (auto& bindMount : bindMounts_) {
auto& pathInMountDir = bindMount.pathInMountDir;
try {
// If pathInMountDir does not exist, then it must be created before
// the bind mount is performed.
boost::system::error_code errorCode;
boost::filesystem::path mountDir = pathInMountDir.c_str();
boost::filesystem::create_directories(mountDir, errorCode);
fusell::privilegedBindMount(
bindMount.pathInClientDir.c_str(), pathInMountDir.c_str());
} catch (...) {
// Consider recording all failed bind mounts in a way that can be
// communicated back to the caller in a structured way.
XLOG(ERR) << "Failed to perform bind mount for "
<< pathInMountDir.stringPiece() << ".";
}
}
}
void EdenMount::performPostClone() {
auto cloneSuccessPath = config_->getCloneSuccessPath();
bool isInitialMount = access(cloneSuccessPath.c_str(), F_OK) != 0;
if (isInitialMount) {
auto repoHooks = config_->getRepoHooks();
auto postCloneScript = repoHooks + RelativePathPiece("post-clone");
auto repoSource = config_->getRepoSource();
XLOG(INFO) << "Running post-clone hook '" << postCloneScript << "' for "
<< path_;
try {
// TODO(mbolin): It would be preferable to pass the name of the
// repository as defined in ~/.edenrc so that the script can derive
// the repoType and repoSource from that. Then the hook would only
// take two args.
auto repoType = config_->getRepoType();
folly::Subprocess proc(
{postCloneScript.c_str(),
repoType,
path_.stringPiece().str(),
repoSource},
folly::Subprocess::Options().pipeStdin());
proc.closeParentFd(STDIN_FILENO);
proc.waitChecked();
XLOG(INFO) << "Finished post-clone hook '" << postCloneScript << "' for "
<< path_;
} catch (const folly::SubprocessSpawnError& ex) {
// If this failed because postCloneScript does not exist, then
// ignore the error because we are tolerant of the case where
// /etc/eden/hooks does not exist, by design.
if (ex.errnoValue() != ENOENT) {
// TODO(13448173): If clone fails, then we should roll back the
// mount.
throw;
}
XLOG(INFO) << "Did not run post-clone hook '" << postCloneScript
<< "' for " << path_ << " because it was not found.";
}
}
// The equivalent of `touch` to signal that clone completed
// successfully.
folly::writeFile(std::string(), cloneSuccessPath.c_str());
}
bool EdenMount::doStateTransition(State expected, State newState) {
return state_.compare_exchange_strong(expected, newState);
}
void EdenMount::destroy() {
fix EdenServer::unmount() to fully wait for mount point cleanup Summary: This fixes EdenServer::unmount() to actually wait for all EdenMount cleanup to complete, and fixes unmountAll() to return a Future that correctly waits for all mount points to be cleaned up. Previously `unmount()` waited for the mount point to be unmounted from the kernel, but did not wait for EdenMount shutdown to complete. Previously EdenMount shutdown was not triggered until the last reference to the shared_ptr<EdenMount> was released. This often happened in the FUSE channel thread that triggered the mountFinished() call--it would still hold a reference to this pointer, and would not release it until after mountFinished() returns. As a result, when the main thread was shutting down, `main()` would call `unmountAll()`, and then return soon after it completed. Some FUSE channel threads may still be running at this point, still performing `EdenMount` shutdown while the main thread was exiting. This could result in crashes and deadlocks as shutdown tried to access objects already destroyed by the main thread. With this change `EdenMount::shutdown()` is triggered explicitly during `mountFinished()`, and `unmount()` will not complete until this finishes. The `EdenMount` object may still exist at this point, and could still be deleted by the FUSE channel thread, but the deletion now only requires freeing the memory and does not require accessing other data that may have been cleaned up by the main thread. We should still clean up the FUSE channel thread handling in the future, to make sure these threads are joined before the main thread exits. However, that cleanup can wait until a separate diff. Ideally I would like to move more of the mount and unmount logic from EdenServer and EdenServiceHandler and put that code in EdenMount instead. Reviewed By: bolinfest Differential Revision: D5541318 fbshipit-source-id: 470332478357a85c314bc40458373cb0f827f62b
2017-08-03 02:52:18 +03:00
auto oldState = state_.exchange(State::DESTROYING);
switch (oldState) {
case State::RUNNING:
case State::STARTING:
case State::UNINITIALIZED: {
// Start the shutdown ourselves.
// Use shutdownImpl() since we have already updated state_ to DESTROYING.
auto shutdownFuture = shutdownImpl();
// We intentionally ignore the returned future.
// shutdown() will automatically destroy us when it completes now that
// we have set the state to DESTROYING
(void)shutdownFuture;
return;
}
case State::SHUTTING_DOWN:
// Nothing else to do. shutdown() will destroy us when it completes.
return;
case State::SHUT_DOWN:
// We were already shut down, and can delete ourselves immediately.
XLOG(DBG1) << "destroying shut-down EdenMount " << getPath();
delete this;
return;
default:
// No other states should be possible.
XLOG(FATAL) << "EdenMount::destroy() called on mount " << getPath()
<< " in unexpected state " << static_cast<uint32_t>(oldState);
fix EdenServer::unmount() to fully wait for mount point cleanup Summary: This fixes EdenServer::unmount() to actually wait for all EdenMount cleanup to complete, and fixes unmountAll() to return a Future that correctly waits for all mount points to be cleaned up. Previously `unmount()` waited for the mount point to be unmounted from the kernel, but did not wait for EdenMount shutdown to complete. Previously EdenMount shutdown was not triggered until the last reference to the shared_ptr<EdenMount> was released. This often happened in the FUSE channel thread that triggered the mountFinished() call--it would still hold a reference to this pointer, and would not release it until after mountFinished() returns. As a result, when the main thread was shutting down, `main()` would call `unmountAll()`, and then return soon after it completed. Some FUSE channel threads may still be running at this point, still performing `EdenMount` shutdown while the main thread was exiting. This could result in crashes and deadlocks as shutdown tried to access objects already destroyed by the main thread. With this change `EdenMount::shutdown()` is triggered explicitly during `mountFinished()`, and `unmount()` will not complete until this finishes. The `EdenMount` object may still exist at this point, and could still be deleted by the FUSE channel thread, but the deletion now only requires freeing the memory and does not require accessing other data that may have been cleaned up by the main thread. We should still clean up the FUSE channel thread handling in the future, to make sure these threads are joined before the main thread exits. However, that cleanup can wait until a separate diff. Ideally I would like to move more of the mount and unmount logic from EdenServer and EdenServiceHandler and put that code in EdenMount instead. Reviewed By: bolinfest Differential Revision: D5541318 fbshipit-source-id: 470332478357a85c314bc40458373cb0f827f62b
2017-08-03 02:52:18 +03:00
}
}
Future<Unit> EdenMount::shutdown() {
// shutdown() should only be called on mounts that have not yet reached
// SHUTTING_DOWN or later states. Confirm this is the case, and move to
// SHUTTING_DOWN.
if (!doStateTransition(State::RUNNING, State::SHUTTING_DOWN) &&
!doStateTransition(State::STARTING, State::SHUTTING_DOWN) &&
!doStateTransition(State::FUSE_DONE, State::SHUTTING_DOWN) &&
!doStateTransition(State::FUSE_ERROR, State::SHUTTING_DOWN)) {
fix EdenServer::unmount() to fully wait for mount point cleanup Summary: This fixes EdenServer::unmount() to actually wait for all EdenMount cleanup to complete, and fixes unmountAll() to return a Future that correctly waits for all mount points to be cleaned up. Previously `unmount()` waited for the mount point to be unmounted from the kernel, but did not wait for EdenMount shutdown to complete. Previously EdenMount shutdown was not triggered until the last reference to the shared_ptr<EdenMount> was released. This often happened in the FUSE channel thread that triggered the mountFinished() call--it would still hold a reference to this pointer, and would not release it until after mountFinished() returns. As a result, when the main thread was shutting down, `main()` would call `unmountAll()`, and then return soon after it completed. Some FUSE channel threads may still be running at this point, still performing `EdenMount` shutdown while the main thread was exiting. This could result in crashes and deadlocks as shutdown tried to access objects already destroyed by the main thread. With this change `EdenMount::shutdown()` is triggered explicitly during `mountFinished()`, and `unmount()` will not complete until this finishes. The `EdenMount` object may still exist at this point, and could still be deleted by the FUSE channel thread, but the deletion now only requires freeing the memory and does not require accessing other data that may have been cleaned up by the main thread. We should still clean up the FUSE channel thread handling in the future, to make sure these threads are joined before the main thread exits. However, that cleanup can wait until a separate diff. Ideally I would like to move more of the mount and unmount logic from EdenServer and EdenServiceHandler and put that code in EdenMount instead. Reviewed By: bolinfest Differential Revision: D5541318 fbshipit-source-id: 470332478357a85c314bc40458373cb0f827f62b
2017-08-03 02:52:18 +03:00
EDEN_BUG() << "attempted to call shutdown() on a non-running EdenMount: "
<< "state was " << static_cast<uint32_t>(state_.load());
fix EdenServer::unmount() to fully wait for mount point cleanup Summary: This fixes EdenServer::unmount() to actually wait for all EdenMount cleanup to complete, and fixes unmountAll() to return a Future that correctly waits for all mount points to be cleaned up. Previously `unmount()` waited for the mount point to be unmounted from the kernel, but did not wait for EdenMount shutdown to complete. Previously EdenMount shutdown was not triggered until the last reference to the shared_ptr<EdenMount> was released. This often happened in the FUSE channel thread that triggered the mountFinished() call--it would still hold a reference to this pointer, and would not release it until after mountFinished() returns. As a result, when the main thread was shutting down, `main()` would call `unmountAll()`, and then return soon after it completed. Some FUSE channel threads may still be running at this point, still performing `EdenMount` shutdown while the main thread was exiting. This could result in crashes and deadlocks as shutdown tried to access objects already destroyed by the main thread. With this change `EdenMount::shutdown()` is triggered explicitly during `mountFinished()`, and `unmount()` will not complete until this finishes. The `EdenMount` object may still exist at this point, and could still be deleted by the FUSE channel thread, but the deletion now only requires freeing the memory and does not require accessing other data that may have been cleaned up by the main thread. We should still clean up the FUSE channel thread handling in the future, to make sure these threads are joined before the main thread exits. However, that cleanup can wait until a separate diff. Ideally I would like to move more of the mount and unmount logic from EdenServer and EdenServiceHandler and put that code in EdenMount instead. Reviewed By: bolinfest Differential Revision: D5541318 fbshipit-source-id: 470332478357a85c314bc40458373cb0f827f62b
2017-08-03 02:52:18 +03:00
}
return shutdownImpl();
}
fix EdenServer::unmount() to fully wait for mount point cleanup Summary: This fixes EdenServer::unmount() to actually wait for all EdenMount cleanup to complete, and fixes unmountAll() to return a Future that correctly waits for all mount points to be cleaned up. Previously `unmount()` waited for the mount point to be unmounted from the kernel, but did not wait for EdenMount shutdown to complete. Previously EdenMount shutdown was not triggered until the last reference to the shared_ptr<EdenMount> was released. This often happened in the FUSE channel thread that triggered the mountFinished() call--it would still hold a reference to this pointer, and would not release it until after mountFinished() returns. As a result, when the main thread was shutting down, `main()` would call `unmountAll()`, and then return soon after it completed. Some FUSE channel threads may still be running at this point, still performing `EdenMount` shutdown while the main thread was exiting. This could result in crashes and deadlocks as shutdown tried to access objects already destroyed by the main thread. With this change `EdenMount::shutdown()` is triggered explicitly during `mountFinished()`, and `unmount()` will not complete until this finishes. The `EdenMount` object may still exist at this point, and could still be deleted by the FUSE channel thread, but the deletion now only requires freeing the memory and does not require accessing other data that may have been cleaned up by the main thread. We should still clean up the FUSE channel thread handling in the future, to make sure these threads are joined before the main thread exits. However, that cleanup can wait until a separate diff. Ideally I would like to move more of the mount and unmount logic from EdenServer and EdenServiceHandler and put that code in EdenMount instead. Reviewed By: bolinfest Differential Revision: D5541318 fbshipit-source-id: 470332478357a85c314bc40458373cb0f827f62b
2017-08-03 02:52:18 +03:00
Future<Unit> EdenMount::shutdownImpl() {
XLOG(DBG1) << "beginning shutdown for EdenMount " << getPath();
return inodeMap_->shutdown().then([this] {
auto oldState = state_.exchange(State::SHUT_DOWN);
if (oldState == State::DESTROYING) {
XLOG(DBG1) << "destroying EdenMount " << getPath()
<< " after shutdown completion";
delete this;
return;
}
XLOG(DBG1) << "shutdown complete for EdenMount " << getPath();
});
}
re-organize the fuse Channel and Session code Summary: The higher level goal is to make it easier to deal with the graceful restart scenario. This diff removes the SessionDeleter class and effectively renames the Channel class to FuseChannel. The FuseChannel represents the channel to the kernel; it can be constructed from a fuse device descriptor that has been obtained either from the privhelper at mount time, or from the graceful restart procedure. Importantly for graceful restart, it is possible to move the fuse device descriptor out of the FuseChannel so that it can be transferred to a new eden process. The graceful restart procedure requires a bit more control over the lifecycle of the fuse event loop so this diff also takes over managing the thread pool for the worker threads. The threads are owned by the MountPoint class which continues to be responsible for starting and stopping the fuse session and notifying EdenServer when it has finished. A nice side effect of this change is that we can remove a couple of inelegant aspects of the integration; the stack size env var stuff and the redundant extra thread to wait for the loop to finish. I opted to expose the dispatcher ops struct via an `extern` to simplify the code in the MountPoint class and avoid adding special interfaces for passing the ops around; they're constant anyway so this doesn't feel especially egregious. Reviewed By: bolinfest Differential Revision: D5751521 fbshipit-source-id: 5ba4fff48f3efb31a809adfc7787555711f649c9
2017-09-09 05:15:17 +03:00
fusell::FuseChannel* EdenMount::getFuseChannel() const {
return channel_.get();
}
const AbsolutePath& EdenMount::getPath() const {
return path_;
}
const AbsolutePath& EdenMount::getSocketPath() const {
return socketPath_;
}
fusell::ThreadLocalEdenStats* EdenMount::getStats() const {
return globalEdenStats_;
}
const vector<BindMount>& EdenMount::getBindMounts() const {
return bindMounts_;
}
TreeInodePtr EdenMount::getRootInode() const {
return inodeMap_->getRootInode();
}
folly::Future<std::shared_ptr<const Tree>> EdenMount::getRootTreeFuture()
const {
auto commitHash = Hash{parentInfo_.rlock()->parents.parent1()};
return objectStore_->getTreeForCommit(commitHash);
}
fuse_ino_t EdenMount::getDotEdenInodeNumber() const {
return dotEdenInodeNumber_;
}
std::shared_ptr<const Tree> EdenMount::getRootTree() const {
// TODO: We should convert callers of this API to use the Future-based
// version.
return getRootTreeFuture().get();
}
Future<InodePtr> EdenMount::getInode(RelativePathPiece path) const {
return inodeMap_->getRootInode()->getChildRecursive(path);
}
InodePtr EdenMount::getInodeBlocking(RelativePathPiece path) const {
return getInode(path).get();
}
TreeInodePtr EdenMount::getTreeInodeBlocking(RelativePathPiece path) const {
return getInodeBlocking(path).asTreePtr();
}
FileInodePtr EdenMount::getFileInodeBlocking(RelativePathPiece path) const {
return getInodeBlocking(path).asFilePtr();
}
folly::Future<std::vector<CheckoutConflict>> EdenMount::checkout(
Hash snapshotHash,
bool force) {
// Hold the snapshot lock for the duration of the entire checkout operation.
//
// This prevents multiple checkout operations from running in parallel.
auto parentsLock = parentInfo_.wlock();
auto oldParents = parentsLock->parents;
auto ctx = std::make_shared<CheckoutContext>(std::move(parentsLock), force);
XLOG(DBG1) << "starting checkout for " << this->getPath() << ": "
<< oldParents << " to " << snapshotHash;
auto fromTreeFuture = objectStore_->getTreeForCommit(oldParents.parent1());
auto toTreeFuture = objectStore_->getTreeForCommit(snapshotHash);
return folly::collect(fromTreeFuture, toTreeFuture)
.then(
[this, ctx](std::tuple<shared_ptr<const Tree>, shared_ptr<const Tree>>
treeResults) {
auto& fromTree = std::get<0>(treeResults);
auto& toTree = std::get<1>(treeResults);
ctx->start(this->acquireRenameLock());
return this->getRootInode()
->checkout(ctx.get(), fromTree, toTree)
.then([toTree]() mutable { return toTree; });
})
.then([this](std::shared_ptr<const Tree> toTree) {
return dirstate_->onSnapshotChanged(toTree.get());
})
.then([this, ctx, oldParents, snapshotHash]() {
// Save the new snapshot hash
XLOG(DBG1) << "updating snapshot for " << this->getPath() << " from "
<< oldParents << " to " << snapshotHash;
this->config_->setParentCommits(snapshotHash);
auto conflicts = ctx->finish(snapshotHash);
// Write a journal entry
// TODO: We don't include any file changes for now. We'll need to
// figure out the desired data to pass to watchman. We intentionally
// don't want to give it the full list of files that logically
// changed--we intentionally don't process files that were changed but
// have never been accessed.
auto journalDelta = make_unique<JournalDelta>();
journalDelta->fromHash = oldParents.parent1();
journalDelta->toHash = snapshotHash;
journal_.wlock()->addDelta(std::move(journalDelta));
return conflicts;
});
}
Future<Unit> EdenMount::diff(InodeDiffCallback* callback, bool listIgnored) {
// Create a DiffContext object for this diff operation.
auto context =
make_unique<DiffContext>(callback, listIgnored, getObjectStore());
const DiffContext* ctxPtr = context.get();
// stateHolder() exists to ensure that the DiffContext and GitIgnoreStack
// exists until the diff completes.
auto stateHolder = [ctx = std::move(context)](){};
auto rootInode = getRootInode();
return getRootTreeFuture()
.then([ctxPtr, rootInode = std::move(rootInode)](
std::shared_ptr<const Tree>&& rootTree) {
return rootInode->diff(
ctxPtr,
RelativePathPiece{},
std::move(rootTree),
ctxPtr->getToplevelIgnore(),
false);
})
.ensure(std::move(stateHolder));
}
Future<Unit> EdenMount::resetParents(const ParentCommits& parents) {
// Hold the snapshot lock around the entire operation.
auto parentsLock = parentInfo_.wlock();
auto oldParents = parentsLock->parents;
XLOG(DBG1) << "resetting snapshot for " << this->getPath() << " from "
<< oldParents << " to " << parents;
// TODO: Maybe we should walk the inodes and see if we can dematerialize some
// files using the new source control state.
//
// It probably makes sense to do this if/when we convert the Dirstate user
// directives into a tree-like data structure.
return objectStore_->getTreeForCommit(parents.parent1())
.then([this](std::shared_ptr<const Tree> rootTree) {
return dirstate_->onSnapshotChanged(rootTree.get());
})
.then([
this,
parents,
oldParents,
parentsLock = std::move(parentsLock)
]() {
this->config_->setParentCommits(parents);
parentsLock->parents.setParents(parents);
auto journalDelta = make_unique<JournalDelta>();
journalDelta->fromHash = oldParents.parent1();
journalDelta->toHash = parents.parent1();
journal_.wlock()->addDelta(std::move(journalDelta));
});
}
struct timespec EdenMount::getLastCheckoutTime() {
auto lastCheckoutTime = lastCheckoutTime_;
auto epochTime = lastCheckoutTime.time_since_epoch();
auto epochSeconds =
std::chrono::duration_cast<std::chrono::seconds>(epochTime);
auto nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(
epochTime - epochSeconds);
struct timespec time;
time.tv_sec = epochSeconds.count();
time.tv_nsec = nsec.count();
return time;
}
Future<Unit> EdenMount::resetParent(const Hash& parent) {
return resetParents(ParentCommits{parent});
}
RenameLock EdenMount::acquireRenameLock() {
return RenameLock{this};
}
SharedRenameLock EdenMount::acquireSharedRenameLock() {
return SharedRenameLock{this};
}
std::string EdenMount::getCounterName(CounterName name) {
if (name == CounterName::LOADED) {
return getPath().stringPiece().str() + ".loaded";
} else if (name == CounterName::UNLOADED) {
return getPath().stringPiece().str() + ".unloaded";
}
folly::throwSystemErrorExplicit(EINVAL, "unknown counter name", name);
}
folly::Future<folly::File> EdenMount::getFuseCompletionFuture() {
return fuseCompletionPromise_.getFuture();
}
folly::Future<folly::Unit> EdenMount::startFuse(
folly::EventBase* eventBase,
std::shared_ptr<folly::Executor> threadPool,
bool debug) {
return folly::makeFutureWith([this, eventBase, threadPool, debug] {
if (!doStateTransition(State::UNINITIALIZED, State::STARTING)) {
throw std::runtime_error("mount point has already been started");
}
eventBase_ = eventBase;
threadPool_ = threadPool;
auto fuseDevice = fusell::privilegedFuseMount(path_.stringPiece());
channel_ = std::make_unique<fusell::FuseChannel>(
std::move(fuseDevice), debug, dispatcher_.get());
// we'll use this shortly to wait until the mount is started successfully.
auto initFuture = initFusePromise_.getFuture();
threads_.reserve(FLAGS_fuseNumThreads);
for (auto i = 0; i < FLAGS_fuseNumThreads; ++i) {
threads_.emplace_back(std::thread([this] { fuseWorkerThread(); }));
}
// wait for init to complete or error; this will throw an exception
// if the init procedure failed.
return initFuture;
});
}
void EdenMount::mountStarted() {
// Don't update status_ if it has already been put into an error
// state or something.
if (doStateTransition(State::STARTING, State::RUNNING)) {
// Let ::start() know that we're up and running
initFusePromise_.setValue();
}
}
void EdenMount::fuseWorkerThread() {
setThreadName(to<std::string>("fuse", path_.basename()));
// The channel is responsible for running the loop. It will
// continue to do so until the fuse session is exited, either
// due to error or because the filesystem was unmounted, or
// because FuseChannel::requestSessionExit() was called.
channel_->processSession();
bool shouldJoin = false;
bool shouldComplete = false;
if (doStateTransition(State::STARTING, State::FUSE_ERROR)) {
// If we didn't get as far as setting the state to RUNNING,
// we must have experienced an error
shouldJoin = true;
initFusePromise_.setException(
std::runtime_error("fuse session failed to initialize"));
} else if (doStateTransition(State::RUNNING, State::FUSE_DONE)) {
// We are the first one to stop, so we get to share the news.
shouldJoin = true;
shouldComplete = true;
}
if (shouldJoin) {
// We are the first thread to exit the loop; we get to arrange
// to join and notify the server of our completion
eventBase_->runInEventBaseThread([this, shouldComplete] {
// Wait for all workers to be done
for (auto& thr : threads_) {
thr.join();
}
// and tear down the fuse session. In case we are performing a graceful
// restart, extract the fuse device now.
folly::File fuseDevice = channel_->stealFuseDevice();
channel_.reset();
if (shouldComplete) {
fuseCompletionPromise_.setValue(std::move(fuseDevice));
}
});
}
}
struct stat EdenMount::initStatData() const {
struct stat st;
memset(&st, 0, sizeof(st));
st.st_uid = uid_;
st.st_gid = gid_;
// We don't really use the block size for anything.
// 4096 is fairly standard for many file systems.
st.st_blksize = 4096;
return st;
}
}
} // facebook::eden