sapling/eden/fs/service/EdenServer.cpp

626 lines
21 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 "eden/fs/service/EdenServer.h"
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
Write the PID to the lockfile and update `eden health` to use it. Summary: We have encountered cases where `eden health` reported `"edenfs not healthy: edenfs not running"` even though the `edenfs` process is still running. Because the existing implementation of `eden health` bases its health check on the output of a `getStatus()` Thrift call, it will erroneously report `"edenfs not running"` even if Eden is running but its Thrift server is not running. This type of false negative could occur if `edenfs` has shutdown the Thrift server, but not the rest of the process (quite possibly, its shutdown is blocked on calls to `umount2()`). This is further problematic because `eden daemon` checks `eden health` before attempting to start the daemon. If it gets a false negative, then `eden daemon` will forge ahead, trying to launch a new instance of the daemon, but it will fail with a nasty error like the following: ``` I1017 11:59:25.188414 3064499 main.cpp:81] Starting edenfs. UID=5256, GID=100, PID=3064499 terminate called after throwing an instance of 'std::runtime_error' what(): another instance of Eden appears to be running for /home/mbolin/local/.eden *** Aborted at 1508266765 (Unix time, try 'date -d 1508266765') *** *** Signal 6 (SIGABRT) (0x1488002ec2b3) received by PID 3064499 (pthread TID 0x7fd0d3787d40) (linux TID 3064499) (maybe from PID 30644 99, UID 5256), stack trace: *** @ 000000000290d3cd folly::symbolizer::(anonymous namespace)::signalHandler(int, siginfo_t*, void*) @ 00007fd0d133cacf (unknown) @ 00007fd0d093e7c8 __GI_raise @ 00007fd0d0940590 __GI_abort @ 00007fd0d1dfeecc __gnu_cxx::__verbose_terminate_handler() @ 00007fd0d1dfcdc5 __cxxabiv1::__terminate(void (*)()) @ 00007fd0d1dfce10 std::terminate() @ 00007fd0d1dfd090 __cxa_throw @ 00000000015fe8ca facebook::eden::EdenServer::acquireEdenLock() @ 000000000160f27b facebook::eden::EdenServer::prepare() @ 00000000016107d5 facebook::eden::EdenServer::run() @ 000000000042c4ee main @ 00007fd0d0929857 __libc_start_main @ 0000000000548ad8 _start Aborted ``` By providing more accurate information to `eden daemon`, if the user tries to run it while the daemon is already running, they will get a more polite error like the following: ``` error: edenfs is already running (pid 274205) ``` This revision addresses this issue by writing the PID of `edenfs` in the lockfile. It updated the implementation of `eden health` to use the PID in the lockfile to assess the health of Eden if the call to `getStatus()` fails. It does this by running: ``` ps -p PID -o comm= ``` and applying some heuristics on the output to assess whether the command associated with that process is the `edenfs` command. If it is, then `eden health` reports the status as `STOPPED` whereas previously it would report it as `DEAD`. Reviewed By: wez Differential Revision: D6086473 fbshipit-source-id: 825421a6818b56ddd7deea257a92c070c2232bdd
2017-10-18 21:18:43 +03:00
#include <folly/FileUtil.h>
#include <folly/SocketAddress.h>
#include <folly/String.h>
#include <folly/executors/CPUThreadPoolExecutor.h>
#include <folly/experimental/logging/xlog.h>
#include <folly/io/async/AsyncSignalHandler.h>
#include <gflags/gflags.h>
#include <thrift/lib/cpp/concurrency/ThreadManager.h>
#include <thrift/lib/cpp2/server/ThriftServer.h>
#include "eden/fs/config/ClientConfig.h"
#include "eden/fs/fuse/privhelper/PrivHelper.h"
#include "eden/fs/inodes/EdenMount.h"
#include "eden/fs/inodes/InodeMap.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/service/EdenServiceHandler.h"
#include "eden/fs/store/EmptyBackingStore.h"
#include "eden/fs/store/LocalStore.h"
#include "eden/fs/store/ObjectStore.h"
#include "eden/fs/store/git/GitBackingStore.h"
#include "eden/fs/store/hg/HgBackingStore.h"
DEFINE_bool(debug, false, "run fuse in debug mode");
DEFINE_int32(num_eden_threads, 12, "the number of eden CPU worker threads");
DEFINE_string(thrift_address, "", "The address for the thrift server socket");
DEFINE_int32(
thrift_num_workers,
std::thread::hardware_concurrency(),
"The number of thrift worker threads");
DEFINE_int32(
thrift_max_requests,
apache::thrift::concurrency::ThreadManager::DEFAULT_MAX_QUEUE_SIZE,
"Maximum number of active thrift requests");
DEFINE_bool(thrift_enable_codel, false, "Enable Codel queuing timeout");
DEFINE_int32(thrift_min_compress_bytes, 0, "Minimum response compression size");
DEFINE_int64(unload_interval_hours, 0, "Frequency of unloading inodes");
DEFINE_int64(
start_delay_minutes,
10,
"start delay for scheduling unloading inodes job");
DEFINE_int64(
unload_age_minutes,
60,
"Minimum age of the inodes to be unloaded");
using apache::thrift::ThriftServer;
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
using folly::Future;
using folly::StringPiece;
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
using folly::Unit;
using folly::makeFuture;
using std::make_shared;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
namespace {
folly::SocketAddress getThriftAddress(
StringPiece argument,
StringPiece edenDir);
std::string getPathToUnixDomainSocket(StringPiece edenDir);
} // namespace
namespace facebook {
namespace eden {
class EdenServer::ThriftServerEventHandler
: public apache::thrift::server::TServerEventHandler,
public folly::AsyncSignalHandler {
public:
explicit ThriftServerEventHandler(EdenServer* edenServer)
: AsyncSignalHandler{nullptr}, edenServer_{edenServer} {}
void preServe(const folly::SocketAddress* /*address*/) override {
// preServe() will be called from the thrift server thread once when it is
// about to start serving.
//
// Register for SIGINT and SIGTERM. We do this in preServe() so we can use
// the thrift server's EventBase to process the signal callbacks.
auto eventBase = folly::EventBaseManager::get()->getEventBase();
attachEventBase(eventBase);
registerSignalHandler(SIGINT);
registerSignalHandler(SIGTERM);
}
void signalReceived(int sig) noexcept override {
// Stop the server.
// Unregister for this signal first, so that we will be terminated
// immediately if the signal is sent again before we finish stopping.
// This makes it easier to kill the daemon if graceful shutdown hangs or
// takes longer than expected for some reason. (For instance, if we
// unmounting the mount points hangs for some reason.)
XLOG(INFO) << "stopping due to signal " << sig;
unregisterSignalHandler(sig);
edenServer_->stop();
}
private:
EdenServer* edenServer_{nullptr};
};
EdenServer::EdenServer(
AbsolutePathPiece edenDir,
AbsolutePathPiece etcEdenDir,
AbsolutePathPiece configPath,
AbsolutePathPiece rocksPath)
: edenDir_(edenDir),
etcEdenDir_(etcEdenDir),
configPath_(configPath),
rocksPath_(rocksPath),
threadPool_(
make_shared<folly::CPUThreadPoolExecutor>(FLAGS_num_eden_threads)) {}
EdenServer::~EdenServer() {
shutdown();
}
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
folly::Future<Unit> EdenServer::unmountAll() {
std::vector<Future<Unit>> futures;
{
auto mountPoints = mountPoints_.wlock();
for (auto& entry : *mountPoints) {
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
const auto& mountPath = entry.first;
try {
fusell::privilegedFuseUnmount(mountPath);
} catch (const std::exception& ex) {
XLOG(ERR) << "Failed to perform unmount for \"" << mountPath
<< "\": " << folly::exceptionStr(ex);
futures.push_back(makeFuture<Unit>(
folly::exception_wrapper(std::current_exception(), ex)));
continue;
}
futures.push_back(entry.second.unmountPromise.getFuture());
}
}
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
// Use collectAll() rather than collect() to wait for all of the unmounts
// to complete, and only check for errors once everything has finished.
return folly::collectAll(futures).then(
[](const std::vector<folly::Try<Unit>>& results) {
for (const auto& result : results) {
result.throwIfFailed();
}
});
}
void EdenServer::scheduleFlushStats() {
mainEventBase_->timer().scheduleTimeoutFn(
[this] {
flushStatsNow();
scheduleFlushStats();
},
std::chrono::seconds(1));
}
void EdenServer::unloadInodes() {
std::vector<TreeInodePtr> roots;
{
auto mountPoints = mountPoints_.wlock();
for (auto& entry : *mountPoints) {
roots.emplace_back(entry.second.edenMount->getRootInode());
}
}
if (!roots.empty()) {
XLOG(INFO) << "UnloadInodeScheduler Unloading Free Inodes";
auto serviceData = stats::ServiceData::get();
uint64_t totalUnloaded = serviceData->getCounter(kPeriodicUnloadCounterKey);
for (auto& rootInode : roots) {
totalUnloaded += rootInode->unloadChildrenNow(
std::chrono::minutes(FLAGS_unload_age_minutes));
}
serviceData->setCounter(kPeriodicUnloadCounterKey, totalUnloaded);
}
scheduleInodeUnload(std::chrono::hours(FLAGS_unload_interval_hours));
}
void EdenServer::scheduleInodeUnload(std::chrono::milliseconds timeout) {
mainEventBase_->timer().scheduleTimeoutFn(
[this] { unloadInodes(); }, timeout);
}
void EdenServer::prepare() {
acquireEdenLock();
// Store a pointer to the EventBase that will be used to drive
// the main thread. The runServer() code will end up driving this EventBase.
mainEventBase_ = folly::EventBaseManager::get()->getEventBase();
createThriftServer();
XLOG(DBG2) << "opening local RocksDB store";
localStore_ = make_shared<LocalStore>(rocksPath_);
XLOG(DBG2) << "done opening local RocksDB store";
// Start stats aggregation
scheduleFlushStats();
// Set the ServiceData counter for tracking number of inodes unloaded by
// periodic job for unloading inodes to zero on EdenServer start.
stats::ServiceData::get()->setCounter(kPeriodicUnloadCounterKey, 0);
// Schedule a periodic job to unload unused inodes based on the last access
// time. currently Eden does not have accurate timestamp tracking for inodes,
// so using unloadChildrenNow just to validate the behaviour. We will have to
// modify current unloadChildrenNow function to unload inodes based on the
// last access time.
if (FLAGS_unload_interval_hours > 0) {
scheduleInodeUnload(std::chrono::minutes(FLAGS_start_delay_minutes));
}
// Remount existing mount points
folly::dynamic dirs = folly::dynamic::object();
try {
dirs = ClientConfig::loadClientDirectoryMap(edenDir_);
} catch (const std::exception& ex) {
XLOG(ERR) << "Could not parse config.json file: " << ex.what()
<< " Skipping remount step.";
}
for (auto& client : dirs.items()) {
MountInfo mountInfo;
mountInfo.mountPoint = client.first.c_str();
auto edenClientPath = edenDir_ + PathComponent("clients") +
PathComponent(client.second.c_str());
mountInfo.edenClientPath = edenClientPath.stringPiece().str();
try {
mount(mountInfo).get();
} catch (const std::exception& ex) {
XLOG(ERR) << "Failed to perform remount for " << client.first.c_str()
<< ": " << ex.what();
}
}
prepareThriftAddress();
}
// Defined separately in RunServer.cpp
void runServer(const EdenServer& server);
void EdenServer::run() {
// Acquire the eden lock, prepare the thrift server, and start our mounts
prepare();
// Run the thrift server
runServer(*this);
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
// Clean up all the server mount points before shutting down the privhelper.
// This is made a little bit more complicated because we're running on
// the main event base thread here, and the unmount handling relies on
// scheduling the unmount to run in our thread; we can't simply block
// on the future returned from unmountAll() as that would prevent those
// actions from completing, so we perform a somewhat inelegant polling
// loop on both the eventBase and the future.
auto unmounted = unmountAll();
CHECK_EQ(mainEventBase_, folly::EventBaseManager::get()->getEventBase());
while (!unmounted.isReady()) {
mainEventBase_->loopOnce();
}
unmounted.get();
// Explicitly stop the privhelper process so we can verify that it
// exits normally.
auto privhelperExitCode = fusell::stopPrivHelper();
if (privhelperExitCode != 0) {
if (privhelperExitCode > 0) {
XLOG(ERR) << "privhelper process exited with unexpected code "
<< privhelperExitCode;
} else {
XLOG(ERR) << "privhelper process was killed by signal "
<< privhelperExitCode;
}
}
}
void EdenServer::addToMountPoints(std::shared_ptr<EdenMount> edenMount) {
auto mountPath = edenMount->getPath().stringPiece();
{
auto mountPoints = mountPoints_.wlock();
auto ret = mountPoints->emplace(mountPath, EdenMountInfo(edenMount));
if (!ret.second) {
// This mount point already exists.
throw EdenError(folly::to<string>(
"mount point \"", mountPath, "\" is already mounted"));
}
}
}
void EdenServer::registerStats(std::shared_ptr<EdenMount> edenMount) {
auto counters = stats::ServiceData::get()->getDynamicCounters();
// Register callback for getting Loaded inodes in the memory
// for a mountPoint.
counters->registerCallback(
edenMount->getCounterName(CounterName::LOADED),
[edenMount] { return edenMount->getInodeMap()->getLoadedInodeCount(); });
// Register callback for getting Unloaded inodes in the
// memory for a mountpoint
counters->registerCallback(
edenMount->getCounterName(CounterName::UNLOADED), [edenMount] {
return edenMount->getInodeMap()->getUnloadedInodeCount();
});
}
void EdenServer::unregisterStats(EdenMount* edenMount) {
auto counters = stats::ServiceData::get()->getDynamicCounters();
counters->unregisterCallback(edenMount->getCounterName(CounterName::LOADED));
counters->unregisterCallback(
edenMount->getCounterName(CounterName::UNLOADED));
}
folly::Future<std::shared_ptr<EdenMount>> EdenServer::mount(
const MountInfo& info) {
auto initialConfig = ClientConfig::loadFromClientDirectory(
AbsolutePathPiece{info.mountPoint},
Change the contents and format for the edenrc file under ~/local/.eden. Summary: The headline changes of this revision are: - Changes the format of the config file from INI to TOML (the `edenrc` file under `~/local/.eden` has been replaced with `config.toml`). This revision includes logic for automatically performing the migration when Eden is restarted. - Inlines data from `/etc/eden/config.d` into the TOML file. Historically, the `edenrc` file for a client would contain the name of the "configuration alias" defined in a config file like `~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden loaded a client, it would have to first read the `edenrc` and then reconstitute the rest of the client configuration by looking up the alias in the set of config files that were used to create the client in the first place. This changes things so that all of the data that was being cross-referenced is now inlined in the client's config file. This makes loading a config considerably simpler at the cost of no longer being able to change the config for multiple clients that were cloned from the same configuration alias in one place. It was questionable whether being able to modify a client from a foreign config after it was created was a safe thing to do, anyway. Eliminating the need for a historic link to the configuration alias will make it easier to support running `eden clone` on an arbitrary local Hg or Git repo. So long as `eden clone` can extract enough information from the local repo to create an appropriate config file for the new Eden client, there is no need for a configuration alias to exist a priori. Since we were already changing the data in the config file, this seemed like an appropriate time to make the switch from INI to TOML, as this was something we wanted to do, anyway. In testing, I discovered a discrepancy between how boost's `boost::property_tree::ptree` and Python's `ConfigParser` handled the following section heading: ``` [repository ZtmpZsillyZeden-clone.LIkh32] ``` Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")` in boost would fail to find this section. Because [[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]], it is not that surprising that boost and `ConfigParser` do not 100% agree on what they accept. Moving to TOML means we have a configuration language with the following desirable properties: - It has a formal spec, unlike INI. This is important because there are parsers in a wide range of programming languages that, in theory, accept a consistent input language. - It is reasonable for humans to write, as it supports comments, unlike JSON. - It supports nested structures, like maps and arrays, without going crazy on the input language it supports, unlike YAML. Eden now depends on the following third-party TOML parsers: * C++ https://github.com/skystrife/cpptoml * Python https://github.com/uiri/toml This revision also changes the organization of `~/local/.eden` slightly. For now, there is still a `config.json` file, but the values are no longer hashes of the realpath of the mount. Instead, we take the basename of the realpath and use that as the name of the directory under `~/local/.eden/clients`. If there is a naming collision, we add the first available integral suffix. Using the basename makes it easier to navigate the `~/local/.eden/clients` directory. Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use INI. Migrating those to TOML will be done in a future revision. Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree` as well as a number of uses of boost due to the elimination of `ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig` no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of this revision because it is no longer relevant. Reviewed By: wez Differential Revision: D6310325 fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
AbsolutePathPiece{info.edenClientPath});
auto repoType = initialConfig->getRepoType();
auto backingStore = getBackingStore(repoType, initialConfig->getRepoSource());
auto objectStore =
std::make_unique<ObjectStore>(getLocalStore(), backingStore);
return EdenMount::create(
std::move(initialConfig),
std::move(objectStore),
getSocketPath(),
getStats())
.then([this](std::shared_ptr<EdenMount> edenMount) {
// Load InodeBase objects for any materialized files in this mount point
// before we start mounting.
auto rootInode = edenMount->getRootInode();
return rootInode->loadMaterializedChildren()
.then([this, edenMount](folly::Try<folly::Unit> t) {
(void)t; // We're explicitly ignoring possible failure in
// loadMaterializedChildren, but only because we were
// previously using .wait() on the future. We could
// just let potential errors propagate.
addToMountPoints(edenMount);
// Start up the fuse workers.
return edenMount->startFuse(
getMainEventBase(), threadPool_, FLAGS_debug);
})
// If an error occurs we want to call mountFinished and throw the
// error here. Once the pool is up and running, the finishFuture
// will ensure that this happens.
.onError([this, edenMount](folly::exception_wrapper ew) {
mountFinished(edenMount.get());
return makeFuture<folly::Unit>(ew);
})
// Explicitly move the remainder of processing to a utility
// thread; we're likely to reach this point in the context of
// a fuse mount thread prior to it responding to the mount
// initiation request from the kernel, so if we were to block
// here, that would lead to deadlock. In addition, if we were
// to run this via mainEventBase_ we could also deadlock
// during started when remounting configured mounts.
.via(threadPool_.get())
.then([edenMount, this] {
// Now that we've started the workers, arrange to call
// mountFinished once the pool is torn down.
auto finishFuture = edenMount->getFuseCompletionFuture().ensure(
[this, edenMount] { mountFinished(edenMount.get()); });
// We're deliberately discarding the future here; we don't
// need to wait for it to finish.
(void)finishFuture;
registerStats(edenMount);
// Perform all of the bind mounts associated with the
// client.
edenMount->performBindMounts();
return edenMount;
});
});
}
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> EdenServer::unmount(StringPiece mountPath) {
try {
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 future = Future<Unit>::makeEmpty();
{
auto mountPoints = mountPoints_.wlock();
auto it = mountPoints->find(mountPath);
if (it == mountPoints->end()) {
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 makeFuture<Unit>(
std::out_of_range("no such mount point " + mountPath.str()));
}
future = it->second.unmountPromise.getFuture();
}
fusell::privilegedFuseUnmount(mountPath);
return future;
} catch (const std::exception& ex) {
XLOG(ERR) << "Failed to perform unmount for \"" << mountPath
<< "\": " << folly::exceptionStr(ex);
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 makeFuture<Unit>(
folly::exception_wrapper(std::current_exception(), ex));
}
}
void EdenServer::mountFinished(EdenMount* edenMount) {
auto mountPath = edenMount->getPath().stringPiece();
XLOG(INFO) << "mount point \"" << mountPath << "\" stopped";
unregisterStats(edenMount);
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
// Erase the EdenMount from our mountPoints_ map
folly::SharedPromise<Unit> unmountPromise;
{
auto mountPoints = mountPoints_.wlock();
auto it = mountPoints->find(mountPath);
CHECK(it != mountPoints->end());
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
unmountPromise = std::move(it->second.unmountPromise);
mountPoints->erase(it);
}
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
// Shutdown the EdenMount, and fulfill the unmount promise
// when the shutdown completes
edenMount->shutdown().then(
[unmountPromise = std::move(unmountPromise)]() mutable {
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
unmountPromise.setValue();
});
}
EdenServer::MountList EdenServer::getMountPoints() const {
MountList results;
{
auto mountPoints = mountPoints_.rlock();
for (const auto& entry : *mountPoints) {
results.emplace_back(entry.second.edenMount);
}
}
return results;
}
shared_ptr<EdenMount> EdenServer::getMount(StringPiece mountPath) const {
auto mount = getMountOrNull(mountPath);
if (!mount) {
throw EdenError(folly::to<string>(
"mount point \"", mountPath, "\" is not known to this eden instance"));
}
return mount;
}
shared_ptr<EdenMount> EdenServer::getMountOrNull(StringPiece mountPath) const {
auto mountPoints = mountPoints_.rlock();
auto it = mountPoints->find(mountPath);
if (it == mountPoints->end()) {
return nullptr;
}
return it->second.edenMount;
}
shared_ptr<BackingStore> EdenServer::getBackingStore(
StringPiece type,
StringPiece name) {
BackingStoreKey key{type.str(), name.str()};
SYNCHRONIZED(lockedStores, backingStores_) {
auto it = lockedStores.find(key);
if (it != lockedStores.end()) {
return it->second;
}
auto store = createBackingStore(type, name);
lockedStores.emplace(key, store);
return store;
}
// Ugh. The SYNCHRONIZED() macro is super lame.
// We have to return something here, since the compiler can't figure out
// that we always return inside SYNCHRONIZED.
XLOG(FATAL) << "unreached";
}
shared_ptr<BackingStore> EdenServer::createBackingStore(
StringPiece type,
StringPiece name) {
if (type == "null") {
return make_shared<EmptyBackingStore>();
} else if (type == "hg") {
auto repoPath = realpath(name);
return make_shared<HgBackingStore>(
repoPath, localStore_.get(), threadPool_.get());
} else if (type == "git") {
auto repoPath = realpath(name);
return make_shared<GitBackingStore>(repoPath, localStore_.get());
} else {
throw std::domain_error(
folly::to<string>("unsupported backing store type: ", type));
}
}
void EdenServer::createThriftServer() {
auto address = getThriftAddress(FLAGS_thrift_address, edenDir_.stringPiece());
server_ = make_shared<ThriftServer>();
server_->setMaxRequests(FLAGS_thrift_max_requests);
server_->setNumIOWorkerThreads(FLAGS_thrift_num_workers);
server_->setEnableCodel(FLAGS_thrift_enable_codel);
server_->setMinCompressBytes(FLAGS_thrift_min_compress_bytes);
handler_ = make_shared<EdenServiceHandler>(this);
server_->setInterface(handler_);
server_->setAddress(address);
serverEventHandler_ = make_shared<ThriftServerEventHandler>(this);
server_->setServerEventHandler(serverEventHandler_);
}
void EdenServer::acquireEdenLock() {
boost::filesystem::path edenPath{edenDir_.stringPiece().str()};
boost::filesystem::path lockPath = edenPath / "lock";
lockFile_ = folly::File(lockPath.string(), O_WRONLY | O_CREAT);
if (!lockFile_.try_lock()) {
throw std::runtime_error(
"another instance of Eden appears to be running for " +
edenDir_.stringPiece().str());
}
Write the PID to the lockfile and update `eden health` to use it. Summary: We have encountered cases where `eden health` reported `"edenfs not healthy: edenfs not running"` even though the `edenfs` process is still running. Because the existing implementation of `eden health` bases its health check on the output of a `getStatus()` Thrift call, it will erroneously report `"edenfs not running"` even if Eden is running but its Thrift server is not running. This type of false negative could occur if `edenfs` has shutdown the Thrift server, but not the rest of the process (quite possibly, its shutdown is blocked on calls to `umount2()`). This is further problematic because `eden daemon` checks `eden health` before attempting to start the daemon. If it gets a false negative, then `eden daemon` will forge ahead, trying to launch a new instance of the daemon, but it will fail with a nasty error like the following: ``` I1017 11:59:25.188414 3064499 main.cpp:81] Starting edenfs. UID=5256, GID=100, PID=3064499 terminate called after throwing an instance of 'std::runtime_error' what(): another instance of Eden appears to be running for /home/mbolin/local/.eden *** Aborted at 1508266765 (Unix time, try 'date -d 1508266765') *** *** Signal 6 (SIGABRT) (0x1488002ec2b3) received by PID 3064499 (pthread TID 0x7fd0d3787d40) (linux TID 3064499) (maybe from PID 30644 99, UID 5256), stack trace: *** @ 000000000290d3cd folly::symbolizer::(anonymous namespace)::signalHandler(int, siginfo_t*, void*) @ 00007fd0d133cacf (unknown) @ 00007fd0d093e7c8 __GI_raise @ 00007fd0d0940590 __GI_abort @ 00007fd0d1dfeecc __gnu_cxx::__verbose_terminate_handler() @ 00007fd0d1dfcdc5 __cxxabiv1::__terminate(void (*)()) @ 00007fd0d1dfce10 std::terminate() @ 00007fd0d1dfd090 __cxa_throw @ 00000000015fe8ca facebook::eden::EdenServer::acquireEdenLock() @ 000000000160f27b facebook::eden::EdenServer::prepare() @ 00000000016107d5 facebook::eden::EdenServer::run() @ 000000000042c4ee main @ 00007fd0d0929857 __libc_start_main @ 0000000000548ad8 _start Aborted ``` By providing more accurate information to `eden daemon`, if the user tries to run it while the daemon is already running, they will get a more polite error like the following: ``` error: edenfs is already running (pid 274205) ``` This revision addresses this issue by writing the PID of `edenfs` in the lockfile. It updated the implementation of `eden health` to use the PID in the lockfile to assess the health of Eden if the call to `getStatus()` fails. It does this by running: ``` ps -p PID -o comm= ``` and applying some heuristics on the output to assess whether the command associated with that process is the `edenfs` command. If it is, then `eden health` reports the status as `STOPPED` whereas previously it would report it as `DEAD`. Reviewed By: wez Differential Revision: D6086473 fbshipit-source-id: 825421a6818b56ddd7deea257a92c070c2232bdd
2017-10-18 21:18:43 +03:00
// Write the PID (with a newline) to the lockfile.
int fd = lockFile_.fd();
folly::ftruncateNoInt(fd, /* len */ 0);
auto pidContents = folly::to<std::string>(getpid(), "\n");
folly::writeNoInt(fd, pidContents.data(), pidContents.size());
}
AbsolutePath EdenServer::getSocketPath() const {
const auto& addr = server_->getAddress();
CHECK_EQ(addr.getFamily(), AF_UNIX);
// Need to make a copy rather than a Piece here because getPath returns
// a temporary std::string instance.
return AbsolutePath{addr.getPath()};
}
void EdenServer::prepareThriftAddress() {
// If we are serving on a local Unix socket, remove any old socket file
// that may be left over from a previous instance.
// We have already acquired the mount point lock at this time, so we know
// that any existing socket is unused and safe to remove.
const auto& addr = server_->getAddress();
if (addr.getFamily() != AF_UNIX) {
return;
}
int rc = unlink(addr.getPath().c_str());
if (rc != 0 && errno != ENOENT) {
// This might happen if we don't have permission to remove the file.
folly::throwSystemError(
"unable to remove old Eden thrift socket ", addr.getPath());
}
}
void EdenServer::stop() const {
server_->stop();
}
void EdenServer::shutdown() {
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
unmountAll().get();
}
void EdenServer::flushStatsNow() const {
for (auto& stats : edenStats_.accessAllThreads()) {
stats.aggregate();
}
}
} // namespace eden
} // namespace facebook
namespace {
/*
* Parse the --thrift_address argument, and return a SocketAddress object
*/
folly::SocketAddress getThriftAddress(
StringPiece argument,
StringPiece edenDir) {
folly::SocketAddress addr;
// If the argument is empty, default to a Unix socket placed next
// to the mount point
if (argument.empty()) {
auto socketPath = getPathToUnixDomainSocket(edenDir);
addr.setFromPath(socketPath);
return addr;
}
// Check to see if the argument looks like a port number
uint16_t port;
bool validPort{false};
try {
port = folly::to<uint16_t>(argument);
validPort = true;
} catch (const std::range_error& ex) {
// validPort = false
}
if (validPort) {
addr.setFromLocalPort(port);
return addr;
}
// TODO: also support IPv4:PORT or [IPv6]:PORT
// Otherwise assume the address refers to a local unix socket path
addr.setFromPath(argument);
return addr;
}
std::string getPathToUnixDomainSocket(StringPiece edenDir) {
boost::filesystem::path edenPath{edenDir.str()};
boost::filesystem::path socketPath = edenPath / "socket";
return socketPath.string();
}
} // unnamed namespace