1 /*
  2  * Copyright (c) 2021, 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 Test ThreadFlock with scoped values
 27  * @enablePreview
 28  * @modules java.base/jdk.internal.misc
 29  * @run junit WithScopedValue
 30  */
 31 
 32 import jdk.internal.misc.ThreadFlock;
 33 import java.util.concurrent.ThreadFactory;
 34 import java.util.concurrent.StructureViolationException;
 35 import java.util.concurrent.atomic.AtomicReference;
 36 import java.util.stream.Stream;
 37 
 38 import org.junit.jupiter.api.Test;
 39 import org.junit.jupiter.params.ParameterizedTest;
 40 import org.junit.jupiter.params.provider.MethodSource;
 41 import static org.junit.jupiter.api.Assertions.*;
 42 
 43 class WithScopedValue {
 44 
 45     private static Stream<ThreadFactory> factories() {
 46         return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
 47     }
 48 
 49     /**
 50      * Test inheritance of a scoped value.
 51      */
 52     @ParameterizedTest
 53     @MethodSource("factories")
 54     void testInheritsScopedValue(ThreadFactory factory) throws Exception {
 55         ScopedValue<String> name = ScopedValue.newInstance();
 56         String value = ScopedValue.callWhere(name, "duke", () -> {
 57             var result = new AtomicReference<String>();
 58             try (var flock = ThreadFlock.open(null)) {
 59                 Thread thread = factory.newThread(() -> {
 60                     // child
 61                     result.set(name.get());
 62                 });
 63                 flock.start(thread);
 64             }
 65             return result.get();
 66         });
 67         assertEquals("duke", value);
 68     }
 69 
 70     /**
 71      * Test exiting a dynamic scope with open thread flocks.
 72      */
 73     @Test
 74     void testStructureViolation1() {
 75         ScopedValue<String> name = ScopedValue.newInstance();
 76         class Box {
 77             ThreadFlock flock1;
 78             ThreadFlock flock2;
 79         }
 80         var box = new Box();
 81         try {
 82             ScopedValue.runWhere(name, "x1", () -> {
 83                 box.flock1 = ThreadFlock.open(null);
 84                 box.flock2 = ThreadFlock.open(null);
 85             });
 86             fail();
 87         } catch (StructureViolationException expected) { }
 88         assertTrue(box.flock1.isClosed());
 89         assertTrue(box.flock2.isClosed());
 90     }
 91 
 92     /**
 93      * Test closing a thread flock while in a dynamic scope and with enclosing thread
 94      * flocks. This test closes enclosing flock1.
 95      */
 96     @Test
 97     void testStructureViolation2() {
 98         ScopedValue<String> name = ScopedValue.newInstance();
 99         try (var flock1 = ThreadFlock.open("flock1")) {
100             ScopedValue.runWhere(name, "x1", () -> {
101                 try (var flock2 = ThreadFlock.open("flock2")) {
102                     ScopedValue.runWhere(name, "x2", () -> {
103                         try (var flock3 = ThreadFlock.open("flock3")) {
104                             ScopedValue.runWhere(name, "x3", () -> {
105                                 var flock4 = ThreadFlock.open("flock4");
106 
107                                 try {
108                                     flock1.close();
109                                     fail();
110                                 } catch (StructureViolationException expected) { }
111 
112                                 assertTrue(flock1.isClosed());
113                                 assertTrue(flock2.isClosed());
114                                 assertTrue(flock3.isClosed());
115                                 assertTrue(flock4.isClosed());
116                             });
117                         }
118                     });
119                 }
120             });
121         }
122     }
123 
124     /**
125      * Test closing a thread flock while in a dynamic scope and with enclosing thread
126      * flocks. This test closes enclosing flock2.
127      */
128     @Test
129     void testStructureViolation3() {
130         ScopedValue<String> name = ScopedValue.newInstance();
131         try (var flock1 = ThreadFlock.open("flock1")) {
132             ScopedValue.runWhere(name, "x1", () -> {
133                 try (var flock2 = ThreadFlock.open("flock2")) {
134                     ScopedValue.runWhere(name, "x2", () -> {
135                         try (var flock3 = ThreadFlock.open("flock3")) {
136                             ScopedValue.runWhere(name, "x3", () -> {
137                                 var flock4 = ThreadFlock.open("flock4");
138 
139                                 try {
140                                     flock2.close();
141                                     fail();
142                                 } catch (StructureViolationException expected) { }
143 
144                                 assertFalse(flock1.isClosed());
145                                 assertTrue(flock2.isClosed());
146                                 assertTrue(flock3.isClosed());
147                                 assertTrue(flock4.isClosed());
148                             });
149                         }
150                     });
151                 }
152             });
153         }
154     }
155 
156     /**
157      * Test closing a thread flock while in a dynamic scope and with enclosing thread
158      * flocks. This test closes enclosing flock3.
159      */
160     @Test
161     void testStructureViolation4() {
162         ScopedValue<String> name = ScopedValue.newInstance();
163         try (var flock1 = ThreadFlock.open("flock1")) {
164             ScopedValue.runWhere(name, "x1", () -> {
165                 try (var flock2 = ThreadFlock.open("flock2")) {
166                     ScopedValue.runWhere(name, "x2", () -> {
167                         try (var flock3 = ThreadFlock.open("flock3")) {
168                             ScopedValue.runWhere(name, "x3", () -> {
169                                 var flock4 = ThreadFlock.open("flock4");
170 
171                                 try {
172                                     flock3.close();
173                                     fail();
174                                 } catch (StructureViolationException expected) { }
175 
176                                 assertFalse(flock1.isClosed());
177                                 assertFalse(flock2.isClosed());
178                                 assertTrue(flock3.isClosed());
179                                 assertTrue(flock4.isClosed());
180                             });
181                         }
182                     });
183                 }
184             });
185         }
186     }
187 
188     /**
189      * Test start when a scoped value is bound after a thread flock is created.
190      */
191     @ParameterizedTest
192     @MethodSource("factories")
193     void testStructureViolation5(ThreadFactory factory) throws Exception {
194         ScopedValue<String> name = ScopedValue.newInstance();
195         try (var flock = ThreadFlock.open(null)) {
196             ScopedValue.runWhere(name, "duke", () -> {
197                 Thread thread = factory.newThread(() -> { });
198                 assertThrows(StructureViolationException.class, () -> flock.start(thread));
199             });
200         }
201     }
202 
203     /**
204      * Test start when a scoped value is re-bound after a thread flock is created.
205      */
206     @ParameterizedTest
207     @MethodSource("factories")
208     void testStructureViolation6(ThreadFactory factory) throws Exception {
209         ScopedValue<String> name = ScopedValue.newInstance();
210         ScopedValue.runWhere(name, "duke", () -> {
211             try (var flock = ThreadFlock.open(null)) {
212                 ScopedValue.runWhere(name, "duchess", () -> {
213                     Thread thread = factory.newThread(() -> { });
214                     assertThrows(StructureViolationException.class, () -> flock.start(thread));
215                 });
216             }
217         });
218     }
219 }