1 /*
  2  * Copyright (c) 2022, 2023, 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  * @summary Basic tests for StructuredTaskScope with scoped values
 27  * @modules jdk.incubator.concurrent
 28  * @run junit WithScopedValue
 29  */
 30 
 31 import jdk.incubator.concurrent.ScopedValue;
 32 import jdk.incubator.concurrent.StructuredTaskScope;
 33 import jdk.incubator.concurrent.StructureViolationException;
 34 import java.util.concurrent.Future;
 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", () -> {
 58             try (var scope = new StructuredTaskScope<String>(null, factory)) {
 59                 Future<String> future = scope.fork(() -> {
 60                     return name.get(); // child should read "x"
 61                 });
 62                 scope.join();
 63                 return future.resultNow();
 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", () -> {
 77             try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
 78                 Future<String> future1 = scope1.fork(() -> {
 79                     try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
 80                         Future<String> future2 = scope2.fork(() -> {
 81                             return name.get(); // grandchild should read "x"
 82                         });
 83                         scope2.join();
 84                         return future2.resultNow();
 85                     }
 86                 });
 87                 scope1.join();
 88                 return future1.resultNow();
 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", () -> {
102             try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
103                 Future<String> future1 = scope1.fork(() -> {
104                     assertEquals(name.get(), "x");  // child should read "x"
105 
106                     // rebind name to "y"
107                     String grandchildValue = ScopedValue.where(name, "y", () -> {
108                         try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
109                             Future<String> future2 = scope2.fork(() -> {
110                                 return name.get(); // grandchild should read "y"
111                             });
112                             scope2.join();
113                             return future2.resultNow();
114                         }
115                     });
116 
117                     assertEquals(name.get(), "x");  // child should read "x"
118                     return grandchildValue;
119                 });
120                 scope1.join();
121                 return future1.resultNow();
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", () -> {
140                     box.scope = new StructuredTaskScope<Object>();
141                 });
142                 fail();
143             } catch (StructureViolationException expected) { }
144 
145             // underlying flock should be closed, fork should return a cancelled task
146             StructuredTaskScope<Object> scope = box.scope;
147             AtomicBoolean ran = new AtomicBoolean();
148             Future<Object> future = scope.fork(() -> {
149                 ran.set(true);
150                 return null;
151             });
152             assertTrue(future.isCancelled());
153             scope.join();
154             assertFalse(ran.get());
155 
156         } finally {
157             StructuredTaskScope<Object> scope = box.scope;
158             if (scope != null) {
159                 scope.close();
160             }
161         }
162     }
163 
164     /**
165      * Test closing a StructuredTaskScope while executing in a dynamic scope.
166      */
167     @Test
168     void testStructureViolation2() throws Exception {
169         ScopedValue<String> name = ScopedValue.newInstance();
170         try (var scope = new StructuredTaskScope<String>()) {
171             ScopedValue.where(name, "x", () -> {
172                 assertThrows(StructureViolationException.class, scope::close);
173             });
174         }
175     }
176 
177     /**
178      * Test fork when a scoped value is bound after a StructuredTaskScope is created.
179      */
180     @Test
181     void testStructureViolation3() throws Exception {
182         ScopedValue<String> name = ScopedValue.newInstance();
183         try (var scope = new StructuredTaskScope<String>()) {
184             ScopedValue.where(name, "x", () -> {
185                 assertThrows(StructureViolationException.class,
186                         () -> scope.fork(() -> "foo"));
187             });
188         }
189     }
190 
191     /**
192      * Test fork when a scoped value is re-bound after a StructuredTaskScope is created.
193      */
194     @Test
195     void testStructureViolation4() throws Exception {
196         ScopedValue<String> name1 = ScopedValue.newInstance();
197         ScopedValue<String> name2 = ScopedValue.newInstance();
198 
199         // rebind
200         ScopedValue.where(name1, "x", () -> {
201             try (var scope = new StructuredTaskScope<String>()) {
202                 ScopedValue.where(name1, "y", () -> {
203                     assertThrows(StructureViolationException.class,
204                             () -> scope.fork(() -> "foo"));
205                 });
206             }
207         });
208 
209         // new binding
210         ScopedValue.where(name1, "x", () -> {
211             try (var scope = new StructuredTaskScope<String>()) {
212                 ScopedValue.where(name2, "y", () -> {
213                     assertThrows(StructureViolationException.class,
214                             () -> scope.fork(() -> "foo"));
215                 });
216             }
217         });
218     }
219 }