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