// // Copyright (C) 2013 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 java.lang; import java.io.PrintStream; /** * our model of java.lang.ThreadGroup, which we need since the VM relies on certain fields * during bootstrapping * * Note that we don't have a native peer, which would help with atomic container updates, because * most of these methods are called by the VM and we don't want to introduce a dependency from * VM to the peers. This is esp. true since we would have to dig out & cast the peer, call it, * and then go backwards extracting the *Info objects we already had in the calling context. * * If we ever want to introduce a peer, it could just delegate to the respective ThreadInfo methods. */ public class ThreadGroup implements Thread.UncaughtExceptionHandler { private final ThreadGroup parent; //--- those are package visible, so we better keep names and access modifiers String name; int maxPriority; int nthreads; Thread[] threads; int ngroups; ThreadGroup[] groups; int nUnstartedThreads; boolean destroyed; boolean daemon; boolean vmAllowSuspension; public ThreadGroup (String name){ this( Thread.currentThread().getThreadGroup(), name); } public ThreadGroup (ThreadGroup parent, String name){ this.parent = parent; this.name = name; // those are inherited from the parent, which therefore can't be null this.maxPriority = parent.maxPriority; this.daemon = parent.daemon; parent.add(this); } //--- the container updates // group updates are just class internal, whereas thread updates happen from java.lang.Thread and hence need // package visibility private synchronized void add (ThreadGroup childGroup){ if (destroyed){ throw new IllegalThreadStateException(); } if (groups == null){ groups = new ThreadGroup[1]; } else { ThreadGroup[] ng = new ThreadGroup[ngroups+1]; System.arraycopy(groups, 0, ng, 0, ngroups); groups = ng; } groups[ngroups++] = childGroup; } private synchronized void remove (ThreadGroup childGroup){ if (!destroyed){ for (int i=0; i<ngroups; i++){ if (groups[i] == childGroup){ if (ngroups > 1){ int ngroups1 = ngroups - 1; ThreadGroup[] ng = new ThreadGroup[ngroups1]; if (i > 0) { System.arraycopy(groups, 0, ng, 0, i); } if (i < ngroups1) { System.arraycopy(groups, i + 1, ng, i, ngroups - i); } groups = ng; ngroups = ngroups1; } else { groups = null; ngroups = 0; } if (nthreads == 0){ notifyAll(); } // check if its time for a suicide in case we are a daemon group if (daemon){ if ((nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)){ destroy(); } } break; } } } } // thread add/remove happens from the native peer, to avoid additional CGs during creation/termination // since the ThreadGroup is per se a shared object. We just include these methods here as a specification // for ThreadInfo /** * called from the Thread ctor, i.e. before it is started * NOTE - this is implemented as a native method to avoid shared field CGs */ synchronized void addUnstarted (){ if (destroyed){ throw new IllegalThreadStateException(); } nUnstartedThreads++; } /** * this is called during Thread.start() * NOTE - this is implemented as a native method to avoid shared field CGs */ synchronized void add (Thread newThread){ if (destroyed){ throw new IllegalThreadStateException(); } if (threads == null){ threads = new Thread[1]; } else { Thread[] nt = new Thread[nthreads+1]; System.arraycopy(threads, 0, nt, 0, nthreads); threads = nt; } threads[nthreads++] = newThread; nUnstartedThreads--; } /** * this is called automatically upon thread termination */ synchronized void threadTerminated (Thread terminatedThread){ if (!destroyed){ for (int i=0; i<nthreads; i++){ if (threads[i] == terminatedThread){ if (nthreads == 1){ threads = null; nthreads = 0; } else { int nthreads1 = nthreads - 1; Thread[] a = new Thread[nthreads1]; if (i > 0) { System.arraycopy(threads, 0, a, 0, i); } if (i < nthreads1) { System.arraycopy(groups, i + 1, a, i, ngroups - i); } threads = a; nthreads = nthreads1; } // OpenJDK does this no matter if the group was destroyed, but that looks like a bug // that could lead to multiple destroys and notifyAll() signals if (nthreads == 0) { notifyAll(); } if (daemon) { if ((nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)) { destroy(); } } break; } } } } //--- public getters and setters public final String getName(){ return name; } public final ThreadGroup getParent(){ return parent; } public final int getMaxPriority(){ return maxPriority; } public final boolean isDaemon(){ return daemon; } public synchronized boolean isDestroyed(){ return destroyed; } public final void setDaemon (boolean daemon){ this.daemon = daemon; } /** * this sets the maxPriority to the min of the argument and the parents maxPriority * and raises maxPriority for all our threads that have a lower one */ public final synchronized void setMaxPriority (int newMaxPriority){ // do nothing if newMaxPriority is outside limits if (newMaxPriority >= Thread.MIN_PRIORITY && newMaxPriority <= Thread.MAX_PRIORITY){ maxPriority = (parent != null) ? Math.min(parent.maxPriority, newMaxPriority) : newMaxPriority; for (int i=0; i<groups.length; i++){ groups[i].setMaxPriority(newMaxPriority); } } } public final boolean parentOf (ThreadGroup tg){ while (true){ if (tg == this) return true; ThreadGroup tgParent = tg.parent; if (tgParent == null) return false; tg = tgParent; } } public final void checkAccess(){ // not sure what to do here } /** * return number of threads including all sub-groups */ public synchronized int activeCount() { if (destroyed){ return 0; } else { int nActive = nthreads; if (ngroups > 0){ for (int i=0; i<ngroups; i++){ nActive += groups[i].activeCount(); } } return nActive; } } /** * copy threads into provided list * kind of a misnomer */ public int enumerate (Thread[] dst){ return enumerate(dst, 0, true); } public int enumerate (Thread[] dst, boolean recurse){ return enumerate(dst, 0, recurse); } private synchronized int enumerate (Thread[] dst, int idx, boolean recurse){ int n = 0; int len = nthreads; if ((idx + len) > dst.length){ len = dst.length - idx; } for (int i = 0; i < len; i++) { if (threads[i].isAlive()) { dst[idx++] = threads[i]; n++; } } if (recurse && (idx < dst.length)) { for (int j = 0; j < ngroups; j++) { n += groups[j].enumerate(dst, idx, true); } } return n; } public synchronized int activeGroupCount() { if (destroyed){ return 0; } else { int nActive = ngroups; if (ngroups > 0){ for (int i=0; i<ngroups; i++){ nActive += groups[i].activeGroupCount(); } } return nActive; } } public int enumerate (ThreadGroup[] dst){ return enumerate(dst, 0, true); } public int enumerate (ThreadGroup[] dst, boolean recurse){ return enumerate(dst, 0, recurse); } private synchronized int enumerate (ThreadGroup[] dst, int idx, boolean recurse){ int n = 0; int len = ngroups; if ((idx + len) > dst.length){ len = dst.length - idx; } for (int i = 0; i < len; i++) { dst[idx++] = groups[i]; n++; } if (recurse && (idx < dst.length)) { for (int j = 0; j < ngroups; j++) { n += groups[j].enumerate(dst, idx, true); } } return n; } static final int OP_STOP = 1; static final int OP_INTERRUPT = 2; static final int OP_SUSPEND = 3; static final int OP_RESUME = 4; public final void stop(){ if (doRecursively(OP_STOP)){ Thread.currentThread().stop(); } } public final void interrupt(){ doRecursively(OP_INTERRUPT); } public final void suspend(){ if (doRecursively(OP_SUSPEND)){ Thread.currentThread().suspend(); } } public final void resume(){ doRecursively(OP_RESUME); } private synchronized boolean doRecursively (int op){ boolean suicide = false; for (int i=0; i<nthreads; i++){ Thread t = threads[i]; switch (op){ case OP_STOP: if (t == Thread.currentThread()) { suicide = true; // defer } else { t.stop(); } break; case OP_INTERRUPT: t.interrupt(); break; case OP_SUSPEND: if (t == Thread.currentThread()) { suicide = true; // defer } else { t.suspend(); } break; case OP_RESUME: t.resume(); break; } } for (int j=0; j<ngroups; j++){ suicide = suicide || groups[j].doRecursively(op); } return suicide; } public synchronized final void destroy(){ if (destroyed || (nthreads > 0)) { throw new IllegalThreadStateException(); } for (int i=0; i<ngroups; i++){ groups[i].destroy(); } if (parent != null){ // no destroying the system group nthreads = 0; threads = null; ngroups = 0; groups = null; destroyed = true; parent.remove(this); } } //--- just for debugging public void list(){ list( System.out, 0); } synchronized void list (PrintStream ps, int indent){ for (int i=0; i<indent; i++) ps.print(' '); ps.println(toString()); indent+= 4; for (int j=0; j<nthreads; j++){ for (int i=0; i<indent; i++) ps.print(' '); ps.println( threads[j]); } for (int k=0; k<ngroups; k++){ groups[k].list( ps, indent); } } public boolean allowThreadSuspension (boolean allowSuspension){ vmAllowSuspension = allowSuspension; return true; } public String toString () { return getClass().getName() + "[name=" + name + ",maxpri=" + maxPriority + ']'; } // we handle this in ThreadInfo, but again this is a public method that could be called from anywhere public void uncaughtException (Thread t, Throwable x) { if (parent != null) { // if we have a parent, delegate parent.uncaughtException(t, x); } else { // check if there is a default handler Thread.UncaughtExceptionHandler xh = Thread.getDefaultUncaughtExceptionHandler(); if (xh != null) { xh.uncaughtException(t, x); } else { // last resort - just print to Ssytem.err if (!(x instanceof ThreadDeath)) { System.err.print("Exception in thread \"" + t.getName() + '"'); x.printStackTrace(System.err); } } } } }