Move autoloading from a map of path names to a real object autoload_t.

Moved the various things we can autoload into static objects.
Next step is to make them thread safe.
This commit is contained in:
ridiculousfish 2012-01-23 11:42:41 -08:00
parent 8403aae928
commit 6e8637fbc9
4 changed files with 234 additions and 295 deletions

View file

@ -45,7 +45,7 @@
#include "halloc_util.h"
#include "wutil.h"
#include "path.h"
#include "builtin_scripts.h"
/*
Completion description strings, mostly for different types of files, such as sockets, block devices, etc.
@ -181,6 +181,8 @@ static complete_entry_t *first_entry=0;
*/
static hash_table_t *condition_cache=0;
static autoload_t completion_autoloader(L"fish_complete_path", internal_completion_scripts, sizeof internal_completion_scripts / sizeof *internal_completion_scripts );
static void complete_free_entry( complete_entry_t *c );
/**
@ -227,7 +229,7 @@ static void complete_destroy()
}
first_entry = 0;
parse_util_load_reset( L"fish_complete_path", 0 );
completion_autoloader.reset( 0 );
}
@ -1360,10 +1362,7 @@ static void complete_load_handler( const wchar_t *cmd )
void complete_load( const wchar_t *name, int reload )
{
CHECK( name, );
parse_util_load( name,
L"fish_complete_path",
&complete_load_handler,
reload );
completion_autoloader.load( name, &complete_load_handler, reload );
}
/**

View file

@ -38,6 +38,7 @@
#include "expand.h"
#include "halloc.h"
#include "halloc_util.h"
#include "builtin_scripts.h"
class function_internal_info_t
{
@ -75,6 +76,9 @@ public:
bool shadows;
};
/* Autoloader for functions */
static autoload_t function_autoloader(L"fish_function_path", internal_function_scripts, sizeof internal_function_scripts / sizeof *internal_function_scripts);
/**
Table containing all functions
*/
@ -136,10 +140,7 @@ static int load( const wchar_t *name )
UNLOCK_FUNCTIONS();
is_autoload = 1;
res = parse_util_load( name,
L"fish_function_path",
&function_remove,
1 );
res = function_autoloader.load( name, &function_remove, 1 );
is_autoload = was_autoload;
return res;
}
@ -291,7 +292,7 @@ void function_remove( const wchar_t *name )
*/
if( !is_autoload )
{
parse_util_unload( name, L"fish_function_path", 0 );
function_autoloader.unload( name, 0 );
}
UNLOCK_FUNCTIONS();
}

View file

@ -53,117 +53,43 @@
*/
#define AUTOLOAD_MIN_AGE 60
struct autoload_function_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) { }
};
/**
A structure representing the autoload state for a specific variable, e.g. fish_complete_path
*/
struct autoload_t
{
private:
typedef std::map<wcstring, autoload_function_t> autoload_functions_map_t;
autoload_functions_map_t autoload_functions;
public:
/* 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;
/**
A table containing all the files that are currently being
loaded. This is here to help prevent recursion.
*/
std::set<wcstring> is_loading_set;
bool is_loading(const wcstring &name) const {
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;
}
autoload_function_t *get_function_with_name(const wcstring &name)
{
autoload_function_t *result = NULL;
autoload_functions_map_t::iterator iter = autoload_functions.find(name);
if (iter != autoload_functions.end())
result = &iter->second;
return result;
}
void remove_all_functions(void)
{
autoload_functions.clear();
}
size_t function_count(void) const
{
return autoload_functions.size();
}
/* 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 *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;
}
/* 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;
}
return resultName;
}
void 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());
}
/**
A string containg the path used to find any files to load. If
this differs from the current environment variable, the
autoloader needs to drop all loaded files and reload them.
*/
wcstring old_path;
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());
}
};
/**
Set of files which have been autoloaded
*/
typedef std::map<wcstring, autoload_t> autoload_map_t;
static autoload_map_t all_loaded_map;
int parse_util_lineno( const wchar_t *str, int len )
{
@ -712,43 +638,32 @@ void parse_util_token_extent( const wchar_t *buff,
}
void parse_util_load_reset( const wchar_t *path_var_name,
void (*on_load)(const wchar_t *cmd) )
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),
builtin_script_count(script_count)
{
CHECK( path_var_name, );
autoload_map_t::iterator condemned = all_loaded_map.find(path_var_name);
if (condemned != all_loaded_map.end())
{
autoload_t &loaded = condemned->second;
if (on_load) {
/* Call the on_load handler on each real function name. */
loaded.apply_handler_to_nonplaceholder_function_names(on_load);
}
/* Empty the functino set */
loaded.remove_all_functions();
all_loaded_map.erase(condemned);
}
}
int parse_util_unload( const wchar_t *cmd,
const wchar_t *path_var_name,
void (*on_load)(const wchar_t *cmd) )
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();
}
}
int autoload_t::unload( const wchar_t *cmd, void (*on_load)(const wchar_t *cmd) )
{
int result = 0;
CHECK( path_var_name, 0 );
CHECK( cmd, 0 );
autoload_map_t::iterator iter = all_loaded_map.find(path_var_name);
if (iter == all_loaded_map.end())
{
return 0;
}
autoload_t &loaded = iter->second;
if (loaded.remove_function_with_name(cmd))
if (this->remove_function_with_name(cmd))
{
if (on_load)
on_load(cmd);
@ -759,58 +674,36 @@ int parse_util_unload( const wchar_t *cmd,
/**
Unload all autoloaded items that have expired, that where loaded in
Unload one autoloaded item that has expired, that where loaded in
the specified path.
\param path_var_name The variable containing the path to autoload in
\param skip unloading the the specified file
\param on_load the callback function to call for every unloaded file
*/
static void parse_util_autounload( const wchar_t *path_var_name,
const wchar_t *skip,
void (*on_load)(const wchar_t *cmd) )
void autoload_t::autounload( const wchar_t *skip,
void (*on_load)(const wchar_t *cmd) )
{
autoload_map_t::iterator iter = all_loaded_map.find(path_var_name);
if (iter == all_loaded_map.end())
if( this->function_count() >= AUTOLOAD_MAX )
{
return;
}
autoload_t &loaded = iter->second;
if( loaded.function_count() >= AUTOLOAD_MAX )
{
time_t cutoff_access = time(0) - AUTOLOAD_MIN_AGE;
const wcstring *lru = loaded.get_lru_function_name(skip, cutoff_access);
const wcstring *lru = this->get_lru_function_name(skip, cutoff_access);
if (lru)
parse_util_unload( lru->c_str(), path_var_name, on_load );
unload( lru->c_str(), on_load );
}
}
static int parse_util_load_internal( const wcstring &cmd,
const struct builtin_script_t *builtin_scripts,
size_t builtin_script_count,
void (*on_load)(const wchar_t *cmd),
int reload,
autoload_t &loaded,
const std::vector<wcstring> &path_list );
int parse_util_load( const wcstring &cmd,
const wcstring &path_var_name,
void (*on_load)(const wchar_t *cmd),
int reload )
int autoload_t::load( const wcstring &cmd,
void (*on_load)(const wchar_t *cmd),
int reload )
{
int res;
int c, c2;
autoload_t *loaded;
CHECK_BLOCK( 0 );
// debug( 0, L"Autoload %ls in %ls", cmd, path_var_name );
parse_util_autounload( path_var_name.c_str(), cmd.c_str(), on_load );
const env_var_t path_var = env_get_string( path_var_name.c_str() );
autounload( cmd.c_str(), on_load );
const env_var_t path_var = env_get_string( env_var_name.c_str() );
/*
Do we know where to look?
@ -819,84 +712,45 @@ int parse_util_load( const wcstring &cmd,
{
return 0;
}
autoload_map_t::iterator iter = all_loaded_map.find(path_var_name);
if (iter != all_loaded_map.end())
/*
Check if the lookup path has changed. If so, drop all loaded
files.
*/
if( path_var != this->path )
{
loaded = &iter->second;
/*
Check if the lookup path has changed. If so, drop all loaded
files and start from scratch.
*/
if( path_var != loaded->old_path )
{
parse_util_load_reset( path_var_name.c_str(), on_load);
reload = parse_util_load( cmd, path_var_name, on_load, reload );
return reload;
}
this->path = path_var;
reset( on_load);
}
/**
Warn and fail on infinite recursion
*/
if (this->is_loading(cmd))
{
debug( 0,
_( L"Could not autoload item '%ls', it is already being autoloaded. "
L"This is a circular dependency in the autoloading scripts, please remove it."),
cmd.c_str() );
return 1;
}
/**
Warn and fail on infinite recursion
*/
if (loaded->is_loading(cmd))
{
debug( 0,
_( L"Could not autoload item '%ls', it is already being autoloaded. "
L"This is a circular dependency in the autoloading scripts, please remove it."),
cmd.c_str() );
return 1;
}
}
else
{
/*
We have never tried to autoload using this path name before,
set up initial data
*/
// debug( 0, L"Create brand new autoload_t for %ls->%ls", path_var_name, path_var.c_str() );
loaded = &all_loaded_map[path_var_name];
loaded->old_path = wcsdup(path_var.c_str());
}
std::vector<wcstring> path_list;
tokenize_variable_array2( path_var, path_list );
c = path_list.size();
loaded->is_loading_set.insert(cmd);
/* Figure out which builtin-scripts array to search (if any) */
const builtin_script_t *builtins = NULL;
size_t builtin_count = 0;
if (path_var_name == L"fish_function_path") {
builtins = internal_function_scripts;
builtin_count = sizeof internal_function_scripts / sizeof *internal_function_scripts;
} else if (path_var_name == L"fish_complete_path") {
builtins = internal_completion_scripts;
builtin_count = sizeof internal_completion_scripts / sizeof *internal_completion_scripts;
}
is_loading_set.insert(cmd);
/*
Do the actual work in the internal helper function
*/
res = parse_util_load_internal( cmd, builtins, builtin_count, on_load, reload, *loaded, path_list );
autoload_map_t::iterator iter2 = all_loaded_map.find(path_var_name);
if (iter2 != all_loaded_map.end()) {
autoload_t *loaded2 = &iter2->second;
if( loaded2 == loaded )
{
/**
Cleanup
*/
std::set<wcstring>::iterator condemned = loaded->is_loading_set.find(cmd);
assert(condemned != loaded->is_loading_set.end());
loaded->is_loading_set.erase(condemned);
}
}
res = this->load_internal( cmd, on_load, reload, path_list );
int erased = is_loading_set.erase(cmd);
assert(erased);
c2 = path_list.size();
@ -920,20 +774,17 @@ static bool script_name_precedes_script_name(const builtin_script_t &script1, co
the code, and the caller can take care of various cleanup work.
*/
static int parse_util_load_internal( const wcstring &cmd,
const builtin_script_t *builtin_scripts,
size_t builtin_script_count,
void (*on_load)(const wchar_t *cmd),
int reload,
autoload_t &loaded,
const std::vector<wcstring> &path_list )
int autoload_t::load_internal( const wcstring &cmd,
void (*on_load)(const wchar_t *cmd),
int reload,
const wcstring_list_t &path_list )
{
size_t i;
int reloaded = 0;
/* Get the function */
autoload_function_t * func = loaded.get_function_with_name(cmd);
autoload_function_t * func = this->get_function_with_name(cmd);
/* Return if already loaded and we are skipping reloading */
if( !reload && func )
@ -988,7 +839,7 @@ static int parse_util_load_internal( const wcstring &cmd,
has_script_source = true;
if( !func )
func = loaded.create_function_with_name(cmd);
func = this->create_function_with_name(cmd);
func->modification_time = buf.st_mtime;
func->load_time = time(NULL);
@ -1018,7 +869,7 @@ static int parse_util_load_internal( const wcstring &cmd,
*/
if( !func )
{
func = loaded.create_function_with_name(cmd);
func = this->create_function_with_name(cmd);
func->load_time = time(NULL);
func->is_placeholder = true;
}

View file

@ -8,6 +8,136 @@
#define FISH_PARSE_UTIL_H
#include <wchar.h>
#include <map>
#include <set>
struct autoload_function_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) { }
};
struct builtin_script_t;
/**
A class that represents a path from which we can autoload, and the autoloaded contents.
*/
class autoload_t {
private:
/** The environment variable name */
const wcstring env_var_name;
/** Builtin script array */
const struct builtin_script_t *const builtin_scripts;
/** Builtin script count */
const size_t builtin_script_count;
/** The path from which we most recently autoloaded */
wcstring path;
/** The map from function name to the function. */
typedef std::map<wcstring, autoload_function_t> autoload_functions_map_t;
autoload_functions_map_t autoload_functions;
/**
A table containing all the files that are currently being
loaded. This is here to help prevent recursion.
*/
std::set<wcstring> is_loading_set;
bool is_loading(const wcstring &name) const {
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;
}
autoload_function_t *get_function_with_name(const wcstring &name)
{
autoload_function_t *result = NULL;
autoload_functions_map_t::iterator iter = autoload_functions.find(name);
if (iter != autoload_functions.end())
result = &iter->second;
return result;
}
void remove_all_functions(void) {
autoload_functions.clear();
}
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 */
autoload_t(const wcstring &env_var_name_var, const builtin_script_t *scripts, size_t script_count );
/**
Autoload the specified file, if it exists in the specified path. Do
not load it multiple times unless it's timestamp changes or
parse_util_unload is called.
Autoloading one file may unload another.
\param cmd the filename to search for. The suffix '.fish' is always added to this name
\param on_unload a callback function to run if a suitable file is found, which has not already been run. unload will also be called for old files which are unloaded.
\param reload wheter to recheck file timestamps on already loaded files
*/
int load( const wcstring &cmd,
void (*on_unload)(const wchar_t *cmd),
int reload );
/**
Reset the loader for the specified path variable. This will cause
all information on loaded files in the specified directory to be
reset.
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
*/
void reset( void (*on_unload)(const wchar_t *cmd) );
/**
Tell the autoloader that the specified file, in the specified path,
is no longer loaded.
\param cmd the filename to search for. The suffix '.fish' is always added to this name
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
\return non-zero if the file was removed, zero if the file had not yet been loaded
*/
int unload( const wchar_t *cmd,
void (*on_unload)(const wchar_t *cmd) );
};
/**
Find the beginning and end of the first subshell in the specified string.
@ -109,48 +239,6 @@ int parse_util_get_offset_from_line( wchar_t *buff, int line );
*/
int parse_util_get_offset( wchar_t *buff, int line, int line_offset );
/**
Autoload the specified file, if it exists in the specified path. Do
not load it multiple times unless it's timestamp changes or
parse_util_unload is called.
Autoloading one file may unload another.
\param cmd the filename to search for. The suffix '.fish' is always added to this name
\param path_var_name the environment variable giving the search path
\param on_unload a callback function to run if a suitable file is found, which has not already been run. unload will also be called for old files which are unloaded.
\param reload wheter to recheck file timestamps on already loaded files
*/
int parse_util_load( const wcstring &cmd,
const wcstring &path_var_name,
void (*on_unload)(const wchar_t *cmd),
int reload );
/**
Reset the loader for the specified path variable. This will cause
all information on loaded files in the specified directory to be
reset.
\param path_var_name the environment variable giving the search path
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
*/
void parse_util_load_reset( const wchar_t *path_var_name,
void (*on_unload)(const wchar_t *cmd) );
/**
Tell the autoloader that the specified file, in the specified path,
is no longer loaded.
\param cmd the filename to search for. The suffix '.fish' is always added to this name
\param path_var_name the environment variable giving the search path
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
\return non-zero if the file was removed, zero if the file had not yet been loaded
*/
int parse_util_unload( const wchar_t *cmd,
const wchar_t *path_var_name,
void (*on_unload)(const wchar_t *cmd) );
/**
Set the argv environment variable to the specified null-terminated
array of strings.