summaryrefslogtreecommitdiff
path: root/variable.c
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2022-05-17 00:50:02 +1200
committerGitHub <noreply@github.com>2022-05-17 00:50:02 +1200
commitf626998c4fa62973cac3a027597f97cdacd0d3c5 (patch)
tree3aa39f13106415581335eff9d08da60e7eab88a1 /variable.c
parenta7577dbfd3ea53cccf7aaf94208069784ad17791 (diff)
Delete autoload data from global features after autoload has completed. (#5910)
* Update naming of critical section assertions macros. * Improved locking for autoload.
Notes
Notes: Merged-By: ioquatix <samuel@codeotaku.com>
Diffstat (limited to 'variable.c')
-rw-r--r--variable.c555
1 files changed, 304 insertions, 251 deletions
diff --git a/variable.c b/variable.c
index 15dc6b9fc0..ae482dfabe 100644
--- a/variable.c
+++ b/variable.c
@@ -49,8 +49,8 @@ static ID autoload, classpath, tmp_classpath;
// This hash table maps file paths to loadable features. We use this to track
// autoload state until it's no longer needed.
-// feature (file path) => struct autoload_i
-static VALUE autoload_featuremap;
+// feature (file path) => struct autoload_data
+static VALUE autoload_features;
// This mutex is used to protect autoloading state. We use a global mutex which
// is held until a per-feature mutex can be created. This ensures there are no
@@ -86,9 +86,9 @@ Init_var_tables(void)
rb_obj_hide(autoload_mutex);
rb_gc_register_mark_object(autoload_mutex);
- autoload_featuremap = rb_ident_hash_new();
- rb_obj_hide(autoload_featuremap);
- rb_gc_register_mark_object(autoload_featuremap);
+ autoload_features = rb_ident_hash_new();
+ rb_obj_hide(autoload_features);
+ rb_gc_register_mark_object(autoload_features);
}
static inline bool
@@ -2073,38 +2073,38 @@ rb_mod_const_missing(VALUE klass, VALUE name)
}
static void
-autoload_mark(void *ptr)
+autoload_table_mark(void *ptr)
{
rb_mark_tbl_no_pin((st_table *)ptr);
}
static void
-autoload_free(void *ptr)
+autoload_table_free(void *ptr)
{
st_free_table((st_table *)ptr);
}
static size_t
-autoload_memsize(const void *ptr)
+autoload_table_memsize(const void *ptr)
{
const st_table *tbl = ptr;
return st_memsize(tbl);
}
static void
-autoload_compact(void *ptr)
+autoload_table_compact(void *ptr)
{
rb_gc_update_tbl_refs((st_table *)ptr);
}
-static const rb_data_type_t autoload_data_type = {
- "autoload",
- {autoload_mark, autoload_free, autoload_memsize, autoload_compact,},
+static const rb_data_type_t autoload_table_type = {
+ "autoload_table",
+ {autoload_table_mark, autoload_table_free, autoload_table_memsize, autoload_table_compact,},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
#define check_autoload_table(av) \
- (struct st_table *)rb_check_typeddata((av), &autoload_data_type)
+ (struct st_table *)rb_check_typeddata((av), &autoload_table_type)
static VALUE
autoload_data(VALUE mod, ID id)
@@ -2112,63 +2112,81 @@ autoload_data(VALUE mod, ID id)
struct st_table *tbl;
st_data_t val;
- if (!st_lookup(RCLASS_IV_TBL(mod), autoload, &val) ||
- !(tbl = check_autoload_table((VALUE)val)) ||
- !st_lookup(tbl, (st_data_t)id, &val)) {
- return 0;
+ // Look up the instance variable table for `autoload`, then index into that table with the given constant name `id`.
+ if (!st_lookup(RCLASS_IV_TBL(mod), autoload, &val) || !(tbl = check_autoload_table((VALUE)val)) || !st_lookup(tbl, (st_data_t)id, &val)) {
+ return 0;
}
+
return (VALUE)val;
}
+// Every autoload constant has exactly one instance of autoload_const, stored in `autoload_features`. Since multiple autoload constants can refer to the same file, every `autoload_const` refers to a de-duplicated `autoload_data`.
struct autoload_const {
- struct ccan_list_node cnode; /* <=> autoload_data_i.constants */
- VALUE mod;
- VALUE ad; /* autoload_data_i */
+ // The linked list node of all constants which are loaded by the related autoload feature.
+ struct ccan_list_node cnode; /* <=> autoload_data.constants */
+
+ // The shared "autoload_data" if multiple constants are defined from the same feature.
+ VALUE autoload_data_value;
+
+ // The module we are loading a constant into.
+ VALUE module;
+
+ // The name of the constant we are loading.
+ VALUE name;
+
+ // The value of the constant (after it's loaded).
VALUE value;
- VALUE file;
- ID id;
+
+ // The constant entry flags which need to be re-applied after autoloading the feature.
rb_const_flag_t flag;
+
+ // The source file and line number that defined this constant (different from feature path).
+ VALUE file;
int line;
};
-/* always on stack, no need to mark */
-struct autoload_state {
- struct autoload_const *ac;
- VALUE result;
+// Each `autoload_data` uniquely represents a specific feature which can be loaded, and a list of constants which it is able to define. We use a mutex to coordinate multiple threads trying to load the same feature.
+struct autoload_data {
+ // The feature path to require to load this constant.
+ VALUE feature;
+
+ // The mutex which is protecting autoloading this feature.
VALUE mutex;
-};
-struct autoload_data_i {
- VALUE feature;
- struct autoload_state *state; /* points to on-stack struct */
+ // The process fork serial number since the autoload mutex will become invalid on fork.
rb_serial_t fork_gen;
+
+ // The linked list of all constants that are going to be loaded by this autoload.
struct ccan_list_head constants; /* <=> autoload_const.cnode */
};
static void
-autoload_i_compact(void *ptr)
+autoload_data_compact(void *ptr)
{
- struct autoload_data_i *p = ptr;
+ struct autoload_data *p = ptr;
+
p->feature = rb_gc_location(p->feature);
+ p->mutex = rb_gc_location(p->mutex);
}
static void
-autoload_i_mark(void *ptr)
+autoload_data_mark(void *ptr)
{
- struct autoload_data_i *p = ptr;
+ struct autoload_data *p = ptr;
rb_gc_mark_movable(p->feature);
+ rb_gc_mark_movable(p->mutex);
}
static size_t
-autoload_i_memsize(const void *ptr)
+autoload_data_memsize(const void *ptr)
{
- return sizeof(struct autoload_data_i);
+ return sizeof(struct autoload_data);
}
-static const rb_data_type_t autoload_data_i_type = {
- "autoload_i",
- {autoload_i_mark, ruby_xfree, autoload_i_memsize, autoload_i_compact},
+static const rb_data_type_t autoload_data_type = {
+ "autoload_data",
+ {autoload_data_mark, ruby_xfree, autoload_data_memsize, autoload_data_compact},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
@@ -2177,8 +2195,8 @@ autoload_c_compact(void *ptr)
{
struct autoload_const *ac = ptr;
- ac->mod = rb_gc_location(ac->mod);
- ac->ad = rb_gc_location(ac->ad);
+ ac->module = rb_gc_location(ac->module);
+ ac->autoload_data_value = rb_gc_location(ac->autoload_data_value);
ac->value = rb_gc_location(ac->value);
ac->file = rb_gc_location(ac->file);
}
@@ -2188,8 +2206,8 @@ autoload_c_mark(void *ptr)
{
struct autoload_const *ac = ptr;
- rb_gc_mark_movable(ac->mod);
- rb_gc_mark_movable(ac->ad);
+ rb_gc_mark_movable(ac->module);
+ rb_gc_mark_movable(ac->autoload_data_value);
rb_gc_mark_movable(ac->value);
rb_gc_mark_movable(ac->file);
}
@@ -2206,16 +2224,16 @@ static const rb_data_type_t autoload_const_type = {
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
-static struct autoload_data_i *
+static struct autoload_data *
get_autoload_data(VALUE acv, struct autoload_const **acp)
{
struct autoload_const *ac = rb_check_typeddata(acv, &autoload_const_type);
- struct autoload_data_i *ele;
+ struct autoload_data *ele;
- ele = rb_check_typeddata(ac->ad, &autoload_data_i_type);
+ ele = rb_check_typeddata(ac->autoload_data_value, &autoload_data_type);
/* do not reach across stack for ->state after forking: */
- if (ele && ele->state && ele->fork_gen != GET_VM()->fork_gen) {
- ele->state = 0;
+ if (ele && ele->fork_gen != GET_VM()->fork_gen) {
+ ele->mutex = Qnil;
ele->fork_gen = 0;
}
if (acp) *acp = ac;
@@ -2226,79 +2244,118 @@ RUBY_FUNC_EXPORTED void
rb_autoload(VALUE mod, ID id, const char *file)
{
if (!file || !*file) {
- rb_raise(rb_eArgError, "empty file name");
+ rb_raise(rb_eArgError, "empty file name");
}
+
rb_autoload_str(mod, id, rb_fstring_cstr(file));
}
static void const_set(VALUE klass, ID id, VALUE val);
static void const_added(VALUE klass, ID const_name);
-void
-rb_autoload_str(VALUE mod, ID id, VALUE file)
+struct autoload_arguments {
+ VALUE module;
+ ID name;
+
+ VALUE path;
+};
+
+static VALUE
+autoload_feature_lookup_or_create(VALUE file)
{
- st_data_t av;
- VALUE ad;
- struct st_table *tbl;
- struct autoload_data_i *ele;
- rb_const_entry_t *ce;
+ VALUE ad = rb_hash_aref(autoload_features, file);
+ struct autoload_data *ele;
- if (!rb_is_const_id(id)) {
- rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"",
- QUOTE_ID(id));
+ if (NIL_P(ad)) {
+ ad = TypedData_Make_Struct(0, struct autoload_data, &autoload_data_type, ele);
+ ele->feature = file;
+ ele->mutex = Qnil;
+ ccan_list_head_init(&ele->constants);
+ rb_hash_aset(autoload_features, file, ad);
}
- Check_Type(file, T_STRING);
- if (!RSTRING_LEN(file)) {
- rb_raise(rb_eArgError, "empty file name");
- }
+ return ad;
+}
- ce = rb_const_lookup(mod, id);
- if (ce && ce->value != Qundef) {
- return;
+static struct st_table* autoload_table_lookup_or_create(VALUE module) {
+ // Get or create an autoload table in the class instance variables:
+ struct st_table *table = RCLASS_IV_TBL(module);
+ VALUE autoload_table_value;
+
+ if (table && st_lookup(table, (st_data_t)autoload, &autoload_table_value)) {
+ return check_autoload_table((VALUE)autoload_table_value);
}
- const_set(mod, id, Qundef);
- tbl = RCLASS_IV_TBL(mod);
- if (tbl && st_lookup(tbl, (st_data_t)autoload, &av)) {
- tbl = check_autoload_table((VALUE)av);
+ if (!table) {
+ table = RCLASS_IV_TBL(module) = st_init_numtable();
}
- else {
- if (!tbl) tbl = RCLASS_IV_TBL(mod) = st_init_numtable();
- av = (st_data_t)TypedData_Wrap_Struct(0, &autoload_data_type, 0);
- st_add_direct(tbl, (st_data_t)autoload, av);
- RB_OBJ_WRITTEN(mod, Qnil, av);
- DATA_PTR(av) = tbl = st_init_numtable();
+
+ autoload_table_value = TypedData_Wrap_Struct(0, &autoload_table_type, 0);
+ st_add_direct(table, (st_data_t)autoload, (st_data_t)autoload_table_value);
+
+ RB_OBJ_WRITTEN(module, Qnil, autoload_table_value);
+ return (DATA_PTR(autoload_table_value) = st_init_numtable());
+}
+
+static VALUE
+autoload_synchronized(VALUE _arguments)
+{
+ struct autoload_arguments *arguments = (struct autoload_arguments *)_arguments;
+
+ rb_const_entry_t *constant_entry = rb_const_lookup(arguments->module, arguments->name);
+ if (constant_entry && constant_entry->value != Qundef) {
+ return Qfalse;
}
- file = rb_fstring(file);
- ad = rb_hash_aref(autoload_featuremap, file);
- if (NIL_P(ad)) {
- ad = TypedData_Make_Struct(0, struct autoload_data_i,
- &autoload_data_i_type, ele);
- ele->feature = file;
- ele->state = 0;
- ccan_list_head_init(&ele->constants);
- rb_hash_aset(autoload_featuremap, file, ad);
+ // Reset any state associated with any previous constant:
+ const_set(arguments->module, arguments->name, Qundef);
+
+ struct st_table *autoload_table = autoload_table_lookup_or_create(arguments->module);
+
+ // Ensure the string is uniqued since we use an identity lookup:
+ VALUE path = rb_fstring(arguments->path);
+
+ VALUE autoload_data_value = autoload_feature_lookup_or_create(path);
+ struct autoload_data *autoload_data = rb_check_typeddata(autoload_data_value, &autoload_data_type);
+
+ {
+ struct autoload_const *autoload_const;
+ VALUE autoload_const_value = TypedData_Make_Struct(0, struct autoload_const, &autoload_const_type, autoload_const);
+ autoload_const->module = arguments->module;
+ autoload_const->name = arguments->name;
+ autoload_const->value = Qundef;
+ autoload_const->flag = CONST_PUBLIC;
+ autoload_const->autoload_data_value = autoload_data_value;
+ ccan_list_add_tail(&autoload_data->constants, &autoload_const->cnode);
+ st_insert(autoload_table, (st_data_t)arguments->name, (st_data_t)autoload_const_value);
}
- else {
- ele = rb_check_typeddata(ad, &autoload_data_i_type);
+
+ return Qtrue;
+}
+
+void
+rb_autoload_str(VALUE mod, ID id, VALUE file)
+{
+ if (!rb_is_const_id(id)) {
+ rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(id));
}
- {
- VALUE acv;
- struct autoload_const *ac;
- acv = TypedData_Make_Struct(0, struct autoload_const,
- &autoload_const_type, ac);
- ac->mod = mod;
- ac->id = id;
- ac->value = Qundef;
- ac->flag = CONST_PUBLIC;
- ac->ad = ad;
- ccan_list_add_tail(&ele->constants, &ac->cnode);
- st_insert(tbl, (st_data_t)id, (st_data_t)acv);
+
+ Check_Type(file, T_STRING);
+ if (!RSTRING_LEN(file)) {
+ rb_raise(rb_eArgError, "empty file name");
}
- const_added(mod, id);
+ struct autoload_arguments arguments = {
+ .module = mod,
+ .name = id,
+ .path = file,
+ };
+
+ VALUE result = rb_mutex_synchronize(autoload_mutex, autoload_synchronized, (VALUE)&arguments);
+
+ if (result == Qtrue) {
+ const_added(mod, id);
+ }
}
static void
@@ -2307,19 +2364,16 @@ autoload_delete(VALUE mod, ID id)
st_data_t val, load = 0, n = id;
if (st_lookup(RCLASS_IV_TBL(mod), (st_data_t)autoload, &val)) {
- struct st_table *tbl = check_autoload_table((VALUE)val);
- struct autoload_data_i *ele;
- struct autoload_const *ac;
+ struct st_table *tbl = check_autoload_table((VALUE)val);
+ struct autoload_data *ele;
+ struct autoload_const *ac;
+
+ st_delete(tbl, &n, &load);
- st_delete(tbl, &n, &load);
/* Qfalse can indicate already deleted */
if (load != Qfalse) {
ele = get_autoload_data((VALUE)load, &ac);
VM_ASSERT(ele);
- if (ele) {
- VM_ASSERT(!ccan_list_empty(&ele->constants));
- }
-
/*
* we must delete here to avoid "already initialized" warnings
* with parallel autoload. Using list_del_init here so list_del
@@ -2336,28 +2390,22 @@ autoload_delete(VALUE mod, ID id)
}
static int
-autoload_by_someone_else(struct autoload_data_i *ele) {
- return ele->state && ele->state->mutex != Qnil && !rb_mutex_owned_p(ele->state->mutex);
+autoload_by_someone_else(struct autoload_data *ele) {
+ return ele->mutex != Qnil && !rb_mutex_owned_p(ele->mutex);
}
static VALUE
check_autoload_required(VALUE mod, ID id, const char **loadingpath)
{
- VALUE file;
- VALUE load = autoload_data(mod, id);
- struct autoload_data_i *ele;
+ VALUE autoload_const_value = autoload_data(mod, id);
+ struct autoload_data *autoload_data;
const char *loading;
- if (!load || !(ele = get_autoload_data(load, 0))) {
+ if (!autoload_const_value || !(autoload_data = get_autoload_data(autoload_const_value, 0))) {
return 0;
}
- file = ele->feature;
-
- Check_Type(file, T_STRING);
- if (!RSTRING_LEN(file) || !*RSTRING_PTR(file)) {
- rb_raise(rb_eArgError, "empty file name");
- }
+ VALUE feature = autoload_data->feature;
/*
* if somebody else is autoloading, we MUST wait for them, since
@@ -2365,19 +2413,19 @@ check_autoload_required(VALUE mod, ID id, const char **loadingpath)
* completes. We must wait until autoload_const_set finishes in
* the other thread.
*/
- if (autoload_by_someone_else(ele)) {
- return load;
+ if (autoload_by_someone_else(autoload_data)) {
+ return autoload_const_value;
}
- loading = RSTRING_PTR(file);
+ loading = RSTRING_PTR(feature);
if (!rb_feature_provided(loading, &loading)) {
- return load;
+ return autoload_const_value;
}
if (loadingpath && loading) {
*loadingpath = loading;
- return load;
+ return autoload_const_value;
}
return 0;
@@ -2403,8 +2451,8 @@ rb_autoloading_value(VALUE mod, ID id, VALUE* value, rb_const_flag_t *flag)
}
static int
-autoload_by_current(struct autoload_data_i *ele) {
- return ele->state && ele->state->mutex != Qnil && rb_mutex_owned_p(ele->state->mutex);
+autoload_by_current(struct autoload_data *ele) {
+ return ele->mutex != Qnil && rb_mutex_owned_p(ele->mutex);
}
// If there is an autoloading constant and it has been set by the current
@@ -2414,7 +2462,7 @@ struct autoload_const *
autoloading_const_entry(VALUE mod, ID id)
{
VALUE load = autoload_data(mod, id);
- struct autoload_data_i *ele;
+ struct autoload_data *ele;
struct autoload_const *ac;
// Find the autoloading state:
@@ -2450,12 +2498,26 @@ autoload_defined_p(VALUE mod, ID id)
static void const_tbl_update(struct autoload_const *, int);
+struct autoload_load_arguments {
+ VALUE module;
+ ID name;
+ int flag;
+
+ VALUE result;
+
+ VALUE mutex;
+
+ // The specific constant which triggered the autoload code to fire:
+ struct autoload_const *autoload_const;
+
+ // The parent autoload data which is shared between multiple constants:
+ struct autoload_data *autoload_data;
+};
+
static VALUE
autoload_const_set(struct autoload_const *ac)
{
- VALUE klass = ac->mod;
- ID id = ac->id;
- check_before_mod_set(klass, id, ac->value, "constant");
+ check_before_mod_set(ac->module, ac->name, ac->value, "constant");
RB_VM_LOCK_ENTER();
{
@@ -2467,110 +2529,117 @@ autoload_const_set(struct autoload_const *ac)
}
static VALUE
-autoload_require(VALUE arg)
+autoload_load_needed(VALUE _arguments)
{
- struct autoload_state *state = (struct autoload_state *)arg;
- struct autoload_const *ac = state->ac;
- struct autoload_data_i *ele;
+ struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
+
+ const char *loading = 0, *src;
+ struct autoload_data *ele;
+
+ if (!autoload_defined_p(arguments->module, arguments->name)) {
+ return Qfalse;
+ }
- ele = rb_check_typeddata(ac->ad, &autoload_data_i_type);
- /* this may release GVL and switch threads: */
- state->result = rb_funcall(rb_vm_top_self(), rb_intern("require"), 1, ele->feature);
+ VALUE load = check_autoload_required(arguments->module, arguments->name, &loading);
+ if (!load) {
+ return Qfalse;
+ }
- return state->result;
+ src = rb_sourcefile();
+ if (src && loading && strcmp(src, loading) == 0) {
+ return Qfalse;
+ }
+
+ struct autoload_const *autoload_const;
+ if (!(ele = get_autoload_data(load, &autoload_const))) {
+ return Qfalse;
+ }
+
+ if (ele->mutex == Qnil) {
+ ele->mutex = rb_mutex_new();
+ ele->fork_gen = GET_VM()->fork_gen;
+ }
+ else if (rb_mutex_owned_p(ele->mutex)) {
+ return Qfalse;
+ }
+
+ arguments->autoload_const = autoload_const;
+ arguments->mutex = ele->mutex;
+
+ return load;
}
static VALUE
-autoload_reset(VALUE arg)
+autoload_feature_require(VALUE _arguments)
{
- struct autoload_state *state = (struct autoload_state *)arg;
- struct autoload_const *ac = state->ac;
- struct autoload_data_i *ele;
+ struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
- ele = rb_check_typeddata(ac->ad, &autoload_data_i_type);
- VALUE mutex = state->mutex;
+ struct autoload_const *autoload_const = arguments->autoload_const;
- // If we are the main thread to execute...
- if (ele->state == state) {
- // Prepare to update the state of the world:
- rb_mutex_lock(autoload_mutex);
+ // We save this for later use in autoload_apply_constants:
+ arguments->autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type);
- // At the last, move a value defined in autoload to constant table:
- if (RTEST(state->result)) {
- // Can help to test race conditions:
- // rb_thread_schedule();
+ arguments->result = rb_funcall(rb_vm_top_self(), rb_intern("require"), 1, arguments->autoload_data->feature);
- struct autoload_const *next;
+ return arguments->result;
+}
- ccan_list_for_each_safe(&ele->constants, ac, next, cnode) {
- if (ac->value != Qundef) {
- autoload_const_set(ac);
- }
- }
- }
+static VALUE
+autoload_apply_constants(VALUE _arguments)
+{
+ struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
- // Reset the autoload state - i.e. clear our state from the autoload data:
- ele->state = 0;
- ele->fork_gen = 0;
+ RUBY_DEBUG_THREAD_SCHEDULE();
+
+ if (arguments->result == Qtrue) {
+ struct autoload_const *autoload_const;
+ struct autoload_const *next;
- rb_mutex_unlock(autoload_mutex);
+ // Iterate over all constants and assign them:
+ ccan_list_for_each_safe(&arguments->autoload_data->constants, autoload_const, next, cnode) {
+ if (autoload_const->value != Qundef) {
+ autoload_const_set(autoload_const);
+ }
+ }
}
- rb_mutex_unlock(mutex);
+ // Since the feature is now loaded, delete the autoload data for it:
+ rb_hash_delete(autoload_features, arguments->autoload_data->feature);
- return 0; /* ignored */
+ return Qtrue;
}
-struct autoload_load_arguments {
- VALUE module;
- ID name;
- struct autoload_state *state;
-};
+static VALUE
+autoload_feature_require_ensure(VALUE _arguments)
+{
+ return rb_mutex_synchronize(autoload_mutex, autoload_apply_constants, _arguments);
+}
static VALUE
-autoload_load_synchronized(VALUE _arguments)
+autoload_try_load(VALUE _arguments)
{
struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
- VALUE load;
- const char *loading = 0, *src;
- struct autoload_data_i *ele;
- struct autoload_const *ac;
+ // We have tried to require the autoload feature, so we shouldn't bother trying again in any other threads. More specifically, `arguments->result` starts of as nil, but then contains the result of `require` which is either true or false. Provided it's not nil, it means some other thread has got as far as evaluating the require statement completely.
+ if (arguments->result != Qnil) arguments->result;
- if (!autoload_defined_p(arguments->module, arguments->name)) {
- return Qfalse;
- }
-
- load = check_autoload_required(arguments->module, arguments->name, &loading);
- if (!load) {
- return Qfalse;
- }
-
- src = rb_sourcefile();
- if (src && loading && strcmp(src, loading) == 0) {
- return Qfalse;
- }
+ // Try to require the autoload feature:
+ rb_ensure(autoload_feature_require, _arguments, autoload_feature_require_ensure, _arguments);
- /* set ele->state for a marker of autoloading thread */
- if (!(ele = get_autoload_data(load, &ac))) {
- return Qfalse;
- }
+ // After we loaded the feature, if the constant is not defined, we remove it completely:
+ rb_const_entry_t *ce = rb_const_lookup(arguments->module, arguments->name);
- struct autoload_state * state = arguments->state;
- state->ac = ac;
+ if (!ce || ce->value == Qundef) {
+ // Absolutely ensure that any other threads will bail out, returning false:
+ arguments->result = Qfalse;
- if (!ele->state) {
- state->mutex = rb_mutex_new();
- ele->fork_gen = GET_VM()->fork_gen;
- ele->state = state;
- }
- else if (rb_mutex_owned_p(ele->state->mutex)) {
- return Qfalse;
+ rb_const_remove(arguments->module, arguments->name);
} else {
- state->mutex = ele->state->mutex;
+ // Otherwise, it was loaded, copy the flags from the autoload constant:
+ ce->flag |= arguments->flag;
}
- return load;
+ return arguments->result;
}
VALUE
@@ -2583,46 +2652,24 @@ rb_autoload_load(VALUE module, ID name)
return Qfalse;
}
- // At this point, we assume there might be autoloading.
-
+ // At this point, we assume there might be autoloading, so fail if it's ractor:
if (UNLIKELY(!rb_ractor_main_p())) {
rb_raise(rb_eRactorUnsafeError, "require by autoload on non-main Ractor is not supported (%s)", rb_id2name(name));
}
// This state is stored on thes stack and is used during the autoload process.
- struct autoload_state state = {.mutex = Qnil, .result = Qfalse};
- struct autoload_load_arguments arguments = {.module = module, .name = name, .state = &state};
+ struct autoload_load_arguments arguments = {.module = module, .name = name, .mutex = Qnil, .result = Qnil};
// Figure out whether we can autoload the named constant:
- VALUE load = rb_mutex_synchronize(autoload_mutex, autoload_load_synchronized, (VALUE)&arguments);
+ VALUE load = rb_mutex_synchronize(autoload_mutex, autoload_load_needed, (VALUE)&arguments);
// This confirms whether autoloading is required or not:
if (load == Qfalse) return load;
- // Every thread that tries to autoload a variable will stop here:
- rb_mutex_lock(state.mutex);
-
- // Every thread goes through this process, but only one of them will ultimately require the file.
-
- int flag = 0;
-
- // Capture any flags on the specified "Qundef" constant so we can re-apply them later:
- if (ce) {
- flag = ce->flag & (CONST_DEPRECATED | CONST_VISIBILITY_MASK);
- }
-
- VALUE result = rb_ensure(autoload_require, (VALUE)&state, autoload_reset, (VALUE)&state);
+ arguments.flag = ce->flag & (CONST_DEPRECATED | CONST_VISIBILITY_MASK);
- // If we did not apply the constant after requiring it, remove it:
- if (!(ce = rb_const_lookup(module, name)) || ce->value == Qundef) {
- rb_const_remove(module, name);
- }
- else if (flag > 0) {
- // Re-apply any flags:
- ce->flag |= flag;
- }
-
- return result;
+ // Only one thread will enter here at a time:
+ return rb_mutex_synchronize(arguments.mutex, autoload_try_load, (VALUE)&arguments);
}
VALUE
@@ -2635,7 +2682,7 @@ VALUE
rb_autoload_at_p(VALUE mod, ID id, int recur)
{
VALUE load;
- struct autoload_data_i *ele;
+ struct autoload_data *ele;
while (!autoload_defined_p(mod, id)) {
if (!recur) return Qnil;
@@ -2862,23 +2909,27 @@ rb_const_remove(VALUE mod, ID id)
rb_const_entry_t *ce;
rb_check_frozen(mod);
+
ce = rb_const_lookup(mod, id);
if (!ce || !rb_id_table_delete(RCLASS_CONST_TBL(mod), id)) {
- if (rb_const_defined_at(mod, id)) {
- rb_name_err_raise("cannot remove %2$s::%1$s",
- mod, ID2SYM(id));
- }
+ if (rb_const_defined_at(mod, id)) {
+ rb_name_err_raise("cannot remove %2$s::%1$s", mod, ID2SYM(id));
+ }
+
undefined_constant(mod, ID2SYM(id));
}
rb_clear_constant_cache_for_id(id);
val = ce->value;
+
if (val == Qundef) {
- autoload_delete(mod, id);
- val = Qnil;
+ autoload_delete(mod, id);
+ val = Qnil;
}
- xfree(ce);
+
+ ruby_xfree(ce);
+
return val;
}
@@ -3162,7 +3213,7 @@ const_set(VALUE klass, ID id, VALUE val)
}
else {
struct autoload_const ac = {
- .mod = klass, .id = id,
+ .module = klass, .name = id,
.value = val, .flag = CONST_PUBLIC,
/* fill the rest with 0 */
};
@@ -3207,10 +3258,10 @@ rb_const_set(VALUE klass, ID id, VALUE val)
const_added(klass, id);
}
-static struct autoload_data_i *
-current_autoload_data(VALUE mod, ID id, struct autoload_const **acp)
+static struct autoload_data *
+autoload_data_for_named_constant(VALUE mod, ID id, struct autoload_const **acp)
{
- struct autoload_data_i *ele;
+ struct autoload_data *ele;
VALUE load = autoload_data(mod, id);
if (!load) return 0;
ele = get_autoload_data(load, acp);
@@ -3227,9 +3278,9 @@ static void
const_tbl_update(struct autoload_const *ac, int autoload_force)
{
VALUE value;
- VALUE klass = ac->mod;
+ VALUE klass = ac->module;
VALUE val = ac->value;
- ID id = ac->id;
+ ID id = ac->name;
struct rb_id_table *tbl = RCLASS_CONST_TBL(klass);
rb_const_flag_t visibility = ac->flag;
rb_const_entry_t *ce;
@@ -3237,12 +3288,13 @@ const_tbl_update(struct autoload_const *ac, int autoload_force)
if (rb_id_table_lookup(tbl, id, &value)) {
ce = (rb_const_entry_t *)value;
if (ce->value == Qundef) {
- struct autoload_data_i *ele = current_autoload_data(klass, id, &ac);
+ RUBY_ASSERT_CRITICAL_SECTION_ENTER();
+ struct autoload_data *ele = autoload_data_for_named_constant(klass, id, &ac);
if (!autoload_force && ele) {
rb_clear_constant_cache_for_id(id);
- ac->value = val; /* autoload_i is non-WB-protected */
+ ac->value = val; /* autoload_data is non-WB-protected */
ac->file = rb_source_location(&ac->line);
}
else {
@@ -3253,6 +3305,7 @@ const_tbl_update(struct autoload_const *ac, int autoload_force)
RB_OBJ_WRITE(klass, &ce->file, ac->file);
ce->line = ac->line;
}
+ RUBY_ASSERT_CRITICAL_SECTION_LEAVE();
return;
}
else {
@@ -3295,7 +3348,7 @@ rb_define_const(VALUE klass, const char *name, VALUE val)
ID id = rb_intern(name);
if (!rb_is_const_id(id)) {
- rb_warn("rb_define_const: invalid name `%s' for constant", name);
+ rb_warn("rb_define_const: invalid name `%s' for constant", name);
}
rb_gc_register_mark_object(val);
rb_const_set(klass, id, val);
@@ -3333,9 +3386,9 @@ set_const_visibility(VALUE mod, int argc, const VALUE *argv,
ce->flag &= ~mask;
ce->flag |= flag;
if (ce->value == Qundef) {
- struct autoload_data_i *ele;
+ struct autoload_data *ele;
- ele = current_autoload_data(mod, id, &ac);
+ ele = autoload_data_for_named_constant(mod, id, &ac);
if (ele) {
ac->flag &= ~mask;
ac->flag |= flag;