Index: rvm/src-generated/options/BooleanOptions.vm.dat =================================================================== --- rvm/src-generated/options/BooleanOptions.vm.dat (revision 15425) +++ rvm/src-generated/options/BooleanOptions.vm.dat (working copy) @@ -38,3 +38,24 @@ countThreadTransitions false Count, and report, the number of thread state transitions. This works better on IA32 than on PPC at the moment. +neverKillThreads false +Never process dead threads. Thread data is kept alive indefinitely. This is useful for debugging the thread death process. + +neverDeinitMutators false +Never inform MMTk that threads have died. This is useful for debugging the thread death process. + +traceBlock false +Trace all actions related to thread blockage. + +traceReallyBlock false +Trace only actions that actually lead to a thread blocking. + +traceAcct false +Trace thread accounting. + +traceTermination false +Trace thread termination. + +traceAboutToTerminate false +Trace uncommon scenarios in the handling of threads that are about to terminate. + Index: rvm/src/org/jikesrvm/mm/mminterface/CollectorThread.java =================================================================== --- rvm/src/org/jikesrvm/mm/mminterface/CollectorThread.java (revision 15425) +++ rvm/src/org/jikesrvm/mm/mminterface/CollectorThread.java (working copy) @@ -346,6 +346,7 @@ if (gcOrdinal == GC_ORDINAL_BASE) { Plan.setCollectionTrigger(handshake.gcTrigger); } + // FIXME: move this code to RVMThread /* block all threads. note that some threads will have already blocked themselves (if they had made their own GC requests). */ if (gcOrdinal == GC_ORDINAL_BASE) { @@ -354,7 +355,8 @@ // fixpoint until there are no threads that we haven't blocked. // fixpoint is needed in case some thread spawns another thread // while we're waiting. that is unlikely but possible. - for (;;) { + int numIterations=0; + for (;;numIterations++) { RVMThread.acctLock.lock(); int numToHandshake=0; for (int i=0;i=2) VM.sysWriteln("waiting for ",RVMThread.handshakeThreads[i].getThreadSlot()," to block"); @@ -394,6 +407,8 @@ } } RVMThread.handshakeLock.unlock(); + + RVMThread.observeNumSTWFixpointIterations(numIterations); RVMThread.processAboutToTerminate(); /* * ensure that any threads that died while Index: rvm/src/org/jikesrvm/scheduler/RVMThread.java =================================================================== --- rvm/src/org/jikesrvm/scheduler/RVMThread.java (revision 15425) +++ rvm/src/org/jikesrvm/scheduler/RVMThread.java (working copy) @@ -152,35 +152,16 @@ /* * debug and statistics */ - /** Trace thread blockage */ - protected static final boolean traceBlock = false; - - /** Trace when a thread is really blocked */ - protected static final boolean traceReallyBlock = false || traceBlock; - - protected static final boolean traceAboutToTerminate = false; - protected static final boolean dumpStackOnBlock = false; // DANGEROUS! can lead to crashes! protected static final boolean traceBind = false; - /** Trace thread start/stop */ - protected static final boolean traceAcct = false; - /** Trace execution */ protected static final boolean trace = false; - /** Trace thread termination */ - private static final boolean traceTermination = false; - /** Trace adjustments to stack size */ private static final boolean traceAdjustments = false; - /** Never kill threads. Useful for testing bugs related to interaction of - thread death with for example MMTk. For production, this should never - be set to true. */ - private static final boolean neverKillThreads = false; - /** Generate statistics? */ private static final boolean STATS = Lock.STATS; @@ -1191,7 +1172,7 @@ sysCall.sysCreateThreadSpecificDataKeys(); sysCall.sysStashVmThreadInPthread(getCurrentThread()); - if (traceAcct) { + if (VM.traceAcct) { VM.sysWriteln("boot thread at ",Magic.objectAsAddress(getCurrentThread())); } @@ -1212,7 +1193,7 @@ } FinalizerThread.boot(); getCurrentThread().enableYieldpoints(); - if (traceAcct) VM.sysWriteln("RVMThread booted"); + if (VM.traceAcct) VM.sysWriteln("RVMThread booted"); } /** @@ -1221,13 +1202,18 @@ */ private void addAboutToTerminate() { monitor().lock(); + monitor().broadcast(); isAboutToTerminate = true; - activeMutatorContext = false; - monitor().broadcast(); + if (!VM.neverDeinitMutators) { + activeMutatorContext = false; + } handleHandshakeRequest(); - deinitMutator(); + if (!VM.neverDeinitMutators) { + deinitMutator(); + } + // WARNING! DANGER! Since we've set isAboutToTerminate to true, when we // release this lock the GC will: // 1) No longer scan the thread's stack (though it will *see* the @@ -1257,7 +1243,7 @@ */ @NoCheckStore public static void processAboutToTerminate() { - if (!neverKillThreads) { + if (!VM.neverKillThreads) { restart: while(true) { int notKilled = 0; acctLock.lock(); @@ -1273,7 +1259,7 @@ } } acctLock.unlock(); - if (notKilled > 0 && traceAboutToTerminate) { + if (notKilled > 0 && VM.traceAboutToTerminate) { VM.sysWriteln("didn't kill ", notKilled, " threads"); } break; @@ -1327,7 +1313,7 @@ acctLock.unlock(); } - if (traceAcct) { + if (VM.traceAcct) { VM.sysWriteln("Thread #", threadSlot, " at ", Magic.objectAsAddress(this)); VM.sysWriteln("stack at ", Magic.objectAsAddress(stack), " up to ", Magic.objectAsAddress(stack).plus(stack.length)); } @@ -1427,7 +1413,7 @@ initMutator(threadSlot); activeMutatorContext = true; - if (traceAcct) { + if (VM.traceAcct) { VM.sysWriteln("registered mutator for ", threadSlot); } @@ -1479,7 +1465,14 @@ this(MemoryManager.newStack((stacksize <= 0) ? STACK_SIZE_NORMAL : (int) stacksize), thread, name, daemon, false, priority); } - final void acknowledgeBlockRequests() { + /** + * Check if the thread has block requests (for example, for suspension and GC). If + * it does, clear the requests and marked the thread as blocked for that request. + * If there were any block requests, do a broadcast() on the thread's monitor(). + * This is an internal method and should only be called from code that implements + * thread blocking. The monitor() lock must be held for this method to work properly. + */ + private void acknowledgeBlockRequests() { boolean hadSome = false; if (VM.VerifyAssertions) VM._assert(blockAdapters != null); @@ -1496,8 +1489,11 @@ } /** - * Checks if the thread is supposed to be blocked. Only call this method when - * already holding the monitor(), for two reasons: + * Checks if the thread system has acknowledged that the thread is supposed + * to be blocked. This will return true if the thread is actually blocking, or + * if the thread is running native code but is guaranteed to block before + * returning to Java. Only call this method when already holding the monitor(), + * for two reasons: *
    *
  1. This method does not acquire the monitor() lock even though it needs * to have it acquired given the data structures that it is accessing. @@ -1550,14 +1546,14 @@ if (VM.VerifyAssertions) VM._assert(!isAboutToTerminate); if (VM.VerifyAssertions) VM._assert(!isBlocking); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " in checkBlockNoSaveContext"); // NB: anything this method calls CANNOT change the contextRegisters // or the JNI env. as well, this code will be running concurrently // with stop-the-world GC! monitor().lock(); isBlocking = true; - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " acquired lock and has notified everyone that we're blocked"); @@ -1582,7 +1578,7 @@ monitor().relock(recCount); } - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " has acknowledged soft handshakes"); @@ -1595,7 +1591,7 @@ if (!isBlocked()) { break; } - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { hadReallyBlocked=true; VM.sysWriteln("Thread #", threadSlot, " is really blocked with status ", getExecStatus()); @@ -1611,12 +1607,12 @@ // request. monitor().await(); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " has awoken; checking if we're still blocked"); } - if (traceBlock || (traceReallyBlock && hadReallyBlocked)) + if (VM.traceBlock || (VM.traceReallyBlock && hadReallyBlocked)) VM.sysWriteln("Thread #", threadSlot, " is unblocking"); // we're about to unblock, so indicate to the world that we're running @@ -1628,7 +1624,7 @@ handleHandshakeRequest(); monitor().unlock(); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " is unblocked"); } @@ -1700,8 +1696,23 @@ checkBlockNoSaveContext(); } + /** + * Internal method for transitioning a thread from IN_JAVA or IN_JAVA_TO_BLOCK to + * either BLOCKED_IN_NATIVE or BLOCKED_IN_JNI, depending on the value of the jni + * parameter. It is always safe to conservatively call this method when transitioning + * to native code, though it is faster to call either enterNative(), + * enterJNIFromCallIntoNative(), or enterJNIFromJNIFunctionCall(). + *

    + * This method takes care of all bookkeeping and notifications required when a + * a thread that has been requested to block instead decides to run native code. + * Threads enter native code never need to block, since they will not be executing + * any Java code. However, such threads must ensure that any system services (like + * GC) that are waiting for this thread to stop are notified that the thread has + * instead chosen to exit Java. As well, any requests to perform a sot handshake + * must be serviced and acknowledged. + */ private void enterNativeBlockedImpl(boolean jni) { - if (traceReallyBlock) + if (VM.traceBlock || VM.traceReallyBlock) VM.sysWriteln("Thread #", threadSlot, " entering native blocked."); // NB: anything this method calls CANNOT change the contextRegisters // or the JNI env. as well, this code will be running concurrently @@ -1715,15 +1726,16 @@ nativeEnteredBlocked++; setExecStatus(BLOCKED_IN_NATIVE); } + handleHandshakeRequest(); acknowledgeBlockRequests(); commitSoftRendezvous = softRendezvousCheckAndClear(); monitor().unlock(); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " done with the locking part of native entry."); if (commitSoftRendezvous) softRendezvousCommit(); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " done enter native blocked."); } @@ -1761,7 +1773,7 @@ @Entrypoint public static final void enterJNIBlockedFromJNIFunctionCall() { RVMThread t=getCurrentThread(); - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { VM.sysWriteln("Thread #",t.getThreadSlot(), " in enterJNIBlockedFromJNIFunctionCall"); VM.sysWriteln("thread address = ",Magic.objectAsAddress(t)); } @@ -1771,7 +1783,7 @@ @Entrypoint public static final void enterJNIBlockedFromCallIntoNative() { RVMThread t=getCurrentThread(); - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { VM.sysWriteln("Thread #",t.getThreadSlot(), " in enterJNIBlockedFromCallIntoNative"); VM.sysWriteln("thread address = ",Magic.objectAsAddress(t)); } @@ -1782,7 +1794,7 @@ @Unpreemptible("May block if the thread was asked to do so, but otherwise will not block") static final void leaveJNIBlockedFromJNIFunctionCall() { RVMThread t = getCurrentThread(); - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { VM.sysWriteln("Thread #", t.getThreadSlot(), " in leaveJNIBlockedFromJNIFunctionCall"); VM.sysWriteln("thread address = ",Magic.objectAsAddress(t)); @@ -1799,7 +1811,7 @@ @Unpreemptible("May block if the thread was asked to do so, but otherwise will not block") public static final void leaveJNIBlockedFromCallIntoNative() { RVMThread t = getCurrentThread(); - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { VM.sysWriteln("Thread #", t.getThreadSlot(), " in leaveJNIBlockedFromCallIntoNative"); VM.sysWriteln("state = ", t.getExecStatus()); @@ -1839,21 +1851,21 @@ @Unpreemptible("Only blocks if the receiver is the current thread, or if asynchronous is set to false and the thread is not already blocked") final int block(BlockAdapter ba, boolean asynchronous) { int result; - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is requesting that thread #", threadSlot, " blocks."); monitor().lock(); int token = ba.requestBlock(this); if (getCurrentThread() == this) { - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " is blocking."); checkBlock(); result = getExecStatus(); } else { - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " is being told to block."); if (isAboutToTerminate) { - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", threadSlot, " is terminating, returning as if blocked in TERMINATED state."); result = TERMINATED; @@ -1862,7 +1874,7 @@ // CAS the execStatus field int newState = setBlockedExecStatus(); result = newState; - if (traceReallyBlock) + if (VM.traceBlock || VM.traceReallyBlock) VM.sysWriteln("Thread #", getCurrentThreadSlot(), " is blocking thread #", threadSlot, " which is in state ", newState); @@ -1877,11 +1889,11 @@ monitor().broadcast(); if (newState == IN_JAVA_TO_BLOCK) { if (!asynchronous) { - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is waiting for thread #", threadSlot, " to block."); while (ba.hasBlockRequest(this, token) && !ba.isBlocked(this) && !isAboutToTerminate) { - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is calling wait until thread #", threadSlot, " blocks."); // will this deadlock when the thread dies? @@ -1889,7 +1901,7 @@ // do a timed wait, and assert that the thread did not disappear // into native in the meantime monitor().timedWaitRelative(1000L * 1000L * 1000L); // 1 sec - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { VM.sysWriteln("Thread #", threadSlot, "'s status is ", getExecStatus()); } @@ -1897,7 +1909,7 @@ } else { monitor().await(); } - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " has returned from the wait call."); } @@ -1911,7 +1923,7 @@ // we own the thread for now - it cannot go back to executing Java // code until we release the lock. before we do so we change its // state accordingly and tell anyone who is waiting. - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " has seen thread #", threadSlot, " in native; changing its status accordingly."); @@ -1921,7 +1933,7 @@ } } monitor().unlock(); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is done telling thread #", threadSlot, " to block."); return result; @@ -2027,7 +2039,7 @@ @Unpreemptible("May block if the thread was asked to do so; otherwise does no actions that would lead to blocking") public static void leaveNative() { if (!attemptLeaveNativeNoBlock()) { - if (traceReallyBlock) { + if (VM.traceBlock || VM.traceReallyBlock) { VM.sysWriteln("Thread #", getCurrentThreadSlot(), " is leaving native blocked"); } @@ -2076,7 +2088,7 @@ } public final void unblock(BlockAdapter ba) { - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is requesting that thread #", threadSlot, " unblocks."); monitor().lock(); @@ -2084,7 +2096,7 @@ ba.setBlocked(this, false); monitor().broadcast(); monitor().unlock(); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Thread #", getCurrentThread().threadSlot, " is done requesting that thread #", threadSlot, " unblocks."); } @@ -2296,7 +2308,7 @@ } thread.run(); } catch (Throwable t) { - if (traceAcct) { + if (VM.traceAcct) { VM.sysWriteln("Thread ",getThreadSlot()," exiting with exception."); } try { @@ -2328,7 +2340,7 @@ currentThread.pthread_id = sysCall.sysPthreadSelf(); currentThread.enableYieldpoints(); sysCall.sysStashVmThreadInPthread(currentThread); - if (traceAcct) { + if (VM.traceAcct) { VM.sysWriteln("Thread #", currentThread.threadSlot, " with pthread id ", currentThread.pthread_id, " running!"); } @@ -2364,7 +2376,7 @@ numActiveDaemons++; } acctLock.unlock(); - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("Thread #", threadSlot, " starting!"); sysCall.sysNativeThreadCreate(Magic.objectAsAddress(this), contextRegisters.ip, contextRegisters.getInnermostFramePointer()); @@ -2376,12 +2388,12 @@ */ @Interruptible public final void terminate() { - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("in terminate() for Thread #", threadSlot); if (VM.VerifyAssertions) VM._assert(getCurrentThread() == this); boolean terminateSystem = false; - if (traceTermination) { + if (VM.traceTermination) { VM.disableGC(); VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of thread termination"); dumpStack(); @@ -2401,7 +2413,7 @@ } } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("doing accounting..."); acctLock.lock(); @@ -2414,7 +2426,7 @@ if (daemon) { numActiveDaemons -= 1; } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("active = ", numActiveThreads, ", daemons = ", numActiveDaemons); if ((numActiveDaemons == numActiveThreads) && (VM.mainThread != null) && VM.mainThread.launched) { @@ -2428,7 +2440,7 @@ terminateSystem = false; } } - if (traceTermination) { + if (VM.traceTermination) { VM.sysWriteln("Thread.terminate: myThread.daemon = ", daemon); VM.sysWriteln(" RVMThread.numActiveThreads = ", RVMThread.numActiveThreads); @@ -2439,11 +2451,11 @@ acctLock.unlock(); - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("done with accounting."); if (terminateSystem) { - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("terminating system."); if (uncaughtExceptionCount > 0) /* Use System.exit so that any shutdown hooks are run. */{ @@ -2472,7 +2484,7 @@ VM._assert(VM.NOT_REACHED); } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("making joinable..."); // this works. we use synchronized because we cannot use the thread's @@ -2482,10 +2494,10 @@ isJoinable = true; notifyAll(); } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("Thread #", threadSlot, " is joinable."); - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("killing jnienv..."); if (jniEnv != null) { @@ -2493,7 +2505,7 @@ JNIEnvironment.deallocateEnvironment(jniEnv); jniEnv = null; } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("making joinable..."); // Switch to uninterruptible portion of termination @@ -2523,7 +2535,7 @@ @Unpreemptible private void terminateUnpreemptible() { // return cached free lock - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("returning cached lock..."); if (cachedFreeLock != null) { @@ -2537,20 +2549,20 @@ cachedFreeLock = null; } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("adding to aboutToTerminate..."); addAboutToTerminate(); // NB we can no longer do anything that would lead to write barriers or // GC - if (traceAcct) { + if (VM.traceAcct) { VM.sysWriteln("acquireCount for my monitor: ", monitor().acquireCount); VM.sysWriteln("timer ticks: ", timerTicks); VM.sysWriteln("yieldpoints taken: ", yieldpointsTaken); VM.sysWriteln("yieldpoints taken fully: ", yieldpointsTakenFully); } - if (traceAcct) + if (VM.traceAcct) VM.sysWriteln("finishing thread termination..."); finishThreadTermination(); @@ -3123,7 +3135,8 @@ /** * Commit the soft handshake rendezvous. This method cannot do anything - * that leads to a write barrier or allocation. + * that leads to a write barrier or allocation. Only call when not holding + * the thread's monitor lock. */ public final void softRendezvousCommit() { softHandshakeDataLock.lock(); @@ -3136,7 +3149,7 @@ /** * Rendezvous with a soft handshake request. Can only be called when the - * thread's monitor is held. + * thread's monitor is not held. */ public final void softRendezvous() { if (softRendezvousCheckAndClear()) @@ -3206,7 +3219,7 @@ if (!t.yieldpointsEnabled()) { if (VM.VerifyAssertions) VM._assert(!t.yieldToOSRRequested); - if (traceBlock && !wasAtYieldpoint) { + if (VM.traceBlock && !wasAtYieldpoint) { VM.sysWriteln("Thread #", t.threadSlot, " deferring yield!"); dumpStack(); } @@ -3821,7 +3834,7 @@ RVMThread myThread = getCurrentThread(); if (VM.VerifyAssertions) VM._assert(myThread != this); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("Joining on Thread #", threadSlot); // this uses synchronized because we cannot have one thread acquire // another thread's lock using the Nicely scheme, as that would result @@ -3831,7 +3844,7 @@ if (ms == 0 && ns == 0) { while (!isJoinable) { wait(this); - if (traceBlock) + if (VM.traceBlock) VM.sysWriteln("relooping in join on Thread #", threadSlot); } } else { @@ -4681,15 +4694,26 @@ static final int[] sloppyExecStatusHistogram = new int[LAST_EXEC_STATUS]; - static final int[] statusAtSTWHistogram = - new int[LAST_EXEC_STATUS]; static final int[] execStatusTransitionHistogram = new int[LAST_EXEC_STATUS*LAST_EXEC_STATUS]; + // GC-specific statistics + static final int[] statusAtSTWHistogram = + new int[LAST_EXEC_STATUS]; + static final int[] statusAtAlreadyBlockedHistogram = + new int[LAST_EXEC_STATUS]; + static final int[] statusAtAsyncSTWHistogram = + new int[LAST_EXEC_STATUS]; + static final int[] numSTWFixpointIterationsHistogram = + new int[10]; + static int numSTWFixpointIterationsOverflow; + static final int[] numToHandshakeHistogram = + new int[100]; + static int numToHandshakeOverflow; + public static void reportThreadTransitionCounts() { VM.sysWriteln("Thread Transition Counts:"); dump1DHisto("Sloppy Exec Status Histogram",sloppyExecStatusHistogram); - dump1DHisto("Status At Stop-the-world Histogram",statusAtSTWHistogram); VM.sysWriteln(" Exec Status Transition Histogram:"); for (int fromI=0;fromI=numSTWFixpointIterationsHistogram.length) { + numSTWFixpointIterationsOverflow++; + } else { + numSTWFixpointIterationsHistogram[num]++; + } + } + + public static void observeNumToHandshake(int num) { + if (num>=numToHandshakeHistogram.length) { + numToHandshakeOverflow++; + } else { + numToHandshakeHistogram[num]++; + } + } // FIXME: add histograms for states returned from various calls to block() // currently we just do it for the block() call in GC STW.