/* * 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 static org.jikesrvm.runtime.VM_SysCall.sysCall; import org.jikesrvm.scheduler.VM_ProcessorLock; import org.vmmagic.pragma.Interruptible; import org.vmmagic.pragma.Uninterruptible; /** * A list of threads waiting for i/o data to become available. * * To avoid blocking a virtual processor on an i/o operation, and to * avoid polling, we maintain a list of file/socket descriptors (fd's) that * need to be checked for data availability. When data becomes available * on an fd, as indicated by the unix "select()" system call, we allow the * corresponding thread to resume execution. * * At the moment we only use this technique for network i/o. The same could be * done for disk i/o, but we currently don't bother: we use blocking disk i/o * and assume that it will complete immediately. */ @Uninterruptible public final class VM_ThreadIOQueue extends VM_ThreadEventWaitQueue implements VM_ThreadEventConstants, VM_ThreadIOConstants { // Note: this class was modified by David Hovemeyer // for Extreme Blue 2002 to implement it as a subclass of // VM_ThreadEventWaitQueue, as part of making a more general // interface for event notification. It also now supports // waiting on sets of file descriptors. /** * Class to safely downcast from <code>VM_ThreadEventWaitData</code> * to <code>VM_ThreadIOWaitData</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 { private VM_ThreadIOWaitData waitData; @Override void visitThreadIOWaitData(VM_ThreadIOWaitData waitData) { this.waitData = waitData; } @Override void visitThreadProcessWaitData(VM_ThreadProcessWaitData waitData) { if (VM.VerifyAssertions) VM._assert(false); } @Override void visitThreadSubArchWaitData(VM_ThreadSubArchWaitData waitData) { if (VM.VerifyAssertions) VM._assert(false); } } /** * Private downcaster object for this queue. * Avoids having to create them repeatedly. */ private final WaitDataDowncaster myDowncaster = new WaitDataDowncaster(); private static final int FD_SETSIZE = 2048; /** * Array containing read, write, and exception file descriptor sets. * Used by sysNetSelect(). */ private int[] allFds = new int[3 * FD_SETSIZE]; /** Offset of read file descriptors in allFds. */ public static final int READ_OFFSET = 0 * FD_SETSIZE; /** Offset of write file descriptors in allFds. */ public static final int WRITE_OFFSET = 1 * FD_SETSIZE; /** Offset of exception file descriptors in allFds. */ public static final int EXCEPT_OFFSET = 2 * FD_SETSIZE; /** * Count of threads observed to be killed while executing * Java code (not native C code). */ private int numKilledInJava; /** Guard for updating "selectInProgress" flag. */ public static final VM_ProcessorLock selectInProgressMutex = new VM_ProcessorLock(); /** * Copy file descriptors from source array to destination array * starting at given offset. The size of the source array is used * to determine the number of descriptors to copy. * @param dest the destination file descriptor array * @param offset offset in destination array * @param src the source file descriptor array * @return number of descriptors added */ private static int addFileDescriptors(int[] dest, int offset, int[] src) { //TODO: Remove manual System.arraycopy for (int aSrc : src) { dest[offset++] = aSrc; } return src.length; } /** * Determine if thread should be woken up by an interrupt. * We only allow this for threads killed using * java.lang.Thread.stop(), which is deprecated and stupid. * So, this method should generally never return true. */ private static boolean isKilled(VM_GreenThread thread) { return thread.waitData.isNative() && thread.isInterrupted() && thread.getCauseOfThreadDeath() != null; } /** * Update array of event data file descriptors to mark the * ones that have become ready or invalid, based on the given array of select * file descriptors. * @param waitDataFds array of file descriptors from the wait data object * @param waitDataOffset offset of the wait data's entries * @param selectFds array of file descriptors returned from the select * syscall * @param setOffset offset of the particular set (read, write, exception) * within the select file descriptor array * @return the number of file descriptors which became ready * or invalid */ private int updateStatus(int[] waitDataFds, int waitDataOffset, int[] selectFds, int setOffset) { if (waitDataFds == null) { return 0; } int numReady = 0; int selectIndex = setOffset + waitDataOffset; for (int i = 0; i < waitDataFds.length; ++i) { //boolean ready = selectFds[selectIndex++] == FD_READY; int fd = selectFds[selectIndex++]; switch (fd) { case FD_READY: waitDataFds[i] |= FD_READY_BIT; ++numReady; break; case FD_INVALID: waitDataFds[i] |= FD_INVALID_BIT; ++numReady; break; default: waitDataFds[i] &= FD_MASK; } } return numReady; } //-----------// // Interface // //-----------// /** * Poll file descriptors to see which ones have become ready. * Called from superclass's {@link VM_ThreadEventWaitQueue#isReady()} method. * We also check for threads that have been killed while * blocked in Java code, since they should be woken up as well. * @return true if poll was successful, false if not */ @Override public boolean pollForEvents() { numKilledInJava = 0; // FIXME: no checking is done to ensure that sets don't overflow. // Interrogate all threads in the queue to determine // which file descriptors they are waiting for VM_GreenThread thread = head; int readCount = 0, writeCount = 0, exceptCount = 0; while (thread != null) { // Was the thread killed while in Java code (i.e., not native?) // If so, it's ready. if (isKilled(thread)) { ++numKilledInJava; } // Add to set of file descriptors to poll using select(). // But don't bother if there are threads killed while in Java, // since we won't actually do the select() in that case. if (numKilledInJava == 0) { // Safe downcast from VM_ThreadEventWaitData to VM_ThreadIOWaitData. thread.waitData.accept(myDowncaster); VM_ThreadIOWaitData waitData = myDowncaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); // Add file descriptors from wait data to the array of // fds that we pass to select(). if (waitData.readFds != null) { waitData.readOffset = readCount; readCount += addFileDescriptors(allFds, READ_OFFSET + readCount, waitData.readFds); } if (waitData.writeFds != null) { waitData.writeOffset = writeCount; writeCount += addFileDescriptors(allFds, WRITE_OFFSET + writeCount, waitData.writeFds); } if (waitData.exceptFds != null) { waitData.exceptOffset = exceptCount; exceptCount += addFileDescriptors(allFds, EXCEPT_OFFSET + exceptCount, waitData.exceptFds); } } thread = (VM_GreenThread)thread.getNext(); } // If there are killed non-native threads, just wake up one of // those and don't bother with the select(). if (numKilledInJava > 0) { return true; } // Do the select() VM_GreenProcessor.getCurrentProcessor().isInSelect = true; selectInProgressMutex.lock("select in progress mutex"); int ret = sysCall.sysNetSelect(allFds, readCount, writeCount, exceptCount); selectInProgressMutex.unlock(); VM_GreenProcessor.getCurrentProcessor().isInSelect = false; // Did the select() succeed? return ret != -1; } /** * Determine whether or not given thread has become ready * to run, i.e., because a file descriptor it was waiting for * became ready. If the thread is ready, update its * wait flags appropriately. */ @Override public boolean isReady(VM_GreenThread thread) { // Safe downcast from VM_ThreadEventWaitData to VM_ThreadIOWaitData. thread.waitData.accept(myDowncaster); VM_ThreadIOWaitData waitData = myDowncaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); // Any threads killed while blocked in Java // are woken up. if (isKilled(thread)) { waitData.setFinishedAndInterrupted(); return true; } // Check read, write, and exception file descriptor sets, // and set the FD_READY_BIT in any of them that are now ready. // Also, set FD_INVALID_BIT in any fds that are invalid. int numReady = 0; numReady += updateStatus(waitData.readFds, waitData.readOffset, allFds, READ_OFFSET); numReady += updateStatus(waitData.writeFds, waitData.writeOffset, allFds, WRITE_OFFSET); numReady += updateStatus(waitData.exceptFds, waitData.exceptOffset, allFds, EXCEPT_OFFSET); // If any fds became ready, then this thread is a candidate for // being scheduled. boolean ready = (numReady > 0); if (ready) { waitData.setFinished(); } return ready; } private void dumpFds(int[] fds) { if (fds == null) { return; } for (int i = 0; i < fds.length; ++i) { VM.sysWrite(fds[i] & FD_MASK); if ((fds[i] & FD_READY_BIT) != 0) { VM.sysWrite('+'); } if ((fds[i] & FD_INVALID_BIT) != 0) { VM.sysWrite('X'); } if (i != fds.length - 1) { VM.sysWrite(','); } } } /** * 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_ThreadIOWaitData. // 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_ThreadIOWaitData waitData = downcaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); VM.sysWrite("(R"); dumpFds(waitData.readFds); VM.sysWrite(";W"); dumpFds(waitData.writeFds); VM.sysWrite(";E"); dumpFds(waitData.exceptFds); VM.sysWrite(')'); } @Interruptible private void appendFds(StringBuffer buffer, int[] fds) { if (fds == null) { return; } for (int i = 0; i < fds.length; ++i) { buffer.append(fds[i] & FD_MASK); if ((fds[i] & FD_READY_BIT) != 0) { buffer.append('+'); } if ((fds[i] & FD_INVALID_BIT) != 0) { buffer.append('X'); } if (i != fds.length - 1) { buffer.append(','); } } } /** * 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_ThreadIOWaitData. WaitDataDowncaster downcaster = new WaitDataDowncaster(); thread.waitData.accept(downcaster); VM_ThreadIOWaitData waitData = downcaster.waitData; if (VM.VerifyAssertions) VM._assert(waitData == thread.waitData); StringBuffer buffer = new StringBuffer(); buffer.append("(R"); appendFds(buffer, waitData.readFds); buffer.append(";W"); appendFds(buffer, waitData.writeFds); buffer.append(";E"); appendFds(buffer, waitData.exceptFds); buffer.append(')'); return buffer.toString(); } }