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