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 }