8308609: java/lang/ScopedValue/StressStackOverflow.java fails with "-XX:-VMContinuations"

Reviewed-by: alanb
This commit is contained in:
Andrew Haley 2023-06-16 12:21:11 +00:00
parent b412fc79c3
commit 44a8aa0691
7 changed files with 82 additions and 70 deletions

View File

@ -1385,9 +1385,8 @@ JVM_ENTRY(jobject, JVM_FindScopedValueBindings(JNIEnv *env, jclass cls))
InstanceKlass* holder = method->method_holder(); InstanceKlass* holder = method->method_holder();
if (name == vmSymbols::runWith_method_name()) { if (name == vmSymbols::runWith_method_name()) {
if ((holder == resolver.Carrier_klass if (holder == vmClasses::Thread_klass()
|| holder == vmClasses::VirtualThread_klass() || holder == resolver.Carrier_klass) {
|| holder == vmClasses::Thread_klass())) {
loc = 1; loc = 1;
} }
} }

View File

@ -1578,7 +1578,7 @@ public class Thread implements Runnable {
*/ */
@Hidden @Hidden
@ForceInline @ForceInline
private void runWith(Object bindings, Runnable op) { final void runWith(Object bindings, Runnable op) {
ensureMaterializedForStackWalk(bindings); ensureMaterializedForStackWalk(bindings);
op.run(); op.run();
Reference.reachabilityFence(bindings); Reference.reachabilityFence(bindings);

View File

@ -433,7 +433,8 @@ class ThreadBuilders {
// run is specified to do nothing when Thread is a virtual thread // run is specified to do nothing when Thread is a virtual thread
if (Thread.currentThread() == this && !runInvoked) { if (Thread.currentThread() == this && !runInvoked) {
runInvoked = true; runInvoked = true;
task.run(); Object bindings = Thread.scopedValueBindings();
runWith(bindings, task);
} }
} }

View File

@ -24,7 +24,6 @@
*/ */
package java.lang; package java.lang;
import java.lang.ref.Reference;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.Locale; import java.util.Locale;
@ -54,7 +53,6 @@ import jdk.internal.vm.StackableScope;
import jdk.internal.vm.ThreadContainer; import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.ThreadContainers; import jdk.internal.vm.ThreadContainers;
import jdk.internal.vm.annotation.ChangesCurrentThread; import jdk.internal.vm.annotation.ChangesCurrentThread;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden; import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.JvmtiMountTransition; import jdk.internal.vm.annotation.JvmtiMountTransition;
@ -306,7 +304,7 @@ final class VirtualThread extends BaseVirtualThread {
event.commit(); event.commit();
} }
Object bindings = scopedValueBindings(); Object bindings = Thread.scopedValueBindings();
try { try {
runWith(bindings, task); runWith(bindings, task);
} catch (Throwable exc) { } catch (Throwable exc) {
@ -334,14 +332,6 @@ final class VirtualThread extends BaseVirtualThread {
} }
} }
@Hidden
@ForceInline
private void runWith(Object bindings, Runnable op) {
ensureMaterializedForStackWalk(bindings);
op.run();
Reference.reachabilityFence(bindings);
}
/** /**
* Mounts this virtual thread onto the current platform thread. On * Mounts this virtual thread onto the current platform thread. On
* return, the current thread is the virtual thread. * return, the current thread is the virtual thread.

View File

@ -42,8 +42,6 @@ javax/management/remote/mandatory/loading/MissingClassTest.java 8145413 windows-
javax/management/remote/mandatory/loading/RMIDownloadTest.java 8308366 windows-x64 javax/management/remote/mandatory/loading/RMIDownloadTest.java 8308366 windows-x64
java/lang/ScopedValue/StressStackOverflow.java 8309646 generic-all
java/lang/instrument/NativeMethodPrefixAgent.java 8307169 generic-all java/lang/instrument/NativeMethodPrefixAgent.java 8307169 generic-all
########## ##########

View File

@ -487,8 +487,6 @@ java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic-
java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-x64 java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-x64
java/lang/invoke/RicochetTest.java 8251969 generic-all java/lang/invoke/RicochetTest.java 8251969 generic-all
java/lang/ScopedValue/StressStackOverflow.java 8303498 linux-s390x
############################################################################ ############################################################################
# jdk_instrument # jdk_instrument

View File

@ -21,8 +21,8 @@
* questions. * questions.
*/ */
/** /*
* @test * @test id=default
* @summary StressStackOverflow the recovery path for ScopedValue * @summary StressStackOverflow the recovery path for ScopedValue
* @enablePreview * @enablePreview
* @run main/othervm/timeout=300 -XX:-TieredCompilation StressStackOverflow * @run main/othervm/timeout=300 -XX:-TieredCompilation StressStackOverflow
@ -30,8 +30,14 @@
* @run main/othervm/timeout=300 StressStackOverflow * @run main/othervm/timeout=300 StressStackOverflow
*/ */
/*
* @test id=no-vmcontinuations
* @requires vm.continuations
* @enablePreview
* @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StressStackOverflow
*/
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.StructureViolationException; import java.util.concurrent.StructureViolationException;
import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.StructuredTaskScope;
@ -42,7 +48,6 @@ public class StressStackOverflow {
public static final ScopedValue<Integer> inheritedValue = ScopedValue.newInstance(); public static final ScopedValue<Integer> inheritedValue = ScopedValue.newInstance();
final ThreadLocalRandom tlr = ThreadLocalRandom.current();
static final TestFailureException testFailureException = new TestFailureException("Unexpected value for ScopedValue"); static final TestFailureException testFailureException = new TestFailureException("Unexpected value for ScopedValue");
int ITERS = 1_000_000; int ITERS = 1_000_000;
@ -50,13 +55,15 @@ public class StressStackOverflow {
TestFailureException(String s) { super(s); } TestFailureException(String s) { super(s); }
} }
static final long MINUTES = 60 * 1_000_000_000L; // 60 * 10**9 ns
// Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable // Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable
// and Runnable interfaces. Which one gets tested depends on the constructor argument. // and Runnable interfaces. Which one gets tested depends on the constructor argument.
class DeepRecursion implements Callable, Supplier, Runnable { class DeepRecursion implements Callable<Object>, Supplier<Object>, Runnable {
static enum Behaviour { enum Behaviour {
CALL, GET, RUN; CALL, GET, RUN;
private static Behaviour[] values = values(); private static final Behaviour[] values = values();
public static Behaviour choose(ThreadLocalRandom tlr) { public static Behaviour choose(ThreadLocalRandom tlr) {
return values[tlr.nextInt(3)]; return values[tlr.nextInt(3)];
} }
@ -70,38 +77,40 @@ public class StressStackOverflow {
public void run() { public void run() {
final var last = el.get(); final var last = el.get();
ITERS--; while (ITERS-- > 0) {
var nextRandomFloat = tlr.nextFloat(); if (System.nanoTime() - startTime > 3 * MINUTES) { // 3 minutes is long enough
try { return;
switch (behaviour) {
case CALL ->
ScopedValue.where(el, el.get() + 1).call(() -> fibonacci_pad(20, this));
case GET ->
ScopedValue.where(el, el.get() + 1).get(() -> fibonacci_pad(20, this));
case RUN ->
ScopedValue.where(el, el.get() + 1).run(() -> fibonacci_pad(20, this));
} }
if (!last.equals(el.get())) {
throw testFailureException;
}
} catch (StackOverflowError e) {
if (nextRandomFloat <= 0.1) {
ScopedValue.where(el, el.get() + 1).run(this);
}
} catch (TestFailureException e) {
throw e;
} catch (Throwable throwable) {
// StackOverflowErrors cause many different failures. These include
// StructureViolationExceptions and InvocationTargetExceptions. This test
// checks that, no matter what the failure mode, scoped values are handled
// correctly.
} finally {
if (!last.equals(el.get())) {
throw testFailureException;
}
}
Thread.yield(); var nextRandomFloat = ThreadLocalRandom.current().nextFloat();
try {
switch (behaviour) {
case CALL -> ScopedValue.where(el, el.get() + 1).call(() -> fibonacci_pad(20, this));
case GET -> ScopedValue.where(el, el.get() + 1).get(() -> fibonacci_pad(20, this));
case RUN -> ScopedValue.where(el, el.get() + 1).run(() -> fibonacci_pad(20, this));
}
if (!last.equals(el.get())) {
throw testFailureException;
}
} catch (StackOverflowError e) {
if (nextRandomFloat <= 0.1) {
ScopedValue.where(el, el.get() + 1).run(this);
}
} catch (TestFailureException e) {
throw e;
} catch (Throwable throwable) {
// StackOverflowErrors cause many different failures. These include
// StructureViolationExceptions and InvocationTargetExceptions. This test
// checks that, no matter what the failure mode, scoped values are handled
// correctly.
} finally {
if (!last.equals(el.get())) {
throw testFailureException;
}
}
Thread.yield();
}
} }
public Object get() { public Object get() {
@ -114,13 +123,10 @@ public class StressStackOverflow {
} }
} }
static final Runnable nop = new Runnable() { static final Runnable nop = () -> {};
public void run() { }
};
// Consume some stack. // Consume some stack.
// //
// The double recursion used here prevents an optimizing JIT from // The double recursion used here prevents an optimizing JIT from
// inlining all the recursive calls, which would make it // inlining all the recursive calls, which would make it
// ineffective. // ineffective.
@ -137,7 +143,7 @@ public class StressStackOverflow {
long fibonacci_pad(int n, Runnable op) { long fibonacci_pad(int n, Runnable op) {
final var last = el.get(); final var last = el.get();
try { try {
return fibonacci_pad1(tlr.nextInt(n), op); return fibonacci_pad1(ThreadLocalRandom.current().nextInt(n), op);
} catch (StackOverflowError err) { } catch (StackOverflowError err) {
if (!inheritedValue.get().equals(I_42)) { if (!inheritedValue.get().equals(I_42)) {
throw testFailureException; throw testFailureException;
@ -152,14 +158,16 @@ public class StressStackOverflow {
// Run op in a new thread. Platform or virtual threads are chosen at random. // Run op in a new thread. Platform or virtual threads are chosen at random.
void runInNewThread(Runnable op) { void runInNewThread(Runnable op) {
var threadFactory var threadFactory
= (tlr.nextBoolean() ? Thread.ofPlatform() : Thread.ofVirtual()).factory(); = (ThreadLocalRandom.current().nextBoolean() ? Thread.ofPlatform() : Thread.ofVirtual()).factory();
try (var scope = new StructuredTaskScope<Object>("", threadFactory)) { try (var scope = new StructuredTaskScope<>("", threadFactory)) {
var handle = scope.fork(() -> { var handle = scope.fork(() -> {
op.run(); op.run();
return null; return null;
}); });
scope.join(); scope.join();
handle.get(); handle.get();
} catch (TestFailureException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -168,12 +176,12 @@ public class StressStackOverflow {
public void run() { public void run() {
try { try {
ScopedValue.where(inheritedValue, 42).where(el, 0).run(() -> { ScopedValue.where(inheritedValue, 42).where(el, 0).run(() -> {
try (var scope = new StructuredTaskScope<Object>()) { try (var scope = new StructuredTaskScope<>()) {
try { try {
if (tlr.nextBoolean()) { if (ThreadLocalRandom.current().nextBoolean()) {
// Repeatedly test Scoped Values set by ScopedValue::call(), get(), and run() // Repeatedly test Scoped Values set by ScopedValue::call(), get(), and run()
final var deepRecursion final var deepRecursion
= new DeepRecursion(DeepRecursion.Behaviour.choose(tlr)); = new DeepRecursion(DeepRecursion.Behaviour.choose(ThreadLocalRandom.current()));
deepRecursion.run(); deepRecursion.run();
} else { } else {
// Recursively run ourself until we get a stack overflow // Recursively run ourself until we get a stack overflow
@ -204,21 +212,39 @@ public class StressStackOverflow {
} catch (StructureViolationException structureViolationException) { } catch (StructureViolationException structureViolationException) {
// Can happen if a stack overflow prevented a StackableScope from // Can happen if a stack overflow prevented a StackableScope from
// being removed. We can continue. // being removed. We can continue.
} catch (TestFailureException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
}); });
} catch (StructureViolationException structureViolationException) { } catch (TestFailureException e) {
throw e;
} catch (Exception e) {
// Can happen if a stack overflow prevented a StackableScope from // Can happen if a stack overflow prevented a StackableScope from
// being removed. We can continue. // being removed. We can continue.
} }
} }
static long startTime = System.nanoTime();
public static void main(String[] args) { public static void main(String[] args) {
var torture = new StressStackOverflow(); var torture = new StressStackOverflow();
while (torture.ITERS > 0) { while (torture.ITERS > 0
torture.run(); && System.nanoTime() - startTime <= 3 * MINUTES) { // 3 minutes is long enough
try {
torture.run();
if (inheritedValue.isBound()) {
throw new TestFailureException("Should not be bound here");
}
} catch (TestFailureException e) {
throw e;
} catch (Exception e) {
// ScopedValueContainer and StructuredTaskScope can
// throw many exceptions on stack overflow. Ignore
// them all.
}
} }
System.out.println("OK"); System.out.println("OK");
} }