From 4ff50eba41766f62f49f6fda64a6f33af520d500 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 27 Apr 2019 15:42:34 -0700 Subject: [PATCH] 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. --- src/autoload.cpp | 259 ++--------------------------------------------- src/autoload.h | 99 +++--------------- src/complete.cpp | 4 +- src/function.cpp | 4 +- 4 files changed, 27 insertions(+), 339 deletions(-) diff --git a/src/autoload.cpp b/src/autoload.cpp index 46588334b..bf6828232 100644 --- a/src/autoload.cpp +++ b/src/autoload.cpp @@ -1,24 +1,14 @@ // The classes responsible for autoloading functions and completions. #include "config.h" // IWYU pragma: keep -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - #include "autoload.h" + +#include + #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 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()) {} -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 autoloader_t::resolve_command(const wcstring &cmd, const environment_t &env) { +maybe_t 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 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 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(); -} diff --git a/src/autoload.h b/src/autoload.h index 0910923ee..2daed1a45 100644 --- a/src/autoload.h +++ b/src/autoload.h @@ -2,28 +2,28 @@ #ifndef FISH_AUTOLOAD_H #define FISH_AUTOLOAD_H -#include -#include - -#include -#include +#include "config.h" // IWYU pragma: keep #include "common.h" #include "env.h" -#include "lru.h" #include "wutil.h" +#include +#include +#include +#include + 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; - - /// 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 { - 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 paths; - /// A table containing all the files that are currently being loaded. - /// This is here to help prevent recursion. - std::unordered_set 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 diff --git a/src/complete.cpp b/src/complete.cpp index 430463fe3..4252fee87 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -381,7 +381,7 @@ class completer_t { }; // Autoloader for completions. -static owning_lock completion_autoloader{autoloader_t(L"fish_complete_path")}; +static owning_lock completion_autoloader{autoload_t(L"fish_complete_path")}; /// Create a new completion entry. void append_completion(std::vector *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 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); } } diff --git a/src/function.cpp b/src/function.cpp index 32346e6eb..75cd7b802 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -63,7 +63,7 @@ struct function_set_t { std::unordered_set 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); } }