Date: Thu, 28 Mar 2024 15:41:19 +0000 (UTC) Message-ID: <781792170.1173.1711640479686@34fc92c9345b> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_1172_1481971555.1711640479686" ------=_Part_1172_1481971555.1711640479686 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
author: Patricio Chilano Mateo (pchilanomate)
The current design of native monitors uses a technique that we name "sne= aky locking" to prevent possible deadlocks of the JVM during safepoints. Th= e implementation of this technique though introduces a race when a monitor = is shared between the VMThread and non-JavaThreads. Also the current design= uses a lot of optimization techniques to try to improve performance making= the code more complex. Maybe we can simplify the code without performance = penalties. The goal of this proposal then is to fix this sneaky scheme by r= edesigning the implementation of native monitors in a way that also simplif= ies the code and keeps a comparable performance to what we have today.
Sneaky locking prevents a deadlock scenario that can happen if, during a= safepoint, the VMThread tries to acquire a monitor whose outer lock (actua= l lockword) has been acquired by a JavaThread that is blocked waiting for t= he safepoint to finish. Below is the skeleton of method Monitor::lock():
void Monitor::lock(Thread * Self) {
if (TryLock()) {
set_own= er(Self);
return;
}
if (Self->is_Java_thread()) {
Th= readBlockInVM tbivm((JavaThread *) Self);
ILock(Self);
} else { ILock(Self);
}
set_owner(Self);
}
Here is a similar use of ThreadBlockInVM in Monitor:wait()
bool Monitor::wait() {
set_owner(NULL);
if (no_safepoint_check= ) {
wait_status =3D IWait(Self, timeout);
} else {
ThreadBl= ockInVM tbivm(jt);
wait_status =3D IWait(Self, timeout);
}
se= t_owner(Self);
}
The state transition from _thread_in_vm to _thread_blocked performed in =
the ThreadBlockInVM constructor is needed if we want to allow safepoints to=
progress because the ILock() call can potentially make blocking calls to t=
he OS. There is also a SafepointMechanism::block_if_requested() call execut=
ed in both the constructor and destructor of the ThreadBlockInVM object. Wh=
y is it needed in the destructor? Because if there is a safepoint currently=
going on after the ILock() call returns the JavaThread has to be stopped a=
nd cannot keep executing VM code.
Because of this desire to check for safepoints when acquiring native monito=
rs we now have possible deadlocking issues if a monitor is shared between J=
avaThreads and the VMThread. Hence the following code was added in Monitor:=
:lock implementing "sneaky locking":
bool can_sneak =3D Self->is_VM_thread() && SafepointSynchro= nize::is_at_safepoint();
if (can_sneak && _owner =3D=3D NULL) {<= br> _snuck =3D true;
set_owner(Self);
}
As we mentioned before this code introduces a race if non-JavaThreads al=
so try to acquire the monitor, since the non-JavaThread could have locked t=
he monitor but not yet set itself as the owner. (Using Monitor::try_lock() =
has the same race). By non-JavaThreads we mean internal VM threads like Con=
currentGCThreads, WorkerThreads, WatcherThread, JfrThreadSampler, etc.
It's worth mentioning though that we could keep the current design of nativ=
e monitors and modify the sneaking code part to actually identify which kin=
d of thread has the outer lock before sneaking (we could use the second bit=
of the lockword for example).
The proposal is based on the introduction of the new class PlatformMonit= or, which serves as a wrapper for the actual synchronization primitives in = each platform(mutexes and condition variables) and provides the basic API f= or the implementation of native monitors. Here is the definition of class P= latformMonitor (for posix OSes; for solaris and windows there is an equival= ent definition):
// Platform specific implementation that underpins VM Monitor/Mutex cl= ass
class PlatformMonitor : public CHeapObj<mtInternal> {
priv= ate:
pthread_mutex_t _mutex[1]; // Native mutex for locking
pth= read_cond_t _cond[1]; // Native condition variable for blocking
public:=
PlatformMonitor();
void lock();
void unlock();
boo= l try_lock();
int wait(jlong millis);
void notify();
void= notify_all();
};
As an example we can see that PlatformMonitor::lock() is a wrapper for p= thread_mutex_lock() and that PlatformMonitor::wait() just uses the underlyi= ng pthread_cond_timedwait() call parsing the waiting time first:
void os::PlatformMonitor::lock() {
int status =3D pthread_mutex_lo= ck(_mutex);
assert_status(status =3D=3D 0, status, "mutex_lock");
}<= br>// Must already be locked
int os::PlatformMonitor::wait(jlong millis)= {
assert(millis >=3D 0, "negative timeout");
if (millis > 0)= {
struct timespec abst;
// We have to watch for overflow when = converting millis to nanos,
// but if millis is that large then we wi= ll end up limiting to
// MAX_SECS anyway, so just do that here.
= if (millis / MILLIUNITS > MAX_SECS) {
millis =3D jlong(MAX_SECS= ) * MILLIUNITS;
}
to_abstime(&abst, millis * (NANOUNITS / M= ILLIUNITS), false);
int ret =3D OS_TIMEOUT;
int status =3D pthr= ead_cond_timedwait(_cond, _mutex, &abst);
assert_status(status = =3D=3D 0 || status =3D=3D ETIMEDOUT, status, "cond_timedwait");
if (s= tatus =3D=3D 0) {
ret =3D OS_OK;
}
return ret;
} el= se {
int status =3D pthread_cond_wait(_cond, _mutex);
assert_st= atus(status =3D=3D 0, status, "cond_wait");
return OS_OK;
}
}<= /pre>Instead of introducing our own custom synchronization schemes on top of = native primitives as we do today, we will use this new class to implement o= ur native monitors. Most of the API calls can thus be implemented as simple= wrappers around PlatformMonitor, adding more assertions and very little ex= tra housekeeping(owner and monitor name). The exception will be the lock() = and wait() calls since we have to add all the logic for handling the safepo= int checking mechanism.
Overall Design Strate= gy
As mentioned before, the idea for implementing native monitors is to mos= tly rely on the simple underlying PlatformMonitor class. However, to be abl= e to remove the lock sneaking code and at the same time avoid deadlocking s= cenarios, we will have to be able to solve these two problematic scenarios:=
- In Monitor::lock() and Monitor::wait(), blocking for a safepoint in th= e ThreadBlockInVM destructor could lead to a deadlock, since the monitor co= uld be shared with the VMThread.
-In Monitor::wait(), blocking for a safepoint in the ThreadBlockInVM con= structor could lead to a deadlock, since we already own the monitor and as = before the monitor could be shared with the VMThread. Doing a plain release= of the lock prior to entering the constructor is not an option since we co= uld miss a notify() call.
To handle the first scenario we will make the JavaThread that has just a= cquired the lock but detects there is a safepoint request in the ThreadBloc= kInVM destructor release the lock before blocking at the safepoint. After r= esuming from it, the JavaThread will have to acquire the lock again as it h= as just called Monitor::lock(). For the second scenario we will change the = ThreadBlockInVM constructor to allow for a possible safepoint request to ma= ke progress but without letting the JavaThread block for it. In order to av= oid adding extra conditionals to the current ThreadBlockInVM jacket to impl= ement this functionality, we will actually create and use a new jacket inst= ead, ThreadBlockInVMWithDeadlockCheck.
Implementation of Monitor::lock()Here is the implementation of Monitor::lock() (without extra checks and = assertions):
void Monitor::lock(Thread * self) {
Monitor* in_flight_monitor =3D= NULL;
while (!_lock.try_lock()) {
// The lock is contended
= if (self->is_Java_thread()) {
ThreadBlockInVMWithDeadlockCheck = tbivmdc((JavaThread *) self, &in_flight_monitor);
in_flight_mon= itor =3D this;
_lock.lock();
}
if (in_flight_monitor != =3D NULL) {
break;
} else {
_lock.lock();
brea= k;
}
}
set_owner(self);
}The locking path for non-JavaThreads and the VMThread is straightforward= . For JavaThreads whenever we return from the TBIVWDC jacket without having= stopped at a safepoint we will just set ourselves as the owners and return= . If we did stop at a safepoint, then the in_flight_monitor local variable = will be NULL, so we will loop again trying to acquire the monitor. The code= that sets in_flight_monitor to NULL and releases the monitor is shown belo= w. It is called from the TBIVWDC destructor in case we need to block for a = safepoint or handshake:
void release_monitor() {
Monitor* in_flight_monitor =3D *_in_fligh= t_monitor_adr;
if (in_flight_monitor !=3D NULL) {
in_flight_monit= or->release_for_safepoint();
*_in_flight_monitor_adr =3D NULL;
= }
}where release_for_safepoint() just releases the lock.
Implementation of Monitor::wait()Here is the implementation of Monitor::wait() (without extra checks and = assertions):
bool Monitor::wait(bool no_safepoint_check, long timeout, bool as_susp= end_equivalent) {
int wait_status;
set_owner(NULL);
if (no_safe= point_check) {
wait_status =3D _lock.wait(timeout);
set_owner(s= elf);
} else {
JavaThread *jt =3D (JavaThread *)self;
Monit= or* in_flight_monitor =3D NULL;
{
ThreadBlockInVMWithDeadlock= Check tbivmdc(jt, &in_flight_monitor);
OSThreadWaitState osts(s= elf->osthread(), false /* not Object.wait() */);
if (as_suspend_= equivalent) {
jt->set_suspend_equivalent();
}
= wait_status =3D _lock.wait(timeout);
in_flight_monitor =3D this; if (as_suspend_equivalent && jt->handle_special_suspend_e= quivalent_condition()) {
_lock.unlock();
jt->java_su= spend_self();
_lock.lock();
}
}
if (in_flight= _monitor !=3D NULL) {
set_owner(self);
} else {
lock(= self);
}
}
return wait_status !=3D 0; // return true IFF time= out
}The use of the in_flight_monitor local variable is analogous to the lock= () case. In the TBIVWDC constructor we will just callback if there is a pen= ding safepoint but the JavaThread will not block. To do that we added a new= bool parameter to SS::block, block_in_safepoint_check, which defaults to t= rue. If this value is true we execute as we do now in Safepoint::block(). I= f we call SS::block with a false value we allow for the safepoint to make p= rogress but never try to grab the Threads_lock. Here is the abbreviated log= ic of Safepoint::block():
Safepoint_lock->lock_without_safepoint_check();
if (is_synchroni= zing()) {
//make changes to allow the safepoint to progress
}
if(= block_in_safepoint_check) {
// current logic
thread->set_thread_= state(_thread_blocked);
Safepoint_lock->unlock();
Threads_lock-&= gt;lock_without_safepoint_check();
thread->set_thread_state(sta= te);
Threads_lock->unlock();
}
else {
// We choose not to = block
thread->set_thread_state(_thread_blocked);
Safepoint_lock-= >unlock();
}