diff options
author | ridiculousfish <corydoras@ridiculousfish.com> | 2012-01-25 00:36:55 -0800 |
---|---|---|
committer | ridiculousfish <corydoras@ridiculousfish.com> | 2012-01-25 00:36:55 -0800 |
commit | e94e1cc72fa6eb95549493788e72219922785733 (patch) | |
tree | 1cac0e597ba57b933f08dd4298125268f14d85b5 /autoload.cpp | |
parent | 4dfe36feb16923b454ea2e44cc512adaff879127 (diff) |
New file autoload.h that will ultimately handle autoloading completions and functions
Diffstat (limited to 'autoload.cpp')
-rw-r--r-- | autoload.cpp | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/autoload.cpp b/autoload.cpp new file mode 100644 index 00000000..1f47edbd --- /dev/null +++ b/autoload.cpp @@ -0,0 +1,164 @@ +/** \file autoload.cpp + +The classes responsible for autoloading functions and completions. +*/ + +#include "config.h" +#include "autoload.h" +#include "wutil.h" +#include <assert.h> + +const size_t kLRULimit = 256; + +/** A node in our LRU map */ +class file_access_node_t { +public: + file_access_node_t *prev, *next; + const wcstring path; + file_access_attempt_t attempt; + + file_access_node_t(const wcstring &pathVar) : path(pathVar) { } + + bool operator<(const file_access_node_t &other) const { return path < other.path; } +}; + +/* By default, things are stale after 60 seconds */ +const time_t kFishDefaultStalenessInterval = 60; + +access_tracker_t::access_tracker_t(time_t stale, int the_mode) : + node_count(0), + mouth(NULL), + stale_interval(stale), + mode(the_mode) +{ + VOMIT_ON_FAILURE(pthread_mutex_init(&lock, NULL)); +} + +access_tracker_t::~access_tracker_t() { + VOMIT_ON_FAILURE(pthread_mutex_destroy(&lock)); +} + +void access_tracker_t::vacuum_one_node(void) { + /* Removes the least recently used access */ + assert(mouth && mouth->prev != mouth); + + /* Remove us from the linked list */ + file_access_node_t *condemned_node = mouth->prev; + condemned_node->prev->next = condemned_node->next; + condemned_node->next->prev = condemned_node->prev; + + /* Remove us from the set */ + access_set.erase(condemned_node); + + /* Deleted */ + node_count--; + delete condemned_node; +} + +void access_tracker_t::promote_node(file_access_node_t *node) { + /* Promotes a node to the mouth, unless we're already there... */ + if (node == mouth) return; + + /* First unhook us */ + node->prev->next = node->next; + node->next->prev = node->prev; + + /* Now become the mouth */ + node->next = mouth; + node->prev = mouth->prev; + mouth = node; +} + +/* Return the node referenced by the given string, or NULL */ +file_access_node_t *access_tracker_t::while_locked_find_node(const wcstring &path) const { + file_access_node_t key(path); + access_set_t::iterator iter = access_set.find(&key); + return iter != access_set.end() ? *iter : NULL; +} + +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; +} + +bool access_tracker_t::access_file_only_cached(const wcstring &path, file_access_attempt_t &attempt) { + bool result = false; + + /* Lock our lock */ + scoped_lock locker(lock); + + /* Search for the node */ + file_access_node_t *node = while_locked_find_node(path); + if (node) { + promote_node(node); + attempt = node->attempt; + attempt.stale = (time(NULL) - node->attempt.last_checked > this->stale_interval); + result = true; + } + return result; +} + +file_access_attempt_t access_tracker_t::access_file(const wcstring &path) { + file_access_attempt_t result; + + /* Try just using our cache */ + if (access_file_only_cached(path, result) && ! result.stale) { + return result; + } + + /* Really access the file. Note we are not yet locked, and don't want to be, since this may be slow. */ + result = attempt_access(path); + + /* Take the lock so we can insert. */ + scoped_lock locker(lock); + + /* Maybe we had it cached and it was stale, or maybe someone else may have put it in the cache while we were unlocked */ + file_access_node_t *node = while_locked_find_node(path); + + if (node != NULL) { + /* Someone else put it in. Promote and overwrite it. */ + node->attempt = result; + promote_node(node); + } else { + /* We did not find this node. Add it. */ + file_access_node_t *node = new file_access_node_t(path); + node->attempt = result; + + /* Insert into the set */ + access_set.insert(node); + + /* Insert it into the linked list */ + if (mouth == NULL) { + /* One element circularly linked list! */ + mouth = node->next = node->prev = node; + } else { + /* Normal circularly linked list operation */ + node->next = mouth; + node->prev = mouth->prev; + mouth = node; + } + + /* We have one more node now */ + ++node_count; + + /* Clean up if we're over our limit */ + while (node_count > kLRULimit) + vacuum_one_node(); + } + return result; +} |