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 --enable-preview -source ${jdk.version} StressStackOverflow.java
 29  * @run main/othervm/timeout=300 -XX:-TieredCompilation --enable-preview StressStackOverflow
 30  * @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 --enable-preview StressStackOverflow
 31  * @run main/othervm/timeout=300 --enable-preview 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 }