-
-
Save xaviershay/58dbd6e72c1a1f47a415 to your computer and use it in GitHub Desktop.
Patches to improve ruby require times - see descriptions at https://gist.github.com/35060fbcefb25cf1a456
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/load.c b/load.c | |
index 547c115..1a02804 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -17,6 +17,238 @@ VALUE ruby_dln_librefs; | |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) | |
#endif | |
+static int rb_feature_exists(VALUE); | |
+ | |
+static VALUE rb_locate_file(VALUE); | |
+static VALUE rb_locate_file_relative(VALUE); | |
+static VALUE rb_locate_file_absolute(VALUE); | |
+static VALUE rb_locate_file_in_load_path(VALUE); | |
+static VALUE rb_locate_rb_file_in_load_path(VALUE, VALUE, VALUE); | |
+static VALUE rb_locate_file_with_extension(VALUE, VALUE); | |
+static VALUE rb_locate_file_with_extensions(VALUE, VALUE); | |
+static int rb_path_is_absolute(VALUE); | |
+static int rb_path_is_relative(VALUE); | |
+static VALUE rb_file_extension(VALUE); | |
+ | |
+VALUE rb_get_expanded_load_path(); | |
+ | |
+const char *available_extensions[] = { | |
+ ".rb", | |
+ DLEXT, | |
+#ifdef DLEXT2 | |
+ DLEXT2, | |
+#endif | |
+ "" | |
+}; | |
+ | |
+#ifdef DLEXT2 | |
+VALUE available_ext_rb_str[4]; | |
+#else | |
+VALUE available_ext_rb_str[3]; | |
+#endif | |
+ | |
+const char *alternate_dl_extensions[] = { | |
+ DLEXT, | |
+#ifdef DLEXT2 | |
+ DLEXT2 | |
+#endif | |
+}; | |
+ | |
+#define CHAR_ARRAY_LEN(array) (sizeof(array) / sizeof(char*)) | |
+#define VALUE_ARRAY_LEN(array) (sizeof(array) / sizeof(VALUE)) | |
+ | |
+static VALUE | |
+rb_locate_file(VALUE filename) | |
+{ | |
+ VALUE full_path = Qnil; | |
+ | |
+ if (rb_path_is_relative(filename)) { | |
+ full_path = rb_locate_file_relative(filename); | |
+ } else if (rb_path_is_absolute(filename)) { | |
+ full_path = rb_locate_file_absolute(filename); | |
+ } else { | |
+ full_path = rb_locate_file_in_load_path(filename); | |
+ } | |
+ | |
+ return full_path; | |
+} | |
+ | |
+static int | |
+rb_path_is_relative(VALUE path) | |
+{ | |
+ const char * path_ptr = RSTRING_PTR(path); | |
+ const char * current_directory = "./"; | |
+ const char * parent_directory = "../"; | |
+ | |
+ return ( | |
+ strncmp(current_directory, path_ptr, 2) == 0 || | |
+ strncmp(parent_directory, path_ptr, 3) == 0 | |
+ ); | |
+} | |
+ | |
+static VALUE | |
+rb_locate_file_relative(VALUE fname) | |
+{ | |
+ VALUE path = rb_file_expand_path(fname, Qnil); | |
+ return rb_locate_file_with_extensions(path, rb_file_extension(path)); | |
+} | |
+ | |
+static int | |
+rb_path_is_absolute(VALUE path) | |
+{ | |
+ // Delegate to file.c | |
+ return rb_is_absolute_path(RSTRING_PTR(path)); | |
+} | |
+ | |
+static VALUE | |
+rb_locate_file_absolute(VALUE fname) | |
+{ | |
+ return rb_locate_file_with_extensions(fname, rb_file_extension(fname)); | |
+} | |
+ | |
+static VALUE | |
+rb_locate_file_in_load_path(VALUE path) | |
+{ | |
+ long i; | |
+ VALUE load_path = rb_get_expanded_load_path(); | |
+ VALUE expanded_file_name = Qnil; | |
+ VALUE base_file_name = Qnil; | |
+ VALUE sep = rb_str_new2("/"); | |
+ VALUE base_extension = rb_file_extension(path); | |
+ | |
+ if (RSTRING_LEN(base_extension) == 0) { | |
+ /* Do an initial loop through the load path only looking for .rb files. | |
+ * This is the most common case, so optimize for it. If not found, fall | |
+ * back so searching all extensions. | |
+ */ | |
+ expanded_file_name = rb_locate_rb_file_in_load_path(path, load_path, sep); | |
+ | |
+ if (expanded_file_name != Qnil) { | |
+ return expanded_file_name; | |
+ } | |
+ } | |
+ | |
+ for (i = 0; i < RARRAY_LEN(load_path); ++i) { | |
+ VALUE directory = RARRAY_PTR(load_path)[i]; | |
+ | |
+ base_file_name = rb_str_plus(directory, sep); | |
+ base_file_name = rb_str_concat(base_file_name, path); | |
+ | |
+ /* The .rb extension will be checked again in this call, which is redundant | |
+ * since it was checked in the loop above. This hasn't been optimized to | |
+ * keep the code cleaner. | |
+ */ | |
+ expanded_file_name = rb_locate_file_with_extensions(base_file_name, base_extension); | |
+ | |
+ if (expanded_file_name != Qnil) { | |
+ return expanded_file_name; | |
+ } | |
+ } | |
+ return Qnil; | |
+} | |
+ | |
+/* This function is only used as an optimization in rb_locate_file_in_load_path */ | |
+static VALUE | |
+rb_locate_rb_file_in_load_path(VALUE path, VALUE load_path, VALUE sep) | |
+{ | |
+ long i; | |
+ VALUE base_file_name; | |
+ VALUE expanded_file_name; | |
+ VALUE rb_ext = rb_str_new2(".rb"); | |
+ | |
+ for (i = 0; i < RARRAY_LEN(load_path); ++i) { | |
+ VALUE directory = RARRAY_PTR(load_path)[i]; | |
+ | |
+ base_file_name = rb_str_plus(directory, sep); | |
+ base_file_name = rb_str_concat(base_file_name, path); | |
+ | |
+ expanded_file_name = rb_locate_file_with_extension(base_file_name, rb_ext); | |
+ | |
+ if (expanded_file_name != Qnil) { | |
+ return expanded_file_name; | |
+ } | |
+ } | |
+ return Qnil; | |
+} | |
+ | |
+static VALUE | |
+rb_locate_file_with_extension(VALUE base_file_name, VALUE extension) { | |
+ VALUE file_name_with_extension = rb_str_plus( | |
+ base_file_name, | |
+ extension); | |
+ | |
+ if (rb_feature_exists(file_name_with_extension)) { | |
+ return file_name_with_extension; | |
+ } else { | |
+ return Qnil; | |
+ } | |
+} | |
+ | |
+static VALUE | |
+rb_locate_file_with_extensions(VALUE base_file_name, VALUE extension) { | |
+ unsigned int j; | |
+ VALUE file_name_with_extension; | |
+ VALUE directory, basename; | |
+ | |
+ if (RSTRING_LEN(extension) == 0) { | |
+ for (j = 0; j < VALUE_ARRAY_LEN(available_ext_rb_str); ++j) { | |
+ file_name_with_extension = rb_str_plus( | |
+ base_file_name, | |
+ available_ext_rb_str[j]); | |
+ | |
+ if (rb_feature_exists(file_name_with_extension)) { | |
+ return file_name_with_extension; | |
+ } | |
+ } | |
+ } else { | |
+ if (rb_feature_exists(base_file_name)) { | |
+ return base_file_name; | |
+ } else { | |
+ for (j = 0; j < VALUE_ARRAY_LEN(available_ext_rb_str); ++j) { | |
+ // Also try loading 'dot.dot.bundle' for 'dot.dot' | |
+ // Also try loading 'test.1.rb' for 'test.1' | |
+ file_name_with_extension = rb_str_plus( | |
+ base_file_name, | |
+ available_ext_rb_str[j]); | |
+ | |
+ if (rb_feature_exists(file_name_with_extension)) { | |
+ return file_name_with_extension; | |
+ } | |
+ } | |
+ | |
+ for (j = 0; j < CHAR_ARRAY_LEN(alternate_dl_extensions); ++j) { | |
+ // Try loading the native DLEXT version of this platform. | |
+ // This allows 'pathname.so' to require 'pathname.bundle' on OSX | |
+ directory = rb_file_dirname(base_file_name); | |
+ basename = rb_funcall(rb_cFile, rb_intern("basename"), 2, | |
+ base_file_name, extension); | |
+ basename = rb_str_cat2(basename, alternate_dl_extensions[j]); | |
+ | |
+ file_name_with_extension = rb_funcall(rb_cFile, rb_intern("join"), 2, | |
+ directory, basename); | |
+ | |
+ if (rb_feature_exists(file_name_with_extension)) { | |
+ return file_name_with_extension; | |
+ } | |
+ } | |
+ } | |
+ } | |
+ return Qnil; | |
+} | |
+ | |
+static VALUE | |
+rb_file_extension(VALUE path) | |
+{ | |
+ return rb_funcall(rb_cFile, rb_intern("extname"), 1, path); | |
+} | |
+ | |
+static int | |
+rb_feature_exists(VALUE expanded_path) | |
+{ | |
+ return rb_funcall(rb_cFile, rb_intern("file?"), 1, expanded_path) == Qtrue; | |
+} | |
+ | |
+ | |
static const char *const loadable_ext[] = { | |
".rb", DLEXT, | |
@@ -746,6 +978,7 @@ Init_load() | |
{ | |
#undef rb_intern | |
#define rb_intern(str) rb_intern2((str), strlen(str)) | |
+ unsigned int j; | |
rb_vm_t *vm = GET_VM(); | |
static const char var_load_path[] = "$:"; | |
ID id_load_path = rb_intern2(var_load_path, sizeof(var_load_path)-1); | |
@@ -769,4 +1002,9 @@ Init_load() | |
ruby_dln_librefs = rb_ary_new(); | |
rb_gc_register_mark_object(ruby_dln_librefs); | |
+ | |
+ for (j = 0; j < CHAR_ARRAY_LEN(available_extensions); ++j) { | |
+ available_ext_rb_str[j] = rb_str_new2(available_extensions[j]); | |
+ rb_gc_register_mark_object(available_ext_rb_str[j]); | |
+ } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/load.c b/load.c | |
index 1a02804..b1e2c1e 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -17,6 +17,7 @@ VALUE ruby_dln_librefs; | |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) | |
#endif | |
+static VALUE get_loaded_features(); | |
static int rb_feature_exists(VALUE); | |
static VALUE rb_locate_file(VALUE); | |
@@ -32,6 +33,14 @@ static VALUE rb_file_extension(VALUE); | |
VALUE rb_get_expanded_load_path(); | |
+static VALUE rb_cLoadedFeaturesProxy; | |
+static void rb_rehash_loaded_features(); | |
+static VALUE rb_loaded_features_hook(int, VALUE*, VALUE); | |
+static void define_loaded_features_proxy(); | |
+static VALUE loaded_features_proxy_new(VALUE); | |
+ | |
+static st_table * get_loaded_features_hash(void); | |
+ | |
const char *available_extensions[] = { | |
".rb", | |
DLEXT, | |
@@ -249,6 +258,90 @@ rb_feature_exists(VALUE expanded_path) | |
} | |
+/* | |
+ * $LOADED_FEATURES is exposed publically as an array, but under the covers | |
+ * we also store this data in a hash for fast lookups. So that we can rebuild | |
+ * the hash whenever $LOADED_FEATURES is changed, we wrap the Array class | |
+ * in a proxy that intercepts all data-modifying methods and rebuilds the | |
+ * hash. | |
+ * | |
+ * Note that the list of intercepted methods is currently non-comprehensive | |
+ * --- it only covers modifications made by the ruby and rubyspec test suites. | |
+ */ | |
+static void | |
+define_loaded_features_proxy() | |
+{ | |
+ const char* methods_to_hook[] = {"<<", "push", "clear", "replace", "delete"}; | |
+ unsigned int i; | |
+ | |
+ rb_cLoadedFeaturesProxy = rb_define_class("LoadedFeaturesProxy", rb_cArray); | |
+ for (i = 0; i < CHAR_ARRAY_LEN(methods_to_hook); ++i) { | |
+ rb_define_method( | |
+ rb_cLoadedFeaturesProxy, | |
+ methods_to_hook[i], | |
+ rb_loaded_features_hook, | |
+ -1); | |
+ } | |
+} | |
+ | |
+static VALUE | |
+rb_loaded_features_hook(int argc, VALUE *argv, VALUE self) | |
+{ | |
+ VALUE ret; | |
+ ret = rb_call_super(argc, argv); | |
+ rb_rehash_loaded_features(); | |
+ return ret; | |
+} | |
+ | |
+static void | |
+rb_rehash_loaded_features() | |
+{ | |
+ int i; | |
+ VALUE features; | |
+ VALUE feature; | |
+ | |
+ st_table* loaded_features_hash = get_loaded_features_hash(); | |
+ | |
+ st_clear(loaded_features_hash); | |
+ | |
+ features = get_loaded_features(); | |
+ | |
+ for (i = 0; i < RARRAY_LEN(features); ++i) { | |
+ feature = RARRAY_PTR(features)[i]; | |
+ st_insert( | |
+ loaded_features_hash, | |
+ (st_data_t)ruby_strdup(RSTRING_PTR(feature)), | |
+ (st_data_t)rb_barrier_new()); | |
+ } | |
+} | |
+ | |
+static st_table * | |
+get_loaded_features_hash(void) | |
+{ | |
+ st_table* loaded_features_hash; | |
+ loaded_features_hash = GET_VM()->loaded_features_hash; | |
+ | |
+ if (!loaded_features_hash) { | |
+ GET_VM()->loaded_features_hash = loaded_features_hash = st_init_strcasetable(); | |
+ } | |
+ | |
+ return loaded_features_hash; | |
+} | |
+ | |
+/* This is cargo-culted from ary_alloc in array.c. I am sure there is | |
+ * a much better way to instantiate this class, I just don't know what it is. | |
+ */ | |
+static VALUE | |
+loaded_features_proxy_new(VALUE klass) | |
+{ | |
+ NEWOBJ(ary, struct RArray); | |
+ OBJSETUP(ary, klass, T_ARRAY); | |
+ FL_SET((ary), RARRAY_EMBED_FLAG); | |
+ RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK; | |
+ RBASIC(ary)->flags |= (0) << RARRAY_EMBED_LEN_SHIFT; | |
+ | |
+ return (VALUE)ary; | |
+} | |
static const char *const loadable_ext[] = { | |
".rb", DLEXT, | |
@@ -990,7 +1083,10 @@ Init_load() | |
rb_define_virtual_variable("$\"", get_loaded_features, 0); | |
rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0); | |
- vm->loaded_features = rb_ary_new(); | |
+ | |
+ define_loaded_features_proxy(); | |
+ | |
+ vm->loaded_features = loaded_features_proxy_new(rb_cLoadedFeaturesProxy); | |
rb_define_global_function("load", rb_f_load, -1); | |
rb_define_global_function("require", rb_f_require, 1); | |
diff --git a/vm.c b/vm.c | |
index bddb4dd..e0b22f3 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -1527,6 +1527,10 @@ rb_vm_mark(void *ptr) | |
rb_mark_tbl(vm->loading_table); | |
} | |
+ if (vm->loaded_features_hash) { | |
+ rb_mark_tbl(vm->loaded_features_hash); | |
+ } | |
+ | |
mark_event_hooks(vm->event_hooks); | |
for (i = 0; i < RUBY_NSIG; i++) { | |
diff --git a/vm_core.h b/vm_core.h | |
index e302e62..4d1bc2b 100644 | |
--- a/vm_core.h | |
+++ b/vm_core.h | |
@@ -324,6 +324,7 @@ typedef struct rb_vm_struct { | |
* objects so do *NOT* mark this when you GC. | |
*/ | |
struct RArray at_exit; | |
+ struct st_table *loaded_features_hash; | |
} rb_vm_t; | |
typedef struct { |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/load.c b/load.c | |
index b1e2c1e..db227f0 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -17,9 +17,21 @@ VALUE ruby_dln_librefs; | |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) | |
#endif | |
+VALUE rb_f_require(VALUE, VALUE); | |
+VALUE rb_f_require_relative(VALUE, VALUE); | |
+static VALUE rb_f_load(int, VALUE *); | |
+VALUE rb_require_safe(VALUE, int); | |
+ | |
static VALUE get_loaded_features(); | |
static int rb_feature_exists(VALUE); | |
+static int rb_file_is_ruby(VALUE path); | |
+static int rb_file_has_been_required(VALUE); | |
+static int rb_file_is_being_required(VALUE); | |
+ | |
+void rb_provide(const char *feature); | |
+static void rb_provide_feature(VALUE); | |
+ | |
static VALUE rb_locate_file(VALUE); | |
static VALUE rb_locate_file_relative(VALUE); | |
static VALUE rb_locate_file_absolute(VALUE); | |
@@ -39,6 +51,7 @@ static VALUE rb_loaded_features_hook(int, VALUE*, VALUE); | |
static void define_loaded_features_proxy(); | |
static VALUE loaded_features_proxy_new(VALUE); | |
+static st_table * get_loading_table(void); | |
static st_table * get_loaded_features_hash(void); | |
const char *available_extensions[] = { | |
@@ -66,6 +79,34 @@ const char *alternate_dl_extensions[] = { | |
#define CHAR_ARRAY_LEN(array) (sizeof(array) / sizeof(char*)) | |
#define VALUE_ARRAY_LEN(array) (sizeof(array) / sizeof(VALUE)) | |
+static int | |
+rb_file_is_ruby(VALUE path) | |
+{ | |
+ const char * ext; | |
+ ext = ruby_find_extname(RSTRING_PTR(path), 0); | |
+ | |
+ return ext && IS_RBEXT(ext); | |
+} | |
+ | |
+static int | |
+rb_file_has_been_required(VALUE expanded_path) | |
+{ | |
+ st_data_t data; | |
+ st_data_t path_key = (st_data_t)RSTRING_PTR(expanded_path); | |
+ st_table *loaded_features_hash = get_loaded_features_hash(); | |
+ | |
+ return st_lookup(loaded_features_hash, path_key, &data); | |
+} | |
+ | |
+static int | |
+rb_file_is_being_required(VALUE full_path) { | |
+ const char *ftptr = RSTRING_PTR(full_path); | |
+ st_data_t data; | |
+ st_table *loading_tbl = get_loading_table(); | |
+ | |
+ return (loading_tbl && st_lookup(loading_tbl, (st_data_t)ftptr, &data)); | |
+} | |
+ | |
static VALUE | |
rb_locate_file(VALUE filename) | |
{ | |
@@ -538,40 +579,51 @@ rb_provided(const char *feature) | |
return rb_feature_provided(feature, 0); | |
} | |
+/* Should return true if the file has or is being loaded, but should | |
+ * not actually load the file. | |
+ */ | |
int | |
-rb_feature_provided(const char *feature, const char **loading) | |
+rb_feature_provided_2(VALUE fname) | |
{ | |
- const char *ext = strrchr(feature, '.'); | |
- volatile VALUE fullpath = 0; | |
- | |
- if (*feature == '.' && | |
- (feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) { | |
- fullpath = rb_file_expand_path(rb_str_new2(feature), Qnil); | |
- feature = RSTRING_PTR(fullpath); | |
- } | |
- if (ext && !strchr(ext, '/')) { | |
- if (IS_RBEXT(ext)) { | |
- if (rb_feature_p(feature, ext, TRUE, FALSE, loading)) return TRUE; | |
- return FALSE; | |
- } | |
- else if (IS_SOEXT(ext) || IS_DLEXT(ext)) { | |
- if (rb_feature_p(feature, ext, FALSE, FALSE, loading)) return TRUE; | |
- return FALSE; | |
+ VALUE full_path = rb_locate_file(fname); | |
+ | |
+ if ( | |
+ full_path != Qnil && | |
+ ( | |
+ rb_file_has_been_required(full_path) || | |
+ rb_file_is_being_required(full_path) | |
+ ) | |
+ ) { | |
+ return TRUE; | |
+ } else { | |
+ return FALSE; | |
} | |
- } | |
- if (rb_feature_p(feature, 0, TRUE, FALSE, loading)) | |
- return TRUE; | |
- return FALSE; | |
+} | |
+ | |
+int | |
+rb_feature_provided(const char *feature, const char **loading) | |
+{ | |
+ VALUE fname = rb_str_new2(feature); | |
+ return rb_feature_provided_2(fname); | |
} | |
static void | |
rb_provide_feature(VALUE feature) | |
{ | |
+ st_table* loaded_features_hash; | |
+ | |
if (OBJ_FROZEN(get_loaded_features())) { | |
rb_raise(rb_eRuntimeError, | |
"$LOADED_FEATURES is frozen; cannot append feature"); | |
} | |
rb_ary_push(get_loaded_features(), feature); | |
+ | |
+ loaded_features_hash = get_loaded_features_hash(); | |
+ st_insert( | |
+ loaded_features_hash, | |
+ (st_data_t)ruby_strdup(RSTRING_PTR(feature)), | |
+ (st_data_t)rb_barrier_new() | |
+ ); | |
} | |
void | |
@@ -892,9 +944,14 @@ load_ext(VALUE path) | |
return (VALUE)dln_load(RSTRING_PTR(path)); | |
} | |
+/* | |
+ * returns the path loaded, or false if the file was already loaded. Raises | |
+ * LoadError if a file cannot be found. | |
+ */ | |
VALUE | |
rb_require_safe(VALUE fname, int safe) | |
{ | |
+ VALUE path = Qnil; | |
volatile VALUE result = Qnil; | |
rb_thread_t *th = GET_THREAD(); | |
volatile VALUE errinfo = th->errinfo; | |
@@ -907,34 +964,41 @@ rb_require_safe(VALUE fname, int safe) | |
PUSH_TAG(); | |
saved.safe = rb_safe_level(); | |
if ((state = EXEC_TAG()) == 0) { | |
- VALUE path; | |
long handle; | |
int found; | |
rb_set_safe_level_force(safe); | |
FilePathValue(fname); | |
rb_set_safe_level_force(0); | |
- found = search_required(fname, &path, safe); | |
- if (found) { | |
- if (!path || !(ftptr = load_lock(RSTRING_PTR(path)))) { | |
+ path = rb_locate_file(fname); | |
+ | |
+ if (safe >= 1 && OBJ_TAINTED(path)) { | |
+ rb_raise(rb_eSecurityError, "Loading from unsafe file %s", RSTRING_PTR(path)); | |
+ } | |
+ | |
result = Qfalse; | |
- } | |
- else { | |
- switch (found) { | |
- case 'r': | |
- rb_load_internal(path, 0); | |
- break; | |
- | |
- case 's': | |
- handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext, | |
- path, 0, path); | |
- rb_ary_push(ruby_dln_librefs, LONG2NUM(handle)); | |
- break; | |
+ if (path == Qnil) { | |
+ load_failed(fname); | |
+ } else { | |
+ if (ftptr = load_lock(RSTRING_PTR(path))) { // Allows circular requires to work | |
+ if (!rb_file_has_been_required(path)) { | |
+ if (rb_file_is_ruby(path)) { | |
+ rb_load_internal(path, 0); | |
+ } else { | |
+ handle = (long)rb_vm_call_cfunc( | |
+ rb_vm_top_self(), | |
+ load_ext, | |
+ path, | |
+ 0, | |
+ path | |
+ ); | |
+ rb_ary_push(ruby_dln_librefs, LONG2NUM(handle)); | |
+ } | |
+ rb_provide_feature(path); | |
+ result = Qtrue; | |
+ } | |
+ } | |
} | |
- rb_provide_feature(path); | |
- result = Qtrue; | |
- } | |
- } | |
} | |
POP_TAG(); | |
load_unlock(ftptr, !state); | |
@@ -950,7 +1014,11 @@ rb_require_safe(VALUE fname, int safe) | |
th->errinfo = errinfo; | |
- return result; | |
+ if (result == Qtrue) { | |
+ return path; | |
+ } else { | |
+ return Qfalse; | |
+ } | |
} | |
VALUE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/enumerator.c b/enumerator.c | |
index 7bb5ecd..b8ce698 100644 | |
--- a/enumerator.c | |
+++ b/enumerator.c | |
@@ -1171,6 +1171,4 @@ Init_Enumerator(void) | |
id_rewind = rb_intern("rewind"); | |
id_each = rb_intern("each"); | |
sym_each = ID2SYM(id_each); | |
- | |
- rb_provide("enumerator.so"); /* for backward compatibility */ | |
} | |
diff --git a/lib/enumerator.rb b/lib/enumerator.rb | |
new file mode 100644 | |
index 0000000..b028c83 | |
--- /dev/null | |
+++ b/lib/enumerator.rb | |
@@ -0,0 +1,3 @@ | |
+# This class is now defined entirely in enumerator.c and is always available. | |
+# This file needs to remain here for backwards compatibility, so that `require | |
+# "enumerator"` will not raise an exception. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/load.c b/load.c | |
index db227f0..496c11d 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -43,6 +43,10 @@ static int rb_path_is_absolute(VALUE); | |
static int rb_path_is_relative(VALUE); | |
static VALUE rb_file_extension(VALUE); | |
+static VALUE rb_get_cached_expansion(VALUE); | |
+static void rb_set_cached_expansion(VALUE, VALUE); | |
+static void rb_clear_cached_expansions(); | |
+ | |
VALUE rb_get_expanded_load_path(); | |
static VALUE rb_cLoadedFeaturesProxy; | |
@@ -53,6 +57,7 @@ static VALUE loaded_features_proxy_new(VALUE); | |
static st_table * get_loading_table(void); | |
static st_table * get_loaded_features_hash(void); | |
+static st_table * get_filename_expansion_hash(void); | |
const char *available_extensions[] = { | |
".rb", | |
@@ -112,6 +117,11 @@ rb_locate_file(VALUE filename) | |
{ | |
VALUE full_path = Qnil; | |
+ full_path = rb_get_cached_expansion(filename); | |
+ | |
+ if (full_path != Qnil) | |
+ return full_path; | |
+ | |
if (rb_path_is_relative(filename)) { | |
full_path = rb_locate_file_relative(filename); | |
} else if (rb_path_is_absolute(filename)) { | |
@@ -120,6 +130,9 @@ rb_locate_file(VALUE filename) | |
full_path = rb_locate_file_in_load_path(filename); | |
} | |
+ if (full_path != Qnil) | |
+ rb_set_cached_expansion(filename, full_path); | |
+ | |
return full_path; | |
} | |
@@ -298,6 +311,30 @@ rb_feature_exists(VALUE expanded_path) | |
return rb_funcall(rb_cFile, rb_intern("file?"), 1, expanded_path) == Qtrue; | |
} | |
+static VALUE | |
+rb_get_cached_expansion(VALUE filename) | |
+{ | |
+ st_data_t data; | |
+ st_data_t path_key = (st_data_t)RSTRING_PTR(filename); | |
+ st_table *filename_expansion_hash = get_filename_expansion_hash(); | |
+ | |
+ if (st_lookup(filename_expansion_hash, path_key, &data)) { | |
+ return (VALUE)data; | |
+ } else { | |
+ return Qnil; | |
+ }; | |
+} | |
+ | |
+static void | |
+rb_set_cached_expansion(VALUE filename, VALUE expanded) | |
+{ | |
+ st_data_t data = (st_data_t)expanded; | |
+ st_data_t path_key = (st_data_t)RSTRING_PTR(filename); | |
+ st_table *filename_expansion_hash = get_filename_expansion_hash(); | |
+ | |
+ st_insert(filename_expansion_hash, path_key, data); | |
+} | |
+ | |
/* | |
* $LOADED_FEATURES is exposed publically as an array, but under the covers | |
@@ -331,6 +368,7 @@ rb_loaded_features_hook(int argc, VALUE *argv, VALUE self) | |
VALUE ret; | |
ret = rb_call_super(argc, argv); | |
rb_rehash_loaded_features(); | |
+ rb_clear_cached_expansions(); | |
return ret; | |
} | |
@@ -356,6 +394,13 @@ rb_rehash_loaded_features() | |
} | |
} | |
+static void | |
+rb_clear_cached_expansions() | |
+{ | |
+ st_table* filename_expansion_hash = get_filename_expansion_hash(); | |
+ st_clear(filename_expansion_hash); | |
+} | |
+ | |
static st_table * | |
get_loaded_features_hash(void) | |
{ | |
@@ -369,6 +414,19 @@ get_loaded_features_hash(void) | |
return loaded_features_hash; | |
} | |
+static st_table * | |
+get_filename_expansion_hash(void) | |
+{ | |
+ st_table* filename_expansion_hash; | |
+ filename_expansion_hash = GET_VM()->filename_expansion_hash; | |
+ | |
+ if (!filename_expansion_hash) { | |
+ GET_VM()->filename_expansion_hash = filename_expansion_hash = st_init_strcasetable(); | |
+ } | |
+ | |
+ return filename_expansion_hash; | |
+} | |
+ | |
/* This is cargo-culted from ary_alloc in array.c. I am sure there is | |
* a much better way to instantiate this class, I just don't know what it is. | |
*/ | |
diff --git a/vm.c b/vm.c | |
index e0b22f3..40dc2eb 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -1531,6 +1531,10 @@ rb_vm_mark(void *ptr) | |
rb_mark_tbl(vm->loaded_features_hash); | |
} | |
+ if (vm->filename_expansion_hash) { | |
+ rb_mark_tbl(vm->filename_expansion_hash); | |
+ } | |
+ | |
mark_event_hooks(vm->event_hooks); | |
for (i = 0; i < RUBY_NSIG; i++) { | |
diff --git a/vm_core.h b/vm_core.h | |
index 4d1bc2b..7818ec0 100644 | |
--- a/vm_core.h | |
+++ b/vm_core.h | |
@@ -325,6 +325,7 @@ typedef struct rb_vm_struct { | |
*/ | |
struct RArray at_exit; | |
struct st_table *loaded_features_hash; | |
+ struct st_table *filename_expansion_hash; | |
} rb_vm_t; | |
typedef struct { |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb | |
index 96b1551..49a7952 100644 | |
--- a/test/ruby/test_require.rb | |
+++ b/test/ruby/test_require.rb | |
@@ -339,4 +339,78 @@ class TestRequire < Test::Unit::TestCase | |
[], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/, | |
bug3756) | |
end | |
+ | |
+ def test_case_insensitive | |
+ load_path = $:.dup | |
+ loaded = $".dup | |
+ path = File.expand_path(__FILE__) | |
+ $:.unshift(File.dirname(path)) | |
+ $".push(path) unless $".include?(path) | |
+ bug4255 = '[ruby-core:34297]' | |
+ assert_equal(false, $bug4255 ||= false, bug4255) | |
+ $bug4255 = true | |
+ f = File.basename(__FILE__, ".*").upcase | |
+ assert_equal(false, require(f)) | |
+ ensure | |
+ $:.replace(load_path) | |
+ $".replace(loaded) | |
+ end if File.identical?(__FILE__, __FILE__.upcase) | |
+ | |
+ def test_feature_is_reloaded_from_new_load_path_entry | |
+ # This is a bit of a weird test, but it is needed to ensure that some | |
+ # caching optimizations are working correctly. | |
+ load_path = $:.dup | |
+ loaded = $".dup | |
+ initial_length = loaded.length | |
+ | |
+ Dir.mktmpdir do |tmp| | |
+ Dir.chdir(tmp) do | |
+ Dir.mkdir "a" | |
+ Dir.mkdir "b" | |
+ File.open("a/test.rb", "w") {|f| f.puts '' } | |
+ File.open("b/test.rb", "w") {|f| f.puts '' } | |
+ | |
+ $".clear | |
+ $:.unshift(File.join(tmp, "b")) | |
+ require 'test.rb' | |
+ assert $"[0].include?('b/test.rb') | |
+ | |
+ $".clear | |
+ $:.unshift(File.join(tmp, "a")) | |
+ require 'test.rb' | |
+ assert $"[0].include?('a/test.rb') | |
+ end | |
+ end | |
+ ensure | |
+ $:.replace(load_path) | |
+ $".replace(loaded) | |
+ end | |
+ | |
+ def test_require_file_with_multiple_dots | |
+ load_path = $:.dup | |
+ loaded = $".dup | |
+ initial_length = loaded.length | |
+ | |
+ Dir.mktmpdir do |tmp| | |
+ Dir.chdir(tmp) do | |
+ Dir.mkdir "a" | |
+ File.open("a/test.1.rb", "w") {|f| f.puts '' } | |
+ | |
+ $".clear | |
+ $:.unshift(File.join(tmp, "a")) | |
+ require 'test.1' | |
+ assert $"[0].include?('test.1.rb') | |
+ | |
+ $".clear | |
+ File.open("a/test.rb.rb", "w") {|f| f.puts '' } | |
+ File.open("a/test.rb", "w") {|f| f.puts '' } | |
+ | |
+ require 'test.rb' | |
+ assert !$"[0].include?('test.rb.rb') | |
+ end | |
+ end | |
+ ensure | |
+ $:.replace(load_path) | |
+ $".replace(loaded) | |
+ end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/load.c b/load.c | |
index 496c11d..776dd4d 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -159,7 +159,7 @@ rb_locate_file_relative(VALUE fname) | |
static int | |
rb_path_is_absolute(VALUE path) | |
{ | |
- // Delegate to file.c | |
+ /* Delegate to file.c */ | |
return rb_is_absolute_path(RSTRING_PTR(path)); | |
} | |
@@ -268,8 +268,9 @@ rb_locate_file_with_extensions(VALUE base_file_name, VALUE extension) { | |
return base_file_name; | |
} else { | |
for (j = 0; j < VALUE_ARRAY_LEN(available_ext_rb_str); ++j) { | |
- // Also try loading 'dot.dot.bundle' for 'dot.dot' | |
- // Also try loading 'test.1.rb' for 'test.1' | |
+ /* Also try loading 'dot.dot.bundle' for 'dot.dot' | |
+ * Also try loading 'test.1.rb' for 'test.1' | |
+ */ | |
file_name_with_extension = rb_str_plus( | |
base_file_name, | |
available_ext_rb_str[j]); | |
@@ -280,8 +281,9 @@ rb_locate_file_with_extensions(VALUE base_file_name, VALUE extension) { | |
} | |
for (j = 0; j < CHAR_ARRAY_LEN(alternate_dl_extensions); ++j) { | |
- // Try loading the native DLEXT version of this platform. | |
- // This allows 'pathname.so' to require 'pathname.bundle' on OSX | |
+ /* Try loading the native DLEXT version of this platform. | |
+ * This allows 'pathname.so' to require 'pathname.bundle' on OSX | |
+ */ | |
directory = rb_file_dirname(base_file_name); | |
basename = rb_funcall(rb_cFile, rb_intern("basename"), 2, | |
base_file_name, extension); | |
@@ -1038,7 +1040,7 @@ rb_require_safe(VALUE fname, int safe) | |
if (path == Qnil) { | |
load_failed(fname); | |
} else { | |
- if (ftptr = load_lock(RSTRING_PTR(path))) { // Allows circular requires to work | |
+ if (ftptr = load_lock(RSTRING_PTR(path))) { /* Allows circular requires to work */ | |
if (!rb_file_has_been_required(path)) { | |
if (rb_file_is_ruby(path)) { | |
rb_load_internal(path, 0); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment