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