/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package art; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.concurrent.CountDownLatch; import java.util.function.Function; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.time.Instant; public class Test924 { public static void run() throws Exception { // Run the test on its own thread, so we have a known state for the "current" thread. Thread t = new Thread("TestThread") { @Override public void run() { try { doTest(); } catch (Exception e) { throw new RuntimeException(e); } } }; t.start(); t.join(); } private static void doTest() throws Exception { Thread t1 = Thread.currentThread(); Thread t2 = getCurrentThread(); // Need to adjust priority, as on-device this may be unexpected (and we prefer not // to special-case this.) t1.setPriority(5); if (t1 != t2) { throw new RuntimeException("Expected " + t1 + " but got " + t2); } System.out.println("currentThread OK"); printThreadInfo(t1); printThreadInfo(null); Thread t3 = new Thread("Daemon Thread"); t3.setDaemon(true); // Do not start this thread, yet. printThreadInfo(t3); // Start, and wait for it to die. t3.start(); t3.join(); Thread.sleep(500); // Wait a little bit. // Thread has died, check that we can still get info. printThreadInfo(t3); // Try a subclass of thread. Thread t4 = new Thread("Subclass") { }; printThreadInfo(t4); doCurrentThreadStateTests(); doStateTests(Thread::new); doStateTests(ExtThread::new); doAllThreadsTests(); doTLSTests(); doTestEvents(); } private static final class ExtThread extends Thread { public ExtThread(Runnable r) { super(r); } } private static class Holder { volatile boolean flag = false; } private static void doCurrentThreadStateTests() throws Exception { System.out.println(Integer.toHexString(getThreadState(null))); System.out.println(Integer.toHexString(getThreadState(Thread.currentThread()))); } private static void doStateTests(Function mkThread) throws Exception { final CountDownLatch cdl1 = new CountDownLatch(1); final CountDownLatch cdl2 = new CountDownLatch(1); final CountDownLatch cdl3_1 = new CountDownLatch(1); final CountDownLatch cdl3_2 = new CountDownLatch(1); final CountDownLatch cdl4 = new CountDownLatch(1); final CountDownLatch cdl5 = new CountDownLatch(1); final Holder h = new Holder(); final long ALMOST_INFINITE = 100000000; // 1.1 days! final NativeWaiter w = new NativeWaiter(); Runnable r = new Runnable() { @Override public void run() { try { cdl1.countDown(); synchronized(cdl1) { cdl1.wait(); } cdl2.countDown(); synchronized(cdl2) { cdl2.wait(ALMOST_INFINITE); } cdl3_1.await(); cdl3_2.countDown(); synchronized(cdl3_2) { // Nothing, just wanted to block on cdl3. } cdl4.countDown(); try { Thread.sleep(ALMOST_INFINITE); } catch (InterruptedException e) { } cdl5.countDown(); while (!h.flag) { // Busy-loop. } nativeLoop(w.struct); } catch (Exception e) { throw new RuntimeException(e); } } }; Thread t = mkThread.apply(r); System.out.println("Thread type is " + t.getClass()); printThreadState(t); t.start(); // Waiting. cdl1.await(); // This is super inconsistent so just wait for the desired state for up to 5 minutes then give // up and continue final int WAITING_INDEF = 0x191; waitForState(t, WAITING_INDEF); synchronized(cdl1) { cdl1.notifyAll(); } // Timed waiting. cdl2.await(); // This is super inconsistent so just wait for the desired state for up to 5 minutes then give // up and continue final int WAITING_TIMED = 0x1a1; waitForState(t, WAITING_TIMED); synchronized(cdl2) { cdl2.notifyAll(); } // Blocked on monitor. synchronized(cdl3_2) { cdl3_1.countDown(); cdl3_2.await(); // While the latch improves the chances to make good progress, scheduling might still be // messy. Wait till we get the right Java-side Thread state. do { Thread.yield(); } while (t.getState() != Thread.State.BLOCKED); // Since internal thread suspension (For GC or other cases) can happen at any time and changes // the thread state we just have it print the majority thread state across 11 calls over 55 // milliseconds. printMajorityThreadState(t, 11, 5); } // Sleeping. cdl4.await(); // This is super inconsistent so just wait for the desired state for up to 5 minutes then give // up and continue final int WAITING_SLEEP = 0xe1; waitForState(t, WAITING_SLEEP); t.interrupt(); // Running. cdl5.await(); Thread.yield(); Thread.sleep(1000); printThreadState(t); h.flag = true; // Native w.waitForNative(); printThreadState(t); w.finish(); // Dying. t.join(); Thread.yield(); Thread.sleep(1000); printThreadState(t); } private static void waitForState(Thread t, int desired) throws Exception { Thread.yield(); Thread.sleep(1000); // This is super inconsistent so just wait for the desired state for up to 5 minutes then give // up and continue int state; Instant deadline = Instant.now().plusSeconds(60 * 5); while ((state = getThreadState(t)) != desired && deadline.isAfter(Instant.now())) { Thread.yield(); Thread.sleep(100); Thread.yield(); } printThreadState(state); } private static void doAllThreadsTests() { Thread[] threads = getAllThreads(); List threadList = new ArrayList<>(Arrays.asList(threads)); // Filter out JIT thread. It may or may not be there depending on configuration. Iterator it = threadList.iterator(); while (it.hasNext()) { Thread t = it.next(); if (t.getName().startsWith("Jit thread pool worker")) { it.remove(); break; } } Collections.sort(threadList, THREAD_COMP); List expectedList = new ArrayList<>(); Set threadsFromTraces = Thread.getAllStackTraces().keySet(); expectedList.add(findThreadByName(threadsFromTraces, "FinalizerDaemon")); expectedList.add(findThreadByName(threadsFromTraces, "FinalizerWatchdogDaemon")); expectedList.add(findThreadByName(threadsFromTraces, "HeapTaskDaemon")); expectedList.add(findThreadByName(threadsFromTraces, "ReferenceQueueDaemon")); // We can't get the signal catcher through getAllStackTraces. So ignore it. // expectedList.add(findThreadByName(threadsFromTraces, "Signal Catcher")); expectedList.add(findThreadByName(threadsFromTraces, "TestThread")); expectedList.add(findThreadByName(threadsFromTraces, "main")); if (!threadList.containsAll(expectedList)) { throw new RuntimeException("Expected " + expectedList + " as subset, got " + threadList); } System.out.println(expectedList); } private static Thread findThreadByName(Set threads, String name) { for (Thread t : threads) { if (t.getName().equals(name)) { return t; } } throw new RuntimeException("Did not find thread " + name + ": " + threads); } private static void doTLSTests() throws Exception { doTLSNonLiveTests(); doTLSLiveTests(); } private static void doTLSNonLiveTests() throws Exception { Thread t = new Thread(); try { setTLS(t, 1); System.out.println("Expected failure setting TLS for non-live thread"); } catch (Exception e) { System.out.println(e.getMessage()); } t.start(); t.join(); try { setTLS(t, 1); System.out.println("Expected failure setting TLS for non-live thread"); } catch (Exception e) { System.out.println(e.getMessage()); } } private static void doTLSLiveTests() throws Exception { setTLS(Thread.currentThread(), 1); long l = getTLS(Thread.currentThread()); if (l != 1) { throw new RuntimeException("Unexpected TLS value: " + l); }; final CountDownLatch cdl1 = new CountDownLatch(1); final CountDownLatch cdl2 = new CountDownLatch(1); Runnable r = new Runnable() { @Override public void run() { try { cdl1.countDown(); cdl2.await(); setTLS(Thread.currentThread(), 2); if (getTLS(Thread.currentThread()) != 2) { throw new RuntimeException("Different thread issue"); } } catch (Exception e) { throw new RuntimeException(e); } } }; Thread t = new Thread(r); t.start(); cdl1.await(); setTLS(Thread.currentThread(), 1); cdl2.countDown(); t.join(); if (getTLS(Thread.currentThread()) != 1) { throw new RuntimeException("Got clobbered"); } } private static void doTestEvents() throws Exception { enableThreadEvents(true); final CountDownLatch cdl1 = new CountDownLatch(1); final CountDownLatch cdl2 = new CountDownLatch(1); Runnable r = new Runnable() { @Override public void run() { try { cdl1.countDown(); cdl2.await(); } catch (Exception e) { throw new RuntimeException(e); } } }; Thread t = new Thread(r, "EventTestThread"); System.out.println("Constructed thread"); Thread.yield(); Thread.sleep(100); System.out.println(Arrays.toString(getThreadEventMessages())); t.start(); cdl1.await(); System.out.println(Arrays.toString(getThreadEventMessages())); cdl2.countDown(); t.join(); System.out.println(Arrays.toString(getThreadEventMessages())); System.out.println("Thread joined"); enableThreadEvents(false); } private final static Comparator THREAD_COMP = new Comparator() { public int compare(Thread o1, Thread o2) { return o1.getName().compareTo(o2.getName()); } }; private final static Map STATE_NAMES = new HashMap(); private final static List STATE_KEYS = new ArrayList(); static { STATE_NAMES.put(0x1, "ALIVE"); STATE_NAMES.put(0x2, "TERMINATED"); STATE_NAMES.put(0x4, "RUNNABLE"); STATE_NAMES.put(0x400, "BLOCKED_ON_MONITOR_ENTER"); STATE_NAMES.put(0x80, "WAITING"); STATE_NAMES.put(0x10, "WAITING_INDEFINITELY"); STATE_NAMES.put(0x20, "WAITING_WITH_TIMEOUT"); STATE_NAMES.put(0x40, "SLEEPING"); STATE_NAMES.put(0x100, "IN_OBJECT_WAIT"); STATE_NAMES.put(0x200, "PARKED"); STATE_NAMES.put(0x100000, "SUSPENDED"); STATE_NAMES.put(0x200000, "INTERRUPTED"); STATE_NAMES.put(0x400000, "IN_NATIVE"); STATE_KEYS.addAll(STATE_NAMES.keySet()); Collections.sort(STATE_KEYS); } // Call getThreadState 'votes' times waiting 'wait' millis between calls and print the most common // result. private static void printMajorityThreadState(Thread t, int votes, int wait) throws Exception { Map states = new HashMap<>(); for (int i = 0; i < votes; i++) { int cur_state = getThreadState(t); states.put(cur_state, states.getOrDefault(cur_state, 0) + 1); Thread.sleep(wait); // Wait a little bit. } int best_state = -1; int highest_count = 0; for (Map.Entry e : states.entrySet()) { if (e.getValue() > highest_count) { highest_count = e.getValue(); best_state = e.getKey(); } } printThreadState(best_state); } private static void printThreadState(Thread t) { printThreadState(getThreadState(t)); } private static void printThreadState(int state) { StringBuilder sb = new StringBuilder(); for (Integer i : STATE_KEYS) { if ((state & i) != 0) { if (sb.length()>0) { sb.append('|'); } sb.append(STATE_NAMES.get(i)); } } if (sb.length() == 0) { sb.append("NEW"); } System.out.println(Integer.toHexString(state) + " = " + sb.toString()); } private static void printThreadInfo(Thread t) { Object[] threadInfo = getThreadInfo(t); if (threadInfo == null || threadInfo.length != 5) { System.out.println(Arrays.toString(threadInfo)); throw new RuntimeException("threadInfo length wrong"); } System.out.println(threadInfo[0]); // Name System.out.println(threadInfo[1]); // Priority System.out.println(threadInfo[2]); // Daemon System.out.println(threadInfo[3]); // Threadgroup System.out.println(threadInfo[4] == null ? "null" : threadInfo[4].getClass()); // Context CL. } public static final class NativeWaiter { public long struct; public NativeWaiter() { struct = nativeWaiterStructAlloc(); } public void waitForNative() { if (struct == 0l) { throw new Error("Already resumed from native!"); } nativeWaiterStructWaitForNative(struct); } public void finish() { if (struct == 0l) { throw new Error("Already resumed from native!"); } nativeWaiterStructFinish(struct); struct = 0; } } private static native long nativeWaiterStructAlloc(); private static native void nativeWaiterStructWaitForNative(long struct); private static native void nativeWaiterStructFinish(long struct); private static native void nativeLoop(long w); private static native Thread getCurrentThread(); private static native Object[] getThreadInfo(Thread t); private static native int getThreadState(Thread t); private static native Thread[] getAllThreads(); private static native void setTLS(Thread t, long l); private static native long getTLS(Thread t); private static native void enableThreadEvents(boolean b); private static native String[] getThreadEventMessages(); }