1 /*
  2  * Copyright (c) 2022, 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  * @enablePreview
 28  * @modules jdk.incubator.concurrent
 29  * @run testng WithScopedValue
 30  */
 31 
 32 import jdk.incubator.concurrent.ScopedValue;
 33 import jdk.incubator.concurrent.StructuredTaskScope;
 34 import jdk.incubator.concurrent.StructureViolationException;
 35 import java.util.concurrent.Future;
 36 import java.util.concurrent.ThreadFactory;
 37 import java.util.concurrent.atomic.AtomicBoolean;
 38 
 39 import org.testng.annotations.DataProvider;
 40 import org.testng.annotations.Test;
 41 import static org.testng.Assert.*;
 42 
 43 @Test
 44 public class WithScopedValue {
 45 
 46     @DataProvider
 47     public Object[][] factories() {
 48         return new Object[][] {
 49                 { Thread.ofPlatform().factory() },
 50                 { Thread.ofVirtual().factory() },
 51         };
 52     }
 53 
 54     /**
 55      * Test that fork inherits a scoped value into a child thread.
 56      */
 57     @Test(dataProvider = "factories")
 58     public void testForkInheritsScopedValue1(ThreadFactory factory) throws Exception {
 59         ScopedValue<String> name = ScopedValue.newInstance();
 60         String value = ScopedValue.where(name, "x", () -> {
 61             try (var scope = new StructuredTaskScope<String>(null, factory)) {
 62                 Future<String> future = scope.fork(() -> {
 63                     return name.get(); // child should read "x"
 64                 });
 65                 scope.join();
 66                 return future.resultNow();
 67             }
 68         });
 69         assertEquals(value, "x");
 70     }
 71 
 72     /**
 73      * Test that fork inherits a scoped value into a grandchild thread.
 74      */
 75     @Test(dataProvider = "factories")
 76     public void testForkInheritsScopedValue2(ThreadFactory factory) throws Exception {
 77         ScopedValue<String> name = ScopedValue.newInstance();
 78         String value = ScopedValue.where(name, "x", () -> {
 79             try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
 80                 Future<String> future1 = scope1.fork(() -> {
 81                     try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
 82                         Future<String> future2 = scope2.fork(() -> {
 83                             return name.get(); // grandchild should read "x"
 84                         });
 85                         scope2.join();
 86                         return future2.resultNow();
 87                     }
 88                 });
 89                 scope1.join();
 90                 return future1.resultNow();
 91             }
 92         });
 93         assertEquals(value, "x");
 94     }
 95 
 96     /**
 97      * Test that fork inherits a rebound scoped value into a grandchild thread.
 98      */
 99     @Test(dataProvider = "factories")
100     public void testForkInheritsScopedValue3(ThreadFactory factory) throws Exception {
101         ScopedValue<String> name = ScopedValue.newInstance();
102         String value = ScopedValue.where(name, "x", () -> {
103             try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
104                 Future<String> future1 = scope1.fork(() -> {
105                     assertEquals(name.get(), "x");  // child should read "x"
106 
107                     // rebind name to "y"
108                     String grandchildValue = ScopedValue.where(name, "y", () -> {
109                         try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
110                             Future<String> future2 = scope2.fork(() -> {
111                                 return name.get(); // grandchild should read "y"
112                             });
113                             scope2.join();
114                             return future2.resultNow();
115                         }
116                     });
117 
118                     assertEquals(name.get(), "x");  // child should read "x"
119                     return grandchildValue;
120                 });
121                 scope1.join();
122                 return future1.resultNow();
123             }
124         });
125         assertEquals(value, "y");
126     }
127 
128     /**
129      * Test exiting a dynamic scope with an open task scope.
130      */
131     public 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     public void testStructureViolation2() throws Exception {
168         ScopedValue<String> name = ScopedValue.newInstance();
169         try (var scope = new StructuredTaskScope<String>()) {
170             ScopedValue.where(name, "x", () -> {
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     public void testStructureViolation3() throws Exception {
180         ScopedValue<String> name = ScopedValue.newInstance();
181         try (var scope = new StructuredTaskScope<String>()) {
182             ScopedValue.where(name, "x", () -> {
183                 assertThrows(StructureViolationException.class,
184                         () -> scope.fork(() -> "foo"));
185             });
186         }
187     }
188 
189     /**
190      * Test fork when a scoped value is re-bound after a StructuredTaskScope is created.
191      */
192     public void testStructureViolation4() throws Exception {
193         ScopedValue<String> name1 = ScopedValue.newInstance();
194         ScopedValue<String> name2 = ScopedValue.newInstance();
195 
196         // rebind
197         ScopedValue.where(name1, "x", () -> {
198             try (var scope = new StructuredTaskScope<String>()) {
199                 ScopedValue.where(name1, "y", () -> {
200                     assertThrows(StructureViolationException.class,
201                             () -> scope.fork(() -> "foo"));
202                 });
203             }
204         });
205 
206         // new binding
207         ScopedValue.where(name1, "x", () -> {
208             try (var scope = new StructuredTaskScope<String>()) {
209                 ScopedValue.where(name2, "y", () -> {
210                     assertThrows(StructureViolationException.class,
211                             () -> scope.fork(() -> "foo"));
212                 });
213             }
214         });
215     }
216 }