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