sapling/eden/fs/service/StartupLogger.h
Xavier Deguillard c5d631fd09 service: unify startup on Windows/Linux/macOS
Summary:
Now that Linux/macOS startup no longer uses fork it becomes trivial to share
the same code with Windows. This improves Windows startup in several different ways:
 - `edenfsctl start` now displays the status of the start process in the console
 - `edenfsctl start` no longer has an arbitrary timeout after which it reports
   a timeout even though EdenFS is still starting up.

This also kills a bunch of Windows specific code that is no longer needed.

Reviewed By: fanzeyi

Differential Revision: D24393690

fbshipit-source-id: 28100aec96da81c92d5b592353edceed332e2364
2020-10-22 16:24:17 -07:00

256 lines
7.9 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#pragma once
#include <folly/Conv.h>
#include <folly/File.h>
#include <folly/Range.h>
#include <folly/lang/Assume.h>
#include <folly/logging/LogLevel.h>
#include <gflags/gflags_declare.h>
#include <memory>
#include <optional>
#include "eden/fs/config/EdenConfig.h"
#include "eden/fs/utils/FileDescriptor.h"
#include "eden/fs/utils/PathFuncs.h"
namespace facebook {
namespace eden {
DECLARE_int32(startupLoggerFd);
class StartupLogger;
class PrivHelper;
class SpawnedProcess;
class FileDescriptor;
/**
* daemonizeIfRequested manages optionally daemonizing the edenfs process.
* Daemonizing is controlled primarily by the `--foreground` command line
* argument NOT being present, and on Windows systems we don't currently
* daemonize, but could do so now that we no longer rely on `fork` to
* implement this feature.
*
* If daemonizing: this function will configure a channel to communicate
* with the child process so that the parent can tell when it has finished
* initializing. The parent will then call into
* DaemonStartupLogger::runParentProcess which waits for initialization
* to complete, prints the status and then terminates. This function will
* therefore never return in the parent process.
*
* In the child process spawned as part of daemonizing, `--startupLoggerFd`
* is passed as a command line argument and the child will use that file
* descriptor to set up a client to communicate status with the parent.
* This function will return a `StartupLogger` instance in the child to
* manage that state.
*
* In the non-daemonizing case, no child is spawned and this function
* will return a `StartupLogger` that simply writes to the configured
* log location.
*/
std::shared_ptr<StartupLogger> daemonizeIfRequested(
folly::StringPiece logPath,
PrivHelper* privHelper,
const std::vector<std::string>& argv);
/**
* StartupLogger provides an API for logging messages that should be displayed
* to the user while edenfs is starting.
*
* If edenfs is daemonizing, the original foreground process will not exit until
* success() or fail() is called. Any messages logged with log() or warn() will
* be shown printed in the original foreground process.
*/
class StartupLogger {
public:
virtual ~StartupLogger();
/**
* Log an informational message.
*
* Note that it is valid to call log() even after success() has been called.
* This can occur if edenfs has been asked to report successful startup
* without waiting for all mount points to be remounted.
*/
template <typename... Args>
void log(Args&&... args) {
writeMessage(
folly::LogLevel::DBG2,
folly::to<std::string>(std::forward<Args>(args)...));
}
/**
* Log a verbose message
*/
template <typename... Args>
void logVerbose(Args&&... args) {
writeMessage(
folly::LogLevel::DBG7,
folly::to<std::string>(std::forward<Args>(args)...));
}
/**
* Log a warning message.
*/
template <typename... Args>
void warn(Args&&... args) {
writeMessage(
folly::LogLevel::WARN,
folly::to<std::string>(std::forward<Args>(args)...));
}
/**
* Indicate that startup has failed.
*
* This exits the current process, and also causes the original foreground
* process to exit if edenfs has daemonized.
*/
template <typename... Args>
[[noreturn]] void exitUnsuccessfully(uint8_t exitCode, Args&&... args) {
writeMessage(
folly::LogLevel::ERR,
folly::to<std::string>(std::forward<Args>(args)...));
failAndExitImpl(exitCode);
folly::assume_unreachable();
}
/**
* Indicate that startup has succeeded.
*
* If edenfs has daemonized this will cause the original foreground edenfs
* process to exit successfully.
*/
void success();
protected:
void writeMessage(folly::LogLevel level, folly::StringPiece message);
virtual void writeMessageImpl(
folly::LogLevel level,
folly::StringPiece message) = 0;
virtual void successImpl() = 0;
[[noreturn]] virtual void failAndExitImpl(uint8_t exitCode) = 0;
};
class DaemonStartupLogger : public StartupLogger {
public:
DaemonStartupLogger() = default;
/**
* Spawn a child process to act as the server.
*
* This method will never return.
* It spawns a child process and then waits for the child to either call
* StartupLogger::success() or
* StartupLogger::fail(), and exits with a status code based on which of these
* was called.
*
* If logPath is non-empty the child process will redirect its stdout and
* stderr file descriptors to the specified log file before returning.
*/
[[noreturn]] void spawn(
folly::StringPiece logPath,
PrivHelper* privHelper,
const std::vector<std::string>& argv);
/** Configure the logger to act as a client of it parent.
* `pipe` is the file descriptor passed down via `--startupLoggerFd`
* and is connected to the parent process which is waiting in the
* `spawn`/`runParentProcess` method.
* This method configures this startup logger for the child so that it
* can communicate the status with the parent.
*/
void initClient(folly::StringPiece logPath, FileDescriptor&& pipe);
protected:
void writeMessageImpl(folly::LogLevel level, folly::StringPiece message)
override;
void successImpl() override;
[[noreturn]] void failAndExitImpl(uint8_t exitCode) override;
private:
friend class DaemonStartupLoggerTest;
using ResultType = uint8_t;
struct ParentResult {
template <typename... Args>
explicit ParentResult(uint8_t code, Args&&... args)
: exitCode(code),
errorMessage(folly::to<std::string>(std::forward<Args>(args)...)) {}
int exitCode;
std::string errorMessage;
};
std::pair<SpawnedProcess, FileDescriptor> spawnImpl(
folly::StringPiece logPath,
PrivHelper* privHelper,
const std::vector<std::string>& argv);
[[noreturn]] void runParentProcess(
FileDescriptor&& readPipe,
SpawnedProcess&& childProc,
folly::StringPiece logPath);
void redirectOutput(folly::StringPiece logPath);
/**
* Wait for the child process to write its initialization status.
*/
ParentResult waitForChildStatus(
FileDescriptor& pipe,
SpawnedProcess& proc,
folly::StringPiece logPath);
ParentResult handleChildCrash(SpawnedProcess& childPid);
void sendResult(ResultType result);
// If stderr has been redirected during process daemonization, origStderr_
// contains a file descriptor referencing the original stderr. It is used to
// continue to print informational messages directly to the user during
// startup even after normal log redirection.
//
// If log redirection has not occurred these will simply be closed File
// objects. The normal logging mechanism is sufficient to show messages to
// the user in this case.
folly::File origStderr_;
std::string logPath_;
// If we have daemonized, pipe_ is a pipe connected to the original foreground
// process. We use this to inform the original process when we have fully
// completed daemon startup.
FileDescriptor pipe_;
};
class ForegroundStartupLogger : public StartupLogger {
public:
ForegroundStartupLogger() = default;
protected:
void writeMessageImpl(folly::LogLevel level, folly::StringPiece message)
override;
void successImpl() override;
[[noreturn]] void failAndExitImpl(uint8_t exitCode) override;
};
class FileStartupLogger : public StartupLogger {
public:
explicit FileStartupLogger(folly::StringPiece startupLogFile);
protected:
void writeMessageImpl(folly::LogLevel level, folly::StringPiece message)
override;
void successImpl() override;
[[noreturn]] void failAndExitImpl(uint8_t exitCode) override;
folly::File logFile_;
};
} // namespace eden
} // namespace facebook