Skip to content

Commit f92358d

Browse files
authored
Fix stop-the-world race. (#108)
Previously the MMTk coordinator thread pretents to be the VM thread and calls SafepointSynchronize::begin() directly. This makes it race with the real VM thread. This commit introduces a dedicated "VM companion thread". The companion therad requests the VM thread to execute a stop-the-world VM operation (a VMOp_ThirdPartyHeapOperation), and the VM operation waits for the stop-the-world GC to finish and lets the VM thread start the world again. This ensures the real VM thread is the only thread that calls SafepointSynchronize::begin(). The MMTk binding initiates stack scanning during stop-the-world. Also made minor fixes to MMTKCollectorThread and MMTKContextThread - Removed is_VM_thread and is_GC_thread methods. - It is not the VM thread. - The `is_GC_thread` method is never used in the real VM thread, and is removed in the upstream in a later revision. - Initialize their native thread names on start. - The native names are visible in GDB when debugging. - Prepended "MMTk" to their names to make them more obvious.
1 parent 145acad commit f92358d

15 files changed

+403
-26
lines changed

mmtk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ lazy_static = "1.1"
2020
# - change branch
2121
# - change repo name
2222
# But other changes including adding/removing whitespaces in commented lines may break the CI.
23-
mmtk = { git = "https://github.com/mmtk/mmtk-core.git", rev = "34acc5472942671f89d18d8313af46f9c4c7783e" }
23+
mmtk = { git = "https://github.com/mmtk/mmtk-core.git", rev = "aec43e51c9dc737bd533f4db5106857b4ac19407" }
2424
# Uncomment the following to build locally
2525
# mmtk = { path = "../repos/mmtk-core" }
2626

openjdk/mmtkCollectorThread.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828

2929
MMTkCollectorThread::MMTkCollectorThread(void* context): NamedThread() {
3030
third_party_heap_collector = context;
31-
set_name("Collector Thread");
31+
set_name("MMTk Collector Thread");
3232
}
3333

3434
void MMTkCollectorThread::run() {
35+
this->initialize_named_thread();
3536
start_worker((void*) this, third_party_heap_collector);
3637
}

openjdk/mmtkCollectorThread.hpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ class MMTkCollectorThread: public NamedThread {
3939
guarantee(false, "MMTkCollectorThread deletion must fix the race with VM termination");
4040
}
4141

42-
// Tester
43-
bool is_VM_thread() const { return true; }
44-
bool is_GC_thread() const { return true; }
45-
4642
inline void* get_context() {
4743
return third_party_heap_collector;
4844
}

openjdk/mmtkContextThread.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@
2727
#include "mmtkContextThread.hpp"
2828

2929
MMTkContextThread::MMTkContextThread() : NamedThread() {
30-
set_name("Controller Context Thread");
30+
set_name("MMTk Controller Context Thread");
3131
}
3232

3333
void MMTkContextThread::run() {
34+
this->initialize_named_thread();
3435
start_control_collector((void*) this);
3536
}

openjdk/mmtkContextThread.hpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ class MMTkContextThread: public NamedThread {
3838
guarantee(false, "VMThread deletion must fix the race with VM termination");
3939
}
4040

41-
// Tester
42-
bool is_VM_thread() const { return true; }
43-
bool is_GC_thread() const { return true; }
44-
4541
// Entry for starting vm thread
4642
virtual void run();
4743
};

openjdk/mmtkHeap.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "mmtkHeap.hpp"
3737
#include "mmtkMutator.hpp"
3838
#include "mmtkUpcalls.hpp"
39+
#include "mmtkVMCompanionThread.hpp"
3940
#include "oops/oop.inline.hpp"
4041
#include "runtime/atomic.hpp"
4142
#include "runtime/handles.inline.hpp"
@@ -105,6 +106,12 @@ jint MMTkHeap::initialize() {
105106
//barrier_set->initialize();
106107
BarrierSet::set_barrier_set(barrier_set);
107108

109+
_companion_thread = new MMTkVMCompanionThread();
110+
if (!os::create_thread(_companion_thread, os::pgc_thread)) {
111+
printf("Failed to create thread");
112+
guarantee(false, "panic");
113+
}
114+
os::start_thread(_companion_thread);
108115
// Set up the GCTaskManager
109116
// _mmtk_gc_task_manager = mmtkGCTaskManager::create(ParallelGCThreads);
110117
return JNI_OK;

openjdk/mmtkHeap.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
class GCMemoryManager;
4545
class MemoryPool;
4646
//class mmtkGCTaskManager;
47-
47+
class MMTkVMCompanionThread;
4848
class MMTkHeap : public CollectedHeap {
4949
MMTkCollectorPolicy* _collector_policy;
5050
SoftRefPolicy* _soft_ref_policy;
@@ -57,6 +57,7 @@ class MMTkHeap : public CollectedHeap {
5757
Monitor* _gc_lock;
5858
ContiguousSpace* _space;
5959
int _num_root_scan_tasks;
60+
MMTkVMCompanionThread* _companion_thread;
6061
public:
6162

6263
MMTkHeap(MMTkCollectorPolicy* policy);
@@ -75,6 +76,9 @@ class MMTkHeap : public CollectedHeap {
7576
virtual HeapWord* mem_allocate(size_t size, bool* gc_overhead_limit_was_exceeded);
7677
HeapWord* mem_allocate_nonmove(size_t size, bool* gc_overhead_limit_was_exceeded);
7778

79+
MMTkVMCompanionThread* companion_thread() const {
80+
return _companion_thread;
81+
}
7882

7983

8084
Name kind() const {

openjdk/mmtkUpcalls.cpp

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,45 @@
3232
#include "mmtkHeap.hpp"
3333
#include "mmtkRootsClosure.hpp"
3434
#include "mmtkUpcalls.hpp"
35+
#include "mmtkVMCompanionThread.hpp"
3536
#include "runtime/mutexLocker.hpp"
3637
#include "runtime/os.hpp"
3738
#include "runtime/safepoint.hpp"
3839
#include "runtime/thread.hpp"
3940
#include "runtime/threadSMR.hpp"
4041
#include "runtime/vmThread.hpp"
4142

42-
static bool gcInProgress = false;
43+
static size_t mmtk_start_the_world_count = 0;
4344

4445
static void mmtk_stop_all_mutators(void *tls, void (*create_stack_scan_work)(void* mutator)) {
45-
gcInProgress = true;
4646
MMTkHeap::_create_stack_scan_work = create_stack_scan_work;
47-
SafepointSynchronize::begin();
47+
48+
log_debug(gc)("Requesting the VM to suspend all mutators...");
49+
MMTkHeap::heap()->companion_thread()->request(MMTkVMCompanionThread::_threads_suspended, true);
50+
log_debug(gc)("Mutators stopped. Now enumerate threads for scanning...");
51+
52+
{
53+
JavaThreadIteratorWithHandle jtiwh;
54+
while (JavaThread *cur = jtiwh.next()) {
55+
MMTkHeap::heap()->report_java_thread_yield(cur);
56+
}
57+
}
58+
log_debug(gc)("Finished enumerating threads.");
4859
}
4960

5061
static void mmtk_resume_mutators(void *tls) {
5162
MMTkHeap::_create_stack_scan_work = NULL;
52-
SafepointSynchronize::end();
53-
MMTkHeap::heap()->gc_lock()->lock_without_safepoint_check();
54-
gcInProgress = false;
55-
MMTkHeap::heap()->gc_lock()->notify_all();
56-
MMTkHeap::heap()->gc_lock()->unlock();
63+
64+
log_debug(gc)("Requesting the VM to resume all mutators...");
65+
MMTkHeap::heap()->companion_thread()->request(MMTkVMCompanionThread::_threads_resumed, true);
66+
log_debug(gc)("Mutators resumed. Now notify any mutators waiting for GC to finish...");
67+
68+
{
69+
MutexLockerEx locker(MMTkHeap::heap()->gc_lock(), true);
70+
mmtk_start_the_world_count++;
71+
MMTkHeap::heap()->gc_lock()->notify_all();
72+
}
73+
log_debug(gc)("Mutators notified.");
5774
}
5875

5976
static void mmtk_spawn_collector_thread(void* tls, void* ctx) {
@@ -76,15 +93,18 @@ static void mmtk_spawn_collector_thread(void* tls, void* ctx) {
7693
}
7794

7895
static void mmtk_block_for_gc() {
79-
gcInProgress = true;
8096
MMTkHeap::heap()->_last_gc_time = os::javaTimeNanos() / NANOSECS_PER_MILLISEC;
97+
log_debug(gc)("Thread (id=%d) will block waiting for GC to finish.", Thread::current()->osthread()->thread_id());
8198
{
82-
Monitor* gc_lock = MMTkHeap::heap()->gc_lock();
83-
MutexLocker ml(gc_lock);
84-
while (gcInProgress) {
85-
gc_lock->wait();
99+
MutexLocker locker(MMTkHeap::heap()->gc_lock());
100+
size_t my_count = mmtk_start_the_world_count;
101+
size_t next_count = my_count + 1;
102+
103+
while (mmtk_start_the_world_count < next_count) {
104+
MMTkHeap::heap()->gc_lock()->wait();
86105
}
87106
}
107+
log_debug(gc)("Thread (id=%d) resumed after GC finished.", Thread::current()->osthread()->thread_id());
88108
}
89109

90110
static void* mmtk_get_mmtk_mutator(void* tls) {

openjdk/mmtkVMCompanionThread.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*
23+
*/
24+
25+
#include "precompiled.hpp"
26+
#include "mmtk.h"
27+
#include "mmtkVMCompanionThread.hpp"
28+
#include "runtime/mutex.hpp"
29+
30+
MMTkVMCompanionThread::MMTkVMCompanionThread():
31+
NamedThread(),
32+
_desired_state(_threads_resumed),
33+
_reached_state(_threads_resumed) {
34+
set_name("MMTK VM Companion Thread");
35+
_lock = new Monitor(Monitor::nonleaf,
36+
"MMTkVMCompanionThread::_lock",
37+
true,
38+
Monitor::_safepoint_check_never);
39+
}
40+
41+
MMTkVMCompanionThread::~MMTkVMCompanionThread() {
42+
guarantee(false, "MMTkVMCompanionThread deletion must fix the race with VM termination");
43+
}
44+
45+
void MMTkVMCompanionThread::run() {
46+
this->initialize_named_thread();
47+
48+
for (;;) {
49+
// Wait for suspend request
50+
log_trace(gc)("MMTkVMCompanionThread: Waiting for suspend request...");
51+
{
52+
MutexLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
53+
assert(_reached_state == _threads_resumed, "Threads should be running at this moment.");
54+
while (_desired_state != _threads_suspended) {
55+
_lock->wait(true);
56+
}
57+
assert(_reached_state == _threads_resumed, "Threads should still be running at this moment.");
58+
}
59+
60+
// Let the VM thread stop the world.
61+
log_trace(gc)("MMTkVMCompanionThread: Letting VMThread execute VM op...");
62+
VM_MMTkSTWOperation op(this);
63+
// VMThread::execute() is blocking. The companion thread will be blocked
64+
// here waiting for the VM thread to execute op, and the VM thread will
65+
// be blocked in reach_suspended_and_wait_for_resume() until a GC thread
66+
// calls request(_threads_resumed).
67+
VMThread::execute(&op);
68+
69+
// Tell the waiter thread that the world has resumed.
70+
log_trace(gc)("MMTkVMCompanionThread: Notifying threads resumption...");
71+
{
72+
MutexLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
73+
assert(_desired_state == _threads_resumed, "start-the-world should be requested.");
74+
assert(_reached_state == _threads_suspended, "Threads should still be suspended at this moment.");
75+
_reached_state = _threads_resumed;
76+
_lock->notify_all();
77+
}
78+
}
79+
}
80+
81+
// Request stop-the-world or start-the-world. This method is supposed to be
82+
// called by a GC thread.
83+
//
84+
// If wait_until_reached is true, the caller will block until all Java threads
85+
// have stopped, or until they have been waken up.
86+
//
87+
// If wait_until_reached is false, the caller will return immediately, while
88+
// the companion thread will ask the VM thread to perform the state transition
89+
// in the background. The caller may call the wait_for_reached method to block
90+
// until the desired state is reached.
91+
void MMTkVMCompanionThread::request(stw_state desired_state, bool wait_until_reached) {
92+
assert(!Thread::current()->is_VM_thread(), "Requests can only be made by GC threads. Found VM thread.");
93+
assert(Thread::current() != this, "Requests can only be made by GC threads. Found companion thread.");
94+
assert(!Thread::current()->is_Java_thread(), "Requests can only be made by GC threads. Found Java thread.");
95+
96+
MutexLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
97+
assert(_desired_state != desired_state, "State %d already requested.", desired_state);
98+
_desired_state = desired_state;
99+
_lock->notify_all();
100+
101+
if (wait_until_reached) {
102+
while (_reached_state != desired_state) {
103+
_lock->wait(true);
104+
}
105+
}
106+
}
107+
108+
// Wait until the desired state is reached. Usually called after calling the
109+
// request method. Supposed to be called by a GC thread.
110+
void MMTkVMCompanionThread::wait_for_reached(stw_state desired_state) {
111+
assert(!Thread::current()->is_VM_thread(), "Supposed to be called by GC threads. Found VM thread.");
112+
assert(Thread::current() != this, "Supposed to be called by GC threads. Found companion thread.");
113+
assert(!Thread::current()->is_Java_thread(), "Supposed to be called by GC threads. Found Java thread.");
114+
115+
MutexLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
116+
assert(_desired_state == desired_state, "State %d not requested.", desired_state);
117+
118+
while (_reached_state != desired_state) {
119+
_lock->wait(true);
120+
}
121+
}
122+
123+
// Called by the VM thread to indicate that all Java threads have stopped.
124+
// This method will block until the GC requests start-the-world.
125+
void MMTkVMCompanionThread::reach_suspended_and_wait_for_resume() {
126+
assert(Thread::current()->is_VM_thread(), "reach_suspended_and_wait_for_resume can only be executed by the VM thread");
127+
128+
MutexLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
129+
130+
// Tell the waiter thread that the world has stopped.
131+
_reached_state = _threads_suspended;
132+
_lock->notify_all();
133+
134+
// Wait until resume-the-world is requested
135+
while (_desired_state != _threads_resumed) {
136+
_lock->wait(true);
137+
}
138+
}

openjdk/mmtkVMCompanionThread.hpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*
23+
*/
24+
25+
#ifndef MMTK_OPENJDK_MMTK_VM_COMPANION_THREAD_HPP
26+
#define MMTK_OPENJDK_MMTK_VM_COMPANION_THREAD_HPP
27+
28+
#include "mmtkVMOperation.hpp"
29+
#include "runtime/mutex.hpp"
30+
#include "runtime/perfData.hpp"
31+
#include "runtime/thread.hpp"
32+
#include "runtime/vmOperations.hpp"
33+
34+
// This thread cooperates with the VMThread to allow stopping the world without
35+
// blocking any GC threads.
36+
//
37+
// In HotSpot, the way to stop all Java threads for stop-the-world GC is
38+
// letting the VMThread execute a blocking VM_Operation. However, the MMTk
39+
// expects the VM to provide two non-blocking methods to stop and start the
40+
// the world, whthout blocking the callers. This thread bridges the API gap
41+
// by calling VMThread::execute on behalf of GC threads upon reques so that it
42+
// blocks this thread instead of GC threads.
43+
class MMTkVMCompanionThread: public NamedThread {
44+
public:
45+
enum stw_state {
46+
_threads_suspended,
47+
_threads_resumed,
48+
};
49+
private:
50+
Monitor* _lock;
51+
stw_state _desired_state;
52+
stw_state _reached_state;
53+
54+
public:
55+
// Constructor
56+
MMTkVMCompanionThread();
57+
~MMTkVMCompanionThread();
58+
59+
virtual void run() override;
60+
61+
// Interface for MMTk Core
62+
void request(stw_state desired_state, bool wait_until_reached);
63+
void wait_for_reached(stw_state reached_state);
64+
65+
// Interface for the VM_MMTkSTWOperation
66+
void reach_suspended_and_wait_for_resume();
67+
};
68+
69+
#endif // MMTK_OPENJDK_MMTK_VM_COMPANION_THREAD_HPP

0 commit comments

Comments
 (0)