1 /*
  2  * Copyright (c) 2022, 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 Thread.yield submits the virtual thread task to the expected queue
 27  * @requires vm.continuations
 28  * @run junit/othervm -Djdk.virtualThreadScheduler.maxPoolSize=1 YieldQueuing
 29  */
 30 
 31 import java.lang.invoke.MethodHandles;
 32 import java.util.List;
 33 import java.util.concurrent.CopyOnWriteArrayList;
 34 import java.util.concurrent.atomic.AtomicBoolean;
 35 import java.util.concurrent.locks.LockSupport;
 36 
 37 import org.junit.jupiter.api.Test;
 38 import org.junit.jupiter.api.BeforeAll;
 39 import static org.junit.jupiter.api.Assertions.*;
 40 
 41 class YieldQueuing {
 42 
 43     @BeforeAll
 44     static void setup() throws Exception {
 45         // waiting for LockSupport to be initialized can change the scheduling
 46         MethodHandles.lookup().ensureInitialized(LockSupport.class);
 47     }
 48 
 49     /**
 50      * Test Thread.yield submits the task for the current virtual thread to a scheduler
 51      * submission queue when there are no tasks in the local queue.
 52      */
 53     @Test
 54     void testYieldWithEmptyLocalQueue() throws Exception {
 55         var list = new CopyOnWriteArrayList<String>();
 56 
 57         var threadsStarted = new AtomicBoolean();
 58 
 59         var threadA = Thread.ofVirtual().unstarted(() -> {
 60             // pin thread until task for B is in submission queue
 61             while (!threadsStarted.get()) {
 62                 Thread.onSpinWait();
 63             }
 64 
 65             list.add("A");
 66             Thread.yield();      // push task for A to submission queue, B should run
 67             list.add("A");
 68         });
 69 
 70         var threadB = Thread.ofVirtual().unstarted(() -> {
 71             list.add("B");
 72         });
 73 
 74         // push tasks for A and B to submission queue
 75         threadA.start();
 76         threadB.start();
 77 
 78         // release A
 79         threadsStarted.set(true);
 80 
 81         // wait for result
 82         threadA.join();
 83         threadB.join();
 84         assertEquals(list, List.of("A", "B", "A"));
 85     }
 86 
 87     /**
 88      * Test Thread.yield submits the task for the current virtual thread to the local
 89      * queue when there are tasks in the local queue.
 90      */
 91     @Test
 92     void testYieldWithNonEmptyLocalQueue() throws Exception {
 93         var list = new CopyOnWriteArrayList<String>();
 94 
 95         var threadsStarted = new AtomicBoolean();
 96 
 97         var threadA = Thread.ofVirtual().unstarted(() -> {
 98             // pin thread until tasks for B and C are in submission queue
 99             while (!threadsStarted.get()) {
100                 Thread.onSpinWait();
101             }
102 
103             list.add("A");
104             LockSupport.park();   // B should run
105             list.add("A");
106         });
107 
108         var threadB = Thread.ofVirtual().unstarted(() -> {
109             list.add("B");
110             LockSupport.unpark(threadA);  // push task for A to local queue
111             Thread.yield();               // push task for B to local queue, A should run
112             list.add("B");
113         });
114 
115         var threadC = Thread.ofVirtual().unstarted(() -> {
116             list.add("C");
117         });
118 
119         // push tasks for A, B and C to submission queue
120         threadA.start();
121         threadB.start();
122         threadC.start();
123 
124         // release A
125         threadsStarted.set(true);
126 
127         // wait for result
128         threadA.join();
129         threadB.join();
130         threadC.join();
131         assertEquals(list, List.of("A", "B", "A", "B", "C"));
132     }
133 }