// // Copyright (C) 2006 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.vm; import java.util.ArrayList; import java.util.BitSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import gov.nasa.jpf.Config; import gov.nasa.jpf.util.HashData; import gov.nasa.jpf.util.Predicate; /** * Contains the list of all ThreadInfos for live java.lang.Thread objects * * We add a thread upon creation (within the ThreadInfo ctor), and remove it * when the corresponding java.lang.Thread object gets recycled by JPF. This means * that: * * the thread list can contain terminated threads * * terminated and recycled threads are (eventually) removed from the list * * the list can shrink along a given path * * thread ids don't have to correspond with storing order !! * * thread ids can be re-used * * Per default, thread ids are re-used in order to be packed (which is required to efficiently * keep track of referencing threads in ElementInfo reftids). If there is a need * to avoid recycled thread ids, set 'vm.reuse_tid=false'. * * NOTE - this ThreadList implementation doubles up as a thread object -> ThreadInfo * map, which is for instance heavily used by the JPF_java_lang_Thread peer. * * This implies that ThreadList is still not fully re-organized in case something * keeps terminated thread objects alive. We could avoid this by having a separate * map for live threads<->ThreadInfos, but this would also have to be a backrackable * container that is highly redundant to ThreadList (the only difference being * that terminated threads could be removed from ThreadList). * */ public class ThreadList implements Cloneable, Iterable<ThreadInfo>, Restorable<ThreadList> { public static class Count { public final int alive; public final int runnableNonDaemons; public final int runnableDaemons; public final int blocked; Count (int alive, int runnableNonDaemons, int runnableDaemons, int blocked){ this.alive = alive; this.runnableNonDaemons = runnableNonDaemons; this.runnableDaemons = runnableDaemons; this.blocked = blocked; } } protected boolean reuseTid; // ThreadInfos for all created but not terminated threads protected ThreadInfo[] threads; // the highest ID created so far along this path protected int maxTid; static class TListMemento implements Memento<ThreadList> { // note that we don't clone/deepcopy ThreadInfos Memento<ThreadInfo>[] tiMementos; int maxTid; @SuppressWarnings({ "unchecked" }) TListMemento(ThreadList tl) { ThreadInfo[] threads = tl.threads; int len = threads.length; maxTid = tl.maxTid; tiMementos = new Memento[len]; for (int i=0; i<len; i++){ ThreadInfo ti = threads[i]; Memento<ThreadInfo> m = null; if (!ti.hasChanged()){ m = ti.cachedMemento; } if (m == null){ m = ti.getMemento(); ti.cachedMemento = m; } tiMementos[i] = m; } } public ThreadList restore(ThreadList tl){ int len = tiMementos.length; ThreadInfo[] threads = new ThreadInfo[len]; for (int i=0; i<len; i++){ Memento<ThreadInfo> m = tiMementos[i]; ThreadInfo ti = m.restore(null); ti.cachedMemento = m; threads[i] = ti; ti.tlIdx = i; } tl.threads = threads; tl.maxTid = maxTid; return tl; } } protected ThreadList() { // nothing here } /** * Creates a new empty thread list. */ public ThreadList (Config config, KernelState ks) { threads = new ThreadInfo[0]; reuseTid = config.getBoolean("vm.reuse_tid", false); } public Memento<ThreadList> getMemento(MementoFactory factory) { return factory.getMemento(this); } public Memento<ThreadList> getMemento(){ return new TListMemento(this); } public Object clone() { ThreadList other = new ThreadList(); other.threads = new ThreadInfo[threads.length]; for (int i=0; i<threads.length; i++) { other.threads[i] = (ThreadInfo) threads[i].clone(); } return other; } /** * add a new ThreadInfo if it isn't already in the list. * Note: the returned id is NOT our internal storage index * * @return (path specific) Thread id */ public int add (ThreadInfo ti) { int n = threads.length; BitSet ids = new BitSet(); for (int i=0; i<n; i++) { ThreadInfo t = threads[i]; if (t == ti) { return t.getId(); } ids.set( t.getId()); } // append it ThreadInfo[] newThreads = new ThreadInfo[n+1]; System.arraycopy(threads, 0, newThreads, 0, n); newThreads[n] = ti; ti.tlIdx = n; threads = newThreads; if (reuseTid){ return ids.nextClearBit(0); } else { return maxTid++; } } public boolean remove (ThreadInfo ti){ int n = threads.length; for (int i=0; i<n; i++) { if (ti == threads[i]){ int n1 = n-1; ThreadInfo[] newThreads = new ThreadInfo[n1]; if (i>0){ System.arraycopy(threads, 0, newThreads, 0, i); } if (i<n1){ System.arraycopy(threads, i+1, newThreads, i, (n1-i)); // update the tlIdx values for (int j=i; j<n1; j++){ ThreadInfo t = threads[j]; if (t != null){ t.tlIdx = j; } } } threads = newThreads; return true; } } return false; } /** * Returns the array of threads. */ public ThreadInfo[] getThreads() { return threads.clone(); } public void hash (HashData hd) { for (int i=0; i<threads.length; i++){ threads[i].hash(hd); } } public ThreadInfo getThreadInfoForId (int tid){ for (int i=0; i<threads.length; i++){ ThreadInfo ti = threads[i]; if (ti.getId() == tid){ return ti; } } return null; } public ThreadInfo getThreadInfoForObjRef (int objRef){ for (int i=0; i<threads.length; i++){ ThreadInfo ti = threads[i]; if (ti.getThreadObjectRef() == objRef){ return ti; } } return null; } public boolean contains (ThreadInfo ti){ int idx = ti.tlIdx; if (idx < threads.length){ return threads[idx] == ti; } return false; } /** * Returns the length of the list. */ public int length () { return threads.length; } /** * Replaces the array of ThreadInfos. */ public void setAll(ThreadInfo[] threads) { this.threads = threads; } public ThreadInfo locate (int objref) { for (int i = 0, l = threads.length; i < l; i++) { if (threads[i].getThreadObjectRef() == objref) { return threads[i]; } } return null; } public void markRoots (Heap heap) { for (int i = 0, l = threads.length; i < l; i++) { if (threads[i].isAlive()) { threads[i].markRoots(heap); } } } public boolean hasAnyMatching(Predicate<ThreadInfo> predicate) { for (int i = 0, l = threads.length; i < l; i++) { if (predicate.isTrue(threads[i])) { return true; } } return false; } public boolean hasAnyMatchingOtherThan(ThreadInfo ti, Predicate<ThreadInfo> predicate) { for (int i = 0, l = threads.length; i < l; i++) { if(ti != threads[i]) { if (predicate.isTrue(threads[i])) { return true; } } } return false; } public boolean hasOnlyMatching(Predicate<ThreadInfo> predicate) { for (int i = 0, l = threads.length; i < l; i++) { if (!predicate.isTrue(threads[i])) { return false; } } return true; } public boolean hasOnlyMatchingOtherThan(ThreadInfo ti, Predicate<ThreadInfo> predicate) { int n=0; for (int i = 0, l = threads.length; i < l; i++) { if(ti != threads[i]) { if (!predicate.isTrue(threads[i])) { return false; } else { n++; } } } return (n>0); } public ThreadInfo[] getAllMatching(Predicate<ThreadInfo> predicate) { List<ThreadInfo> list = new ArrayList<ThreadInfo>(); int n = 0; for (int i = 0, l = threads.length; i < l; i++) { ThreadInfo ti = threads[i]; if (predicate.isTrue(ti)) { list.add(ti); n++; } } return list.toArray(new ThreadInfo[n]); } public ThreadInfo[] getAllMatchingWith(final ThreadInfo ti, Predicate<ThreadInfo> predicate) { List<ThreadInfo> list = new ArrayList<ThreadInfo>(); int n = 0; for (int i = 0, l = threads.length; i < l; i++) { ThreadInfo t = threads[i]; if (predicate.isTrue(t) || (ti==t)) { list.add(t); n++; } } return list.toArray(new ThreadInfo[n]); } public ThreadInfo[] getAllMatchingWithout(final ThreadInfo ti, Predicate<ThreadInfo> predicate) { List<ThreadInfo> list = new ArrayList<ThreadInfo>(); int n = 0; for (int i = 0, l = threads.length; i < l; i++) { ThreadInfo t = threads[i]; if (predicate.isTrue(t) && (ti != t)) { list.add(t); n++; } } return list.toArray(new ThreadInfo[n]); } public int getMatchingCount(Predicate<ThreadInfo> predicate) { int n = 0; for (int i = 0, l = threads.length; i < l; i++) { ThreadInfo ti = threads[i]; if (predicate.isTrue(ti)) { n++; } } return n; } public ThreadInfo getFirstMatching(Predicate<ThreadInfo> predicate) { for (int i = 0, l = threads.length; i < l; i++) { ThreadInfo t = threads[i]; if (predicate.isTrue(t)) { return t; } } return null; } public Count getCountWithout (ThreadInfo tiExclude){ int alive=0, runnableNonDaemons=0, runnableDaemons=0, blocked=0; for (int i = 0; i < threads.length; i++) { ThreadInfo ti = threads[i]; if (ti != tiExclude){ if (ti.isAlive()) { alive++; if (ti.isRunnable()) { if (ti.isDaemon()) { runnableDaemons++; } else { runnableNonDaemons++; } } else { blocked++; } } } } return new Count(alive, runnableNonDaemons, runnableDaemons, blocked); } public Count getCount(){ return getCountWithout(null); } public void dump () { int i=0; for (ThreadInfo t : threads) { System.err.println("[" + i++ + "] " + t); } } public Iterator<ThreadInfo> iterator() { return new Iterator<ThreadInfo>() { int i = 0; public boolean hasNext() { return threads != null && threads.length>0 && i<threads.length; } public ThreadInfo next() { if (threads != null && threads.length>0 && i<threads.length){ return threads[i++]; } else { throw new NoSuchElementException(); } } public void remove() { throw new UnsupportedOperationException("Iterator<ThreadInfo>.remove()"); } }; } class CanonicalLiveIterator implements Iterator<ThreadInfo> { int nextGid = -1; int nextIdx = -1; CanonicalLiveIterator(){ setNext(); } // <2do> not overly efficient, but we assume small thread lists anyways void setNext (){ int lastGid = nextGid; int nextGid = Integer.MAX_VALUE; int nextIdx = -1; for (int i=0; i<threads.length; i++){ ThreadInfo ti = threads[i]; if (ti.isAlive()){ int gid = ti.getGlobalId(); if ((gid > lastGid) && (gid < nextGid)){ nextGid = gid; nextIdx = i; } } } CanonicalLiveIterator.this.nextGid = nextGid; CanonicalLiveIterator.this.nextIdx = nextIdx; } public boolean hasNext() { return (nextIdx >= 0); } public ThreadInfo next() { if (nextIdx >= 0){ ThreadInfo tiNext = threads[nextIdx]; setNext(); return tiNext; } else { throw new NoSuchElementException(); } } public void remove() { throw new UnsupportedOperationException("Iterator<ThreadInfo>.remove()"); } } /** * an iterator for a canonical order over all live threads */ public Iterator<ThreadInfo> canonicalLiveIterator(){ return new CanonicalLiveIterator(); } /** * only for debugging purposes, this is expensive */ public void checkConsistency(boolean isStore) { for (int i = 0; i < threads.length; i++) { ThreadInfo ti = threads[i]; ti.checkConsistency(isStore); } } }