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