Skip to content

Commit 5b8cc51

Browse files
committed
[GR-45324] [GR-45325] Stability fixes of Breakpoint.
PullRequest: graal/14244
2 parents 2493bd5 + 53780d5 commit 5b8cc51

File tree

2 files changed

+111
-9
lines changed
  • truffle/src

2 files changed

+111
-9
lines changed

truffle/src/com.oracle.truffle.api.debug.test/src/com/oracle/truffle/api/debug/test/BreakpointTest.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -51,10 +51,13 @@
5151
import java.io.File;
5252
import java.net.URI;
5353
import java.util.List;
54+
import java.util.concurrent.ExecutionException;
5455
import java.util.concurrent.ExecutorService;
5556
import java.util.concurrent.Executors;
5657
import java.util.concurrent.Future;
58+
import java.util.concurrent.TimeUnit;
5759
import java.util.concurrent.atomic.AtomicBoolean;
60+
import java.util.concurrent.atomic.AtomicReference;
5861

5962
import org.graalvm.polyglot.Context;
6063
import org.graalvm.polyglot.Source;
@@ -973,6 +976,94 @@ public void testGlobalBreakpointsInMultipleSessions() throws Throwable {
973976

974977
}
975978

979+
/**
980+
* Runs repeated evaluations of the provided Source in a separate thread, until join() is
981+
* called.
982+
*/
983+
private static final class EvalLoop {
984+
985+
private final AtomicBoolean evaluating = new AtomicBoolean(true);
986+
private final Thread evalLoop;
987+
private final AtomicReference<Throwable> evalThrowable = new AtomicReference<>();
988+
989+
EvalLoop(DebuggerTester tester, Source source) {
990+
evalLoop = new Thread(() -> {
991+
while (evaluating.get()) {
992+
tester.startEval(source);
993+
boolean suspended;
994+
do {
995+
suspended = false;
996+
try {
997+
tester.expectDone();
998+
} catch (AssertionError ae) {
999+
if (ae.getCause() != null && ae.getCause().getMessage().startsWith("Expected done but got event Suspended at")) {
1000+
// We've hit the breakpoint. We'll resume automatically.
1001+
suspended = true;
1002+
} else {
1003+
throw ae;
1004+
}
1005+
}
1006+
} while (suspended);
1007+
}
1008+
});
1009+
evalLoop.setUncaughtExceptionHandler((thread, thrwbl) -> evalThrowable.set(thrwbl));
1010+
evalLoop.start();
1011+
}
1012+
1013+
void join() throws InterruptedException, ExecutionException {
1014+
evaluating.set(false);
1015+
evalLoop.join();
1016+
if (evalThrowable.get() != null) {
1017+
throw new ExecutionException(evalThrowable.get());
1018+
}
1019+
}
1020+
}
1021+
1022+
@Test
1023+
public void testBreakpointInstallDuringDispose() throws InterruptedException, ExecutionException {
1024+
final Source source = testSource("ROOT(\n" +
1025+
" LOOP(1000,\n" +
1026+
" STATEMENT)\n" + // break here
1027+
")\n");
1028+
try (DebuggerSession session = startSession()) {
1029+
EvalLoop evalLoop = new EvalLoop(tester, source);
1030+
ExecutorService exec = Executors.newSingleThreadExecutor();
1031+
for (int repeatCount = 0; repeatCount <= 1000; repeatCount++) {
1032+
Breakpoint breakpoint = Breakpoint.newBuilder(getSourceImpl(source)).lineIs(3).build();
1033+
breakpoint.setEnabled(false);
1034+
session.install(breakpoint);
1035+
Future<?> installation = exec.submit(() -> {
1036+
breakpoint.setEnabled(true);
1037+
});
1038+
Thread.sleep(0, repeatCount % 100);
1039+
breakpoint.dispose();
1040+
installation.get();
1041+
}
1042+
exec.shutdown();
1043+
// We want the test infrastructure to interrupt this to see the full thread dump.
1044+
exec.awaitTermination(1, TimeUnit.DAYS);
1045+
evalLoop.join();
1046+
}
1047+
}
1048+
1049+
@Test
1050+
public void testBreakpointParallelInstallDispose() throws InterruptedException, ExecutionException {
1051+
// Install and dispose of breakpoints parallel with the execution.
1052+
final Source source = testSource("ROOT(\n" +
1053+
" LOOP(1000,\n" +
1054+
" STATEMENT)\n" + // break here
1055+
")\n");
1056+
try (DebuggerSession session = startSession()) {
1057+
EvalLoop evalLoop = new EvalLoop(tester, source);
1058+
for (int repeatCount = 0; repeatCount <= 100; repeatCount++) {
1059+
Breakpoint breakpoint = Breakpoint.newBuilder(getSourceImpl(source)).lineIs(3).build();
1060+
session.install(breakpoint);
1061+
breakpoint.dispose();
1062+
}
1063+
evalLoop.join();
1064+
}
1065+
}
1066+
9761067
@Test
9771068
public void testResolveListener() {
9781069
final Source source = testSource("ROOT(\n" +

truffle/src/com.oracle.truffle.api.debug/src/com/oracle/truffle/api/debug/Breakpoint.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import com.oracle.truffle.api.instrumentation.ExecuteSourceListener;
6767
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
6868
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
69+
import com.oracle.truffle.api.instrumentation.Instrumenter;
6970
import com.oracle.truffle.api.instrumentation.LoadSourceSectionEvent;
7071
import com.oracle.truffle.api.instrumentation.LoadSourceSectionListener;
7172
import com.oracle.truffle.api.instrumentation.SourceFilter;
@@ -198,7 +199,7 @@ public enum Kind {
198199

199200
private final AtomicReference<EventBinding<?>> sourceBinding = new AtomicReference<>();
200201
private final List<EventBinding<? extends ExecutionEventNodeFactory>> execBindings = new ArrayList<>();
201-
private LocationsInExecutedSources locationsInExecutedSources;
202+
private volatile LocationsInExecutedSources locationsInExecutedSources;
202203
private volatile boolean breakpointBindingReady;
203204

204205
Breakpoint(BreakpointLocation key, SuspendAnchor suspendAnchor) {
@@ -273,6 +274,7 @@ public boolean isEnabled() {
273274
*/
274275
public void setEnabled(boolean enabled) {
275276
boolean doInstall = false;
277+
Debugger d;
276278
synchronized (this) {
277279
if (disposed) {
278280
// cannot enable disposed breakpoints
@@ -284,10 +286,13 @@ public void setEnabled(boolean enabled) {
284286
}
285287
this.enabled = enabled;
286288
}
289+
d = debugger;
287290
}
288291
if (doInstall) {
289292
if (enabled) {
290-
install();
293+
if (d != null) {
294+
installInstrumentation(d.getInstrumenter());
295+
} // else when debugger == null, the breakpoint was disposed.
291296
} else {
292297
uninstall();
293298
}
@@ -371,9 +376,9 @@ public void dispose() {
371376
DebuggerSession[] breakpointSessions = null;
372377
Debugger breakpointDebugger = null;
373378
LocationsInExecutedSources locations = null;
379+
setEnabled(false);
374380
synchronized (this) {
375381
if (!disposed) {
376-
setEnabled(false);
377382
breakpointSessions = sessions.toArray(new DebuggerSession[sessions.size()]);
378383
breakpointDebugger = debugger;
379384
debugger = null;
@@ -511,6 +516,7 @@ synchronized void installGlobal(Debugger d) {
511516
}
512517

513518
private void install(Debugger d) {
519+
assert d != null;
514520
assert Thread.holdsLock(this);
515521
if (this.debugger != null && this.debugger != d) {
516522
throw new IllegalStateException("Breakpoint is already installed in a different Debugger instance.");
@@ -542,18 +548,19 @@ boolean install(DebuggerSession d, boolean failOnError) {
542548
install(d.getDebugger());
543549
}
544550
if (enabled) {
545-
install();
551+
installInstrumentation(d.getDebugger().getInstrumenter());
546552
}
547553
return true;
548554
}
549555

550-
private void install() {
556+
private void installInstrumentation(Instrumenter instrumenter) {
551557
EventBinding<?> binding = sourceBinding.get();
552558
if (binding == null || binding.isDisposed()) {
553559
BreakpointLocation.LocationFilters filters = locationKey.createLocationFilters(suspendAnchor);
554560
if (locationKey.isLoadBindingNeeded()) {
555-
locationsInExecutedSources = new LocationsInExecutedSources();
556-
EventBinding<?> loadBinding = debugger.getInstrumenter().createLoadSourceSectionBinding(filters.nearestFilter, filters.sectionFilter, locationsInExecutedSources, true);
561+
LocationsInExecutedSources locations = new LocationsInExecutedSources();
562+
locationsInExecutedSources = locations;
563+
EventBinding<?> loadBinding = instrumenter.createLoadSourceSectionBinding(filters.nearestFilter, filters.sectionFilter, locations, true);
557564
if (sourceBinding.compareAndSet(null, loadBinding)) {
558565
try {
559566
loadBinding.attach();
@@ -569,7 +576,7 @@ private void install() {
569576
}
570577
if (needExecBinding) {
571578
EventBinding<? extends ExecutionEventNodeFactory> execBinding;
572-
execBinding = debugger.getInstrumenter().attachExecutionEventFactory(filters.nearestFilter, filters.sectionFilter, new BreakpointNodeFactory());
579+
execBinding = instrumenter.attachExecutionEventFactory(filters.nearestFilter, filters.sectionFilter, new BreakpointNodeFactory());
573580
execBindingAdded(execBinding);
574581
}
575582
}
@@ -591,6 +598,10 @@ public void onLoad(LoadSourceSectionEvent event) {
591598
boolean doAssign;
592599
EventBinding<?> execBinding = null;
593600
synchronized (this) {
601+
if (debugger == null) {
602+
// disposed
603+
return;
604+
}
594605
if (!(doAssign = executedSources.contains(source))) {
595606
SourceSection oldSection = loadedSections.put(source, section);
596607
if (oldSection == null) {

0 commit comments

Comments
 (0)