Add autoload_file_cache_t

This will provide the "backing store" for autoloading.
This commit is contained in:
ridiculousfish 2019-04-26 13:25:41 -07:00
parent 6ec7c50ace
commit b7ad6b5bdc

View file

@ -25,6 +25,126 @@
/// The time before we'll recheck an autoloaded file.
static const int kAutoloadStalenessInterval = 15;
/// Represents a file that we might want to autoload.
struct autoloadable_file_t {
/// The path to the file.
wcstring path;
/// The metadata for the file.
file_id_t file_id;
};
/// Class representing a cache of files that may be autoloaded.
/// This is responsible for performing cached accesses to a set of paths.
class autoload_file_cache_t {
/// A timestamp is a monotonic point in time.
using timestamp_t = std::chrono::time_point<std::chrono::steady_clock>;
/// The directories from which to load.
const wcstring_list_t dirs_;
/// Our LRU cache of checks that were misses.
/// The key is the command, the value is the time of the check.
struct misses_lru_cache_t : public lru_cache_t<misses_lru_cache_t, timestamp_t> {};
misses_lru_cache_t misses_cache_;
/// The set of files that we have returned to the caller, along with the time of the check.
/// The key is the command (not the path).
struct known_file_t {
autoloadable_file_t file;
timestamp_t last_checked;
};
std::unordered_map<wcstring, known_file_t> known_files_;
/// \return the current timestamp.
static timestamp_t current_timestamp() { return std::chrono::steady_clock::now(); }
/// \return whether a timestamp is fresh enough to use.
static bool is_fresh(timestamp_t then, timestamp_t now);
/// Attempt to find an autoloadable file by searching our path list for a given comand.
/// \return the file, or none() if none.
maybe_t<autoloadable_file_t> locate_file(const wcstring &cmd) const;
public:
/// Initialize with a set of directories.
explicit autoload_file_cache_t(wcstring_list_t dirs) : dirs_(std::move(dirs)) {}
/// \return the directories.
const wcstring_list_t &dirs() const { return dirs_; }
/// Check if a command \p cmd can be loaded.
/// If \p allow_stale is true, allow stale entries; otherwise discard them.
/// This returns an autoloadable file, or none() if there is no such file.
maybe_t<autoloadable_file_t> check(const wcstring &cmd, bool allow_stale = false);
};
maybe_t<autoloadable_file_t> autoload_file_cache_t::locate_file(const wcstring &cmd) const {
// Re-use the storage for path.
wcstring path;
for (const wcstring &dir : dirs()) {
// Construct the path as dir/cmd.fish
path = dir;
path += L"/";
path += cmd;
path += L".fish";
file_id_t file_id = file_id_for_path(path);
if (file_id != kInvalidFileID) {
// Found it.
autoloadable_file_t result;
result.path = std::move(path);
result.file_id = file_id;
return result;
}
}
return none();
}
bool autoload_file_cache_t::is_fresh(timestamp_t then, timestamp_t now) {
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now - then);
return seconds.count() < kAutoloadStalenessInterval;
}
maybe_t<autoloadable_file_t> autoload_file_cache_t::check(const wcstring &cmd, bool allow_stale) {
const auto now = current_timestamp();
// Check hits.
auto iter = known_files_.find(cmd);
if (iter != known_files_.end()) {
if (allow_stale || is_fresh(iter->second.last_checked, now)) {
// Re-use this cached hit.
return iter->second.file;
}
// The file is stale, remove it.
known_files_.erase(iter);
}
// Check misses.
if (timestamp_t *miss = misses_cache_.get(cmd)) {
if (allow_stale || is_fresh(*miss, now)) {
// Re-use this cached miss.
return none();
}
// The miss is stale, remove it.
misses_cache_.evict_node(cmd);
}
// We couldn't satisfy this request from the cache. Hit the disk.
// Don't re-use 'now', the disk access could have taken a long time.
maybe_t<autoloadable_file_t> file = locate_file(cmd);
if (file.has_value()) {
auto ins = known_files_.emplace(cmd, known_file_t{*file, current_timestamp()});
assert(ins.second && "Known files cache should not have contained this cmd");
(void)ins;
} else {
bool ins = misses_cache_.insert(cmd, current_timestamp());
assert(ins && "Misses cache should not have contained this cmd");
(void)ins;
}
return file;
}
file_access_attempt_t access_file(const wcstring &path, int mode) {
file_access_attempt_t result = {};
file_id_t file_id = file_id_for_path(path);