1 /* 2 * Copyright (c) 2021, 2022 Red Hat, Inc. 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 24 /** 25 * @test 26 * @summary StressStackOverflow the recovery path for ScopedValue 27 * @modules jdk.incubator.concurrent 28 * @compile StressStackOverflow.java 29 * @run main/othervm/timeout=300 -XX:-TieredCompilation StressStackOverflow 30 * @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 StressStackOverflow 31 * @run main/othervm/timeout=300 StressStackOverflow 32 */ 33 34 import java.util.concurrent.Callable; 35 import java.util.concurrent.ThreadFactory; 36 import java.util.concurrent.ThreadLocalRandom; 37 import jdk.incubator.concurrent.ScopedValue; 38 import jdk.incubator.concurrent.StructureViolationException; 39 import jdk.incubator.concurrent.StructuredTaskScope; 40 41 public class StressStackOverflow { 42 public static final ScopedValue<Integer> el = ScopedValue.newInstance(); 43 44 public static final ScopedValue<Integer> inheritedValue = ScopedValue.newInstance(); 45 46 final ThreadLocalRandom tlr = ThreadLocalRandom.current(); 47 static final TestFailureException testFailureException = new TestFailureException("Unexpected value for ScopedValue"); 48 int ITERS = 1_000_000; 49 50 static class TestFailureException extends RuntimeException { 51 TestFailureException(String s) { super(s); } 52 } 53 54 // Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable 55 // and Runnable interfaces. Which one gets tested depends on the constructor argument. 56 class DeepRecursion implements Callable, Runnable { 57 58 static enum Behaviour {CALL, RUN} 59 final Behaviour behaviour; 60 61 public DeepRecursion(Behaviour behaviour) { 62 this.behaviour = behaviour; 63 } 64 65 public void run() { 66 final var last = el.get(); 67 ITERS--; 68 var nextRandomFloat = tlr.nextFloat(); 69 try { 70 switch (behaviour) { 71 case CALL -> 72 ScopedValue.where(el, el.get() + 1).call(() -> fibonacci_pad(20, this)); 73 case RUN -> 74 ScopedValue.where(el, el.get() + 1).run(() -> fibonacci_pad(20, this)); 75 } 76 if (!last.equals(el.get())) { 77 throw testFailureException; 78 } 79 } catch (StackOverflowError e) { 80 if (nextRandomFloat <= 0.1) { 81 ScopedValue.where(el, el.get() + 1).run(this); 82 } 83 } catch (TestFailureException e) { 84 throw e; 85 } catch (Throwable throwable) { 86 // StackOverflowErrors cause many different failures. These include 87 // StructureViolationExceptions and InvocationTargetExceptions. This test 88 // checks that, no matter what the failure mode, scoped values are handled 89 // correctly. 90 } finally { 91 if (!last.equals(el.get())) { 92 throw testFailureException; 93 } 94 } 95 96 Thread.yield(); 97 } 98 99 public Object call() { 100 run(); 101 return null; 102 } 103 } 104 105 static final Runnable nop = new Runnable() { 106 public void run() { } 107 }; 108 109 // Consume some stack. 110 // 111 112 // The double recursion used here prevents an optimizing JIT from 113 // inlining all the recursive calls, which would make it 114 // ineffective. 115 private long fibonacci_pad1(int n, Runnable op) { 116 if (n <= 1) { 117 op.run(); 118 return n; 119 } 120 return fibonacci_pad1(n - 1, op) + fibonacci_pad1(n - 2, nop); 121 } 122 123 private static final Integer I_42 = 42; 124 125 long fibonacci_pad(int n, Runnable op) { 126 final var last = el.get(); 127 try { 128 return fibonacci_pad1(tlr.nextInt(n), op); 129 } catch (StackOverflowError err) { 130 if (!inheritedValue.get().equals(I_42)) { 131 throw testFailureException; 132 } 133 if (!last.equals(el.get())) { 134 throw testFailureException; 135 } 136 throw err; 137 } 138 } 139 140 // Run op in a new thread. Platform or virtual threads are chosen at random. 141 void runInNewThread(Runnable op) { 142 var threadFactory 143 = (tlr.nextBoolean() ? Thread.ofPlatform() : Thread.ofVirtual()).factory(); 144 try (var scope = new StructuredTaskScope<Object>("", threadFactory)) { 145 var future = scope.fork(() -> { 146 op.run(); 147 return null; 148 }); 149 future.get(); 150 scope.join(); 151 } catch (Exception e) { 152 throw new RuntimeException(e); 153 } 154 } 155 156 public void run() { 157 try { 158 ScopedValue.where(inheritedValue, 42).where(el, 0).run(() -> { 159 try (var scope = new StructuredTaskScope<Object>()) { 160 try { 161 if (tlr.nextBoolean()) { 162 // Repeatedly test Scoped Values set by ScopedValue::call() and ScopedValue::run() 163 final var deepRecursion 164 = new DeepRecursion(tlr.nextBoolean() ? DeepRecursion.Behaviour.CALL : DeepRecursion.Behaviour.RUN); 165 deepRecursion.run(); 166 } else { 167 // Recursively run ourself until we get a stack overflow 168 // Catch the overflow and make sure the recovery path works 169 // for values inherited from a StructuredTaskScope. 170 Runnable op = new Runnable() { 171 public void run() { 172 try { 173 fibonacci_pad(20, this); 174 } catch (StackOverflowError e) { 175 } catch (TestFailureException e) { 176 throw e; 177 } catch (Throwable throwable) { 178 // StackOverflowErrors cause many different failures. These include 179 // StructureViolationExceptions and InvocationTargetExceptions. This test 180 // checks that, no matter what the failure mode, scoped values are handled 181 // correctly. 182 } finally { 183 if (!inheritedValue.get().equals(I_42)) { 184 throw testFailureException; 185 } 186 } 187 } 188 }; 189 runInNewThread(op); 190 } 191 scope.join(); 192 } catch (StructureViolationException structureViolationException) { 193 // Can happen if a stack overflow prevented a StackableScope from 194 // being removed. We can continue. 195 } catch (Exception e) { 196 throw new RuntimeException(e); 197 } 198 } 199 }); 200 } catch (StructureViolationException structureViolationException) { 201 // Can happen if a stack overflow prevented a StackableScope from 202 // being removed. We can continue. 203 } 204 } 205 206 public static void main(String[] args) { 207 var torture = new StressStackOverflow(); 208 while (torture.ITERS > 0) { 209 torture.run(); 210 } 211 System.out.println("OK"); 212 } 213 }