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 }