1 /*
  2  * Copyright (c) 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 id=default
 26  * @bug 8312498
 27  * @summary Basic test for JVMTI GetThreadState with virtual threads
 28  * @modules java.base/java.lang:+open
 29  * @library /test/lib
 30  * @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest
 31  */
 32 
 33 /*
 34  * @test id=no-vmcontinuations
 35  * @requires vm.continuations
 36  * @modules java.base/java.lang:+open
 37  * @library /test/lib
 38  * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations --enable-native-access=ALL-UNNAMED GetThreadStateTest
 39  */
 40 
 41 import java.util.StringJoiner;
 42 import java.util.concurrent.atomic.AtomicBoolean;
 43 import java.util.concurrent.locks.LockSupport;
 44 
 45 import jdk.test.lib.thread.VThreadRunner;
 46 import jdk.test.lib.thread.VThreadPinner;
 47 import org.junit.jupiter.api.BeforeAll;
 48 import org.junit.jupiter.api.Test;
 49 import org.junit.jupiter.params.ParameterizedTest;
 50 import org.junit.jupiter.params.provider.ValueSource;
 51 import static org.junit.jupiter.api.Assertions.*;
 52 
 53 class GetThreadStateTest {
 54 
 55     @BeforeAll
 56     static void setup() {
 57         System.loadLibrary("GetThreadStateTest");
 58         init();
 59 
 60         // need >=2 carriers for testing pinning when main thread is a virtual thread
 61         if (Thread.currentThread().isVirtual()) {
 62             VThreadRunner.ensureParallelism(2);
 63         }
 64     }
 65 
 66     /**
 67      * Test state of new/unstarted thread.
 68      */
 69     @Test
 70     void testUnstarted() {
 71         var thread = Thread.ofVirtual().unstarted(() -> { });
 72         check(thread, /*new*/ 0);
 73     }
 74 
 75     /**
 76      * Test state of terminated thread.
 77      */
 78     @Test
 79     void testTerminated() throws Exception {
 80         var thread = Thread.ofVirtual().start(() -> { });
 81         thread.join();
 82         check(thread, JVMTI_THREAD_STATE_TERMINATED);
 83     }
 84 
 85     /**
 86      * Test state of runnable thread.
 87      */
 88     @Test
 89     void testRunnable() throws Exception {
 90         var started = new AtomicBoolean();
 91         var done = new AtomicBoolean();
 92         var thread = Thread.ofVirtual().start(() -> {
 93             started.set(true);
 94 
 95             // spin until done
 96             while (!done.get()) {
 97                 Thread.onSpinWait();
 98             }
 99         });
100         try {
101             // wait for thread to start execution
102             awaitTrue(started);
103 
104             // thread should be runnable
105             int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE;
106             check(thread, expected);
107 
108             // re-test with interrupt status set
109             thread.interrupt();
110             check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
111         } finally {
112             done.set(true);
113             thread.join();
114         }
115     }
116 
117     /**
118      * Test state of thread waiting to enter a monitor when pinned and not pinned.
119      */
120     @ParameterizedTest
121     @ValueSource(booleans = { true, false })
122     void testMonitorEnter(boolean pinned) throws Exception {
123         var started = new AtomicBoolean();
124         Object lock = new Object();
125         var thread = Thread.ofVirtual().unstarted(() -> {
126             started.set(true);
127             if (pinned) {
128                 VThreadPinner.runPinned(() -> {
129                     synchronized (lock) { }
130                 });
131             } else {
132                 synchronized (lock) { }
133             }
134         });
135         try {
136             synchronized (lock) {
137                 // start thread and wait for it to start execution
138                 thread.start();
139                 awaitTrue(started);
140 
141                 // thread should block on monitor enter
142                 int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
143                 await(thread, expected);
144 
145                 // re-test with interrupt status set
146                 thread.interrupt();
147                 check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
148             }
149         } finally {
150             thread.join();
151         }
152     }
153 
154     /**
155      * Test state of thread waiting in Object.wait().
156      */
157     @Test
158     void testObjectWait() throws Exception {
159         var started = new AtomicBoolean();
160         Object lock = new Object();
161         var thread = Thread.ofVirtual().start(() -> {
162             synchronized (lock) {
163                 started.set(true);
164                 try {
165                     lock.wait();
166                 } catch (InterruptedException e) { }
167             }
168         });
169         try {
170             // wait for thread to start execution
171             awaitTrue(started);
172 
173             // thread should wait
174             int expected = JVMTI_THREAD_STATE_ALIVE |
175                     JVMTI_THREAD_STATE_WAITING |
176                     JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
177                     JVMTI_THREAD_STATE_IN_OBJECT_WAIT;
178             await(thread, expected);
179 
180             // notify so thread waits to re-enter monitor
181             synchronized (lock) {
182                 lock.notifyAll();
183                 expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
184                 check(thread, expected);
185 
186                 // re-test with interrupt status set
187                 thread.interrupt();
188                 check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
189             }
190         } finally {
191             thread.interrupt();
192             thread.join();
193         }
194     }
195 
196     /**
197      * Test state of thread waiting in Object.wait(millis).
198      */
199     @Test
200     void testObjectWaitMillis() throws Exception {
201         var started = new AtomicBoolean();
202         Object lock = new Object();
203         var thread = Thread.ofVirtual().start(() -> {
204             synchronized (lock) {
205                 started.set(true);
206                 try {
207                     lock.wait(Long.MAX_VALUE);
208                 } catch (InterruptedException e) { }
209             }
210         });
211         try {
212             // wait for thread to start execution
213             awaitTrue(started);
214 
215             // thread should wait
216             int expected = JVMTI_THREAD_STATE_ALIVE |
217                     JVMTI_THREAD_STATE_WAITING |
218                     JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
219                     JVMTI_THREAD_STATE_IN_OBJECT_WAIT;
220             await(thread, expected);
221 
222             // notify so thread waits to re-enter monitor
223             synchronized (lock) {
224                 lock.notifyAll();
225                 expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
226                 check(thread, expected);
227 
228                 // re-test with interrupt status set
229                 thread.interrupt();
230                 check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED);
231             }
232         } finally {
233             thread.interrupt();
234             thread.join();
235         }
236     }
237 
238     /**
239      * Test state of thread parked with LockSupport.park when pinned and not pinned.
240      */
241     @ParameterizedTest
242     @ValueSource(booleans = { true, false })
243     void testPark(boolean pinned) throws Exception {
244         var started = new AtomicBoolean();
245         var done = new AtomicBoolean();
246         var thread = Thread.ofVirtual().start(() -> {
247             started.set(true);
248             if (pinned) {
249                 VThreadPinner.runPinned(() -> {
250                     while (!done.get()) {
251                         LockSupport.park();
252                     }
253                 });
254             } else {
255                 while (!done.get()) {
256                     LockSupport.park();
257                 }
258             }
259         });
260         try {
261             // wait for thread to start execution
262             awaitTrue(started);
263 
264             // thread should park
265             int expected = JVMTI_THREAD_STATE_ALIVE |
266                     JVMTI_THREAD_STATE_WAITING |
267                     JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
268                     JVMTI_THREAD_STATE_PARKED;
269             await(thread, expected);
270         } finally {
271             done.set(true);
272             LockSupport.unpark(thread);
273             thread.join();
274         }
275     }
276 
277     /**
278      * Test state of thread parked with LockSupport.parkNanos when pinned and not pinned.
279      */
280     @ParameterizedTest
281     @ValueSource(booleans = { true, false })
282     void testParkNanos(boolean pinned) throws Exception {
283         var started = new AtomicBoolean();
284         var done = new AtomicBoolean();
285         var thread = Thread.ofVirtual().start(() -> {
286             started.set(true);
287             if (pinned) {
288                 VThreadPinner.runPinned(() -> {
289                     while (!done.get()) {
290                         LockSupport.parkNanos(Long.MAX_VALUE);
291                     }
292                 });
293             } else {
294                 while (!done.get()) {
295                     LockSupport.parkNanos(Long.MAX_VALUE);
296                 }
297             }
298         });
299         try {
300             // wait for thread to start execution
301             awaitTrue(started);
302 
303             // thread should park
304             int expected = JVMTI_THREAD_STATE_ALIVE |
305                     JVMTI_THREAD_STATE_WAITING |
306                     JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
307                     JVMTI_THREAD_STATE_PARKED;
308             await(thread, expected);
309         } finally {
310             done.set(true);
311             LockSupport.unpark(thread);
312             thread.join();
313         }
314     }
315 
316     /**
317      * Waits for the boolean value to become true.
318      */
319     private static void awaitTrue(AtomicBoolean ref) throws Exception {
320         while (!ref.get()) {
321             Thread.sleep(20);
322         }
323     }
324 
325     /**
326      * Asserts that the given thread has the expected JVMTI state.
327      */
328     private static void check(Thread thread, int expected) {
329         System.err.format("  expect state=0x%x (%s) ...%n", expected, jvmtiStateToString(expected));
330         int state = jvmtiState(thread);
331         System.err.format("  thread state=0x%x (%s)%n", state, jvmtiStateToString(state));
332         assertEquals(expected, state);
333     }
334 
335     /**
336      * Waits indefinitely for the given thread to get to the target JVMTI state.
337      */
338     private static void await(Thread thread, int targetState) throws Exception {
339         System.err.format("  await state=0x%x (%s) ...%n", targetState, jvmtiStateToString(targetState));
340         int state = jvmtiState(thread);
341         System.err.format("  thread state=0x%x (%s)%n", state, jvmtiStateToString(state));
342         while (state != targetState) {
343             assertTrue(thread.isAlive(), "Thread has terminated");
344             Thread.sleep(20);
345             state = jvmtiState(thread);
346             System.err.format("  thread state=0x%x (%s)%n", state, jvmtiStateToString(state));
347         }
348     }
349 
350     private static final int JVMTI_THREAD_STATE_ALIVE = 0x0001;
351     private static final int JVMTI_THREAD_STATE_TERMINATED = 0x0002;
352     private static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004;
353     private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
354     private static final int JVMTI_THREAD_STATE_WAITING = 0x0080;
355     private static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010;
356     private static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020;
357     private static final int JVMTI_THREAD_STATE_SLEEPING = 0x0040;
358     private static final int JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100;
359     private static final int JVMTI_THREAD_STATE_PARKED = 0x0200;
360     private static final int JVMTI_THREAD_STATE_SUSPENDED = 0x100000;
361     private static final int JVMTI_THREAD_STATE_INTERRUPTED = 0x200000;
362     private static final int JVMTI_THREAD_STATE_IN_NATIVE = 0x400000;
363 
364     private static native void init();
365     private static native int jvmtiState(Thread thread);
366 
367     private static String jvmtiStateToString(int state) {
368         StringJoiner sj = new StringJoiner(" | ");
369         if ((state & JVMTI_THREAD_STATE_ALIVE) != 0)
370             sj.add("JVMTI_THREAD_STATE_ALIVE");
371         if ((state & JVMTI_THREAD_STATE_TERMINATED) != 0)
372             sj.add("JVMTI_THREAD_STATE_TERMINATED");
373         if ((state & JVMTI_THREAD_STATE_RUNNABLE) != 0)
374             sj.add("JVMTI_THREAD_STATE_RUNNABLE");
375         if ((state & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0)
376             sj.add("JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER");
377         if ((state & JVMTI_THREAD_STATE_WAITING) != 0)
378             sj.add("JVMTI_THREAD_STATE_WAITING");
379         if ((state & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0)
380             sj.add("JVMTI_THREAD_STATE_WAITING_INDEFINITELY");
381         if ((state & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0)
382             sj.add("JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT");
383         if ((state & JVMTI_THREAD_STATE_IN_OBJECT_WAIT) != 0)
384             sj.add("JVMTI_THREAD_STATE_IN_OBJECT_WAIT");
385         if ((state & JVMTI_THREAD_STATE_PARKED) != 0)
386             sj.add("JVMTI_THREAD_STATE_PARKED");
387         if ((state & JVMTI_THREAD_STATE_SUSPENDED) != 0)
388             sj.add("JVMTI_THREAD_STATE_SUSPENDED");
389         if ((state & JVMTI_THREAD_STATE_INTERRUPTED) != 0)
390             sj.add("JVMTI_THREAD_STATE_INTERRUPTED");
391         if ((state & JVMTI_THREAD_STATE_IN_NATIVE) != 0)
392             sj.add("JVMTI_THREAD_STATE_IN_NATIVE");
393         String s = sj.toString();
394         return s.isEmpty() ? "<empty>" : s;
395     }
396 }