mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 14:28:17 +03:00
fc48ab8614
Summary: EdenConfig had a bunch of hard-coded logic around stat()ing files and parsing TOML. Besides code clarity, this had several problems: * Using an EdenConfig in a unit test would touch the actual filesystem. * We didn't support reading configs from other sources. For example, we've talked about reading EdenConfig from .hgrc. * ReloadableConfig knew too much about EdenConfig's internal working. * Changing configs in tests involves calling setValue(), which is an unsafe API in the limit. This diff introduces a new abstraction called ConfigSource, with one implementation, TomlFileConfigSource. ConfigSource can indicate that it's time for a reload, and can apply its values to a non-const EdenConfig instance. Reviewed By: kmancini Differential Revision: D41830417 fbshipit-source-id: a0ff90c4a1adb870e4b34da5d16238a6e7b75be2
223 lines
6.1 KiB
C++
223 lines
6.1 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This software may be used and distributed according to the terms of the
|
|
* GNU General Public License version 2.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <fmt/core.h>
|
|
#include <folly/File.h>
|
|
#include <chrono>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
#include "eden/fs/utils/PathFuncs.h"
|
|
|
|
namespace facebook::eden {
|
|
|
|
/**
|
|
* Why a file is considered to have changed. Evaluates as true in
|
|
* conditionals if a file is considered changed.
|
|
*/
|
|
class FileChangeReason {
|
|
public:
|
|
enum Reason {
|
|
NONE,
|
|
SIZE,
|
|
DEV,
|
|
INO,
|
|
MODE,
|
|
CTIME_,
|
|
MTIME,
|
|
};
|
|
|
|
const Reason reason;
|
|
|
|
/* implicit */ FileChangeReason(Reason reason) : reason{reason} {}
|
|
|
|
explicit operator bool() const {
|
|
return reason != NONE;
|
|
}
|
|
|
|
friend bool operator==(FileChangeReason lhs, FileChangeReason rhs) {
|
|
return lhs.reason == rhs.reason;
|
|
}
|
|
|
|
friend bool operator!=(FileChangeReason lhs, FileChangeReason rhs) {
|
|
return lhs.reason != rhs.reason;
|
|
}
|
|
|
|
/**
|
|
* Return a human-readable string for why a file changed.
|
|
*/
|
|
std::string_view str() const;
|
|
};
|
|
|
|
/**
|
|
* Subset of `struct stat` and `BY_HANDLE_FILE_INFORMATION` for detecting
|
|
* whether a file's attributes have changed.
|
|
*/
|
|
struct FileStat {
|
|
mode_t mode = 0;
|
|
uint64_t size = 0;
|
|
timespec mtime = {};
|
|
timespec ctime = {};
|
|
uint32_t device = 0;
|
|
uint64_t inode = 0;
|
|
};
|
|
|
|
/**
|
|
* Reads a FileStat from the given file descriptor.
|
|
*/
|
|
folly::Expected<FileStat, int> getFileStat(int fd);
|
|
|
|
/**
|
|
* Reads a FileStat from the given path.
|
|
*/
|
|
folly::Expected<FileStat, int> getFileStat(const char* path);
|
|
|
|
/**
|
|
* If two stat results are equal, returns a value that converts to
|
|
* boolean true. Otherwise, returns the reason why we consider the
|
|
* field to have changed.
|
|
*/
|
|
FileChangeReason hasFileChanged(
|
|
const FileStat& stat1,
|
|
const FileStat& stat2) noexcept;
|
|
|
|
/**
|
|
* FileChangeMonitor monitors a file for changes. The "invokeIfUpdated()"
|
|
* method initiates a check and, if the file has changed, will run the
|
|
* provided call-back method. Typical usage:
|
|
* - construct, a FileChangeMonitor eg. FileChangeMonitor fcm{path, 4s}
|
|
* - periodically call invokeIfUpdated() to check for changes and potentially
|
|
* run the call-back.
|
|
*
|
|
* FileChangeMonitor performs checks on demand. The throttleDuration setting
|
|
* can further limit resource usage (to a maximum of 1 check/throttleDuration).
|
|
*
|
|
* FileChangeMonitor is not thread safe - users are responsible for locking as
|
|
* necessary.
|
|
*/
|
|
class FileChangeMonitor {
|
|
public:
|
|
/**
|
|
* Construct a FileChangeMonitor for the provided filePath.
|
|
* @param throttleDuration specifies minimum time between file stats.
|
|
*/
|
|
FileChangeMonitor(
|
|
AbsolutePathPiece filePath,
|
|
std::chrono::milliseconds throttleDuration)
|
|
: filePath_{filePath}, throttleDuration_{throttleDuration} {
|
|
resetToForceChange();
|
|
}
|
|
|
|
~FileChangeMonitor() = default;
|
|
|
|
FileChangeMonitor(const FileChangeMonitor& fcm) = default;
|
|
|
|
FileChangeMonitor(FileChangeMonitor&& fcm) = default;
|
|
|
|
FileChangeMonitor& operator=(const FileChangeMonitor& fcm) = default;
|
|
|
|
FileChangeMonitor& operator=(FileChangeMonitor&& fcm) = default;
|
|
|
|
/**
|
|
* Check if the monitored file has been updated in a meaningful way.
|
|
* Meaningful changes include:
|
|
* - file modifications where file can be opened;
|
|
* - file modifications, where file cannot be opened AND its stat or open
|
|
* error code has changed;
|
|
* We suppress file change notifications where the same error exists. For
|
|
* example, no call-back will be issued for a file that has changed but, open
|
|
* still returns EACCES.
|
|
*/
|
|
std::optional<folly::Expected<folly::File, int>> checkIfUpdated(
|
|
bool noThrottle = false);
|
|
|
|
/**
|
|
* If the monitored file has changed in a "meaningful" way, the
|
|
* FileChangeProcessor call-back will be invoked. The call-back will always
|
|
* be invoked on the first call. This method will not catch exceptions
|
|
* thrown by the call-back.
|
|
* @ see checkIfUpdated for details on "meaningful" changes.
|
|
* @return TRUE if the call-back was invoked, else, FALSE.
|
|
*/
|
|
template <typename Fn>
|
|
bool invokeIfUpdated(Fn&& fn, bool noThrottle = false) {
|
|
auto result = checkIfUpdated(noThrottle);
|
|
if (!result.has_value()) {
|
|
return false;
|
|
}
|
|
if (result.value().hasValue()) {
|
|
fn(std::move(result.value()).value(), 0, filePath_);
|
|
} else {
|
|
fn(std::move(folly::File()), result->error(), filePath_);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Change the path of the monitored file. Resets stat and lastCheck_ to force
|
|
* the next "invokeIfUpdated()" to be TRUE.
|
|
*/
|
|
void setFilePath(AbsolutePathPiece filePath);
|
|
|
|
/**
|
|
* @return the monitored file path.
|
|
*/
|
|
AbsolutePath getFilePath();
|
|
|
|
private:
|
|
/**
|
|
* @return TRUE if the time elapsed since last call is less than
|
|
* throttleDuration.
|
|
*/
|
|
bool throttle();
|
|
|
|
/** Stat the monitored file and compare results against last call to
|
|
* isChanged(). It updates statErrno_ which can be used to by invokeIfUpdated
|
|
* to make optimizations.
|
|
*/
|
|
bool isChanged();
|
|
|
|
/**
|
|
* Reset to base state. The next call to isChanged will return true
|
|
* (this requires throttle to NOT activate). Useful during initialization and
|
|
* if the monitored file's path has changed.
|
|
*/
|
|
void resetToForceChange() {
|
|
// Set values for stat to force changedSinceUpdate() to return TRUE.
|
|
// We use a novel setting to force change to be detected
|
|
fileStat_ = {};
|
|
fileStat_.mtime.tv_sec = 1;
|
|
|
|
statErrno_ = 0;
|
|
openErrno_ = 0;
|
|
// Set lastCheck in past so throttle does not apply.
|
|
lastCheck_ = std::chrono::steady_clock::now() - throttleDuration_ -
|
|
std::chrono::seconds{1};
|
|
}
|
|
|
|
AbsolutePath filePath_;
|
|
FileStat fileStat_;
|
|
int statErrno_{0};
|
|
int openErrno_{0};
|
|
std::chrono::milliseconds throttleDuration_;
|
|
std::chrono::steady_clock::time_point lastCheck_;
|
|
};
|
|
|
|
} // namespace facebook::eden
|
|
|
|
template <>
|
|
struct fmt::formatter<facebook::eden::FileChangeReason>
|
|
: formatter<std::string_view> {
|
|
template <typename Context>
|
|
auto format(facebook::eden::FileChangeReason reason, Context& ctx) const {
|
|
return formatter<std::string_view>::format(reason.str(), ctx);
|
|
}
|
|
};
|