From e94e1cc72fa6eb95549493788e72219922785733 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 25 Jan 2012 00:36:55 -0800 Subject: [PATCH] New file autoload.h that will ultimately handle autoloading completions and functions --- FishsFish.xcodeproj/project.pbxproj | 4 + Makefile.in | 18 +-- autoload.cpp | 164 ++++++++++++++++++++++++++++ autoload.h | 75 +++++++++++++ 4 files changed, 252 insertions(+), 9 deletions(-) create mode 100644 autoload.cpp create mode 100644 autoload.h diff --git a/FishsFish.xcodeproj/project.pbxproj b/FishsFish.xcodeproj/project.pbxproj index 16cd92eac..84aecc703 100644 --- a/FishsFish.xcodeproj/project.pbxproj +++ b/FishsFish.xcodeproj/project.pbxproj @@ -110,12 +110,16 @@ D0A0856513B3ACEE0099B651 /* xdgmimeint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xdgmimeint.cpp; sourceTree = ""; }; D0A0856613B3ACEE0099B651 /* xdgmimemagic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xdgmimemagic.cpp; sourceTree = ""; }; D0A0856713B3ACEE0099B651 /* xdgmimeparent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xdgmimeparent.cpp; sourceTree = ""; }; + D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = autoload.cpp; sourceTree = ""; }; + D0C6FCCB14CFA4B7004CE8AD /* autoload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = autoload.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ D0A084F013B3AC130099B651 = { isa = PBXGroup; children = ( + D0C6FCCB14CFA4B7004CE8AD /* autoload.h */, + D0C6FCC914CFA4B0004CE8AD /* autoload.cpp */, D0A0850313B3ACEE0099B651 /* builtin.h */, D0A0853013B3ACEE0099B651 /* builtin_commandline.cpp */, D0A0853113B3ACEE0099B651 /* builtin_complete.cpp */, diff --git a/Makefile.in b/Makefile.in index 7f57ca36f..9b0fa4dda 100644 --- a/Makefile.in +++ b/Makefile.in @@ -95,7 +95,7 @@ FISH_OBJS := function.o builtin.o complete.o env.o exec.o expand.o \ highlight.o history.o kill.o parser.o proc.o reader.o sanity.o \ tokenizer.o wildcard.o wgetopt.o wutil.o input.o output.o intern.o \ env_universal.o env_universal_common.o input_common.o event.o \ - signal.o io.o parse_util.o common.o screen.o path.o \ + signal.o io.o parse_util.o common.o screen.o path.o autoload.o \ parser_keywords.o iothread.o builtin_scripts.o FISH_INDENT_OBJS := fish_indent.o print_help.o common.o \ @@ -905,8 +905,9 @@ clean: # DO NOT DELETE THIS LINE -- make depend depends on it. +autoload.o: autoload.h common.h util.h builtin.o: config.h fallback.h util.h wutil.h builtin.h io.h function.h -builtin.o: complete.h proc.h parser.h event.h common.h reader.h env.h +builtin.o: common.h complete.h proc.h parser.h event.h reader.h env.h builtin.o: wgetopt.h sanity.h tokenizer.h wildcard.h input_common.h input.h builtin.o: intern.h signal.h exec.h highlight.h halloc.h halloc_util.h builtin.o: parse_util.h parser_keywords.h expand.h path.h builtin_set.cpp @@ -921,7 +922,6 @@ builtin_complete.o: io.h common.h complete.h wgetopt.h parser.h proc.h builtin_complete.o: event.h reader.h builtin_jobs.o: config.h fallback.h util.h wutil.h builtin.h io.h proc.h builtin_jobs.o: parser.h event.h common.h wgetopt.h -builtin_scripts.o: builtin_scripts.h builtin_set.o: config.h signal.h fallback.h util.h wutil.h builtin.h io.h builtin_set.o: env.h common.h expand.h wgetopt.h proc.h parser.h event.h builtin_ulimit.o: config.h fallback.h util.h builtin.h io.h common.h @@ -942,8 +942,8 @@ env_universal.o: config.h signal.h fallback.h util.h common.h wutil.h env_universal.o: env_universal_common.h env_universal.h env_universal_common.o: config.h signal.h fallback.h util.h common.h wutil.h env_universal_common.o: env_universal_common.h -event.o: config.h signal.h fallback.h util.h wutil.h function.h proc.h io.h -event.o: parser.h event.h common.h halloc_util.h +event.o: config.h signal.h fallback.h util.h wutil.h function.h common.h +event.o: proc.h io.h parser.h event.h halloc_util.h exec.o: config.h signal.h fallback.h util.h common.h wutil.h proc.h io.h exec.o: exec.h parser.h event.h builtin.h function.h env.h wildcard.h exec.o: sanity.h expand.h halloc.h halloc_util.h parse_util.h @@ -966,8 +966,8 @@ fish_tests.o: parser.h event.h tokenizer.h output.h exec.h path.h halloc.h fish_tests.o: halloc_util.h fishd.o: config.h signal.h fallback.h util.h common.h wutil.h fishd.o: env_universal_common.h halloc.h halloc_util.h path.h print_help.h -function.o: config.h signal.h wutil.h fallback.h util.h function.h proc.h -function.o: io.h parser.h event.h common.h intern.h reader.h parse_util.h +function.o: config.h signal.h wutil.h fallback.h util.h function.h common.h +function.o: proc.h io.h parser.h event.h intern.h reader.h parse_util.h function.o: parser_keywords.h env.h expand.h halloc.h halloc_util.h halloc.o: config.h fallback.h util.h common.h halloc.h halloc_util.o: config.h fallback.h util.h common.h halloc.h @@ -984,7 +984,7 @@ input_common.o: config.h fallback.h util.h common.h wutil.h input_common.h input_common.o: env_universal.h env_universal_common.h iothread.h intern.o: config.h fallback.h util.h wutil.h common.h intern.h io.o: config.h fallback.h util.h wutil.h exec.h proc.h io.h common.h halloc.h -iothread.o: iothread.h +iothread.o: iothread.h signal.h key_reader.o: config.h fallback.h input_common.h kill.o: config.h signal.h fallback.h util.h wutil.h kill.h proc.h io.h kill.o: sanity.h common.h env.h exec.h halloc.h path.h @@ -993,7 +993,7 @@ output.o: config.h signal.h fallback.h util.h wutil.h expand.h common.h output.o: output.h halloc_util.h highlight.h env.h parse_util.o: config.h fallback.h util.h wutil.h common.h tokenizer.h parse_util.o: parse_util.h expand.h intern.h exec.h proc.h io.h env.h -parse_util.o: signal.h wildcard.h halloc_util.h builtin_scripts.h +parse_util.o: signal.h wildcard.h halloc_util.h parser.o: config.h signal.h fallback.h util.h common.h wutil.h proc.h io.h parser.o: parser.h event.h parser_keywords.h tokenizer.h exec.h wildcard.h parser.o: function.h builtin.h env.h expand.h reader.h sanity.h diff --git a/autoload.cpp b/autoload.cpp new file mode 100644 index 000000000..1f47edbd1 --- /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 + +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; +} diff --git a/autoload.h b/autoload.h new file mode 100644 index 000000000..dc3445e42 --- /dev/null +++ b/autoload.h @@ -0,0 +1,75 @@ +/** \file autoload.h + + The classes responsible for autoloading functions and completions. +*/ + +#ifndef FISH_PARSE_UTIL_H +#define FISH_PARSE_UTIL_H + +#include +#include +#include +#include +#include "common.h" + +extern const time_t kFishDefaultStalenessInterval; + +/** A class responsible for recording an attempt to access a file. */ +class file_access_attempt_t { +public: + 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 */ + bool stale; /** Whether the access attempt is stale */ + int error; /** If we could not access the file, the error code */ +}; + +class file_access_node_t; + +/** A predicate to compare dereferenced pointers */ +struct dereference_less_t { + template + bool operator()(ptr_t p1, ptr_t p2) const { return *p1 < *p2; } +}; + + +/** A class responsible for tracking accesses to files, including auto-expiration. */ +class access_tracker_t { + private: + + file_access_node_t * while_locked_find_node(const wcstring &str) const; + void vacuum_one_node(void); + void promote_node(file_access_node_t *); + + file_access_attempt_t attempt_access(const wcstring& path) const; + + unsigned int node_count; + typedef std::set access_set_t; + access_set_t access_set; + file_access_node_t *mouth; + + /* Lock for thread safety */ + pthread_mutex_t lock; + + /** How long until a file access attempt is considered stale. */ + const time_t stale_interval; + + /** Mode for waccess calls */ + const int mode; + + public: + + /** Constructor, that takes a staleness interval */ + access_tracker_t(time_t stale, int the_mode); + + /** Destructor */ + ~access_tracker_t(); + + /** Attempt to access the given file, if the last cached access is stale. Caches and returns the access attempt. */ + file_access_attempt_t access_file(const wcstring &path); + + /** Returns whether there is a cached access (even if stale), without touching the disk; if the result is true, return by reference that access attempt. */ + bool access_file_only_cached(const wcstring &path, file_access_attempt_t &attempt); +}; + +#endif