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 }