/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Common Public License (CPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/cpl1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.scheduler.greenthreads; import org.jikesrvm.VM; import org.jikesrvm.runtime.VM_Magic; import static org.jikesrvm.runtime.VM_SysCall.sysCall; import org.jikesrvm.scheduler.VM_ProcessorLock; import org.vmmagic.pragma.Interruptible; import org.vmmagic.pragma.Uninterruptible; /** * A wait queue for threads that are waiting for a process * to exit. Used to implement the <code>exitValue()</code> * and <code>waitFor()</code> methods of <code>java.lang.Process</code>. * * <p> The correct operation of this queue relies on the * fact that at most one thread in the system will wait * for a particular process to exit. The reason for * this restriction is that Unix semantics only allow us to * perform a <code>waitpid()</code> once * for a given process id. {@link org.jikesrvm.scheduler.greenthreads.VM_Process} uses Java synchronization * to enforce this property. * * <p> Note that a strange issue arises on Linux: it is only possible * to wait for a child process to exit from the pthread that forked * the process. (This due to the threading model used by Linux, where * pthreads have some characteristics of processes.) * Because of this limitation, in order for a Java thread to wait for * a process to exit, it must migrate to the <code>VM_Processor</code> * that created the process. A <code>VM_ProcessorLock</code> * in the virtual processor object protects access to this queue, since it * may be accessed from another <code>VM_Processor</code>. * When polling for exited processes, we have to be careful to ignore * <code>VM_GreenThread</code>s that are still being dispatched on another * virtual processor. * * <p> I would imagine that AIX pthreads work correctly with respect to * allowing arbitrary pthreads to perform a <code>waitpid()</code>. * * @see org.jikesrvm.scheduler.greenthreads.VM_Process */ @Uninterruptible public class VM_ThreadProcessWaitQueue extends VM_ThreadEventWaitQueue implements VM_ThreadEventConstants { /** * Class to safely downcast from <code>VM_ThreadEventWaitData</code> * to <code>VM_ThreadProcessWaitData</code>. * We use this because an actual Java cast could result in * a thread switch, which is obviously bad in uninterruptible * code. */ @Uninterruptible private static class WaitDataDowncaster extends VM_ThreadEventWaitDataVisitor { public VM_ThreadProcessWaitData waitData; @Override public void visitThreadIOWaitData(VM_ThreadIOWaitData waitData) { if (VM.VerifyAssertions) VM._assert(false); } @Override public void visitThreadProcessWaitData(VM_ThreadProcessWaitData waitData) { this.waitData = waitData; } @Override void visitThreadSubArchWaitData(VM_ThreadSubArchWaitData waitData) { if (VM.VerifyAssertions) VM._assert(false); } } /** * This queue's private wait data downcaster object. * Having it avoids the need to create them repeatedly. */ private final WaitDataDowncaster myDowncaster = new WaitDataDowncaster(); /** * Maximum number of processes that we can wait for. * Hopefully this is large enough. */ public static final int MAX_NUM_PIDS = 256; /** * Value to mark pids which have finished. */ public static final int PROCESS_FINISHED = -99; /** * Array specifying pids to query to see if the * processes they represent have exited. */ private int[] pidArray; /** * Array for returning exit status of pids which have exited. */ private int[] exitStatusArray; /** * Number of interrupted threads. */ private int numInterrupted; /** * We don't depend on <code>waitpid()</code> being thread-safe. */ private static final VM_ProcessorLock waitPidLock = new VM_ProcessorLock(); /** * Constructor. */ public VM_ThreadProcessWaitQueue() { pidArray = new int[MAX_NUM_PIDS]; exitStatusArray = new int[MAX_NUM_PIDS]; } /** * Check whether processes waited for by threads * on this queue have exited. */ @Override public boolean pollForEvents() { VM_GreenThread thread; int numPids = 0; numInterrupted = 0; // Build array of pids to query thread = head; while (thread != null) { // Under no circumstances do we want to activate a thread // that is still being dispatched on another virtual processor. if (!thread.beingDispatched) { // Was the thread interrupted? if (thread.isInterrupted()) { ++numInterrupted; } if (numInterrupted == 0) { // Safe downcast from VM_ThreadEventWaitData to VM_ThreadProcessWaitData thread.waitData.accept(myDowncaster); VM_ThreadProcessWaitData waitData = myDowncaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); // Add pid to array of pids to query pidArray[numPids] = waitData.pid; } } // !thread.beingDispatched ++numPids; thread = (VM_GreenThread)thread.getNext(); } // while // If any threads are interrupted, then they're ready now, // so don't bother querying pids if (numInterrupted > 0) { return true; } waitPidLock.lock("mutex while reading pids"); // Call sysWaitPids() to see which (if any) have finished sysCall.sysWaitPids(VM_Magic.objectAsAddress(pidArray), VM_Magic.objectAsAddress(exitStatusArray), numPids); waitPidLock.unlock(); numPids = 0; // Mark threads whose processes have finished thread = head; while (thread != null) { if (pidArray[numPids] == PROCESS_FINISHED) { // Safe downcast from VM_ThreadEventWaitData to VM_ThreadProcessWaitData thread.waitData.accept(myDowncaster); VM_ThreadProcessWaitData waitData = myDowncaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); waitData.finished = true; waitData.exitStatus = exitStatusArray[numPids]; } ++numPids; thread = (VM_GreenThread)thread.getNext(); } return true; } /** * Is given thread ready to wake up (either because the * process it was waiting for finished, or it was * interrupted)? */ @Override public boolean isReady(VM_GreenThread thread) { // Do not wake up threads being dispatched on another processor! if (thread.beingDispatched) { return false; } // Wake up the thread if it has been interrupted if (thread.isInterrupted()) { thread.waitData.setFinishedAndInterrupted(); return true; } // Safe downcast from VM_ThreadEventWaitData to VM_ThreadProcessWaitData thread.waitData.accept(myDowncaster); VM_ThreadProcessWaitData waitData = myDowncaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); // See if this thread's process has finished boolean ready = waitData.finished; if (ready) { waitData.setFinished(); } return ready; } /** * Dump text description of what given thread is waiting for. * For debugging. */ @Interruptible @Override void dumpWaitDescription(VM_GreenThread thread) { // Safe downcast from VM_ThreadEventWaitData to VM_ThreadProcessWaitData. // Because this method may be called by other VM_Processors without // locking (and thus execute concurrently with other methods), do NOT // use the queue's private downcaster object. Instead, create one // from scratch. WaitDataDowncaster downcaster = new WaitDataDowncaster(); thread.waitData.accept(downcaster); VM_ThreadProcessWaitData waitData = downcaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); VM.sysWrite("pid=", waitData.pid); } /** * Get string describing what given thread is waiting for. * This method must be interruptible! */ @Interruptible @Override String getWaitDescription(VM_GreenThread thread) { // Safe downcast from VM_ThreadEventWaitData to VM_ThreadProcessWaitData. WaitDataDowncaster downcaster = new WaitDataDowncaster(); thread.waitData.accept(downcaster); VM_ThreadProcessWaitData waitData = downcaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); return "pid=" + waitData.pid; } }