diff --git a/src/autoload.cpp b/src/autoload.cpp index 807a44311..dba7525cd 100644 --- a/src/autoload.cpp +++ b/src/autoload.cpp @@ -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; + + /// 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 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 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 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 check(const wcstring &cmd, bool allow_stale = false); +}; + +maybe_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(now - then); + return seconds.count() < kAutoloadStalenessInterval; +} + +maybe_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 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);