mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-24 18:40:42 +08:00
Remove autoload_t, rename autoloader_t to autoload_t
Now that there are no more clients of autoload_t, delete it and rename autoloader_t to autoload_t. Also clean up the headers.
This commit is contained in:
parent
68a28106b2
commit
4ff50eba41
259
src/autoload.cpp
259
src/autoload.cpp
|
@ -1,24 +1,14 @@
|
|||
// The classes responsible for autoloading functions and completions.
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "autoload.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "exec.h"
|
||||
#include "lru.h"
|
||||
#include "parser.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
|
@ -148,17 +138,17 @@ maybe_t<autoloadable_file_t> autoload_file_cache_t::check(const wcstring &cmd, b
|
|||
return file;
|
||||
}
|
||||
|
||||
autoloader_t::autoloader_t(wcstring env_var_name)
|
||||
autoload_t::autoload_t(wcstring env_var_name)
|
||||
: env_var_name_(std::move(env_var_name)), cache_(make_unique<autoload_file_cache_t>()) {}
|
||||
|
||||
autoloader_t::autoloader_t(autoloader_t &&) = default;
|
||||
autoloader_t::~autoloader_t() = default;
|
||||
autoload_t::autoload_t(autoload_t &&) = default;
|
||||
autoload_t::~autoload_t() = default;
|
||||
|
||||
bool autoloader_t::can_autoload(const wcstring &cmd) {
|
||||
bool autoload_t::can_autoload(const wcstring &cmd) {
|
||||
return cache_->check(cmd, true /* allow stale */).has_value();
|
||||
}
|
||||
|
||||
wcstring_list_t autoloader_t::get_autoloaded_commands() const {
|
||||
wcstring_list_t autoload_t::get_autoloaded_commands() const {
|
||||
wcstring_list_t result;
|
||||
result.reserve(autoloaded_files_.size());
|
||||
for (const auto &kv : autoloaded_files_) {
|
||||
|
@ -169,7 +159,7 @@ wcstring_list_t autoloader_t::get_autoloaded_commands() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
maybe_t<wcstring> autoloader_t::resolve_command(const wcstring &cmd, const environment_t &env) {
|
||||
maybe_t<wcstring> autoload_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();
|
||||
|
||||
|
@ -200,235 +190,8 @@ maybe_t<wcstring> autoloader_t::resolve_command(const wcstring &cmd, const envir
|
|||
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);
|
||||
if (file_id != kInvalidFileID && 0 == waccess(path, mode)) {
|
||||
result.file_id = file_id;
|
||||
}
|
||||
result.last_checked = time(NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
void autoloader_t::perform_autoload(const wcstring &path) {
|
||||
void autoload_t::perform_autoload(const wcstring &path) {
|
||||
wcstring script_source = L"source " + escape_string(path, ESCAPE_ALL);
|
||||
exec_subshell(script_source, parser_t::principal_parser(),
|
||||
false /* do not apply exit status */);
|
||||
}
|
||||
|
||||
autoload_t::autoload_t(wcstring env_var_name_var) : env_var_name(std::move(env_var_name_var)) {}
|
||||
|
||||
void autoload_t::entry_was_evicted(wcstring key, autoload_function_t node) {
|
||||
// This should only ever happen on the main thread.
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
}
|
||||
|
||||
int autoload_t::unload(const wcstring &cmd) { return this->evict_node(cmd); }
|
||||
|
||||
int autoload_t::load(const wcstring &cmd, bool reload) {
|
||||
int res;
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
|
||||
// TODO: Justify this principal_parser.
|
||||
auto &parser = parser_t::principal_parser();
|
||||
auto &vars = parser.vars();
|
||||
|
||||
if (!this->paths) {
|
||||
auto path_var = vars.get(env_var_name);
|
||||
if (path_var.missing_or_empty()) return 0;
|
||||
this->paths = path_var->as_list();
|
||||
}
|
||||
|
||||
// Mark that we're loading this. Hang onto the iterator for fast erasing later. Note that
|
||||
// std::set has guarantees about not invalidating iterators, so this is safe to do across the
|
||||
// callouts below.
|
||||
auto insert_result = is_loading_set.insert(cmd);
|
||||
auto where = insert_result.first;
|
||||
bool inserted = insert_result.second;
|
||||
|
||||
// Warn and fail on infinite recursion. It's OK to do this because this function is only called
|
||||
// on the main thread.
|
||||
if (!inserted) {
|
||||
// We failed to insert.
|
||||
const wchar_t *fmt =
|
||||
_(L"Could not autoload item '%ls', it is already being autoloaded. "
|
||||
L"This is a circular dependency in the autoloading scripts, please remove it.");
|
||||
debug(0, fmt, cmd.c_str());
|
||||
return 1;
|
||||
}
|
||||
// Try loading it.
|
||||
assert(paths && "Should have paths");
|
||||
res = this->locate_file_and_maybe_load_it(cmd, true, reload, *this->paths);
|
||||
// Clean up.
|
||||
is_loading_set.erase(where);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool autoload_t::can_load(const wcstring &cmd, const environment_t &vars) {
|
||||
auto path_var = vars.get(env_var_name);
|
||||
if (path_var.missing_or_empty()) return false;
|
||||
|
||||
std::vector<wcstring> path_list;
|
||||
path_var->to_list(path_list);
|
||||
return this->locate_file_and_maybe_load_it(cmd, false, false, path_list);
|
||||
}
|
||||
|
||||
void autoload_t::invalidate() {
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
scoped_lock locker(lock);
|
||||
paths.reset();
|
||||
this->evict_all_nodes();
|
||||
}
|
||||
|
||||
/// Check whether the given command is loaded.
|
||||
bool autoload_t::has_tried_loading(const wcstring &cmd) {
|
||||
scoped_lock locker(lock);
|
||||
autoload_function_t *func = this->get(cmd);
|
||||
return func != NULL;
|
||||
}
|
||||
|
||||
/// @return Whether this function is stale.
|
||||
static bool is_stale(const autoload_function_t *func) {
|
||||
return time(NULL) - func->access.last_checked > kAutoloadStalenessInterval;
|
||||
}
|
||||
|
||||
autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcstring &cmd,
|
||||
bool allow_eviction) {
|
||||
ASSERT_IS_LOCKED(lock);
|
||||
autoload_function_t *func = this->get(cmd);
|
||||
if (!func) {
|
||||
if (allow_eviction) {
|
||||
this->insert(cmd, autoload_function_t(false));
|
||||
} else {
|
||||
this->insert_no_eviction(cmd, autoload_function_t(false));
|
||||
}
|
||||
func = this->get(cmd);
|
||||
assert(func);
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
static bool use_cached(autoload_function_t *func, bool really_load, bool allow_stale_functions) {
|
||||
if (!func) {
|
||||
return false; // can't use a function that doesn't exist
|
||||
}
|
||||
if (really_load && !func->is_placeholder && !func->is_loaded) {
|
||||
return false; // can't use an unloaded function
|
||||
}
|
||||
if (!allow_stale_functions && is_stale(func)) {
|
||||
return false; // can't use a stale function
|
||||
}
|
||||
return true; // I guess we can use it
|
||||
}
|
||||
|
||||
/// This internal helper function does all the real work. By using two functions, the internal
|
||||
/// function can return on various places in the code, and the caller can take care of various
|
||||
/// cleanup work.
|
||||
/// @param cmd the command name ('grep')
|
||||
/// @param really_load Whether to actually parse it as a function, or just check it it exists
|
||||
/// @param reload Whether to reload it if it's already loaded
|
||||
/// @param path_list The set of paths to check
|
||||
/// @return If really_load is true, returns whether the function was loaded. Otherwise returns
|
||||
/// whether the function existed.
|
||||
bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload,
|
||||
const wcstring_list_t &path_list) {
|
||||
// Note that we are NOT locked in this function!
|
||||
bool reloaded = false;
|
||||
|
||||
// Try using a cached function. If we really want the function to be loaded, require that it be
|
||||
// really loaded. If we're not reloading, allow stale functions.
|
||||
{
|
||||
bool allow_stale_functions = !reload;
|
||||
scoped_lock locker(lock);
|
||||
autoload_function_t *func = this->get(cmd); // get the function
|
||||
|
||||
// If we can use this function, return whether we were able to access it.
|
||||
if (use_cached(func, really_load, allow_stale_functions)) {
|
||||
return func->access.accessible();
|
||||
}
|
||||
}
|
||||
|
||||
// The source of the script will end up here.
|
||||
wcstring script_source;
|
||||
|
||||
// Whether we found an accessible file.
|
||||
bool found_file = false;
|
||||
|
||||
// Iterate over path searching for suitable completion files.
|
||||
for (size_t i = 0; i < path_list.size() && !found_file; i++) {
|
||||
wcstring next = path_list.at(i);
|
||||
wcstring path = next + L"/" + cmd + L".fish";
|
||||
|
||||
const file_access_attempt_t access = access_file(path, R_OK);
|
||||
if (!access.accessible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we're actually going to take the lock.
|
||||
scoped_lock locker(lock);
|
||||
autoload_function_t *func = this->get(cmd);
|
||||
|
||||
// Generate the source if we need to load it.
|
||||
bool need_to_load_function =
|
||||
really_load &&
|
||||
(func == NULL || func->access.file_id != access.file_id || !func->is_loaded);
|
||||
if (need_to_load_function) {
|
||||
// Generate the script source.
|
||||
script_source = L"source " + escape_string(path, ESCAPE_ALL);
|
||||
|
||||
// Mark that our function is no longer a placeholder, because we will load it.
|
||||
if (func) {
|
||||
func->is_placeholder = false;
|
||||
}
|
||||
|
||||
// Mark that we're reloading it.
|
||||
reloaded = true;
|
||||
}
|
||||
|
||||
// Create the function if we haven't yet. This does not load it. Do not trigger
|
||||
// eviction unless we are actually loading, because we don't want to evict off of
|
||||
// the main thread.
|
||||
if (!func) func = get_autoloaded_function_with_creation(cmd, really_load);
|
||||
|
||||
// It's a fiction to say the script is loaded at this point, but we're definitely
|
||||
// going to load it down below.
|
||||
if (need_to_load_function) func->is_loaded = true;
|
||||
|
||||
// Unconditionally record our access time.
|
||||
func->access = access;
|
||||
found_file = true;
|
||||
}
|
||||
|
||||
// If no file or builtin script was found we insert a placeholder function. Later we only
|
||||
// research if the current time is at least five seconds later. This way, the files won't be
|
||||
// searched over and over again.
|
||||
if (!found_file && script_source.empty()) {
|
||||
scoped_lock locker(lock);
|
||||
// Generate a placeholder.
|
||||
autoload_function_t *func = this->get(cmd);
|
||||
if (!func) {
|
||||
if (really_load) {
|
||||
this->insert(cmd, autoload_function_t(true));
|
||||
} else {
|
||||
this->insert_no_eviction(cmd, autoload_function_t(true));
|
||||
}
|
||||
func = this->get(cmd);
|
||||
assert(func);
|
||||
}
|
||||
func->access.last_checked = time(NULL);
|
||||
}
|
||||
|
||||
// If we have a script, either built-in or a file source, then run it.
|
||||
if (really_load && !script_source.empty()) {
|
||||
// Do nothing on failure.
|
||||
// TODO: rationalize this use of principal_parser, or inject the loading from outside.
|
||||
exec_subshell(script_source, parser_t::principal_parser(),
|
||||
false /* do not apply exit status */);
|
||||
}
|
||||
|
||||
if (really_load) {
|
||||
return reloaded;
|
||||
}
|
||||
|
||||
return found_file || !script_source.empty();
|
||||
}
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
#ifndef FISH_AUTOLOAD_H
|
||||
#define FISH_AUTOLOAD_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "lru.h"
|
||||
#include "wutil.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
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
|
||||
/// autoload_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
|
||||
/// autoload_t does not have any internal locks; it is the responsibility of the caller to lock
|
||||
/// it.
|
||||
class autoloader_t {
|
||||
class autoload_t {
|
||||
/// The environment variable whose paths we observe.
|
||||
const wcstring env_var_name_;
|
||||
|
||||
|
@ -40,10 +40,10 @@ class autoloader_t {
|
|||
|
||||
public:
|
||||
/// Construct an autoloader that loads from the paths given by \p env_var_name.
|
||||
explicit autoloader_t(wcstring env_var_name);
|
||||
explicit autoload_t(wcstring env_var_name);
|
||||
|
||||
autoloader_t(autoloader_t &&);
|
||||
~autoloader_t();
|
||||
autoload_t(autoload_t &&);
|
||||
~autoload_t();
|
||||
|
||||
/// Given a command, get a path to autoload.
|
||||
/// For example, if the environment variable is 'fish_function_path' and the command is 'foo',
|
||||
|
@ -87,79 +87,4 @@ class autoloader_t {
|
|||
}
|
||||
};
|
||||
|
||||
/// Record of an attempt to access a file.
|
||||
struct file_access_attempt_t {
|
||||
/// If filled, the file ID of the checked file.
|
||||
/// Otherwise the file did not exist or was otherwise inaccessible.
|
||||
/// Note that this will never contain kInvalidFileID.
|
||||
maybe_t<file_id_t> file_id;
|
||||
|
||||
/// When we last checked the file.
|
||||
time_t last_checked;
|
||||
|
||||
/// Whether or not we believe we can access this file.
|
||||
bool accessible() const { return file_id.has_value(); }
|
||||
};
|
||||
file_access_attempt_t access_file(const wcstring &path, int mode);
|
||||
|
||||
struct autoload_function_t {
|
||||
explicit autoload_function_t(bool placeholder)
|
||||
: access(), is_loaded(false), is_placeholder(placeholder) {}
|
||||
|
||||
/// The last access attempt recorded
|
||||
file_access_attempt_t access;
|
||||
/// Have we actually loaded this function?
|
||||
bool is_loaded;
|
||||
/// Whether we are a placeholder that stands in for "no such function". If this is true, then
|
||||
/// is_loaded must be false.
|
||||
bool is_placeholder;
|
||||
};
|
||||
|
||||
/// 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:
|
||||
/// Lock for thread safety.
|
||||
std::mutex lock;
|
||||
/// The environment variable name.
|
||||
const wcstring env_var_name;
|
||||
/// The paths from which to autoload, or missing if none.
|
||||
maybe_t<wcstring_list_t> paths;
|
||||
/// A table containing all the files that are currently being loaded.
|
||||
/// This is here to help prevent recursion.
|
||||
std::unordered_set<wcstring> is_loading_set;
|
||||
|
||||
void remove_all_functions() { this->evict_all_nodes(); }
|
||||
|
||||
bool locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload,
|
||||
const wcstring_list_t &path_list);
|
||||
|
||||
autoload_function_t *get_autoloaded_function_with_creation(const wcstring &cmd,
|
||||
bool allow_eviction);
|
||||
|
||||
public:
|
||||
// CRTP override
|
||||
void entry_was_evicted(wcstring key, autoload_function_t node);
|
||||
|
||||
// Create an autoload_t for the given environment variable name.
|
||||
explicit autoload_t(wcstring env_var_name_var);
|
||||
|
||||
/// Autoload the specified file, if it exists in the specified path. Do not load it multiple
|
||||
/// times unless its timestamp changes or parse_util_unload is called.
|
||||
/// @param cmd the filename to search for. The suffix '.fish' is always added to this name
|
||||
/// @param reload wheter to recheck file timestamps on already loaded files
|
||||
int load(const wcstring &cmd, bool reload);
|
||||
|
||||
/// Check whether we have tried loading the given command. Does not do any I/O.
|
||||
bool has_tried_loading(const wcstring &cmd);
|
||||
|
||||
/// Tell the autoloader that the specified file, in the specified path, is no longer loaded.
|
||||
/// Returns non-zero if the file was removed, zero if the file had not yet been loaded
|
||||
int unload(const wcstring &cmd);
|
||||
|
||||
/// Check whether the given command could be loaded, but do not load it.
|
||||
bool can_load(const wcstring &cmd, const environment_t &vars);
|
||||
|
||||
/// Invalidates all entries. Uesd when the underlying path variable changes.
|
||||
void invalidate();
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -381,7 +381,7 @@ class completer_t {
|
|||
};
|
||||
|
||||
// Autoloader for completions.
|
||||
static owning_lock<autoloader_t> completion_autoloader{autoloader_t(L"fish_complete_path")};
|
||||
static owning_lock<autoload_t> completion_autoloader{autoload_t(L"fish_complete_path")};
|
||||
|
||||
/// Create a new completion entry.
|
||||
void append_completion(std::vector<completion_t> *completions, wcstring comp, wcstring desc,
|
||||
|
@ -867,7 +867,7 @@ static void complete_load(const wcstring &name, bool reload) {
|
|||
const environment_t &vars = parser_t::principal_parser().vars();
|
||||
maybe_t<wcstring> path_to_load = completion_autoloader.acquire()->resolve_command(name, vars);
|
||||
if (path_to_load) {
|
||||
autoloader_t::perform_autoload(*path_to_load);
|
||||
autoload_t::perform_autoload(*path_to_load);
|
||||
completion_autoloader.acquire()->mark_autoload_finished(name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ struct function_set_t {
|
|||
std::unordered_set<wcstring> autoload_tombstones;
|
||||
|
||||
/// The autoloader for our functions.
|
||||
autoloader_t autoloader{L"fish_function_path"};
|
||||
autoload_t autoloader{L"fish_function_path"};
|
||||
|
||||
/// Remove a function.
|
||||
/// \return true if successful, false if it doesn't exist.
|
||||
|
@ -112,7 +112,7 @@ static void try_autoload(const wcstring &name) {
|
|||
// Release the lock and perform any autoload, then reacquire the lock and clean up.
|
||||
if (path_to_autoload) {
|
||||
// Crucially, the lock is acquired *after* do_autoload_file_at_path().
|
||||
autoloader_t::perform_autoload(*path_to_autoload);
|
||||
autoload_t::perform_autoload(*path_to_autoload);
|
||||
function_set.acquire()->autoloader.mark_autoload_finished(name);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user