Add autoloader_t

autoloader_t will be the reimplementation of autoloading. Crucically it no
longer manages any locking or loading itself; instead all locking and loading
is performed by clients. This makes it easier to test and helps limit its
responsibilities.
This commit is contained in:
ridiculousfish 2019-04-27 11:00:36 -07:00
parent b7ad6b5bdc
commit 3950dab9ff
2 changed files with 108 additions and 2 deletions

View file

@ -70,6 +70,9 @@ class autoload_file_cache_t {
/// Initialize with a set of directories.
explicit autoload_file_cache_t(wcstring_list_t dirs) : dirs_(std::move(dirs)) {}
/// Initialize with empty directories.
autoload_file_cache_t() = default;
/// \return the directories.
const wcstring_list_t &dirs() const { return dirs_; }
@ -145,6 +148,46 @@ maybe_t<autoloadable_file_t> autoload_file_cache_t::check(const wcstring &cmd, b
return file;
}
autoloader_t::autoloader_t(wcstring env_var_name)
: env_var_name_(std::move(env_var_name)), cache_(make_unique<autoload_file_cache_t>()) {}
autoloader_t::~autoloader_t() = default;
bool autoloader_t::can_autoload(const wcstring &cmd) {
return cache_->check(cmd, true /* allow stale */).has_value();
}
maybe_t<wcstring> autoloader_t::resolve_command(const wcstring &cmd, const environment_t &env) {
// Are we currently in the process of autoloading this?
if (current_autoloading_.count(cmd) > 0) return none();
// Check to see if our paths have changed. If so, replace our cache.
// Note we don't have to modify autoloadable_files_. We'll naturally detect if those have
// changed when we query the cache.
maybe_t<env_var_t> mvar = env.get(env_var_name_);
const wcstring_list_t empty;
const wcstring_list_t &paths = mvar ? mvar->as_list() : empty;
if (paths != cache_->dirs()) {
cache_ = make_unique<autoload_file_cache_t>(paths);
}
// Do we have an entry to load?
auto mfile = cache_->check(cmd);
if (!mfile) return none();
// Is this file the same as what we previously autoloaded?
auto iter = autoloaded_files_.find(cmd);
if (iter != autoloaded_files_.end() && iter->second == mfile->file_id) {
// The file has been autoloaded and is unchanged.
return none();
}
// We're going to (tell our caller to) autoload this command.
current_autoloading_.insert(cmd);
autoloaded_files_[cmd] = mfile->file_id;
return std::move(mfile->path);
}
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);

View file

@ -13,6 +13,71 @@
#include "lru.h"
#include "wutil.h"
class autoload_file_cache_t;
class environment_t;
/// autoloader_t is a class that knows how to autoload .fish files from a list of directories. This
/// is used by autoloading functions and completions. It maintains a file cache, which is
/// responsible for potentially cached accesses of files, and then a list of files that have
/// actually been autoloaded. A client may request a file to autoload given a command name, and may
/// be returned a path which it is expected to source.
/// autoloader_t does not have any internal locks; it is the responsibility of the caller to lock
/// it.
class autoloader_t {
/// The environment variable whose paths we observe.
const wcstring env_var_name_;
/// A map from command to the files we have autoloaded.
std::unordered_map<wcstring, file_id_t> autoloaded_files_;
/// The list of commands that we are currently autoloading.
std::unordered_set<wcstring> current_autoloading_;
/// The autoload cache.
/// This is a unique_ptr because want to change it if the value of our environment variable
/// changes. This is never null (but it may be a cache with no paths).
std::unique_ptr<autoload_file_cache_t> cache_;
public:
/// Construct an autoloader that loads from the paths given by \p env_var_name.
explicit autoloader_t(wcstring env_var_name);
~autoloader_t();
/// Given a command, get a path to autoload.
/// For example, if the environment variable is 'fish_function_path' and the command is 'foo',
/// this will look for a file 'foo.fish' in one of the directories given by fish_function_path.
/// If there is no such file, OR if the file has been previously resolved and is now unchanged,
/// this will return none. But if the file is either new or changed, this will return the path.
/// After returning a path, the command is marked in-progress until the caller calls
/// mark_autoload_finished() with the same command. Note this does not actually execute any
/// code; it is the caller's responsibility to load the file.
maybe_t<wcstring> resolve_command(const wcstring &cmd, const environment_t &env);
/// Mark that a command previously returned from path_to_autoload is finished autoloading.
void mark_autoload_finished(const wcstring &cmd) {
size_t amt = current_autoloading_.erase(cmd);
assert(amt > 0 && "cmd was not being autoloaded");
(void)amt;
}
/// \return whether a command is currently being autoloaded.
bool autoload_in_progress(const wcstring &cmd) const {
return current_autoloading_.count(cmd) > 0;
}
/// \return whether a command could potentially be autoloaded.
/// This does not actually mark the command as being autoloaded.
bool can_autoload(const wcstring &cmd);
/// Mark that all autoloaded files have been forgotten.
/// Future calls to path_to_autoload() will return previously-returned paths.
void clear() {
// Note there is no reason to invalidate the cache here.
autoloaded_files_.clear();
}
};
/// Record of an attempt to access a file.
struct file_access_attempt_t {
/// If filled, the file ID of the checked file.
@ -41,8 +106,6 @@ struct autoload_function_t {
bool is_placeholder;
};
class environment_t;
/// Class representing a path from which we can autoload and the autoloaded contents.
class autoload_t : public lru_cache_t<autoload_t, autoload_function_t> {
private: