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