Skip to content

Commit 6c1400a

Browse files
committed
[GR-64734] Use thread states to ensure waited-on TLAs are submitted to threads that are responsive.
1 parent 1882eef commit 6c1400a

File tree

7 files changed

+269
-91
lines changed

7 files changed

+269
-91
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright (c) 2025, 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+
package com.oracle.truffle.espresso.blocking;
24+
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.Objects;
29+
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.concurrent.ExecutionException;
31+
import java.util.concurrent.Future;
32+
33+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
34+
import com.oracle.truffle.api.ThreadLocalAction;
35+
import com.oracle.truffle.api.TruffleSafepoint;
36+
import com.oracle.truffle.api.nodes.Node;
37+
import com.oracle.truffle.espresso.impl.SuppressFBWarnings;
38+
import com.oracle.truffle.espresso.meta.EspressoError;
39+
import com.oracle.truffle.espresso.runtime.EspressoContext;
40+
import com.oracle.truffle.espresso.runtime.staticobject.StaticObject;
41+
import com.oracle.truffle.espresso.threads.ThreadAccess;
42+
import com.oracle.truffle.espresso.vm.InterpreterToVM;
43+
import com.oracle.truffle.espresso.vm.VM;
44+
45+
public final class ThreadRequests {
46+
private static final Thread[] EMPTY_THREAD_ARRAY = new Thread[0];
47+
48+
public abstract static class Request<T> extends ThreadLocalAction {
49+
private final Map<Thread, T> result;
50+
51+
/**
52+
* Performs the request on the given thread, and return the result of that action.
53+
* <p>
54+
* If this returns {@code null}, the
55+
* {@link #request(EspressoContext, Request, Node, StaticObject[], Object[]) request} will
56+
* use {@link #placeHolderValue()} for its result.
57+
*/
58+
public abstract T action(Thread current);
59+
60+
/**
61+
* The placeholder value for threads that are unresponsive.
62+
* <p>
63+
* Responsive threads for which {@link #action(Thread)} returns {@code null} will also use
64+
* this value as their result.
65+
*/
66+
public T placeHolderValue() {
67+
return null;
68+
}
69+
70+
@TruffleBoundary
71+
public Request(boolean hasSideEffects, boolean synchronous) {
72+
super(hasSideEffects, synchronous);
73+
this.result = new ConcurrentHashMap<>();
74+
}
75+
76+
@Override
77+
protected final void perform(Access access) {
78+
T value = action(access.getThread());
79+
if (value != null) {
80+
result.put(access.getThread(), value);
81+
}
82+
}
83+
84+
final Map<Thread, T> result() {
85+
return result;
86+
}
87+
}
88+
89+
public static VM.StackTrace[] getStackTraces(EspressoContext ctx, int maxDepth, Node location, StaticObject... threads) {
90+
StaticObject[] threadList = getThreadList(ctx, threads);
91+
VM.StackTrace[] result = new VM.StackTrace[threadList.length];
92+
request(ctx, new GetStackTrace(maxDepth), location, threadList, result);
93+
return result;
94+
}
95+
96+
public static StaticObject[] findDeadlocks(EspressoContext ctx, boolean monitorsOnly, Node location, StaticObject... threads) {
97+
StaticObject[] threadList = getThreadList(ctx, threads);
98+
FindDeadLocks findDeadLocks = new FindDeadLocks(ctx, Thread.currentThread(), monitorsOnly);
99+
request(ctx, findDeadLocks, location, threadList, new Void[threadList.length]);
100+
return findDeadLocks.getDeadLockedThreads();
101+
}
102+
103+
/**
104+
* From the given {@code threads} array, finds the threads which are
105+
* {@link ThreadAccess#isResponsive(StaticObject) responsive}, then submits the {@code request}
106+
* as a {@link ThreadLocalAction} only on these responsive threads.
107+
* <p>
108+
* The {@code result} array will then be filled with the result of the request, with
109+
* {@code result[i]} corresponding to the result of running the action on the thread in
110+
* {@code threads[i]}.
111+
* <p>
112+
* The passed {@code threads} array must be non-null. Consider using
113+
* {@link EspressoContext#getActiveThreads()} for requesting on all threads.
114+
*/
115+
@TruffleBoundary
116+
public static <T> void request(EspressoContext ctx, Request<T> request, Node location, StaticObject[] threads, T[] result) {
117+
Objects.requireNonNull(threads);
118+
Objects.requireNonNull(result);
119+
if (threads.length != result.length) {
120+
throw EspressoError.shouldNotReachHere("Wrong usage of ThreadRequests.request");
121+
}
122+
try {
123+
// Prevent responsive threads from entering native.
124+
freeze(ctx, threads, true);
125+
126+
// Filter to remove unresponsive threads.
127+
ThreadAccess access = ctx.getThreadAccess();
128+
List<Thread> running = new ArrayList<>();
129+
130+
for (int i = 0; i < threads.length; i++) {
131+
StaticObject t = threads[i];
132+
if (t == access.getCurrentGuestThread() || // current thread is always responsive.
133+
StaticObject.notNull(t) && isResponsive(access, t)) {
134+
running.add(access.getHost(t));
135+
}
136+
}
137+
138+
// Submit the request to responsive threads, and wait for completion.
139+
Future<Void> future = ctx.getEnv().submitThreadLocal(running.toArray(EMPTY_THREAD_ARRAY), request);
140+
TruffleSafepoint.setBlockedThreadInterruptible(location, f -> {
141+
try {
142+
future.get();
143+
} catch (ExecutionException e) {
144+
throw EspressoError.shouldNotReachHere(e);
145+
}
146+
}, future);
147+
148+
// Build the result map.
149+
Map<Thread, T> tlaResult = request.result();
150+
for (int i = 0; i < threads.length; i++) {
151+
StaticObject t = threads[i];
152+
if (StaticObject.notNull(t)) {
153+
Thread host = access.getHost(t);
154+
if (tlaResult.containsKey(host)) {
155+
result[i] = tlaResult.get(host);
156+
continue;
157+
}
158+
}
159+
result[i] = request.placeHolderValue();
160+
}
161+
} finally {
162+
// Re-allow threads to enter native.
163+
freeze(ctx, threads, false);
164+
}
165+
}
166+
167+
private static StaticObject[] getThreadList(EspressoContext ctx, StaticObject[] threads) {
168+
StaticObject[] threadList = threads;
169+
if (threadList == null) {
170+
threadList = ctx.getActiveThreads();
171+
}
172+
return threadList;
173+
}
174+
175+
private static boolean isResponsive(ThreadAccess access, StaticObject thread) {
176+
return StaticObject.notNull(thread) &&
177+
access.isAlive(thread) &&
178+
!access.isVirtualThread(thread) &&
179+
access.isResponsive(thread);
180+
}
181+
182+
@SuppressFBWarnings(value = {"IMSE"}, justification = "Not dubious, may happen if failed to lock all native transitions.")
183+
private static void freeze(EspressoContext ctx, StaticObject[] threads, boolean block) {
184+
for (StaticObject t : threads) {
185+
if (StaticObject.notNull(t)) {
186+
try {
187+
ctx.getThreadAccess().blockNativeTransitions(t, block);
188+
} catch (IllegalMonitorStateException e) {
189+
// This may happen on unlock if a safepoint threw while we were locking at the
190+
// start of the request.
191+
}
192+
}
193+
}
194+
}
195+
196+
private static final class GetStackTrace extends Request<VM.StackTrace> {
197+
GetStackTrace(int maxDepth) {
198+
super(false, false);
199+
this.maxDepth = maxDepth;
200+
}
201+
202+
private final int maxDepth;
203+
204+
@Override
205+
public VM.StackTrace action(Thread current) {
206+
return InterpreterToVM.getStackTrace(InterpreterToVM.DefaultHiddenFramesFilter.INSTANCE, maxDepth);
207+
}
208+
}
209+
210+
private static final class FindDeadLocks extends Request<Void> {
211+
FindDeadLocks(EspressoContext ctx, Thread initiatingThread, boolean monitorsOnly) {
212+
super(false, true);
213+
this.context = ctx;
214+
this.initiatingThread = initiatingThread;
215+
this.monitorsOnly = monitorsOnly;
216+
}
217+
218+
private final EspressoContext context;
219+
private final Thread initiatingThread;
220+
private final boolean monitorsOnly;
221+
222+
private volatile StaticObject[] deadLockedThreads = null;
223+
224+
@Override
225+
public Void action(Thread current) {
226+
if (current == initiatingThread) {
227+
deadLockedThreads = context.getEspressoEnv().getThreadRegistry().findDeadlocks(monitorsOnly);
228+
}
229+
return null;
230+
}
231+
232+
public StaticObject[] getDeadLockedThreads() {
233+
return deadLockedThreads;
234+
}
235+
}
236+
}

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/descriptors/EspressoSymbols.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,7 @@ public static class Names {
987987
public static final Symbol<Name> HIDDEN_THREAD_PARK_LOCK = SYMBOLS.putName("0HIDDEN_THREAD_PARK_LOCK");
988988
public static final Symbol<Name> HIDDEN_HOST_THREAD = SYMBOLS.putName("0HIDDEN_HOST_THREAD");
989989
public static final Symbol<Name> HIDDEN_ESPRESSO_MANAGED = SYMBOLS.putName("0HIDDEN_ESPRESSO_MANAGED");
990+
public static final Symbol<Name> HIDDEN_TO_NATIVE_LOCK = SYMBOLS.putName("0HIDDEN_TO_NATIVE_LOCK");
990991
public static final Symbol<Name> HIDDEN_INTERRUPTED = SYMBOLS.putName("0HIDDEN_INTERRUPTED");
991992
public static final Symbol<Name> HIDDEN_THREAD_PENDING_MONITOR = SYMBOLS.putName("0HIDDEN_THREAD_PENDING_MONITOR");
992993
public static final Symbol<Name> HIDDEN_THREAD_WAITING_MONITOR = SYMBOLS.putName("0HIDDEN_THREAD_WAITING_MONITOR");

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ private static class HiddenField {
214214
new HiddenField(Names.HIDDEN_INTERRUPTED, Types._boolean, VersionRange.lower(13), NO_ADDITIONAL_FLAGS),
215215
new HiddenField(Names.HIDDEN_HOST_THREAD),
216216
new HiddenField(Names.HIDDEN_ESPRESSO_MANAGED, Types._boolean, VersionRange.ALL, NO_ADDITIONAL_FLAGS),
217+
new HiddenField(Names.HIDDEN_TO_NATIVE_LOCK, Types.java_lang_Object, VersionRange.ALL, Constants.ACC_FINAL),
217218
new HiddenField(Names.HIDDEN_DEPRECATION_SUPPORT),
218219
new HiddenField(Names.HIDDEN_THREAD_UNPARK_SIGNALS, Types._int, VersionRange.ALL, Constants.ACC_VOLATILE),
219220
new HiddenField(Names.HIDDEN_THREAD_PARK_LOCK, Types.java_lang_Object, VersionRange.ALL, Constants.ACC_FINAL),

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ public Meta(EspressoContext context) {
564564
.maybeHiddenfield(java_lang_Thread);
565565
HIDDEN_HOST_THREAD = java_lang_Thread.requireHiddenField(Names.HIDDEN_HOST_THREAD);
566566
HIDDEN_ESPRESSO_MANAGED = java_lang_Thread.requireHiddenField(Names.HIDDEN_ESPRESSO_MANAGED);
567+
HIDDEN_TO_NATIVE_LOCK = java_lang_Thread.requireHiddenField(Names.HIDDEN_TO_NATIVE_LOCK);
567568
HIDDEN_DEPRECATION_SUPPORT = java_lang_Thread.requireHiddenField(Names.HIDDEN_DEPRECATION_SUPPORT);
568569
HIDDEN_THREAD_UNPARK_SIGNALS = java_lang_Thread.requireHiddenField(Names.HIDDEN_THREAD_UNPARK_SIGNALS);
569570
HIDDEN_THREAD_PARK_LOCK = java_lang_Thread.requireHiddenField(Names.HIDDEN_THREAD_PARK_LOCK);
@@ -1851,6 +1852,7 @@ private DiffVersionLoadHelper diff() {
18511852
public final Method java_lang_Thread_getThreadGroup;
18521853
public final Field HIDDEN_HOST_THREAD;
18531854
public final Field HIDDEN_ESPRESSO_MANAGED;
1855+
public final Field HIDDEN_TO_NATIVE_LOCK;
18541856
public final Field HIDDEN_INTERRUPTED;
18551857
public final Field HIDDEN_THREAD_UNPARK_SIGNALS;
18561858
public final Field HIDDEN_THREAD_PARK_LOCK;

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_Thread.java

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,24 @@
2424

2525
import static com.oracle.truffle.espresso.substitutions.SubstitutionFlag.IsTrivial;
2626
import static com.oracle.truffle.espresso.threads.EspressoThreadRegistry.getThreadId;
27+
import static com.oracle.truffle.espresso.threads.ThreadState.TIMED_SLEEPING;
2728

2829
import java.util.Arrays;
29-
import java.util.concurrent.ExecutionException;
30-
import java.util.concurrent.Future;
3130
import java.util.concurrent.TimeUnit;
3231
import java.util.concurrent.atomic.AtomicLong;
3332

3433
import com.oracle.truffle.api.CompilerDirectives;
3534
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
36-
import com.oracle.truffle.api.ThreadLocalAction;
37-
import com.oracle.truffle.api.TruffleSafepoint;
3835
import com.oracle.truffle.api.dsl.Bind;
3936
import com.oracle.truffle.api.dsl.Cached;
4037
import com.oracle.truffle.api.dsl.Specialization;
4138
import com.oracle.truffle.api.nodes.DirectCallNode;
4239
import com.oracle.truffle.api.nodes.Node;
4340
import com.oracle.truffle.espresso.EspressoLanguage;
4441
import com.oracle.truffle.espresso.blocking.GuestInterruptedException;
42+
import com.oracle.truffle.espresso.blocking.ThreadRequests;
4543
import com.oracle.truffle.espresso.impl.Field;
4644
import com.oracle.truffle.espresso.impl.Klass;
47-
import com.oracle.truffle.espresso.meta.EspressoError;
4845
import com.oracle.truffle.espresso.meta.Meta;
4946
import com.oracle.truffle.espresso.runtime.EspressoContext;
5047
import com.oracle.truffle.espresso.runtime.staticobject.StaticObject;
@@ -55,7 +52,6 @@
5552
import com.oracle.truffle.espresso.substitutions.SubstitutionNode;
5653
import com.oracle.truffle.espresso.substitutions.SubstitutionProfiler;
5754
import com.oracle.truffle.espresso.substitutions.VersionFilter;
58-
import com.oracle.truffle.espresso.threads.State;
5955
import com.oracle.truffle.espresso.threads.ThreadAccess;
6056
import com.oracle.truffle.espresso.threads.Transition;
6157
import com.oracle.truffle.espresso.vm.InterpreterToVM;
@@ -357,48 +353,20 @@ public static StaticObject getStackTrace(StaticObject thread, int maxDepth, Espr
357353
if (hostThread == Thread.currentThread()) {
358354
stackTrace = InterpreterToVM.getStackTrace(InterpreterToVM.DefaultHiddenFramesFilter.INSTANCE, maxDepth);
359355
} else {
360-
stackTrace = asyncGetStackTrace(hostThread, maxDepth, context, node);
361-
if (stackTrace == null) {
356+
stackTrace = asyncGetStackTrace(thread, maxDepth, context, node);
357+
if (stackTrace == null) { // unresponsive.
362358
return StaticObject.NULL;
363359
}
364360
}
365361

366-
return context.getMeta().java_lang_StackTraceElement.allocateReferenceArray(stackTrace.size, i -> {
367-
StaticObject ste = context.getMeta().java_lang_StackTraceElement.allocateInstance(context);
368-
VM.fillInElement(ste, stackTrace.trace[i], context.getMeta());
369-
return ste;
370-
});
362+
return stackTrace.toGuest(context);
371363
}
372364

373365
@TruffleBoundary
374-
private static VM.StackTrace asyncGetStackTrace(Thread thread, int maxDepth, EspressoContext context, Node node) {
366+
private static VM.StackTrace asyncGetStackTrace(StaticObject thread, int maxDepth, EspressoContext context, Node node) {
375367
assert maxDepth >= 0;
376-
CollectStackTraceAction action = new CollectStackTraceAction(maxDepth);
377-
Future<Void> future = context.getEnv().submitThreadLocal(new Thread[]{thread}, action);
378-
TruffleSafepoint.setBlockedThreadInterruptible(node, f -> {
379-
try {
380-
future.get();
381-
} catch (ExecutionException e) {
382-
throw EspressoError.shouldNotReachHere(e);
383-
}
384-
}, future);
385-
return action.result;
386-
}
387-
388-
private static final class CollectStackTraceAction extends ThreadLocalAction {
389-
private final int maxDepth;
390-
VM.StackTrace result;
391-
392-
protected CollectStackTraceAction(int maxDepth) {
393-
super(false, false);
394-
this.maxDepth = maxDepth;
395-
}
396-
397-
@Override
398-
protected void perform(Access access) {
399-
assert access.getThread() == Thread.currentThread();
400-
result = InterpreterToVM.getStackTrace(InterpreterToVM.DefaultHiddenFramesFilter.INSTANCE, maxDepth);
401-
}
368+
VM.StackTrace[] stackTraces = ThreadRequests.getStackTraces(context, maxDepth, node, thread);
369+
return stackTraces[0];
402370
}
403371

404372
@Substitution(languageFilter = VersionFilter.Java20OrLater.class)

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/threads/SuspendLock.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,12 @@ private void suspendHandshake() {
9494
boolean wasInterrupted = false;
9595
shouldSuspend = true;
9696
access.getContext().getEnv().submitThreadLocal(new Thread[]{access.getHost(thread)}, new SuspendAction(this));
97-
while (!isSuspended()) {
97+
while (access.isResponsive(thread) && // Don't bother waiting on unresponsive threads.
98+
!isSuspended()) {
9899
shouldSuspend = true;
99100
try {
100101
synchronized (handshakeLock) {
101-
if (!access.isAlive(thread)) {
102+
if (access.isAlive(thread)) {
102103
// If thread terminates, we don't want to wait forever
103104
handshakeLock.wait(100);
104105
} else {

0 commit comments

Comments
 (0)