Skip to content

Commit 0ededb3

Browse files
authored
Add API disable_collection() (#474)
* Rename current enable_collection() to initialize_collection() * Implement enable/disable_collection() * Add tests for enable/disable_collection
1 parent e63457c commit 0ededb3

11 files changed

+175
-19
lines changed

src/memory_manager.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub fn start_control_collector<VM: VMBinding>(mmtk: &MMTK<VM>, tls: VMWorkerThre
3838
/// Initialize an MMTk instance. A VM should call this method after creating an [MMTK](../mmtk/struct.MMTK.html)
3939
/// instance but before using any of the methods provided in MMTk. This method will attempt to initialize a
4040
/// logger. If the VM would like to use its own logger, it should initialize the logger before calling this method.
41+
/// Note that, to allow MMTk to do GC properly, `initialize_collection()` needs to be called after this call when
42+
/// the VM's thread system is ready to spawn GC workers.
4143
///
4244
/// Arguments:
4345
/// * `mmtk`: A reference to an MMTk instance to initialize.
@@ -177,20 +179,63 @@ pub fn start_worker<VM: VMBinding>(
177179
worker.run(mmtk);
178180
}
179181

180-
/// Allow MMTk to trigger garbage collection. A VM should only call this method when it is ready for the mechanisms required for
181-
/// collection during the boot process. MMTk will invoke Collection::spawn_worker_thread() to create GC threads during
182-
/// this funciton call.
182+
/// Initialize the scheduler and GC workers that are required for doing garbage collections.
183+
/// This is a mandatory call for a VM during its boot process once its thread system
184+
/// is ready. This should only be called once. This call will invoke Collection::spawn_worker_thread()
185+
/// to create GC threads.
183186
///
184187
/// Arguments:
185188
/// * `mmtk`: A reference to an MMTk instance.
186189
/// * `tls`: The thread that wants to enable the collection. This value will be passed back to the VM in
187190
/// Collection::spawn_worker_thread() so that the VM knows the context.
188-
pub fn enable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>, tls: VMThread) {
191+
pub fn initialize_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>, tls: VMThread) {
192+
assert!(
193+
!mmtk.plan.is_initialized(),
194+
"MMTk collection has been initialized (was initialize_collection() already called before?)"
195+
);
189196
mmtk.scheduler.initialize(mmtk.options.threads, mmtk, tls);
190197
VM::VMCollection::spawn_worker_thread(tls, None); // spawn controller thread
191198
mmtk.plan.base().initialized.store(true, Ordering::SeqCst);
192199
}
193200

201+
/// Allow MMTk to trigger garbage collection when heap is full. This should only be used in pair with disable_collection().
202+
/// See the comments on disable_collection(). If disable_collection() is not used, there is no need to call this function at all.
203+
/// Note this call is not thread safe, only one VM thread should call this.
204+
///
205+
/// Arguments:
206+
/// * `mmtk`: A reference to an MMTk instance.
207+
pub fn enable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
208+
debug_assert!(
209+
!mmtk.plan.should_trigger_gc_when_heap_is_full(),
210+
"enable_collection() is called when GC is already enabled."
211+
);
212+
mmtk.plan
213+
.base()
214+
.trigger_gc_when_heap_is_full
215+
.store(true, Ordering::SeqCst);
216+
}
217+
218+
/// Disallow MMTk to trigger garbage collection. When collection is disabled, you can still allocate through MMTk. But MMTk will
219+
/// not trigger a GC even if the heap is full. In such a case, the allocation will exceed the MMTk's heap size (the soft heap limit).
220+
/// However, there is no guarantee that the physical allocation will succeed, and if it succeeds, there is no guarantee that further allocation
221+
/// will keep succeeding. So if a VM disables collection, it needs to allocate with careful consideration to make sure that the physical memory
222+
/// allows the amount of allocation. We highly recommend not using this method. However, we support this to accomodate some VMs that require this
223+
/// behavior. This call does not disable explicit GCs (through handle_user_collection_request()).
224+
/// Note this call is not thread safe, only one VM thread should call this.
225+
///
226+
/// Arguments:
227+
/// * `mmtk`: A reference to an MMTk instance.
228+
pub fn disable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
229+
debug_assert!(
230+
mmtk.plan.should_trigger_gc_when_heap_is_full(),
231+
"disable_collection() is called when GC is not enabled."
232+
);
233+
mmtk.plan
234+
.base()
235+
.trigger_gc_when_heap_is_full
236+
.store(false, Ordering::SeqCst);
237+
}
238+
194239
/// Process MMTk run-time options.
195240
///
196241
/// Arguments:

src/plan/global.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,12 @@ pub trait Plan: 'static + Sync + Downcast {
252252
self.base().initialized.load(Ordering::SeqCst)
253253
}
254254

255+
fn should_trigger_gc_when_heap_is_full(&self) -> bool {
256+
self.base()
257+
.trigger_gc_when_heap_is_full
258+
.load(Ordering::SeqCst)
259+
}
260+
255261
fn prepare(&mut self, tls: VMWorkerThread);
256262
fn release(&mut self, tls: VMWorkerThread);
257263

@@ -370,8 +376,11 @@ pub enum GcStatus {
370376
BasePlan should contain all plan-related state and functions that are _fundamental_ to _all_ plans. These include VM-specific (but not plan-specific) features such as a code space or vm space, which are fundamental to all plans for a given VM. Features that are common to _many_ (but not intrinsically _all_) plans should instead be included in CommonPlan.
371377
*/
372378
pub struct BasePlan<VM: VMBinding> {
373-
// Whether MMTk is now ready for collection. This is set to true when enable_collection() is called.
379+
/// Whether MMTk is now ready for collection. This is set to true when initialize_collection() is called.
374380
pub initialized: AtomicBool,
381+
/// Should we trigger a GC when the heap is full? It seems this should always be true. However, we allow
382+
/// bindings to temporarily disable GC, at which point, we do not trigger GC even if the heap is full.
383+
pub trigger_gc_when_heap_is_full: AtomicBool,
375384
pub gc_status: Mutex<GcStatus>,
376385
pub last_stress_pages: AtomicUsize,
377386
pub stacks_prepared: AtomicBool,
@@ -503,6 +512,7 @@ impl<VM: VMBinding> BasePlan<VM> {
503512
),
504513

505514
initialized: AtomicBool::new(false),
515+
trigger_gc_when_heap_is_full: AtomicBool::new(true),
506516
gc_status: Mutex::new(GcStatus::NotInGC),
507517
last_stress_pages: AtomicUsize::new(0),
508518
stacks_prepared: AtomicBool::new(false),

src/policy/space.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,14 @@ pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
275275

276276
fn acquire(&self, tls: VMThread, pages: usize) -> Address {
277277
trace!("Space.acquire, tls={:?}", tls);
278-
// Should we poll to attempt to GC? If tls is collector, we cant attempt a GC.
279-
let should_poll = VM::VMActivePlan::is_mutator(tls);
280-
// Is a GC allowed here? enable_collection() has to be called so we know GC is initialized.
281-
let allow_poll = should_poll && VM::VMActivePlan::global().is_initialized();
278+
// Should we poll to attempt to GC?
279+
// - If tls is collector, we cannot attempt a GC.
280+
// - If gc is disabled, we cannot attempt a GC.
281+
let should_poll = VM::VMActivePlan::is_mutator(tls)
282+
&& VM::VMActivePlan::global().should_trigger_gc_when_heap_is_full();
283+
// Is a GC allowed here? If we should poll but are not allowed to poll, we will panic.
284+
// initialize_collection() has to be called so we know GC is initialized.
285+
let allow_gc = should_poll && VM::VMActivePlan::global().is_initialized();
282286

283287
trace!("Reserving pages");
284288
let pr = self.get_page_resource();
@@ -288,8 +292,8 @@ pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
288292

289293
if should_poll && VM::VMActivePlan::global().poll(false, self.as_space()) {
290294
debug!("Collection required");
291-
if !allow_poll {
292-
panic!("Collection is not enabled.");
295+
if !allow_gc {
296+
panic!("GC is not allowed here: collection is not initialized (did you call initialize_collection()?).");
293297
}
294298
pr.clear_request(pages_reserved);
295299
VM::VMCollection::block_for_gc(VMMutatorThread(tls)); // We have checked that this is mutator
@@ -330,8 +334,8 @@ pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
330334
}
331335
Err(_) => {
332336
// We thought we had memory to allocate, but somehow failed the allocation. Will force a GC.
333-
if !allow_poll {
334-
panic!("Physical allocation failed when polling not allowed!");
337+
if !allow_gc {
338+
panic!("Physical allocation failed when GC is not allowed!");
335339
}
336340

337341
let gc_performed = VM::VMActivePlan::global().poll(true, self.as_space());

src/vm/collection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ pub trait Collection<VM: VMBinding> {
4242
/// Ask the VM to spawn a GC thread for MMTk. A GC thread may later call into the VM through these VM traits. Some VMs
4343
/// have assumptions that those calls needs to be within VM internal threads.
4444
/// As a result, MMTk does not spawn GC threads itself to avoid breaking this kind of assumptions.
45-
/// MMTk calls this method to spawn GC threads during [`enable_collection()`](../memory_manager/fn.enable_collection.html).
45+
/// MMTk calls this method to spawn GC threads during [`initialize_collection()`](../memory_manager/fn.initialize_collection.html).
4646
///
4747
/// Arguments:
4848
/// * `tls`: The thread pointer for the parent thread that we spawn new threads from. This is the same `tls` when the VM
49-
/// calls `enable_collection()` and passes as an argument.
49+
/// calls `initialize_collection()` and passes as an argument.
5050
/// * `ctx`: The GC worker context for the GC thread. If `None` is passed, it means spawning a GC thread for the GC controller,
5151
/// which does not have a worker context.
5252
fn spawn_worker_thread(tls: VMThread, ctx: Option<&GCWorker<VM>>);

vmbindings/dummyvm/src/api.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,18 @@ pub extern "C" fn start_worker(tls: VMWorkerThread, worker: &'static mut GCWorke
6666
}
6767

6868
#[no_mangle]
69-
pub extern "C" fn enable_collection(tls: VMThread) {
70-
memory_manager::enable_collection(&SINGLETON, tls)
69+
pub extern "C" fn initialize_collection(tls: VMThread) {
70+
memory_manager::initialize_collection(&SINGLETON, tls)
71+
}
72+
73+
#[no_mangle]
74+
pub extern "C" fn disable_collection() {
75+
memory_manager::disable_collection(&SINGLETON)
76+
}
77+
78+
#[no_mangle]
79+
pub extern "C" fn enable_collection() {
80+
memory_manager::enable_collection(&SINGLETON)
7181
}
7282

7383
#[no_mangle]

vmbindings/dummyvm/src/collection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ impl Collection<DummyVM> for VMCollection {
1616
}
1717

1818
fn block_for_gc(_tls: VMMutatorThread) {
19-
unimplemented!();
19+
panic!("block_for_gc is not implemented")
2020
}
2121

2222
fn spawn_worker_thread(_tls: VMThread, _ctx: Option<&GCWorker<DummyVM>>) {
23-
unimplemented!();
23+
2424
}
2525

2626
fn prepare_mutator<T: MutatorContext<DummyVM>>(_tls_w: VMWorkerThread, _tls_m: VMMutatorThread, _mutator: &T) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use crate::api::*;
2+
use mmtk::util::opaque_pointer::*;
3+
use mmtk::AllocationSemantics;
4+
5+
/// This test allocates after calling disable_collection(). When we exceed the heap limit, MMTk will NOT trigger a GC.
6+
/// And the allocation will succeed.
7+
#[test]
8+
pub fn allocate_with_disable_collection() {
9+
const MB: usize = 1024 * 1024;
10+
// 1MB heap
11+
gc_init(MB);
12+
initialize_collection(VMThread::UNINITIALIZED);
13+
let handle = bind_mutator(VMMutatorThread(VMThread::UNINITIALIZED));
14+
// Allocate 1MB. It should be fine.
15+
let addr = alloc(handle, MB, 8, 0, AllocationSemantics::Default);
16+
assert!(!addr.is_zero());
17+
// Disable GC
18+
disable_collection();
19+
// Allocate another MB. This exceeds the heap size. But as we have disabled GC, MMTk will not trigger a GC, and allow this allocation.
20+
let addr = alloc(handle, MB, 8, 0, AllocationSemantics::Default);
21+
assert!(!addr.is_zero());
22+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use crate::api::*;
2+
use mmtk::util::opaque_pointer::*;
3+
use mmtk::AllocationSemantics;
4+
5+
/// This test allocates after calling initialize_collection(). When we exceed the heap limit, MMTk will trigger a GC. And block_for_gc will be called.
6+
/// We havent implemented block_for_gc so it will panic.
7+
#[test]
8+
#[should_panic(expected = "block_for_gc is not implemented")]
9+
pub fn allocate_with_initialize_collection() {
10+
const MB: usize = 1024 * 1024;
11+
// 1MB heap
12+
gc_init(MB);
13+
initialize_collection(VMThread::UNINITIALIZED);
14+
let handle = bind_mutator(VMMutatorThread(VMThread::UNINITIALIZED));
15+
// Attempt to allocate 2MB. This will trigger GC.
16+
let addr = alloc(handle, 2 * MB, 8, 0, AllocationSemantics::Default);
17+
assert!(!addr.is_zero());
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use crate::api::*;
2+
use mmtk::util::opaque_pointer::*;
3+
use mmtk::AllocationSemantics;
4+
5+
/// This test allocates after calling initialize_collection(). When we exceed the heap limit, MMTk will trigger a GC. And block_for_gc will be called.
6+
/// We havent implemented block_for_gc so it will panic. This test is similar to allocate_with_initialize_collection, except that we once disabled GC in the test.
7+
#[test]
8+
#[should_panic(expected = "block_for_gc is not implemented")]
9+
pub fn allocate_with_re_enable_collection() {
10+
const MB: usize = 1024 * 1024;
11+
// 1MB heap
12+
gc_init(MB);
13+
initialize_collection(VMThread::UNINITIALIZED);
14+
let handle = bind_mutator(VMMutatorThread(VMThread::UNINITIALIZED));
15+
// Allocate 1MB. It should be fine.
16+
let addr = alloc(handle, MB, 8, 0, AllocationSemantics::Default);
17+
assert!(!addr.is_zero());
18+
// Disable GC. So we can keep allocate without triggering a GC.
19+
disable_collection();
20+
let addr = alloc(handle, MB, 8, 0, AllocationSemantics::Default);
21+
assert!(!addr.is_zero());
22+
// Enable GC again. When we allocate, we should see a GC triggered immediately.
23+
enable_collection();
24+
let addr = alloc(handle, MB, 8, 0, AllocationSemantics::Default);
25+
assert!(!addr.is_zero());
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use crate::api::*;
2+
use mmtk::util::opaque_pointer::*;
3+
use mmtk::AllocationSemantics;
4+
5+
/// This test allocates without calling initialize_collection(). When we exceed the heap limit, a GC should be triggered by MMTk.
6+
/// But as we haven't enabled collection, GC is not initialized, so MMTk will panic.
7+
#[test]
8+
#[should_panic(expected = "GC is not allowed here")]
9+
pub fn allocate_without_initialize_collection() {
10+
const MB: usize = 1024 * 1024;
11+
// 1MB heap
12+
gc_init(MB);
13+
let handle = bind_mutator(VMMutatorThread(VMThread::UNINITIALIZED));
14+
// Attempt to allocate 2MB memory. This should trigger a GC, but as we never call initialize_collection(), we cannot do GC.
15+
let addr = alloc(handle, 2 * MB, 8, 0, AllocationSemantics::Default);
16+
assert!(!addr.is_zero());
17+
}

vmbindings/dummyvm/src/tests/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44
mod issue139;
55
mod handle_mmap_oom;
66
mod handle_mmap_conflict;
7+
mod allocate_without_initialize_collection;
8+
mod allocate_with_initialize_collection;
9+
mod allcoate_with_disable_collection;
10+
mod allocate_with_re_enable_collection;
711
mod malloc;

0 commit comments

Comments
 (0)