Skip to content

Commit 8a88c44

Browse files
committed
[GR-57537] Avoid recursive lock method for jdwp suspension and pin objects while in suspended state.
PullRequest: graal/19127
2 parents f897b31 + ebd01c6 commit 8a88c44

File tree

5 files changed

+125
-113
lines changed

5 files changed

+125
-113
lines changed

espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/Ids.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@
2222
*/
2323
package com.oracle.truffle.espresso.jdwp.api;
2424

25-
import com.oracle.truffle.espresso.jdwp.impl.DebuggerController;
26-
2725
import java.lang.ref.WeakReference;
26+
import java.util.ArrayList;
2827
import java.util.Arrays;
2928
import java.util.HashMap;
29+
import java.util.List;
3030
import java.util.function.Supplier;
3131
import java.util.regex.Matcher;
3232
import java.util.regex.Pattern;
3333

34+
import com.oracle.truffle.espresso.jdwp.impl.DebuggerController;
35+
3436
/**
3537
* Class that keeps an ID representation of all entities when communicating with a debugger through
3638
* JDWP. Each entity will be assigned a unique ID. Only weak references are kept for entities.
@@ -57,6 +59,10 @@ public final class Ids<T> {
5759

5860
private DebuggerController controller;
5961

62+
private List<Object> pinnedObjects = new ArrayList<>();
63+
64+
private volatile boolean pinningState = false;
65+
6066
@SuppressWarnings({"unchecked", "rawtypes"})
6167
public Ids(T nullObject) {
6268
this.nullObject = nullObject;
@@ -84,8 +90,7 @@ public long getIdAsLong(T object) {
8490
}
8591
}
8692
// check the anonymous inner class map
87-
if (object instanceof KlassRef) {
88-
KlassRef klass = (KlassRef) object;
93+
if (object instanceof KlassRef klass) {
8994
Long id = innerClassIDMap.get(klass.getNameAsString());
9095
if (id != null) {
9196
// inject new klass under existing ID
@@ -155,13 +160,16 @@ private synchronized long generateUniqueId(T object) {
155160
expandedArray[objects.length] = new WeakReference<>(object);
156161
objects = expandedArray;
157162
log(() -> "Generating new ID: " + id + " for object: " + object);
158-
if (object instanceof KlassRef) {
159-
KlassRef klass = (KlassRef) object;
163+
if (object instanceof KlassRef klass) {
160164
Matcher matcher = ANON_INNER_CLASS_PATTERN.matcher(klass.getNameAsString());
161165
if (matcher.matches()) {
162166
innerClassIDMap.put(klass.getNameAsString(), id);
163167
}
164168
}
169+
// pin object when VM in suspended state
170+
if (pinningState) {
171+
pinnedObjects.add(object);
172+
}
165173
return id;
166174
}
167175

@@ -203,4 +211,19 @@ private void log(Supplier<String> supplier) {
203211
controller.finest(supplier);
204212
}
205213
}
214+
215+
public synchronized void pinAll() {
216+
pinningState = true;
217+
for (WeakReference<T> object : objects) {
218+
Object toPin = object.get();
219+
if (toPin != null) {
220+
pinnedObjects.add(toPin);
221+
}
222+
}
223+
}
224+
225+
public synchronized void unpinAll() {
226+
pinningState = false;
227+
pinnedObjects.clear();
228+
}
206229
}

espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerController.java

Lines changed: 48 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public final class DebuggerController implements ContextsListener {
7575
private final Map<Object, SimpleLock> suspendLocks = Collections.synchronizedMap(new HashMap<>());
7676
private final Map<Object, SuspendedInfo> suspendedInfos = Collections.synchronizedMap(new HashMap<>());
7777
private final Map<Object, SteppingInfo> commandRequestIds = new HashMap<>();
78-
private final Map<Object, ThreadJob<?>> threadJobs = new HashMap<>();
78+
private final Map<Object, InvokeJob<?>> invokeJobs = new HashMap<>();
7979
private final Map<Object, FieldBreakpointEvent> fieldBreakpointExpected = new HashMap<>();
8080
private final Map<Object, MethodBreakpointEvent> methodBreakpointExpected = new HashMap<>();
8181
private final Map<Breakpoint, BreakpointInfo> breakpointInfos = new HashMap<>();
@@ -176,6 +176,10 @@ public JDWPContext getContext() {
176176
return context;
177177
}
178178

179+
public Ids<Object> getIds() {
180+
return ids;
181+
}
182+
179183
public SuspendedInfo getSuspendedInfo(Object thread) {
180184
return suspendedInfos.get(thread);
181185
}
@@ -386,6 +390,7 @@ public Object[] getVisibleGuestThreads() {
386390
}
387391

388392
void forceResumeAll() {
393+
ids.unpinAll();
389394
for (Object thread : getVisibleGuestThreads()) {
390395
boolean resumed = false;
391396
SimpleLock suspendLock = getSuspendLock(thread);
@@ -398,6 +403,7 @@ void forceResumeAll() {
398403
}
399404

400405
public void resumeAll() {
406+
ids.unpinAll();
401407
for (Object thread : getVisibleGuestThreads()) {
402408
SimpleLock suspendLock = getSuspendLock(thread);
403409
synchronized (suspendLock) {
@@ -453,6 +459,9 @@ public void immediateSuspend(Object eventThread, byte suspendPolicy, Callable<Vo
453459
suspend(thread);
454460
}
455461
}
462+
// pin all objects when VM in suspended state
463+
ids.pinAll();
464+
456465
// immediately suspend the event thread
457466
suspend(eventThread, SuspendStrategy.EVENT_THREAD, Collections.singletonList(callBack), true);
458467
break;
@@ -469,6 +478,8 @@ public void suspendAll() {
469478
for (Object thread : getVisibleGuestThreads()) {
470479
suspend(thread);
471480
}
481+
// pin all objects
482+
ids.pinAll();
472483
}
473484

474485
private synchronized SimpleLock getSuspendLock(Object thread) {
@@ -480,7 +491,7 @@ private synchronized SimpleLock getSuspendLock(Object thread) {
480491
return lock;
481492
}
482493

483-
private String getThreadName(Object thread) {
494+
String getThreadName(Object thread) {
484495
return getContext().getThreadName(thread);
485496
}
486497

@@ -631,19 +642,18 @@ public void suspend(Object thread, byte suspendPolicy, List<Callable<Void>> jobs
631642
case SuspendStrategy.ALL:
632643
fine(() -> "Suspend ALL");
633644

634-
Thread suspendThread = new Thread(new Runnable() {
635-
@Override
636-
public void run() {
637-
// suspend other threads
638-
for (Object activeThread : getVisibleGuestThreads()) {
639-
if (activeThread != thread) {
640-
fine(() -> "Request thread suspend for other thread: " + getThreadName(activeThread));
641-
DebuggerController.this.suspend(activeThread);
642-
}
645+
Thread suspendThread = new Thread(() -> {
646+
// suspend other threads
647+
for (Object activeThread : getVisibleGuestThreads()) {
648+
if (activeThread != thread) {
649+
fine(() -> "Request thread suspend for other thread: " + getThreadName(activeThread));
650+
DebuggerController.this.suspend(activeThread);
643651
}
644652
}
645653
});
646654
suspendThread.start();
655+
// pin all objects
656+
ids.pinAll();
647657
suspendEventThread(thread, forceSuspend, jobs);
648658
break;
649659
}
@@ -661,83 +671,60 @@ private static void runJobs(List<Callable<Void>> jobs) {
661671

662672
private void suspendEventThread(Object thread, boolean forceSuspend, List<Callable<Void>> jobs) {
663673
fine(() -> "Suspending event thread: " + getThreadName(thread) + " with new suspension count: " + threadSuspension.getSuspensionCount(thread));
664-
lockThread(thread, forceSuspend, true, jobs);
674+
lockThread(thread, forceSuspend, jobs);
665675
}
666676

667-
private void lockThread(Object thread, boolean forceSuspend, boolean isFirstCall, List<Callable<Void>> jobs) {
677+
private void lockThread(Object thread, boolean forceSuspend, List<Callable<Void>> jobs) {
668678
SimpleLock lock = getSuspendLock(thread);
669-
// in case a thread job is already posted on this thread
670-
checkThreadJobsAndRun(thread, forceSuspend);
671679
synchronized (lock) {
672680
if (!forceSuspend && !threadSuspension.isHardSuspended(thread)) {
673681
// thread was resumed from other command, so don't suspend now
674682
return;
675683
}
684+
685+
if (lock.isLocked()) {
686+
threadSuspension.suspendThread(thread);
687+
runJobs(jobs);
688+
}
689+
}
690+
while (!Thread.currentThread().isInterrupted()) {
676691
try {
677-
if (lock.isLocked() && isFirstCall) {
678-
threadSuspension.suspendThread(thread);
679-
runJobs(jobs);
680-
}
681-
while (lock.isLocked()) {
682-
fine(() -> "lock.wait() for thread: " + getThreadName(thread));
692+
synchronized (lock) {
693+
if (!lock.isLocked()) {
694+
// released from other thread, so break loop
695+
break;
696+
}
683697
// no reason to hold a hard suspension status, since now
684698
// we have the actual suspension status and suspended information
685699
threadSuspension.removeHardSuspendedThread(thread);
686-
lock.wait();
700+
fine(() -> "lock.wait() for thread: " + getThreadName(thread));
701+
// Having the thread lock, we can check if an invoke job was posted outside of
702+
// locking, and if so, we postpone blocking the thread until next time around.
703+
if (!invokeJobs.containsKey(thread)) {
704+
lock.wait();
705+
}
687706
}
688707
} catch (InterruptedException e) {
689708
// the thread was interrupted, so let it run dry
690709
// make sure the interrupted flag is set though
691710
Thread.currentThread().interrupt();
692711
}
712+
checkInvokeJobsAndRun(thread);
693713
}
694-
695-
checkThreadJobsAndRun(thread, forceSuspend);
696-
getGCPrevention().releaseActiveWhileSuspended(thread);
697714
fine(() -> "lock wakeup for thread: " + getThreadName(thread));
698715
}
699716

700-
private void checkThreadJobsAndRun(Object thread, boolean forceSuspend) {
701-
if (threadJobs.containsKey(thread)) {
702-
// re-acquire the thread lock after completing
703-
// the job, to avoid the thread resuming.
704-
SimpleLock suspendLock = getSuspendLock(thread);
705-
synchronized (suspendLock) {
706-
suspendLock.acquire();
707-
}
708-
// a thread job was posted on this thread
709-
// only wake up to perform the job a go back to sleep
710-
ThreadJob<?> job = threadJobs.remove(thread);
711-
byte suspensionStrategy = job.getSuspensionStrategy();
712-
713-
if (suspensionStrategy == SuspendStrategy.ALL) {
714-
Object[] allThreads = getVisibleGuestThreads();
715-
// resume all threads during invocation of method to avoid potential deadlocks
716-
for (Object activeThread : allThreads) {
717-
if (activeThread != thread) {
718-
resume(activeThread);
719-
}
720-
}
721-
// perform the job on this thread
722-
job.runJob();
723-
// suspend all other threads after the invocation
724-
for (Object activeThread : allThreads) {
725-
if (activeThread != thread) {
726-
suspend(activeThread);
727-
}
728-
}
729-
} else {
730-
job.runJob();
731-
}
732-
lockThread(thread, forceSuspend, false, Collections.emptyList());
717+
private void checkInvokeJobsAndRun(Object thread) {
718+
if (invokeJobs.containsKey(thread)) {
719+
InvokeJob<?> job = invokeJobs.remove(thread);
720+
job.runJob(this);
733721
}
734722
}
735723

736-
public void postJobForThread(ThreadJob<?> job) {
724+
public void postInvokeJobForThread(InvokeJob<?> job) {
737725
SimpleLock lock = getSuspendLock(job.getThread());
738726
synchronized (lock) {
739-
threadJobs.put(job.getThread(), job);
740-
lock.release();
727+
invokeJobs.put(job.getThread(), job);
741728
lock.notifyAll();
742729
}
743730
}

espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/GCPrevention.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,13 @@
2222
*/
2323
package com.oracle.truffle.espresso.jdwp.impl;
2424

25-
import java.util.ArrayList;
26-
import java.util.HashMap;
2725
import java.util.HashSet;
2826

2927
public final class GCPrevention {
3028

3129
// simply hold a strong reference to all objects for which
3230
// GC should be disabled
3331
private final HashSet<Object> prevent = new HashSet<>();
34-
private final HashMap<Object, ArrayList<Object>> activeWhileSuspended = new HashMap<>();
3532

3633
public void disableGC(Object object) {
3734
prevent.add(object);
@@ -45,13 +42,4 @@ public void clearAll() {
4542
prevent.clear();
4643
}
4744

48-
public synchronized void setActiveWhileSuspended(Object guestThread, Object obj) {
49-
activeWhileSuspended.putIfAbsent(guestThread, new ArrayList<>());
50-
activeWhileSuspended.get(guestThread).add(obj);
51-
}
52-
53-
public synchronized void releaseActiveWhileSuspended(Object guestThread) {
54-
activeWhileSuspended.remove(guestThread);
55-
}
56-
5745
}
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
import java.util.concurrent.Callable;
2626

27-
public final class ThreadJob<T> {
27+
public final class InvokeJob<T> {
2828

2929
private final Object jobLock = new Object();
3030
private final Object thread;
@@ -33,11 +33,11 @@ public final class ThreadJob<T> {
3333
private boolean resultAvailable;
3434
private JobResult<T> result;
3535

36-
public ThreadJob(Object guestThread, Callable<T> task) {
36+
public InvokeJob(Object guestThread, Callable<T> task) {
3737
this(guestThread, task, SuspendStrategy.EVENT_THREAD);
3838
}
3939

40-
public ThreadJob(Object guestThread, Callable<T> task, byte suspensionStrategy) {
40+
public InvokeJob(Object guestThread, Callable<T> task, byte suspensionStrategy) {
4141
this.thread = guestThread;
4242
this.callable = task;
4343
this.suspensionStrategy = suspensionStrategy;
@@ -47,17 +47,33 @@ public Object getThread() {
4747
return thread;
4848
}
4949

50-
public byte getSuspensionStrategy() {
51-
return suspensionStrategy;
52-
}
53-
54-
public void runJob() {
50+
public void runJob(DebuggerController controller) {
51+
Object[] visibleGuestThreads = controller.getVisibleGuestThreads();
5552
result = new JobResult<>();
5653
try {
54+
if (suspensionStrategy == SuspendStrategy.ALL) {
55+
controller.getIds().unpinAll();
56+
// resume all other threads during invocation of method to avoid potential deadlocks
57+
for (Object activeThread : visibleGuestThreads) {
58+
if (activeThread != thread) {
59+
controller.resume(activeThread);
60+
}
61+
}
62+
}
63+
// perform the job on this thread
5764
result.setResult(callable.call());
5865
} catch (Throwable e) {
5966
result.setException(e);
6067
} finally {
68+
if (suspensionStrategy == SuspendStrategy.ALL) {
69+
controller.getIds().pinAll();
70+
// suspend all other threads after the invocation
71+
for (Object activeThread : visibleGuestThreads) {
72+
if (activeThread != thread) {
73+
controller.suspend(activeThread);
74+
}
75+
}
76+
}
6177
resultAvailable = true;
6278
synchronized (jobLock) {
6379
jobLock.notifyAll();

0 commit comments

Comments
 (0)