1 /*
  2  * Copyright (c) 2022, 2024, Oracle and/or its affiliates. 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  * @bug 8284199 8296779 8306647
 27  * @summary Basic tests for StructuredTaskScope with scoped values
 28  * @enablePreview
 29  * @run junit WithScopedValue
 30  */
 31 
 32 import java.util.concurrent.StructuredTaskScope;
 33 import java.util.concurrent.StructuredTaskScope.Subtask;
 34 import java.util.concurrent.StructuredTaskScope.Joiner;
 35 import java.util.concurrent.StructureViolationException;
 36 import java.util.concurrent.ThreadFactory;
 37 import java.util.concurrent.atomic.AtomicBoolean;
 38 import java.util.stream.Stream;
 39 
 40 import org.junit.jupiter.api.Test;
 41 import org.junit.jupiter.params.ParameterizedTest;
 42 import org.junit.jupiter.params.provider.MethodSource;
 43 import static org.junit.jupiter.api.Assertions.*;
 44 
 45 class WithScopedValue {
 46 
 47     private static Stream<ThreadFactory> factories() {
 48         return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
 49     }
 50 
 51     /**
 52      * Test that fork inherits a scoped value into a child thread.
 53      */
 54     @ParameterizedTest
 55     @MethodSource("factories")
 56     void testForkInheritsScopedValue1(ThreadFactory factory) throws Exception {
 57         ScopedValue<String> name = ScopedValue.newInstance();
 58         String value = ScopedValue.where(name, "x").call(() -> {
 59             try (var scope = StructuredTaskScope.open(Joiner.awaitAll(),
 60                                                       cf -> cf.withThreadFactory(factory))) {
 61                 Subtask<String> subtask = scope.fork(() -> {
 62                     return name.get(); // child should read "x"
 63                 });
 64                 scope.join();
 65                 return subtask.get();
 66             }
 67         });
 68         assertEquals(value, "x");
 69     }
 70 
 71     /**
 72      * Test that fork inherits a scoped value into a grandchild thread.
 73      */
 74     @ParameterizedTest
 75     @MethodSource("factories")
 76     void testForkInheritsScopedValue2(ThreadFactory factory) throws Exception {
 77         ScopedValue<String> name = ScopedValue.newInstance();
 78         String value = ScopedValue.where(name, "x").call(() -> {
 79             try (var scope1 = StructuredTaskScope.open(Joiner.awaitAll(),
 80                                                        cf -> cf.withThreadFactory(factory))) {
 81                 Subtask<String> subtask1 = scope1.fork(() -> {
 82                     try (var scope2 = StructuredTaskScope.open(Joiner.awaitAll(),
 83                                                                cf -> cf.withThreadFactory(factory))) {
 84                         Subtask<String> subtask2 = scope2.fork(() -> {
 85                             return name.get(); // grandchild should read "x"
 86                         });
 87                         scope2.join();
 88                         return subtask2.get();
 89                     }
 90                 });
 91                 scope1.join();
 92                 return subtask1.get();
 93             }
 94         });
 95         assertEquals(value, "x");
 96     }
 97 
 98     /**
 99      * Test that fork inherits a rebound scoped value into a grandchild thread.
100      */
101     @ParameterizedTest
102     @MethodSource("factories")
103     void testForkInheritsScopedValue3(ThreadFactory factory) throws Exception {
104         ScopedValue<String> name = ScopedValue.newInstance();
105         String value = ScopedValue.where(name, "x").call(() -> {
106             try (var scope1 = StructuredTaskScope.open(Joiner.awaitAll(),
107                                                        cf -> cf.withThreadFactory(factory))) {
108                 Subtask<String> subtask1 = scope1.fork(() -> {
109                     assertEquals(name.get(), "x");  // child should read "x"
110 
111                     // rebind name to "y"
112                     String grandchildValue = ScopedValue.where(name, "y").call(() -> {
113                         try (var scope2 = StructuredTaskScope.open(Joiner.awaitAll(),
114                                                                    cf -> cf.withThreadFactory(factory))) {
115                             Subtask<String> subtask2 = scope2.fork(() -> {
116                                 return name.get(); // grandchild should read "y"
117                             });
118                             scope2.join();
119                             return subtask2.get();
120                         }
121                     });
122 
123                     assertEquals(name.get(), "x");  // child should read "x"
124                     return grandchildValue;
125                 });
126                 scope1.join();
127                 return subtask1.get();
128             }
129         });
130         assertEquals(value, "y");
131     }
132 
133     /**
134      * Test exiting a dynamic scope with an open task scope.
135      */
136     @Test
137     void testStructureViolation1() throws Exception {
138         ScopedValue<String> name = ScopedValue.newInstance();
139         class Box {
140             StructuredTaskScope<Object, Void> scope;
141         }
142         var box = new Box();
143         try {
144             try {
145                 ScopedValue.where(name, "x").run(() -> {
146                     box.scope = StructuredTaskScope.open(Joiner.awaitAll());
147                 });
148                 fail();
149             } catch (StructureViolationException expected) { }
150 
151             // underlying flock should be closed and fork should fail to start a thread
152             StructuredTaskScope<Object, Void> scope = box.scope;
153             AtomicBoolean ran = new AtomicBoolean();
154             Subtask<Object> subtask = scope.fork(() -> {
155                 ran.set(true);
156                 return null;
157             });
158             scope.join();
159             assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
160             assertFalse(ran.get());
161         } finally {
162             StructuredTaskScope<Object, Void> scope = box.scope;
163             if (scope != null) {
164                 scope.close();
165             }
166         }
167     }
168 
169     /**
170      * Test closing a StructuredTaskScope while executing in a dynamic scope.
171      */
172     @Test
173     void testStructureViolation2() throws Exception {
174         ScopedValue<String> name = ScopedValue.newInstance();
175         try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
176             ScopedValue.where(name, "x").run(() -> {
177                 assertThrows(StructureViolationException.class, scope::close);
178             });
179         }
180     }
181 
182     /**
183      * Test fork when a scoped value is bound after a StructuredTaskScope is created.
184      */
185     @Test
186     void testStructureViolation3() throws Exception {
187         ScopedValue<String> name = ScopedValue.newInstance();
188         try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
189             ScopedValue.where(name, "x").run(() -> {
190                 assertThrows(StructureViolationException.class,
191                         () -> scope.fork(() -> "foo"));
192             });
193         }
194     }
195 
196     /**
197      * Test fork when a scoped value is re-bound after a StructuredTaskScope is created.
198      */
199     @Test
200     void testStructureViolation4() throws Exception {
201         ScopedValue<String> name1 = ScopedValue.newInstance();
202         ScopedValue<String> name2 = ScopedValue.newInstance();
203 
204         // rebind
205         ScopedValue.where(name1, "x").run(() -> {
206             try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
207                 ScopedValue.where(name1, "y").run(() -> {
208                     assertThrows(StructureViolationException.class,
209                             () -> scope.fork(() -> "foo"));
210                 });
211             }
212         });
213 
214         // new binding
215         ScopedValue.where(name1, "x").run(() -> {
216             try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
217                 ScopedValue.where(name2, "y").run(() -> {
218                     assertThrows(StructureViolationException.class,
219                             () -> scope.fork(() -> "foo"));
220                 });
221             }
222         });
223     }
224 }