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