From 8e56763c981789701a6ef655634c82873881617b Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 25 Jan 2012 11:47:45 -0800 Subject: [PATCH] LRU cache work --- autoload.cpp | 134 ++++++++++++++++++++++++++++++++++++++++++------- autoload.h | 69 +++++++++++++++++++++++-- parse_util.cpp | 112 ++++++++--------------------------------- parse_util.h | 42 +++++----------- 4 files changed, 214 insertions(+), 143 deletions(-) diff --git a/autoload.cpp b/autoload.cpp index 1f47edbd1..a886df522 100644 --- a/autoload.cpp +++ b/autoload.cpp @@ -10,6 +10,26 @@ The classes responsible for autoloading functions and completions. const size_t kLRULimit = 256; +file_access_attempt_t access_file(const wcstring &path, int mode) { + file_access_attempt_t result = {0}; + struct stat statbuf; + if (wstat(path.c_str(), &statbuf)) { + result.error = errno; + } else { + result.mod_time = statbuf.st_mtime; + if (waccess(path.c_str(), mode)) { + result.error = errno; + } else { + result.accessible = true; + } + } + + // Note that we record the last checked time after the call, on the assumption that in a slow filesystem, the lag comes before the kernel check, not after. + result.stale = false; + result.last_checked = time(NULL); + return result; +} + /** A node in our LRU map */ class file_access_node_t { public: @@ -77,23 +97,7 @@ file_access_node_t *access_tracker_t::while_locked_find_node(const wcstring &pat } file_access_attempt_t access_tracker_t::attempt_access(const wcstring& path) const { - file_access_attempt_t result = {0}; - struct stat statbuf; - if (wstat(path.c_str(), &statbuf)) { - result.error = errno; - } else { - result.mod_time = statbuf.st_mtime; - if (waccess(path.c_str(), this->mode)) { - result.error = errno; - } else { - result.accessible = true; - } - } - - // Note that we record the last checked time after the call, on the assumption that in a slow filesystem, the lag comes before the kernel check, not after. - result.stale = false; - result.last_checked = time(NULL); - return result; + return ::access_file(path, this->mode); } bool access_tracker_t::access_file_only_cached(const wcstring &path, file_access_attempt_t &attempt) { @@ -162,3 +166,99 @@ file_access_attempt_t access_tracker_t::access_file(const wcstring &path) { } return result; } + +lru_cache_impl_t::lru_cache_impl_t() : node_count(0), mouth(L"") { + /* Hook up the mouth to itself: a one node circularly linked list */ + mouth.prev = mouth.next = &mouth; +} + +void lru_cache_impl_t::evict_node(lru_node_t *condemned_node) { + /* We should never evict the mouth */ + assert(condemned_node != NULL && condemned_node != &mouth); + + /* Remove it from the linked list */ + condemned_node->prev->next = condemned_node->next; + condemned_node->next->prev = condemned_node->prev; + + /* Remove us from the set */ + node_set.erase(condemned_node); + node_count--; + + /* Tell it */ + condemned_node->evicted(); +} + +void lru_cache_impl_t::evict_last_node(void) { + /* Simple */ + evict_node(mouth.prev); +} + +bool lru_cache_impl_t::evict_node(const wcstring &key) { + /* Construct a fake node as our key */ + lru_node_t node_key(key); + + /* Look for it in the set */ + node_set_t::iterator iter = node_set.find(&node_key); + if (iter == node_set.end()) + return false; + + /* Evict the given node */ + evict_node(*iter); + return true; +} + +void lru_cache_impl_t::promote_node(lru_node_t *node) { + /* We should never promote the mouth */ + assert(node != &mouth); + + /* First unhook us */ + node->prev->next = node->next; + node->next->prev = node->prev; + + /* Put us after the mouth */ + node->next = mouth.next; + node->prev = &mouth; + mouth.next = node; +} + +bool lru_cache_impl_t::add_node(lru_node_t *node) { + assert(node != NULL && node != &mouth); + + /* Try inserting; return false if it was already in the set */ + if (! node_set.insert(node).second) + return false; + + /* Update the count */ + node_count++; + + /* Add the node after the mouth */ + node->next = mouth.next; + node->prev = &mouth; + mouth.next = node; + + /* Success */ + return true; +} + +lru_node_t *lru_cache_impl_t::get_node(const wcstring &key) { + lru_node_t *result = NULL; + + /* Construct a fake node as our key */ + lru_node_t node_key(key); + + /* Look for it in the set */ + node_set_t::iterator iter = node_set.find(&node_key); + + /* If we found a node, promote and return it */ + if (iter != node_set.end()) { + result = *iter; + promote_node(result); + } + return result; +} + +void lru_cache_impl_t::evict_all_nodes() { + while (node_count > 0) { + evict_last_node(); + } +} diff --git a/autoload.h b/autoload.h index dc3445e42..db8e89bfb 100644 --- a/autoload.h +++ b/autoload.h @@ -3,8 +3,8 @@ The classes responsible for autoloading functions and completions. */ -#ifndef FISH_PARSE_UTIL_H -#define FISH_PARSE_UTIL_H +#ifndef FISH_AUTOLOAD_H +#define FISH_AUTOLOAD_H #include #include @@ -14,9 +14,8 @@ extern const time_t kFishDefaultStalenessInterval; -/** A class responsible for recording an attempt to access a file. */ -class file_access_attempt_t { -public: +/** A struct responsible for recording an attempt to access a file. */ +struct file_access_attempt_t { time_t mod_time; /** The modification time of the file */ time_t last_checked; /** When we last checked the file */ bool accessible; /** Whether we believe we could access this file */ @@ -32,6 +31,7 @@ struct dereference_less_t { bool operator()(ptr_t p1, ptr_t p2) const { return *p1 < *p2; } }; +file_access_attempt_t access_file(const wcstring &path, int mode); /** A class responsible for tracking accesses to files, including auto-expiration. */ class access_tracker_t { @@ -72,4 +72,63 @@ class access_tracker_t { bool access_file_only_cached(const wcstring &path, file_access_attempt_t &attempt); }; +class lru_node_t { + friend class lru_cache_impl_t; + /** Our linked list pointer */ + lru_node_t *prev, *next; + +public: + /** The key used to look up in the cache */ + const wcstring key; + + /** Constructor */ + lru_node_t(const wcstring &keyVar) : prev(NULL), next(NULL), key(keyVar) { } + bool operator<(const lru_node_t &other) const { return key < other.key; } + + /** Callback when the node is evicted from an LRU cache */ + virtual void evicted(void) { } +}; + +class lru_cache_impl_t { + private: + void promote_node(lru_node_t *); + void evict_node(lru_node_t *node); + void evict_last_node(void); + + /** Count of nodes */ + unsigned int node_count; + + /** The set of nodes */ + typedef std::set node_set_t; + node_set_t node_set; + + /** Head of the linked list */ + lru_node_t mouth; + + public: + /** Constructor */ + lru_cache_impl_t(); + + /** Returns the node for a given key, or NULL */ + lru_node_t *get_node(const wcstring &key); + + /** Evicts the node for a given key, returning true if a node was evicted. */ + bool evict_node(const wcstring &key); + + /** Adds a node under the given key. Returns true if the node was added, false if the node was not because a node with that key is already in the set. */ + bool add_node(lru_node_t *node); + + /** Evicts all nodes */ + void evict_all_nodes(void); +}; + +/** Template cover to avoid casting */ +template +class lru_cache_t : public lru_cache_impl_t { +public: + node_type_t *get_node(const wcstring &key) { return static_cast(lru_cache_impl_t::get_node(key)); } + bool add_node(node_type_t *node) { return lru_cache_impl_t::add_node(node); } +}; + + #endif diff --git a/parse_util.cpp b/parse_util.cpp index 4b4aa7e06..c30660d84 100644 --- a/parse_util.cpp +++ b/parse_util.cpp @@ -54,43 +54,6 @@ #define AUTOLOAD_MIN_AGE 60 -/* Get the name of the function that was least recently loaded, if it was loaded before cutoff_access. Return NULL if no function qualifies. */ -const wcstring *autoload_t::get_lru_function_name(const wcstring &skip, time_t cutoff_access) const -{ - const wcstring *resultName = NULL; - const autoload_function_t *resultFunction = NULL; - autoload_functions_map_t::const_iterator iter; - for (iter = autoload_functions.begin(); iter != autoload_functions.end(); iter++) - { - /* Skip the skip */ - if (iter->first == skip) continue; - - /* Skip items that are still loading */ - if (is_loading(iter->first)) continue; - - /* Skip placeholder items */ - if (iter->second.is_placeholder) continue; - - /* Check cutoff_access */ - if (iter->second.load_time > cutoff_access) continue; - - /* Remember this if it was used earlier */ - if (resultFunction == NULL || iter->second.load_time < resultFunction->load_time) { - resultName = &iter->first; - resultFunction = &iter->second; - } - } - return resultName; -} - -void autoload_t::apply_handler_to_nonplaceholder_function_names(void (*handler)(const wchar_t *cmd)) const -{ - autoload_functions_map_t::const_iterator iter; - for (iter = autoload_functions.begin(); iter != autoload_functions.end(); iter++) - handler(iter->first.c_str()); -} - - int parse_util_lineno( const wchar_t *str, int len ) { /** @@ -638,6 +601,13 @@ void parse_util_token_extent( const wchar_t *buff, } +autoload_function_t::~autoload_function_t() { } + +void autoload_function_t::evicted(void) { + delete this; +} + + autoload_t::autoload_t(const wcstring &env_var_name_var, const builtin_script_t * const scripts, size_t script_count) : env_var_name(env_var_name_var), builtin_scripts(scripts), @@ -647,50 +617,14 @@ autoload_t::autoload_t(const wcstring &env_var_name_var, const builtin_script_t void autoload_t::reset( void (*on_load)(const wchar_t *cmd) ) { - if (! autoload_functions.empty()) { - if (on_load) { - /* Call the on_load handler on each real function name. */ - this->apply_handler_to_nonplaceholder_function_names(on_load); - } - /* Empty the functino set */ - this->remove_all_functions(); - } + function_cache.evict_all_nodes(); + /* TODO: Must call on_load on all non-placeholders */ } int autoload_t::unload( const wchar_t *cmd, void (*on_load)(const wchar_t *cmd) ) { - int result = 0; - CHECK( cmd, 0 ); - - if (this->remove_function_with_name(cmd)) - { - if (on_load) - on_load(cmd); - result = 1; - } - return result; -} - -/** - - Unload one autoloaded item that has expired, that where loaded in - the specified path. - - \param skip unloading the the specified file - \param on_load the callback function to call for every unloaded file - -*/ -void autoload_t::autounload( const wchar_t *skip, - void (*on_load)(const wchar_t *cmd) ) -{ - if( this->function_count() >= AUTOLOAD_MAX ) - { - time_t cutoff_access = time(0) - AUTOLOAD_MIN_AGE; - const wcstring *lru = this->get_lru_function_name(skip, cutoff_access); - if (lru) - unload( lru->c_str(), on_load ); - } + return function_cache.evict_node(cmd); } int autoload_t::load( const wcstring &cmd, @@ -702,7 +636,6 @@ int autoload_t::load( const wcstring &cmd, CHECK_BLOCK( 0 ); - autounload( cmd.c_str(), on_load ); const env_var_t path_var = env_get_string( env_var_name.c_str() ); /* @@ -791,8 +724,8 @@ int autoload_t::load_internal( const wcstring &cmd, return 0; /* Nothing to do if we just checked it */ - if (func && time(NULL) - func->load_time <= 1) - return 0; + if (func && time(NULL) - func->access.last_checked <= 1) + return 0; /* The source of the script will end up here */ wcstring script_source; @@ -825,24 +758,19 @@ int autoload_t::load_internal( const wcstring &cmd, */ for( i=0; imodification_time != buf.st_mtime ) ) - { + const file_access_attempt_t access = access_file(path, R_OK); + if (access.accessible) { + if (! func || access.mod_time != func->access.mod_time) { wcstring esc = escape_string(path, 1); script_source = L". " + esc; has_script_source = true; if( !func ) - func = this->create_function_with_name(cmd); - - func->modification_time = buf.st_mtime; - func->load_time = time(NULL); + func = new autoload_function_t(cmd); + func->access = access; if( on_load ) on_load(cmd.c_str()); @@ -855,7 +783,7 @@ int autoload_t::load_internal( const wcstring &cmd, If we are rechecking an autoload file, and it hasn't changed, update the 'last check' timestamp. */ - func->load_time = time(NULL); + func->access = access; } break; @@ -869,8 +797,8 @@ int autoload_t::load_internal( const wcstring &cmd, */ if( !func ) { - func = this->create_function_with_name(cmd); - func->load_time = time(NULL); + func = new autoload_function_t(cmd); + func->access.last_checked = time(NULL); func->is_placeholder = true; } } diff --git a/parse_util.h b/parse_util.h index 26e69363e..84ec68f79 100644 --- a/parse_util.h +++ b/parse_util.h @@ -7,20 +7,22 @@ #ifndef FISH_PARSE_UTIL_H #define FISH_PARSE_UTIL_H +#include "autoload.h" #include #include #include -struct autoload_function_t +struct autoload_function_t : public lru_node_t { - bool is_placeholder; //whether we are a placeholder that stands in for "no such function" - time_t modification_time; // st_mtime - time_t load_time; // when function was loaded - - - autoload_function_t() : is_placeholder(false), modification_time(0), load_time(0) { } + autoload_function_t(const wcstring &key) : lru_node_t(key), is_placeholder(false) { bzero(&access, sizeof access); } + virtual ~autoload_function_t(); + virtual void evicted(void); + + file_access_attempt_t access; /** The last access attempt */ + bool is_placeholder; /** Whether we are a placeholder that stands in for "no such function" */ }; + struct builtin_script_t; /** @@ -28,6 +30,9 @@ struct builtin_script_t; */ class autoload_t { private: + /** Access tracker */ + lru_cache_t function_cache; + /** The environment variable name */ const wcstring env_var_name; @@ -54,10 +59,6 @@ private: return is_loading_set.find(name) != is_loading_set.end(); } - autoload_function_t *create_function_with_name(const wcstring &name) { - return &autoload_functions[name]; - } - bool remove_function_with_name(const wcstring &name) { return autoload_functions.erase(name) > 0; } @@ -78,26 +79,9 @@ private: size_t function_count(void) const { return autoload_functions.size(); } - - /** Returns the name of the function that was least recently loaded, if it was loaded before cutoff_access. Return NULL if no function qualifies. */ - const wcstring *get_lru_function_name(const wcstring &skip, time_t cutoff_access) const; - + int load_internal( const wcstring &cmd, void (*on_load)(const wchar_t *cmd), int reload, const wcstring_list_t &path_list ); - /** - - Unload all autoloaded items that have expired, that where loaded in - the specified path. - - \param skip unloading the the specified file - \param on_load the callback function to call for every unloaded file - - */ - void autounload( const wchar_t *skip, - void (*on_load)(const wchar_t *cmd) ); - - void apply_handler_to_nonplaceholder_function_names(void (*handler)(const wchar_t *cmd)) const; - public: /** Create an autoload_t for the given environment variable name */