Skip to content

Commit 1173fb9

Browse files
qinsoonwks
authored andcommitted
Reintroduce GC disable/enable API
The MMTk core reintroduced the `disable_collection` and `enable_collection`. They are implemented in MMTk core and has a built-in depth counter which requires invocations of `enable_collection` to be paired with the same number of `disable_collection` invocations. On the Ruby side, we implemented CRuby-style `GC.disable` and `GC.enable` methods without depth counting on top of MMTk's new API, ensuring existing test cases pass. `GC.start` will not trigger GC when GC is disabled, making it behave differently from CRuby's default GC. Some related test cases are excluded when using MMTk. Related PRs: - mmtk/mmtk-core#1457 - mmtk/mmtk-ruby#160
1 parent ed1585b commit 1173fb9

8 files changed

Lines changed: 185 additions & 14 deletions

File tree

gc.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5894,6 +5894,29 @@ rb_gc_after_fork(rb_pid_t pid)
58945894
rb_gc_impl_after_fork(rb_gc_get_objspace(), pid);
58955895
}
58965896

5897+
bool
5898+
rb_gc_during_gc_could_malloc_region_start()
5899+
{
5900+
// Do nothing when using MMTk.
5901+
WHEN_USING_MMTK({
5902+
return false;
5903+
})
5904+
5905+
return rb_gc_disable_no_rest();
5906+
}
5907+
5908+
void rb_gc_during_gc_could_malloc_region_end(bool already_disabled)
5909+
{
5910+
// Do nothing when using MMTk.
5911+
WHEN_USING_MMTK({
5912+
return;
5913+
})
5914+
5915+
if (already_disabled == Qfalse) {
5916+
rb_gc_enable();
5917+
}
5918+
}
5919+
58975920
bool
58985921
rb_gc_obj_shareable_p(VALUE obj)
58995922
{

gc/default/default.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,7 @@ bool
16351635
rb_gc_impl_gc_enabled_p(void *objspace_ptr)
16361636
{
16371637
WHEN_USING_MMTK({
1638-
return mmtk_is_collection_enabled();
1638+
return rb_mmtk_is_collection_enabled();
16391639
})
16401640

16411641
rb_objspace_t *objspace = objspace_ptr;
@@ -1646,7 +1646,7 @@ void
16461646
rb_gc_impl_gc_enable(void *objspace_ptr)
16471647
{
16481648
WHEN_USING_MMTK({
1649-
mmtk_enable_collection();
1649+
rb_mmtk_enable_collection();
16501650
return;
16511651
})
16521652

@@ -1659,7 +1659,7 @@ void
16591659
rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc)
16601660
{
16611661
WHEN_USING_MMTK({
1662-
mmtk_disable_collection();
1662+
rb_mmtk_disable_collection();
16631663
return;
16641664
})
16651665

@@ -3554,7 +3554,7 @@ rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr)
35543554
rb_gc_set_obj_free_on_exit_started();
35553555

35563556
// Disable GC like the default GC does.
3557-
mmtk_disable_collection();
3557+
rb_mmtk_disable_collection();
35583558

35593559
// Running data/file finalizers on exit, the MMTk style.
35603560
// When using MMTk, we maintain a list of obj_free candidates in the Rust code,
@@ -7542,10 +7542,10 @@ rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool i
75427542
rb_objspace_t *objspace = objspace_ptr;
75437543
#if USE_MMTK
75447544
if (rb_mmtk_enabled_p()) {
7545-
// Note: GC.start will initiates garbage collection even if manually disabled.
7546-
// Therefore, we need to force GC.
7547-
// We do a full-heap GC if full_mark is true. In StickyImmix this may or may not trigger defragmentation.
7548-
// There is currently no way to force a defragmentation GC.
7545+
// Note: In CRuby, manual invocations of GC.start will still initiate garbage collection,
7546+
// but in MMTk, disabling GC means GC cannot be triggered in any way.
7547+
// The following statement may not necessarily trigger GC.
7548+
// TODO: Find a way to reconcile them.
75497549
mmtk_handle_user_collection_request(GET_THREAD(), true, full_mark);
75507550

75517551
gc_finalize_deferred(objspace);

internal/gc.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,10 @@ struct rb_gc_object_metadata_entry {
175175
* necessary. */
176176
#define DURING_GC_COULD_MALLOC_REGION_START() \
177177
assert(rb_during_gc()); \
178-
VALUE _already_disabled = rb_gc_disable_no_rest()
178+
VALUE _already_disabled = rb_gc_during_gc_could_malloc_region_start()
179179

180180
#define DURING_GC_COULD_MALLOC_REGION_END() \
181-
if (_already_disabled == Qfalse) rb_gc_enable()
181+
rb_gc_during_gc_could_malloc_region_end(_already_disabled)
182182

183183
/* gc.c */
184184
RUBY_ATTR_MALLOC void *ruby_mimmalloc(size_t size);
@@ -278,6 +278,10 @@ void rb_gc_update_values(long n, VALUE *values);
278278
const char *rb_gc_active_gc_name(void);
279279
int rb_gc_modular_gc_loaded_p(void);
280280

281+
/* Defined in gc.c; used by object_tracing.c */
282+
bool rb_gc_during_gc_could_malloc_region_start();
283+
void rb_gc_during_gc_could_malloc_region_end(bool already_disabled);
284+
281285
RUBY_SYMBOL_EXPORT_END
282286

283287
static inline VALUE

internal/mmtk.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,9 @@ void mmtk_prepare_to_fork(void);
220220

221221
void mmtk_after_fork(MMTk_VMThread tls);
222222

223-
void mmtk_enable_collection(void);
223+
bool mmtk_enable_collection(void);
224224

225-
void mmtk_disable_collection(void);
225+
bool mmtk_disable_collection(void);
226226

227227
bool mmtk_is_collection_enabled(void);
228228

internal/mmtk_support.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ void rb_mmtk_gc_probe_slowpath(bool enter);
178178
// xmalloc accounting
179179
void rb_mmtk_xmalloc_increase_body(size_t new_size, size_t old_size);
180180

181+
// Disabling / enabling GC
182+
void rb_mmtk_disable_collection();
183+
void rb_mmtk_enable_collection();
184+
bool rb_mmtk_is_collection_enabled();
185+
186+
// Block for GC
187+
void rb_mmtk_block_for_gc(MMTk_VMMutatorThread tls);
188+
181189
// Commandline options parsing
182190
void rb_mmtk_pre_process_opts(int argc, char **argv);
183191
void rb_mmtk_post_process_opts(const char *arg);

mmtk_support.c

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,19 @@ struct rb_mmtk_xmalloc_accounting{
112112
size_t malloc_total;
113113
} rb_mmtk_xmalloc_accounting_t;
114114

115+
// States for disabling GC
116+
struct RubyMMTKGCDisablingState {
117+
// This mutex only protects the `gc_is_disabled` field.
118+
pthread_mutex_t mutex;
119+
bool gc_is_disabled;
120+
};
121+
115122
struct RubyMMTKGlobal {
116123
pthread_mutex_t mutex;
117124
pthread_cond_t cond_world_stopped;
118125
pthread_cond_t cond_world_started;
119126
rb_atomic_t mutator_blocking_count;
127+
struct RubyMMTKGCDisablingState gc_disabling_state;
120128
unsigned int fork_hook_vm_lock_lev;
121129
bool world_stopped;
122130
size_t start_the_world_count;
@@ -125,6 +133,10 @@ struct RubyMMTKGlobal {
125133
.cond_world_stopped = PTHREAD_COND_INITIALIZER,
126134
.cond_world_started = PTHREAD_COND_INITIALIZER,
127135
.mutator_blocking_count = 0,
136+
.gc_disabling_state = (struct RubyMMTKGCDisablingState) {
137+
.mutex = PTHREAD_MUTEX_INITIALIZER,
138+
.gc_is_disabled = false,
139+
},
128140
.fork_hook_vm_lock_lev = 0,
129141
.world_stopped = false,
130142
.start_the_world_count = 0,
@@ -1802,7 +1814,83 @@ rb_mmtk_block_for_gc_internal(void *unused)
18021814
RUBY_DEBUG_LOG("GC finished.");
18031815
}
18041816

1805-
static void
1817+
////////////////////////////////////////////////////////////////////////////////
1818+
// Disabling / enabling GC
1819+
////////////////////////////////////////////////////////////////////////////////
1820+
1821+
void
1822+
rb_mmtk_disable_collection()
1823+
{
1824+
// Only mutator threads can disable GC.
1825+
rb_mmtk_assert_mutator();
1826+
1827+
struct RubyMMTKGCDisablingState *state = &rb_mmtk_global.gc_disabling_state;
1828+
1829+
for (;;) {
1830+
pthread_mutex_lock(&state->mutex);
1831+
bool gc_is_disabled = state->gc_is_disabled;
1832+
if (!gc_is_disabled) {
1833+
// GC is not disabled, yet. We try once to disable it.
1834+
bool successful = mmtk_disable_collection();
1835+
if (successful) {
1836+
// If it is successful, we have disabled GC. We set the state value.
1837+
state->gc_is_disabled = true;
1838+
gc_is_disabled = true;
1839+
}
1840+
}
1841+
pthread_mutex_unlock(&state->mutex);
1842+
1843+
if (gc_is_disabled) {
1844+
// Either GC has already been disabled before this call,
1845+
// or we successfully disabled GC. We can return.
1846+
return;
1847+
} else {
1848+
// Otherwise, another thread must have triggered GC, and GC will start soon.
1849+
// We block until the next GC finishes and try again.
1850+
rb_mmtk_block_for_gc((MMTk_VMMutatorThread)GET_RACTOR());
1851+
}
1852+
}
1853+
}
1854+
1855+
void
1856+
rb_mmtk_enable_collection()
1857+
{
1858+
// Only mutator threads can enable GC.
1859+
rb_mmtk_assert_mutator();
1860+
1861+
struct RubyMMTKGCDisablingState *state = &rb_mmtk_global.gc_disabling_state;
1862+
1863+
pthread_mutex_lock(&state->mutex);
1864+
bool gc_is_disabled = state->gc_is_disabled;
1865+
if (gc_is_disabled) {
1866+
// GC is already disabled. We enable GC. This is non-blocking.
1867+
mmtk_enable_collection();
1868+
state->gc_is_disabled = false;
1869+
}
1870+
pthread_mutex_unlock(&state->mutex);
1871+
1872+
}
1873+
1874+
bool
1875+
rb_mmtk_is_collection_enabled()
1876+
{
1877+
// Only mutator threads can enable GC.
1878+
rb_mmtk_assert_mutator();
1879+
1880+
struct RubyMMTKGCDisablingState *state = &rb_mmtk_global.gc_disabling_state;
1881+
1882+
pthread_mutex_lock(&state->mutex);
1883+
bool gc_is_disabled = state->gc_is_disabled;
1884+
pthread_mutex_unlock(&state->mutex);
1885+
1886+
return !gc_is_disabled;
1887+
}
1888+
1889+
////////////////////////////////////////////////////////////////////////////////
1890+
// Blocking for GC
1891+
////////////////////////////////////////////////////////////////////////////////
1892+
1893+
void
18061894
rb_mmtk_block_for_gc(MMTk_VMMutatorThread tls)
18071895
{
18081896
rb_mmtk_assert_mutator();

test/.excludes-mmtk/TestGc.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
exclude(:test_gc_config_full_mark_by_default, "testing behaviour specific to default GC")
55
exclude(:test_gc_config_invalid_args, "testing behaviour specific to default GC")
66
exclude(:test_gc_config_setting_returns_updated_config_hash, "testing behaviour specific to default GC")
7+
exclude(:test_gc_disabled_start, "When GC is disabled on MMTk, GC cannot be triggered in any way.")
78
exclude(:test_gc_internals, "testing behaviour specific to default GC")
89
exclude(:test_gc_parameter, "testing behaviour specific to default GC")
910
exclude(:test_gc_parameter_init_slots, "testing behaviour specific to default GC")

vm.c

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4612,7 +4612,27 @@ Init_VM(void)
46124612
#if USE_MMTK
46134613
if (rb_mmtk_enabled_p()) {
46144614
// Now that the VM says it's time to enable GC, we enable GC for MMTk, too.
4615+
4616+
// We don't use rb_bug just to be safe. See Init_BareVM.
4617+
4618+
// Assert MMTk-level raw state.
4619+
if (mmtk_is_collection_enabled()) {
4620+
fprintf(stderr, "ERROR: GC is enabled at the MMTk level, but should still be disabled.\n");
4621+
abort();
4622+
}
4623+
4624+
// We enable GC at the MMTk level.
46154625
mmtk_enable_collection();
4626+
4627+
// Assert both MMTk-level raw state and binding-level wrapped state.
4628+
if (!mmtk_is_collection_enabled()) {
4629+
fprintf(stderr, "ERROR: GC is disabled at the MMTk level, but should be enabled now.\n");
4630+
abort();
4631+
}
4632+
if (!rb_mmtk_is_collection_enabled()) {
4633+
fprintf(stderr, "ERROR: GC is disabled at the binding level, but should be enabled now.\n");
4634+
abort();
4635+
}
46164636
}
46174637
#endif
46184638

@@ -4674,7 +4694,34 @@ Init_BareVM(void)
46744694
// When creating a ractor, it will bind mutator.
46754695
// We pass NULL as the tls because `Collection::spawn_gc_thread` in the mmtk-ruby binding does not use it anyway.
46764696
mmtk_initialize_collection(NULL);
4677-
// Note: GC is disabled at this moment.
4697+
4698+
// mmtk_initialize_collection() leaves collection enabled; explicitly disable it here so
4699+
// it stays off until Init_VM() re-enables it once the VM is fully bootstrapped. This is
4700+
// the only thread in the process at this point, and no GC has had a chance to run yet
4701+
// (nor could one be requested at this point in bootstrap), so this must always succeed
4702+
// immediately; if it doesn't, something is fundamentally broken, so abort rather than
4703+
// spin or silently continue with GC still enabled.
4704+
//
4705+
// Deliberately not rb_bug() here: rb_bug()'s crash-report path (rb_vm_bugreport()) is
4706+
// documented to be able to trigger a secondary SIGSEGV when walking frames on an
4707+
// abnormal VM state (see the comment in bug_report_file() in error.c), and at this point
4708+
// in bootstrap the main thread's execution context and ractor aren't set up yet -- so
4709+
// reporting via rb_bug() here risks turning a clean diagnostic into a mystery crash.
4710+
4711+
// Assert MMTk-level raw state.
4712+
if (!mmtk_is_collection_enabled()) {
4713+
fprintf(stderr, "ERROR: GC is disabled at the MMTk level, but should be enabled.\n");
4714+
abort();
4715+
}
4716+
4717+
// We disable GC at the MMTk level.
4718+
mmtk_disable_collection();
4719+
4720+
// Assert MMTk-level raw state.
4721+
if (mmtk_is_collection_enabled()) {
4722+
fprintf(stderr, "ERROR: GC is still enabled at the MMTk level, but should be disabled now.\n");
4723+
abort();
4724+
}
46784725
})
46794726

46804727
// setup main thread

0 commit comments

Comments
 (0)