sapling/eden/fs/inodes/EdenMount.cpp

540 lines
19 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 <folly/ExceptionWrapper.h>
#include <folly/FBString.h>
#include <folly/File.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 std::make_unique;
using std::unique_ptr;
using std::vector;
using folly::Future;
using folly::makeFuture;
using folly::StringPiece;
using folly::Unit;
using folly::setThreadName;
using folly::to;
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::unique_ptr<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::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);
if (oldState == State::RUNNING) {
// 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;
} else if (oldState == State::SHUTTING_DOWN) {
// Nothing else to do. shutdown() will destroy us when it completes.
return;
} else if (oldState == State::SHUT_DOWN) {
// We were already shut down, and can delete ourselves immediately.
XLOG(DBG1) << "destroying shut-down EdenMount " << getPath();
delete this;
} else {
// No other states should be possible.
XLOG(FATAL) << "EdenMount::destroy() called on mount " << getPath()
<< " in unexpected state " << static_cast<uint32_t>(oldState);
}
}
Future<Unit> EdenMount::shutdown() {
// shutdown() should only be called on mounts in the RUNNING state.
// Confirm this is the case, and move to SHUTTING_DOWN.
auto expected = State::RUNNING;
if (!state_.compare_exchange_strong(expected, State::SHUTTING_DOWN)) {
EDEN_BUG() << "attempted to call shutdown() on a non-running EdenMount: "
<< "state was " << static_cast<uint32_t>(expected);
}
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::unique_ptr<Tree>> EdenMount::getRootTreeFuture() const {
auto commitHash = Hash{parentInfo_.rlock()->parents.parent1()};
return objectStore_->getTreeForCommit(commitHash);
}
fuse_ino_t EdenMount::getDotEdenInodeNumber() const {
return dotEdenInodeNumber_;
}
std::unique_ptr<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<unique_ptr<Tree>, unique_ptr<Tree>> treeResults) {
auto& fromTree = std::get<0>(treeResults);
auto& toTree = std::get<1>(treeResults);
// TODO: We should change the code to use shared_ptr<Tree>.
// The ObjectStore should always return shared_ptrs so it can cache
// them if we want to do so in the future.
auto toTreeCopy = make_unique<Tree>(*toTree);
ctx->start(this->acquireRenameLock());
return this->getRootInode()
->checkout(ctx.get(), std::move(fromTree), std::move(toTree))
.then([toTreeCopy = std::move(toTreeCopy)]() mutable {
return std::move(toTreeCopy);
});
})
.then([this](std::unique_ptr<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::unique_ptr<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::unique_ptr<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);
}
void EdenMount::start(
folly::EventBase* eventBase,
std::function<void()> onStop,
bool debug) {
std::unique_lock<std::mutex> lock(mutex_);
if (fuseStatus_ != FuseStatus::UNINIT) {
throw std::runtime_error("mount point has already been started");
}
eventBase_ = eventBase;
onStop_ = onStop;
fuseStatus_ = FuseStatus::STARTING;
auto fuseDevice = fusell::privilegedFuseMount(path_.stringPiece());
channel_ = std::make_unique<fusell::FuseChannel>(
std::move(fuseDevice), debug, dispatcher_.get());
// Now, while holding the initialization mutex, start up the workers.
threads_.reserve(FLAGS_fuseNumThreads);
for (auto i = 0; i < FLAGS_fuseNumThreads; ++i) {
threads_.emplace_back(std::thread([this] { fuseWorkerThread(); }));
}
// Wait until the mount is started successfully.
while (fuseStatus_ == FuseStatus::STARTING) {
statusCV_.wait(lock);
}
if (fuseStatus_ == FuseStatus::ERROR) {
throw std::runtime_error("fuse session failed to initialize");
}
}
void EdenMount::mountStarted() {
std::lock_guard<std::mutex> guard(mutex_);
// Don't update status_ if it has already been put into an error
// state or something.
if (fuseStatus_ == FuseStatus::STARTING) {
fuseStatus_ = FuseStatus::RUNNING;
statusCV_.notify_one();
}
}
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 shouldCallonStop = false;
bool shouldJoin = false;
{
std::lock_guard<std::mutex> guard(mutex_);
if (fuseStatus_ == FuseStatus::STARTING) {
// If we didn't get as far as setting the state to RUNNING,
// we must have experienced an error
fuseStatus_ = FuseStatus::ERROR;
statusCV_.notify_one();
shouldJoin = true;
} else if (fuseStatus_ == FuseStatus::RUNNING) {
// We are the first one to stop, so we get to share the news.
fuseStatus_ = FuseStatus::STOPPING;
shouldCallonStop = true;
shouldJoin = 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, shouldCallonStop] {
// Wait for all workers to be done
for (auto& thr : threads_) {
thr.join();
}
// and tear down the fuse session. For a graceful restart,
// we will want to FuseChannel::stealFuseDevice() before
// this point, or perhaps pass it through the onStop_
// call.
channel_.reset();
// Do a little dance to steal ownership of the indirect
// reference to the EdenMount that is held by the
// onStop_ function; we can't leave it owned by ourselves
// because that reference will block the completion of
// the shutdown future.
std::function<void()> stopper;
std::swap(stopper, onStop_);
// And let the edenMount know that all is done
if (shouldCallonStop) {
stopper();
}
});
}
}
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