jdk-24/test/jdk/java/lang/ScopedValue/StressStackOverflow.java
Alan Bateman 707154235b 8331189: Implementation of Scoped Values (Third Preview)
Reviewed-by: aph, jpai, mcimadamore
2024-05-30 15:41:56 +00:00

262 lines
10 KiB
Java

/*
* Copyright (c) 2021, 2022 Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=default
* @summary Stress ScopedValue stack overflow recovery path
* @enablePreview
* @run main/othervm/timeout=300 StressStackOverflow
*/
/*
* @test id=no-TieredCompilation
* @enablePreview
* @run main/othervm/timeout=300 -XX:-TieredCompilation StressStackOverflow
*/
/*
* @test id=TieredStopAtLevel1
* @enablePreview
* @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 StressStackOverflow
*/
/*
* @test id=no-vmcontinuations
* @requires vm.continuations
* @enablePreview
* @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StressStackOverflow
*/
import java.lang.ScopedValue.CallableOp;
import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.StructureViolationException;
import java.util.concurrent.StructuredTaskScope;
import java.util.function.Supplier;
public class StressStackOverflow {
public static final ScopedValue<Integer> el = ScopedValue.newInstance();
public static final ScopedValue<Integer> inheritedValue = ScopedValue.newInstance();
static final TestFailureException testFailureException = new TestFailureException("Unexpected value for ScopedValue");
int ITERS = 1_000_000;
static class TestFailureException extends RuntimeException {
TestFailureException(String s) { super(s); }
}
static final long DURATION_IN_NANOS = Duration.ofMinutes(1).toNanos();
// Test the ScopedValue recovery mechanism for stack overflows. We implement both CallableOp
// and Runnable interfaces. Which one gets tested depends on the constructor argument.
class DeepRecursion implements CallableOp<Object, RuntimeException>, Supplier<Object>, Runnable {
enum Behaviour {
CALL, RUN;
private static final Behaviour[] values = values();
public static Behaviour choose(ThreadLocalRandom tlr) {
return values[tlr.nextInt(3)];
}
}
final Behaviour behaviour;
public DeepRecursion(Behaviour behaviour) {
this.behaviour = behaviour;
}
public void run() {
final var last = el.get();
while (ITERS-- > 0) {
if (System.nanoTime() - startTime > DURATION_IN_NANOS) {
return;
}
var nextRandomFloat = ThreadLocalRandom.current().nextFloat();
try {
switch (behaviour) {
case CALL -> ScopedValue.where(el, el.get() + 1).call(() -> 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() {
run();
return null;
}
public Object call() {
return get();
}
}
static final Runnable nop = () -> {};
// Consume some stack.
//
// The double recursion used here prevents an optimizing JIT from
// inlining all the recursive calls, which would make it
// ineffective.
private long fibonacci_pad1(int n, Runnable op) {
if (n <= 1) {
op.run();
return n;
}
return fibonacci_pad1(n - 1, op) + fibonacci_pad1(n - 2, nop);
}
private static final Integer I_42 = 42;
long fibonacci_pad(int n, Runnable op) {
final var last = el.get();
try {
return fibonacci_pad1(ThreadLocalRandom.current().nextInt(n), op);
} catch (StackOverflowError err) {
if (!inheritedValue.get().equals(I_42)) {
throw testFailureException;
}
if (!last.equals(el.get())) {
throw testFailureException;
}
throw err;
}
}
// Run op in a new thread. Platform or virtual threads are chosen at random.
void runInNewThread(Runnable op) {
var threadFactory
= (ThreadLocalRandom.current().nextBoolean() ? Thread.ofPlatform() : Thread.ofVirtual()).factory();
try (var scope = new StructuredTaskScope<>("", threadFactory)) {
var handle = scope.fork(() -> {
op.run();
return null;
});
scope.join();
handle.get();
} catch (TestFailureException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void run() {
try {
ScopedValue.where(inheritedValue, 42).where(el, 0).run(() -> {
try (var scope = new StructuredTaskScope<>()) {
try {
if (ThreadLocalRandom.current().nextBoolean()) {
// Repeatedly test Scoped Values set by ScopedValue::call(), get(), and run()
final var deepRecursion
= new DeepRecursion(DeepRecursion.Behaviour.choose(ThreadLocalRandom.current()));
deepRecursion.run();
} else {
// Recursively run ourself until we get a stack overflow
// Catch the overflow and make sure the recovery path works
// for values inherited from a StructuredTaskScope.
Runnable op = new Runnable() {
public void run() {
try {
fibonacci_pad(20, this);
} catch (StackOverflowError e) {
} 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 (!inheritedValue.get().equals(I_42)) {
throw testFailureException;
}
}
}
};
runInNewThread(op);
}
scope.join();
} catch (StructureViolationException structureViolationException) {
// Can happen if a stack overflow prevented a StackableScope from
// being removed. We can continue.
} catch (TestFailureException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} catch (TestFailureException e) {
throw e;
} catch (Exception e) {
// Can happen if a stack overflow prevented a StackableScope from
// being removed. We can continue.
}
}
static long startTime = System.nanoTime();
public static void main(String[] args) {
var torture = new StressStackOverflow();
while (torture.ITERS > 0
&& System.nanoTime() - startTime <= DURATION_IN_NANOS) {
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");
}
}