Skip to content

Commit 642575d

Browse files
committed
[GR-34100] ParkEvent improvements.
PullRequest: graal/13155
2 parents 63b06e1 + 9427517 commit 642575d

File tree

11 files changed

+204
-259
lines changed

11 files changed

+204
-259
lines changed

substratevm/mx.substratevm/suite.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@
428428
"com.oracle.svm.core.graal.aarch64",
429429
],
430430
"requiresConcealed" : {
431+
"java.base" : [
432+
"jdk.internal.misc",
433+
],
431434
"jdk.internal.vm.ci" : [
432435
"jdk.vm.ci.code",
433436
],

substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ public interface pthread_key_tPointer extends PointerBase {
147147
@CFunction(transition = Transition.NO_TRANSITION)
148148
public static native int pthread_mutex_init(pthread_mutex_t mutex, pthread_mutexattr_t mutexattr);
149149

150+
@CFunction(value = "pthread_mutex_trylock", transition = Transition.NO_TRANSITION)
151+
public static native int pthread_mutex_trylock_no_transition(pthread_mutex_t mutex);
152+
150153
@CFunction(transition = Transition.TO_NATIVE)
151154
public static native int pthread_mutex_lock(pthread_mutex_t mutex);
152155

substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadConditionUtils.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public static int initCondition(Pthread.pthread_cond_t cond) {
6363
return Pthread.pthread_cond_init(cond, attr);
6464
}
6565

66-
@Uninterruptible(reason = "Called from uninterruptible code.")
66+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
6767
private static void getAbsoluteTimeNanos(timespec result) {
6868
/*
6969
* We need the real-time clock to compute absolute deadlines when a conditional wait should
@@ -88,15 +88,15 @@ private static void getAbsoluteTimeNanos(timespec result) {
8888
}
8989
}
9090

91-
/** Turn a delay in nanoseconds into a deadline in a Time.timespec. */
92-
@Uninterruptible(reason = "Called from uninterruptible code.")
93-
public static void delayNanosToDeadlineTimespec(long delayNanos, Time.timespec result) {
91+
/** Turn a duration in nanoseconds into a deadline in a Time.timespec. */
92+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
93+
public static void durationNanosToDeadlineTimespec(long durationNanos, Time.timespec result) {
9494
timespec currentTimespec = StackValue.get(timespec.class);
9595
getAbsoluteTimeNanos(currentTimespec);
9696

97-
assert delayNanos >= 0;
98-
long sec = TimeUtils.addOrMaxValue(currentTimespec.tv_sec(), TimeUtils.divideNanosToSeconds(delayNanos));
99-
long nsec = currentTimespec.tv_nsec() + TimeUtils.remainderNanosToSeconds(delayNanos);
97+
assert durationNanos >= 0;
98+
long sec = TimeUtils.addOrMaxValue(currentTimespec.tv_sec(), TimeUtils.divideNanosToSeconds(durationNanos));
99+
long nsec = currentTimespec.tv_nsec() + TimeUtils.remainderNanosToSeconds(durationNanos);
100100
if (nsec >= TimeUtils.nanosPerSecond) {
101101
sec = TimeUtils.addOrMaxValue(sec, 1);
102102
nsec -= TimeUtils.nanosPerSecond;
@@ -108,7 +108,7 @@ public static void delayNanosToDeadlineTimespec(long delayNanos, Time.timespec r
108108
}
109109

110110
@Uninterruptible(reason = "Called from uninterruptible code.")
111-
public static long deadlineTimespecToDelayNanos(Time.timespec deadlineTimespec) {
111+
public static long deadlineTimespecToDurationNanos(Time.timespec deadlineTimespec) {
112112
timespec currentTimespec = StackValue.get(timespec.class);
113113
getAbsoluteTimeNanos(currentTimespec);
114114

substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/pthread/PthreadVMLockSupport.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ public void blockNoTransitionUnspecifiedOwner() {
315315
@Override
316316
public long block(long waitNanos) {
317317
Time.timespec deadlineTimespec = UnsafeStackValue.get(Time.timespec.class);
318-
PthreadConditionUtils.delayNanosToDeadlineTimespec(waitNanos, deadlineTimespec);
318+
PthreadConditionUtils.durationNanosToDeadlineTimespec(waitNanos, deadlineTimespec);
319319

320320
mutex.clearCurrentThreadOwner();
321321
final int timedWaitResult = Pthread.pthread_cond_timedwait(getStructPointer(), ((PthreadVMMutex) getMutex()).getStructPointer(), deadlineTimespec);
@@ -326,14 +326,14 @@ public long block(long waitNanos) {
326326
}
327327
/* Check for other errors from the timed wait. */
328328
PthreadVMLockSupport.checkResult(timedWaitResult, "pthread_cond_timedwait");
329-
return PthreadConditionUtils.deadlineTimespecToDelayNanos(deadlineTimespec);
329+
return PthreadConditionUtils.deadlineTimespecToDurationNanos(deadlineTimespec);
330330
}
331331

332332
@Override
333333
@Uninterruptible(reason = "Called from uninterruptible code.", callerMustBe = true)
334334
public long blockNoTransition(long waitNanos) {
335335
Time.timespec deadlineTimespec = StackValue.get(Time.timespec.class);
336-
PthreadConditionUtils.delayNanosToDeadlineTimespec(waitNanos, deadlineTimespec);
336+
PthreadConditionUtils.durationNanosToDeadlineTimespec(waitNanos, deadlineTimespec);
337337

338338
mutex.clearCurrentThreadOwner();
339339
final int timedwaitResult = Pthread.pthread_cond_timedwait_no_transition(getStructPointer(), ((PthreadVMMutex) getMutex()).getStructPointer(), deadlineTimespec);
@@ -344,7 +344,7 @@ public long blockNoTransition(long waitNanos) {
344344
}
345345
/* Check for other errors from the timed wait. */
346346
PthreadVMLockSupport.checkResult(timedwaitResult, "pthread_cond_timedwait");
347-
return PthreadConditionUtils.deadlineTimespecToDelayNanos(deadlineTimespec);
347+
return PthreadConditionUtils.deadlineTimespecToDurationNanos(deadlineTimespec);
348348
}
349349

350350
@Override

substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/thread/PosixPlatformThreads.java

Lines changed: 93 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
6161
import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
6262
import com.oracle.svm.core.log.Log;
63+
import com.oracle.svm.core.monitor.JavaMonitor;
6364
import com.oracle.svm.core.os.IsDefined;
6465
import com.oracle.svm.core.posix.PosixUtils;
6566
import com.oracle.svm.core.posix.headers.Errno;
@@ -75,9 +76,12 @@
7576
import com.oracle.svm.core.thread.ParkEvent;
7677
import com.oracle.svm.core.thread.ParkEvent.ParkEventFactory;
7778
import com.oracle.svm.core.thread.PlatformThreads;
79+
import com.oracle.svm.core.util.TimeUtils;
7880
import com.oracle.svm.core.util.UnsignedUtils;
7981
import com.oracle.svm.core.util.VMError;
8082

83+
import jdk.internal.misc.Unsafe;
84+
8185
@AutomaticallyRegisteredImageSingleton(PlatformThreads.class)
8286
public final class PosixPlatformThreads extends PlatformThreads {
8387

@@ -287,16 +291,32 @@ final class Target_java_lang_Thread {
287291
Pthread.pthread_t pthreadIdentifier;
288292
}
289293

290-
class PosixParkEvent extends ParkEvent {
294+
/**
295+
* {@link PosixParkEvent} is based on HotSpot class {@code Parker} in {@code os_posix.cpp}, as of
296+
* JDK 19 (git commit hash: f640fc5a1eb876a657d0de011dcd9b9a42b88eec, JDK tag: jdk-19+30).
297+
* <p>
298+
* HotSpot has two constructs with a similar purpose: {@code ParkEvent} and {@code Parker}. The
299+
* latter implements JSR 166 synchronization primitives {@link Unsafe#park} and
300+
* {@link Unsafe#unpark}, just like we do here, therefore we base this implementation on
301+
* {@code Parker}. Our implementation of Java object monitors, {@link JavaMonitor}, uses the JSR 166
302+
* primitives, so it can potentially experience interference from unrelated calls to
303+
* {@link Unsafe#unpark}. This is a difference to HotSpot's {@code ObjectMonitor}, which uses a
304+
* separate HotSpot {@code ParkEvent} instance. Another difference is that {@code Parker} and the
305+
* code below return control to the caller on spurious wakeups, unlike HotSpot's {@code ParkEvent}.
306+
* This does not affect correctness.
307+
*/
308+
final class PosixParkEvent extends ParkEvent {
309+
private static final Unsafe U = Unsafe.getUnsafe();
310+
private static final long EVENT_OFFSET = U.objectFieldOffset(PosixParkEvent.class, "event");
291311

292312
private Pthread.pthread_mutex_t mutex;
293313
private Pthread.pthread_cond_t cond;
294314

295-
/**
296-
* The ticket: false implies unavailable, true implies available. Volatile so it can be safely
297-
* updated in {@link #reset()} without holding the lock.
298-
*/
299-
protected volatile boolean event;
315+
/** Permit: 1 if an unpark is pending, otherwise 0. */
316+
private volatile int event = 0;
317+
318+
/** Whether the owner is currently parked. Guarded by {@link #mutex}. */
319+
private boolean parked = false;
300320

301321
PosixParkEvent() {
302322
// Allocate mutex and condition in a single step so that they are adjacent in memory.
@@ -308,61 +328,79 @@ class PosixParkEvent extends ParkEvent {
308328
final Pthread.pthread_mutexattr_t mutexAttr = WordFactory.nullPointer();
309329
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_init(mutex, mutexAttr), "mutex initialization");
310330
PosixUtils.checkStatusIs0(PthreadConditionUtils.initCondition(cond), "condition variable initialization");
331+
// Note: HotSpot has another pthread_cond_t without CLOCK_MONOTONIC for absolute timed waits
311332
}
312333

313334
@Override
314335
protected void reset() {
315-
event = false;
336+
event = 0;
316337
}
317338

318339
@Override
319340
protected void condWait() {
320-
StackOverflowCheck.singleton().makeYellowZoneAvailable();
321-
try {
322-
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_lock(mutex), "park(): mutex lock");
323-
try {
324-
while (!event) {
325-
int status = Pthread.pthread_cond_wait(cond, mutex);
326-
PosixUtils.checkStatusIs0(status, "park(): condition variable wait");
327-
}
328-
event = false;
329-
} finally {
330-
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_unlock(mutex), "park(): mutex unlock");
331-
}
332-
} finally {
333-
StackOverflowCheck.singleton().protectYellowZone();
341+
park(false, 0);
342+
}
343+
344+
@Override
345+
protected void condTimedWait(long durationNanos) {
346+
if (durationNanos > 0) {
347+
park(false, durationNanos);
334348
}
335349
}
336350

337351
@Override
338-
protected void condTimedWait(long delayNanos) {
352+
protected boolean tryFastPark() {
353+
// We depend on getAndSet having full barrier semantics since we are not locking
354+
return U.getAndSetInt(this, EVENT_OFFSET, 0) != 0;
355+
}
356+
357+
@Override
358+
protected void park(boolean isAbsolute, long time) {
359+
if (time < 0 || (isAbsolute && time == 0)) {
360+
return; // don't wait at all
361+
}
339362
StackOverflowCheck.singleton().makeYellowZoneAvailable();
340363
try {
341-
/* Encode the delay as a deadline in a Time.timespec. */
342-
Time.timespec deadlineTimespec = UnsafeStackValue.get(Time.timespec.class);
343-
PthreadConditionUtils.delayNanosToDeadlineTimespec(delayNanos, deadlineTimespec);
344-
345-
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_lock(mutex), "park(long): mutex lock");
364+
int status = Pthread.pthread_mutex_trylock_no_transition(mutex);
365+
if (status == Errno.EBUSY()) {
366+
return; // can only mean another thread is unparking us: don't wait
367+
}
368+
PosixUtils.checkStatusIs0(status, "park: mutex trylock");
346369
try {
347-
while (!event) {
348-
int status = Pthread.pthread_cond_timedwait(cond, mutex, deadlineTimespec);
349-
if (status == Errno.ETIMEDOUT()) {
350-
break;
351-
} else if (status != 0) {
352-
Log.log().newline()
353-
.string("[PosixParkEvent.condTimedWait(delayNanos: ").signed(delayNanos).string("): Should not reach here.")
354-
.string(" mutex: ").hex(mutex)
355-
.string(" cond: ").hex(cond)
356-
.string(" deadlineTimeSpec.tv_sec: ").signed(deadlineTimespec.tv_sec())
357-
.string(" deadlineTimespec.tv_nsec: ").signed(deadlineTimespec.tv_nsec())
358-
.string(" status: ").signed(status).string(" ").string(Errno.strerror(status))
359-
.string("]").newline();
360-
PosixUtils.checkStatusIs0(status, "park(long): condition variable timed wait");
370+
if (event == 0) {
371+
assert !parked;
372+
parked = true;
373+
if (!isAbsolute && time == 0) {
374+
status = Pthread.pthread_cond_wait(cond, mutex);
375+
PosixUtils.checkStatusIs0(status, "park(): condition variable wait");
376+
} else {
377+
long durationNanos = TimeUtils.durationNanos(isAbsolute, time);
378+
Time.timespec deadlineTimespec = UnsafeStackValue.get(Time.timespec.class);
379+
PthreadConditionUtils.durationNanosToDeadlineTimespec(durationNanos, deadlineTimespec);
380+
381+
status = Pthread.pthread_cond_timedwait(cond, mutex, deadlineTimespec);
382+
if (status != 0 && status != Errno.ETIMEDOUT()) {
383+
Log.log().newline()
384+
.string("[PosixParkEvent.park(durationNanos: ").signed(durationNanos).string("): Should not reach here.")
385+
.string(" mutex: ").hex(mutex)
386+
.string(" cond: ").hex(cond)
387+
.string(" deadlineTimeSpec.tv_sec: ").signed(deadlineTimespec.tv_sec())
388+
.string(" deadlineTimespec.tv_nsec: ").signed(deadlineTimespec.tv_nsec())
389+
.string(" status: ").signed(status).string(" ").string(Errno.strerror(status))
390+
.string("]").newline();
391+
PosixUtils.checkStatusIs0(status, "park(boolean, long): condition variable timed wait");
392+
}
361393
}
394+
parked = false;
362395
}
363-
event = false;
396+
event = 0;
397+
364398
} finally {
365-
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_unlock(mutex), "park(long): mutex unlock");
399+
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_unlock(mutex), "park: mutex unlock");
400+
401+
// Paranoia to ensure our locked and lock-free paths interact
402+
// correctly with each other and Java-level accesses.
403+
U.fullFence();
366404
}
367405
} finally {
368406
StackOverflowCheck.singleton().protectYellowZone();
@@ -373,13 +411,23 @@ protected void condTimedWait(long delayNanos) {
373411
protected void unpark() {
374412
StackOverflowCheck.singleton().makeYellowZoneAvailable();
375413
try {
414+
int s;
415+
boolean p;
376416
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_lock(mutex), "PosixParkEvent.unpark(): mutex lock");
377417
try {
378-
event = true;
379-
PosixUtils.checkStatusIs0(Pthread.pthread_cond_broadcast(cond), "PosixParkEvent.unpark(): condition variable broadcast");
418+
s = event;
419+
event = 1;
420+
p = parked;
380421
} finally {
381422
PosixUtils.checkStatusIs0(Pthread.pthread_mutex_unlock(mutex), "PosixParkEvent.unpark(): mutex unlock");
382423
}
424+
if (s == 0 && p) {
425+
/*
426+
* Signal without holding the mutex, which is safe and avoids futile wakeups if the
427+
* platform does not implement wait morphing.
428+
*/
429+
PosixUtils.checkStatusIs0(Pthread.pthread_cond_signal(cond), "PosixParkEvent.unpark(): condition variable signal");
430+
}
383431
} finally {
384432
StackOverflowCheck.singleton().protectYellowZone();
385433
}
@@ -400,9 +448,4 @@ class PosixParkEventFactory implements ParkEventFactory {
400448
public ParkEvent acquire() {
401449
return new PosixParkEvent();
402450
}
403-
404-
@Override
405-
public boolean usesParkEventList() {
406-
return false;
407-
}
408451
}

substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformThreads.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,13 @@ protected void condWait() {
231231
}
232232

233233
@Override
234-
protected void condTimedWait(long delayNanos) {
234+
protected void condTimedWait(long durationNanos) {
235235
StackOverflowCheck.singleton().makeYellowZoneAvailable();
236236
try {
237237
final int maxTimeout = 0x10_000_000;
238-
long delayMillis = Math.max(0, TimeUtils.roundUpNanosToMillis(delayNanos));
238+
long durationMillis = Math.max(0, TimeUtils.roundUpNanosToMillis(durationNanos));
239239
do { // at least once to consume potential unpark
240-
int timeout = (delayMillis < maxTimeout) ? (int) delayMillis : maxTimeout;
240+
int timeout = (durationMillis < maxTimeout) ? (int) durationMillis : maxTimeout;
241241
int status = SynchAPI.WaitForSingleObject(eventHandle, timeout);
242242
if (status == SynchAPI.WAIT_OBJECT_0()) {
243243
break; // unparked
@@ -246,8 +246,8 @@ protected void condTimedWait(long delayNanos) {
246246
Log.log().newline().string("GetLastError returned: ").hex(WinBase.GetLastError()).newline();
247247
throw VMError.shouldNotReachHere("WaitForSingleObject failed");
248248
}
249-
delayMillis -= timeout;
250-
} while (delayMillis > 0);
249+
durationMillis -= timeout;
250+
} while (durationMillis > 0);
251251
} finally {
252252
StackOverflowCheck.singleton().protectYellowZone();
253253
}
@@ -279,9 +279,4 @@ class WindowsParkEventFactory implements ParkEventFactory {
279279
public ParkEvent acquire() {
280280
return new WindowsParkEvent();
281281
}
282-
283-
@Override
284-
public boolean usesParkEventList() {
285-
return false;
286-
}
287282
}

0 commit comments

Comments
 (0)