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