Created

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

Patches to improve ruby require times - see descriptions at https://gist.github.com/35060fbcefb25cf1a456

View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
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]);
+ }
}
View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
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 {
View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
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
View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
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.
View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
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 {
View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
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
View require_0.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
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
Something went wrong with that request. Please try again.