Skip to content

Commit 60d45b2

Browse files
authored
Restore implicit relationship between autoload_const and autoload_data during GC. (#5911)
1 parent 8a907da commit 60d45b2

File tree

2 files changed

+136
-62
lines changed

2 files changed

+136
-62
lines changed

test/ruby/test_autoload.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,26 @@ def remove_autoload_constant
492492
TestAutoload.class_eval {remove_const(:AutoloadTest)} if defined? TestAutoload::AutoloadTest
493493
end
494494

495+
def test_autoload_module_gc
496+
Dir.mktmpdir('autoload') do |tmpdir|
497+
autoload_path = File.join(tmpdir, "autoload_module_gc.rb")
498+
File.write(autoload_path, "X = 1; Y = 2;")
499+
500+
x = Module.new
501+
x.autoload :X, "./feature.rb"
502+
503+
1000.times do
504+
y = Module.new
505+
y.autoload :Y, "./feature.rb"
506+
end
507+
508+
x = y = nil
509+
510+
# Ensure the internal data structures are cleaned up correctly / don't crash:
511+
GC.start
512+
end
513+
end
514+
495515
def test_autoload_parallel_race
496516
Dir.mktmpdir('autoload') do |tmpdir|
497517
autoload_path = File.join(tmpdir, "autoload_parallel_race.rb")

variable.c

Lines changed: 116 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,6 +2176,22 @@ autoload_data_mark(void *ptr)
21762176

21772177
rb_gc_mark_movable(p->feature);
21782178
rb_gc_mark_movable(p->mutex);
2179+
2180+
// Allow GC to free us if no modules refer to this via autoload_const.autoload_data
2181+
if (ccan_list_empty(&p->constants)) {
2182+
rb_hash_delete(autoload_features, p->feature);
2183+
}
2184+
}
2185+
2186+
static void
2187+
autoload_data_free(void *ptr)
2188+
{
2189+
struct autoload_data *p = ptr;
2190+
2191+
// We may leak some memory at VM shutdown time, no big deal...?
2192+
if (ccan_list_empty(&p->constants)) {
2193+
ruby_xfree(p);
2194+
}
21792195
}
21802196

21812197
static size_t
@@ -2186,12 +2202,12 @@ autoload_data_memsize(const void *ptr)
21862202

21872203
static const rb_data_type_t autoload_data_type = {
21882204
"autoload_data",
2189-
{autoload_data_mark, ruby_xfree, autoload_data_memsize, autoload_data_compact},
2205+
{autoload_data_mark, autoload_data_free, autoload_data_memsize, autoload_data_compact},
21902206
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
21912207
};
21922208

21932209
static void
2194-
autoload_c_compact(void *ptr)
2210+
autoload_const_compact(void *ptr)
21952211
{
21962212
struct autoload_const *ac = ptr;
21972213

@@ -2202,7 +2218,7 @@ autoload_c_compact(void *ptr)
22022218
}
22032219

22042220
static void
2205-
autoload_c_mark(void *ptr)
2221+
autoload_const_mark(void *ptr)
22062222
{
22072223
struct autoload_const *ac = ptr;
22082224

@@ -2213,41 +2229,52 @@ autoload_c_mark(void *ptr)
22132229
}
22142230

22152231
static size_t
2216-
autoload_c_memsize(const void *ptr)
2232+
autoload_const_memsize(const void *ptr)
22172233
{
22182234
return sizeof(struct autoload_const);
22192235
}
22202236

2237+
static void
2238+
autoload_const_free(void *ptr)
2239+
{
2240+
struct autoload_const *autoload_const = ptr;
2241+
2242+
ccan_list_del(&autoload_const->cnode);
2243+
ruby_xfree(ptr);
2244+
}
2245+
22212246
static const rb_data_type_t autoload_const_type = {
22222247
"autoload_const",
2223-
{autoload_c_mark, ruby_xfree, autoload_c_memsize, autoload_c_compact,},
2248+
{autoload_const_mark, autoload_const_free, autoload_const_memsize, autoload_const_compact,},
22242249
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
22252250
};
22262251

22272252
static struct autoload_data *
2228-
get_autoload_data(VALUE acv, struct autoload_const **acp)
2253+
get_autoload_data(VALUE autoload_const_value, struct autoload_const **autoload_const_pointer)
22292254
{
2230-
struct autoload_const *ac = rb_check_typeddata(acv, &autoload_const_type);
2231-
struct autoload_data *ele;
2255+
struct autoload_const *autoload_const = rb_check_typeddata(autoload_const_value, &autoload_const_type);
2256+
2257+
struct autoload_data *autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type);
22322258

2233-
ele = rb_check_typeddata(ac->autoload_data_value, &autoload_data_type);
22342259
/* do not reach across stack for ->state after forking: */
2235-
if (ele && ele->fork_gen != GET_VM()->fork_gen) {
2236-
ele->mutex = Qnil;
2237-
ele->fork_gen = 0;
2260+
if (autoload_data && autoload_data->fork_gen != GET_VM()->fork_gen) {
2261+
autoload_data->mutex = Qnil;
2262+
autoload_data->fork_gen = 0;
22382263
}
2239-
if (acp) *acp = ac;
2240-
return ele;
2264+
2265+
if (autoload_const_pointer) *autoload_const_pointer = autoload_const;
2266+
2267+
return autoload_data;
22412268
}
22422269

22432270
RUBY_FUNC_EXPORTED void
2244-
rb_autoload(VALUE mod, ID id, const char *file)
2271+
rb_autoload(VALUE module, ID name, const char *feature)
22452272
{
2246-
if (!file || !*file) {
2247-
rb_raise(rb_eArgError, "empty file name");
2273+
if (!feature || !*feature) {
2274+
rb_raise(rb_eArgError, "empty feature name");
22482275
}
22492276

2250-
rb_autoload_str(mod, id, rb_fstring_cstr(file));
2277+
rb_autoload_str(module, name, rb_fstring_cstr(feature));
22512278
}
22522279

22532280
static void const_set(VALUE klass, ID id, VALUE val);
@@ -2256,25 +2283,47 @@ static void const_added(VALUE klass, ID const_name);
22562283
struct autoload_arguments {
22572284
VALUE module;
22582285
ID name;
2259-
2260-
VALUE path;
2286+
VALUE feature;
22612287
};
22622288

22632289
static VALUE
2264-
autoload_feature_lookup_or_create(VALUE file)
2290+
autoload_feature_lookup_or_create(VALUE feature, struct autoload_data **autoload_data_pointer)
22652291
{
2266-
VALUE ad = rb_hash_aref(autoload_features, file);
2267-
struct autoload_data *ele;
2292+
RUBY_ASSERT_MUTEX_OWNED(autoload_mutex);
2293+
RUBY_ASSERT_CRITICAL_SECTION_ENTER();
22682294

2269-
if (NIL_P(ad)) {
2270-
ad = TypedData_Make_Struct(0, struct autoload_data, &autoload_data_type, ele);
2271-
ele->feature = file;
2272-
ele->mutex = Qnil;
2273-
ccan_list_head_init(&ele->constants);
2274-
rb_hash_aset(autoload_features, file, ad);
2295+
VALUE autoload_data_value = rb_hash_aref(autoload_features, feature);
2296+
struct autoload_data *autoload_data;
2297+
2298+
if (NIL_P(autoload_data_value)) {
2299+
autoload_data_value = TypedData_Make_Struct(0, struct autoload_data, &autoload_data_type, autoload_data);
2300+
autoload_data->feature = feature;
2301+
autoload_data->mutex = Qnil;
2302+
ccan_list_head_init(&autoload_data->constants);
2303+
2304+
if (autoload_data_pointer) *autoload_data_pointer = autoload_data;
2305+
2306+
rb_hash_aset(autoload_features, feature, autoload_data_value);
2307+
} else if (autoload_data_pointer) {
2308+
*autoload_data_pointer = rb_check_typeddata(autoload_data_value, &autoload_data_type);
22752309
}
22762310

2277-
return ad;
2311+
RUBY_ASSERT_CRITICAL_SECTION_LEAVE();
2312+
return autoload_data_value;
2313+
}
2314+
2315+
static VALUE
2316+
autoload_feature_clear_if_empty(VALUE argument)
2317+
{
2318+
RUBY_ASSERT_MUTEX_OWNED(autoload_mutex);
2319+
2320+
struct autoload_data *autoload_data = (struct autoload_data *)argument;
2321+
2322+
if (ccan_list_empty(&autoload_data->constants)) {
2323+
rb_hash_delete(autoload_features, autoload_data->feature);
2324+
}
2325+
2326+
return Qnil;
22782327
}
22792328

22802329
static struct st_table* autoload_table_lookup_or_create(VALUE module) {
@@ -2313,10 +2362,10 @@ autoload_synchronized(VALUE _arguments)
23132362
struct st_table *autoload_table = autoload_table_lookup_or_create(arguments->module);
23142363

23152364
// Ensure the string is uniqued since we use an identity lookup:
2316-
VALUE path = rb_fstring(arguments->path);
2365+
VALUE feature = rb_fstring(arguments->feature);
23172366

2318-
VALUE autoload_data_value = autoload_feature_lookup_or_create(path);
2319-
struct autoload_data *autoload_data = rb_check_typeddata(autoload_data_value, &autoload_data_type);
2367+
struct autoload_data *autoload_data;
2368+
VALUE autoload_data_value = autoload_feature_lookup_or_create(feature, &autoload_data);
23202369

23212370
{
23222371
struct autoload_const *autoload_const;
@@ -2334,59 +2383,63 @@ autoload_synchronized(VALUE _arguments)
23342383
}
23352384

23362385
void
2337-
rb_autoload_str(VALUE mod, ID id, VALUE file)
2386+
rb_autoload_str(VALUE module, ID name, VALUE feature)
23382387
{
2339-
if (!rb_is_const_id(id)) {
2340-
rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(id));
2388+
if (!rb_is_const_id(name)) {
2389+
rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(name));
23412390
}
23422391

2343-
Check_Type(file, T_STRING);
2344-
if (!RSTRING_LEN(file)) {
2345-
rb_raise(rb_eArgError, "empty file name");
2392+
Check_Type(feature, T_STRING);
2393+
if (!RSTRING_LEN(feature)) {
2394+
rb_raise(rb_eArgError, "empty feature name");
23462395
}
23472396

23482397
struct autoload_arguments arguments = {
2349-
.module = mod,
2350-
.name = id,
2351-
.path = file,
2398+
.module = module,
2399+
.name = name,
2400+
.feature = feature,
23522401
};
23532402

23542403
VALUE result = rb_mutex_synchronize(autoload_mutex, autoload_synchronized, (VALUE)&arguments);
23552404

23562405
if (result == Qtrue) {
2357-
const_added(mod, id);
2406+
const_added(module, name);
23582407
}
23592408
}
23602409

23612410
static void
2362-
autoload_delete(VALUE mod, ID id)
2411+
autoload_delete(VALUE module, ID name)
23632412
{
2364-
st_data_t val, load = 0, n = id;
2413+
RUBY_ASSERT_CRITICAL_SECTION_ENTER();
2414+
2415+
st_data_t value, load = 0, key = name;
23652416

2366-
if (st_lookup(RCLASS_IV_TBL(mod), (st_data_t)autoload, &val)) {
2367-
struct st_table *tbl = check_autoload_table((VALUE)val);
2368-
struct autoload_data *ele;
2369-
struct autoload_const *ac;
2417+
if (st_lookup(RCLASS_IV_TBL(module), (st_data_t)autoload, &value)) {
2418+
struct st_table *table = check_autoload_table((VALUE)value);
23702419

2371-
st_delete(tbl, &n, &load);
2420+
st_delete(table, &key, &load);
23722421

23732422
/* Qfalse can indicate already deleted */
23742423
if (load != Qfalse) {
2375-
ele = get_autoload_data((VALUE)load, &ac);
2376-
if (!ele) VM_ASSERT(ele);
2424+
struct autoload_const *autoload_const;
2425+
get_autoload_data((VALUE)load, &autoload_const);
2426+
23772427
/*
23782428
* we must delete here to avoid "already initialized" warnings
23792429
* with parallel autoload. Using list_del_init here so list_del
2380-
* works in autoload_c_free
2430+
* works in autoload_const_free
23812431
*/
2382-
ccan_list_del_init(&ac->cnode);
2432+
ccan_list_del_init(&autoload_const->cnode);
23832433

2384-
if (tbl->num_entries == 0) {
2385-
n = autoload;
2386-
st_delete(RCLASS_IV_TBL(mod), &n, &val);
2434+
// If the autoload table is empty, we can delete it.
2435+
if (table->num_entries == 0) {
2436+
name = autoload;
2437+
st_delete(RCLASS_IV_TBL(module), &name, &value);
23872438
}
23882439
}
23892440
}
2441+
2442+
RUBY_ASSERT_CRITICAL_SECTION_LEAVE();
23902443
}
23912444

23922445
static int
@@ -2589,9 +2642,9 @@ autoload_feature_require(VALUE _arguments)
25892642
static VALUE
25902643
autoload_apply_constants(VALUE _arguments)
25912644
{
2592-
struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
2645+
RUBY_ASSERT_CRITICAL_SECTION_ENTER();
25932646

2594-
RUBY_DEBUG_THREAD_SCHEDULE();
2647+
struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
25952648

25962649
if (arguments->result == Qtrue) {
25972650
struct autoload_const *autoload_const;
@@ -2605,8 +2658,7 @@ autoload_apply_constants(VALUE _arguments)
26052658
}
26062659
}
26072660

2608-
// Since the feature is now loaded, delete the autoload data for it:
2609-
rb_hash_delete(autoload_features, arguments->autoload_data->feature);
2661+
RUBY_ASSERT_CRITICAL_SECTION_LEAVE();
26102662

26112663
return Qtrue;
26122664
}
@@ -2623,7 +2675,7 @@ autoload_try_load(VALUE _arguments)
26232675
struct autoload_load_arguments *arguments = (struct autoload_load_arguments*)_arguments;
26242676

26252677
// 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.
2626-
if (arguments->result != Qnil) arguments->result;
2678+
if (arguments->result != Qnil) return arguments->result;
26272679

26282680
// Try to require the autoload feature:
26292681
rb_ensure(autoload_feature_require, _arguments, autoload_feature_require_ensure, _arguments);
@@ -2672,6 +2724,8 @@ rb_autoload_load(VALUE module, ID name)
26722724

26732725
// Only one thread will enter here at a time:
26742726
return rb_mutex_synchronize(arguments.mutex, autoload_try_load, (VALUE)&arguments);
2727+
2728+
// rb_mutex_synchronize(autoload_mutex, autoload_feature_clear_if_empty, (VALUE)&arguments.autoload_data);
26752729
}
26762730

26772731
VALUE

0 commit comments

Comments
 (0)