| /* KqueueSelectorImpl.java -- Selector for systems with kqueue event notification. |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.java.nio; |
| |
| |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.channels.ClosedSelectorException; |
| import java.nio.channels.SelectableChannel; |
| import java.nio.channels.SelectionKey; |
| import java.nio.channels.Selector; |
| import java.nio.channels.spi.AbstractSelectableChannel; |
| import java.nio.channels.spi.AbstractSelector; |
| import java.nio.channels.spi.SelectorProvider; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * A {@link Selector} implementation that uses the <code>kqueue</code> |
| * event notification facility. |
| * |
| * @author Casey Marshall (csm@gnu.org) |
| */ |
| public class KqueueSelectorImpl extends AbstractSelector |
| { |
| // Prepended underscore to field name to make it distinct |
| // from the method with the similar name. |
| private static final int _sizeof_struct_kevent; |
| |
| private static final int MAX_DOUBLING_CAPACITY = 16384; |
| private static final int CAP_INCREMENT = 1024; |
| private static final int INITIAL_CAPACITY; |
| |
| static |
| { |
| try |
| { |
| System.loadLibrary("javanio"); |
| } |
| catch (Exception x) |
| { |
| x.printStackTrace(); |
| } |
| |
| if (kqueue_supported ()) |
| _sizeof_struct_kevent = sizeof_struct_kevent(); |
| else |
| _sizeof_struct_kevent = -1; |
| INITIAL_CAPACITY = 16 * _sizeof_struct_kevent; |
| } |
| |
| /** |
| * Tell if kqueue-based selectors are supported on this system. |
| * |
| * @return True if this system has kqueue support, and support for it was |
| * compiled in to Classpath. |
| */ |
| public static native boolean kqueue_supported(); |
| |
| /* Our native file descriptor. */ |
| private int kq; |
| |
| private HashMap/*<Integer,KqueueSelectionKeyImpl>*/ keys; |
| private HashSet/*<KqueueSelectionKeyImpl>*/ selected; |
| private Thread blockedThread; |
| private ByteBuffer events; |
| |
| private static final int OP_ACCEPT = SelectionKey.OP_ACCEPT; |
| private static final int OP_CONNECT = SelectionKey.OP_CONNECT; |
| private static final int OP_READ = SelectionKey.OP_READ; |
| private static final int OP_WRITE = SelectionKey.OP_WRITE; |
| |
| public KqueueSelectorImpl(SelectorProvider provider) throws IOException |
| { |
| super(provider); |
| kq = implOpen(); |
| keys = new HashMap/*<KqueueSelectionKeyImpl>*/(); |
| events = ByteBuffer.allocateDirect(INITIAL_CAPACITY); |
| } |
| |
| protected void implCloseSelector() throws IOException |
| { |
| implClose(kq); |
| kq = -1; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.nio.channels.Selector#keys() |
| */ |
| public Set keys() |
| { |
| if (!isOpen()) |
| throw new ClosedSelectorException(); |
| |
| return new HashSet(keys.values()); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.nio.channels.Selector#select() |
| */ |
| public int select() throws IOException |
| { |
| return doSelect(-1); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.nio.channels.Selector#select(long) |
| */ |
| public int select(long timeout) throws IOException |
| { |
| if (timeout == 0) |
| timeout = -1; |
| return doSelect(timeout); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.nio.channels.Selector#selectedKeys() |
| */ |
| public Set selectedKeys() |
| { |
| if (!isOpen()) |
| throw new ClosedSelectorException(); |
| |
| return selected; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.nio.channels.Selector#selectNow() |
| */ |
| public int selectNow() throws IOException |
| { |
| return doSelect(0); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.nio.channels.Selector#wakeup() |
| */ |
| public Selector wakeup() |
| { |
| if (blockedThread != null) |
| blockedThread.interrupt(); |
| return this; |
| } |
| |
| public String toString() |
| { |
| return super.toString() + " [ fd: " + kq + " ]"; |
| } |
| |
| public boolean equals(Object o) |
| { |
| if (!(o instanceof KqueueSelectorImpl)) |
| return false; |
| |
| return ((KqueueSelectorImpl) o).kq == kq; |
| } |
| |
| int doSelect(long timeout) throws IOException |
| { |
| Set cancelled = cancelledKeys(); |
| synchronized (cancelled) |
| { |
| synchronized (keys) |
| { |
| for (Iterator it = cancelled.iterator(); it.hasNext(); ) |
| { |
| KqueueSelectionKeyImpl key = (KqueueSelectionKeyImpl) it.next(); |
| key.interestOps = 0; |
| } |
| |
| int events_size = (2 * _sizeof_struct_kevent) * keys.size(); |
| int num_events = 0; |
| |
| for (Iterator it = keys.entrySet().iterator(); it.hasNext(); ) |
| { |
| Map.Entry e = (Map.Entry) it.next(); |
| KqueueSelectionKeyImpl key = (KqueueSelectionKeyImpl) e.getValue(); |
| |
| SelectableChannel ch = key.channel(); |
| if (ch instanceof VMChannelOwner) |
| { |
| if (!((VMChannelOwner) ch).getVMChannel().getState().isValid()) |
| { |
| // closed channel; removed from kqueue automatically. |
| it.remove(); |
| continue; |
| } |
| } |
| |
| // If this key is registering a read filter, add it to the buffer. |
| if (key.needCommitRead()) |
| { |
| kevent_set(events, num_events, key.fd, |
| key.interestOps & (OP_READ | OP_ACCEPT), |
| key.activeOps & (OP_READ | OP_ACCEPT), key.key); |
| num_events++; |
| } |
| |
| // If this key is registering a write filter, add it to the buffer. |
| if (key.needCommitWrite()) |
| { |
| kevent_set(events, num_events, key.fd, |
| key.interestOps & (OP_WRITE | OP_CONNECT), |
| key.activeOps & (OP_WRITE | OP_CONNECT), key.key); |
| num_events++; |
| } |
| } |
| events.rewind().limit(events.capacity()); |
| |
| //System.out.println("dump of keys to select:"); |
| //dump_selection_keys(events.duplicate()); |
| |
| int n = 0; |
| try |
| { |
| //System.out.println("[" + kq + "] kevent enter selecting from " + keys.size()); |
| begin(); |
| blockedThread = Thread.currentThread(); |
| if (blockedThread.isInterrupted()) |
| timeout = 0; |
| n = kevent(kq, events, num_events, |
| events.capacity() / _sizeof_struct_kevent, timeout); |
| } |
| finally |
| { |
| end(); |
| blockedThread = null; |
| Thread.interrupted(); |
| //System.out.println("[" + kq + "kevent exit selected " + n); |
| } |
| |
| //System.out.println("dump of keys selected:"); |
| //dump_selection_keys((ByteBuffer) events.duplicate().limit(n * _sizeof_struct_kevent)); |
| |
| // Commit the operations we've just added in the call to kevent. |
| for (Iterator it = keys.values().iterator(); it.hasNext(); ) |
| { |
| KqueueSelectionKeyImpl key = (KqueueSelectionKeyImpl) it.next(); |
| key.activeOps = key.interestOps; |
| } |
| |
| selected = new HashSet/*<KqueueSelectionKeyImpl>*/(n); |
| int x = 0; |
| for (int i = 0; i < n; i++) |
| { |
| events.position(x).limit(x + _sizeof_struct_kevent); |
| x += _sizeof_struct_kevent; |
| int y = fetch_key(events.slice()); |
| KqueueSelectionKeyImpl key = |
| (KqueueSelectionKeyImpl) keys.get(new Integer(y)); |
| |
| if (key == null) |
| { |
| System.out.println("WARNING! no key found for selected key " + y); |
| continue; |
| } |
| // Keys that have been cancelled may be returned here; don't |
| // add them to the selected set. |
| if (!key.isValid()) |
| continue; |
| key.readyOps = ready_ops(events.slice(), key.interestOps); |
| selected.add(key); |
| } |
| |
| // Finally, remove the cancelled keys. |
| for (Iterator it = cancelled.iterator(); it.hasNext(); ) |
| { |
| KqueueSelectionKeyImpl key = (KqueueSelectionKeyImpl) it.next(); |
| keys.remove(new Integer(key.key)); |
| deregister(key); |
| it.remove(); |
| } |
| |
| reallocateBuffer(); |
| |
| return selected.size(); |
| } |
| } |
| } |
| |
| protected SelectionKey register(AbstractSelectableChannel channel, |
| int interestOps, |
| Object attachment) |
| { |
| int native_fd = -1; |
| try |
| { |
| if (channel instanceof VMChannelOwner) |
| native_fd = ((VMChannelOwner) channel).getVMChannel() |
| .getState().getNativeFD(); |
| else |
| throw new IllegalArgumentException("cannot handle channel type " + |
| channel.getClass().getName()); |
| } |
| catch (IOException ioe) |
| { |
| throw new IllegalArgumentException("channel is closed or invalid"); |
| } |
| |
| KqueueSelectionKeyImpl result = new KqueueSelectionKeyImpl(this, channel); |
| result.interestOps = interestOps; |
| result.attach(attachment); |
| result.fd = native_fd; |
| result.key = System.identityHashCode(result); |
| synchronized (keys) |
| { |
| while (keys.containsKey(new Integer(result.key))) |
| result.key++; |
| keys.put(new Integer(result.key), result); |
| reallocateBuffer(); |
| } |
| return result; |
| } |
| |
| void setInterestOps(KqueueSelectionKeyImpl key, int ops) |
| { |
| synchronized (keys) |
| { |
| key.interestOps = ops; |
| } |
| } |
| |
| /** |
| * Reallocate the events buffer. This is the destination buffer for |
| * events returned by kevent. This method will: |
| * |
| * * Grow the buffer if there is insufficent space for all registered |
| * events. |
| * * Shrink the buffer if it is more than twice the size needed. |
| * |
| */ |
| private void reallocateBuffer() |
| { |
| synchronized (keys) |
| { |
| if (events.capacity() < (2 * _sizeof_struct_kevent) * keys.size()) |
| { |
| int cap = events.capacity(); |
| if (cap >= MAX_DOUBLING_CAPACITY) |
| cap += CAP_INCREMENT; |
| else |
| cap = cap << 1; |
| |
| events = ByteBuffer.allocateDirect(cap); |
| } |
| else if (events.capacity() > 4 * (_sizeof_struct_kevent) * keys.size() + 1 |
| && events.capacity() > INITIAL_CAPACITY) |
| { |
| int cap = events.capacity(); |
| cap = cap >>> 1; |
| events = ByteBuffer.allocateDirect(cap); |
| } |
| } |
| } |
| |
| //synchronized void updateOps(KqueueSelectionKeyImpl key, int interestOps) |
| //{ |
| // updateOps(key, interestOps, 0, false); |
| //} |
| |
| /*void updateOps(KqueueSelectionKeyImpl key, int interestOps, |
| int activeOps, int fd) |
| { |
| //System.out.println(">> updating kqueue selection key:"); |
| //dump_selection_keys(key.nstate.duplicate()); |
| //System.out.println("<<"); |
| synchronized (keys) |
| { |
| kevent_set(key.nstate, fd, interestOps, activeOps, key.key); |
| } |
| //System.out.println(">> updated kqueue selection key:"); |
| //dump_selection_keys(key.nstate.duplicate()); |
| //System.out.println("<<"); |
| }*/ |
| |
| private void dump_selection_keys(ByteBuffer keys) |
| { |
| // WARNING! This method is not guaranteed to be portable! This works |
| // on darwin/x86, but the sizeof and offsetof these fields may be |
| // different on other platforms! |
| int i = 0; |
| keys.order(ByteOrder.nativeOrder()); |
| while (keys.hasRemaining()) |
| { |
| System.out.println("struct kevent { ident: " |
| + Integer.toString(keys.getInt()) |
| + " filter: " |
| + Integer.toHexString(keys.getShort() & 0xFFFF) |
| + " flags: " |
| + Integer.toHexString(keys.getShort() & 0xFFFF) |
| + " fflags: " |
| + Integer.toHexString(keys.getInt()) |
| + " data: " |
| + Integer.toHexString(keys.getInt()) |
| + " udata: " |
| + Integer.toHexString(keys.getInt()) |
| + " }"); |
| } |
| } |
| |
| /** |
| * Return the size of a <code>struct kevent</code> on this system. |
| * |
| * @return The size of <code>struct kevent</code>. |
| */ |
| private static native int sizeof_struct_kevent(); |
| |
| /** |
| * Opens a kqueue descriptor. |
| * |
| * @return The new kqueue descriptor. |
| * @throws IOException If opening fails. |
| */ |
| private static native int implOpen() throws IOException; |
| |
| /** |
| * Closes the kqueue file descriptor. |
| * |
| * @param kq The kqueue file descriptor. |
| * @throws IOException |
| */ |
| private static native void implClose(int kq) throws IOException; |
| |
| /** |
| * Initialize the specified native state for the given interest ops. |
| * |
| * @param nstate The native state structures; in this buffer should be |
| * the <code>struct kevent</code>s created for a key. |
| * @param fd The file descriptor. If 0, the native FD is unmodified. |
| * @param interestOps The operations to enable. |
| * @param key A unique key that will reference the associated key later. |
| * @param delete Set to true if this event should be deleted from the |
| * kqueue (if false, this event is added/updated). |
| */ |
| private static native void kevent_set(ByteBuffer nstate, int i, int fd, |
| int interestOps, int activeOps, int key); |
| |
| /** |
| * Poll for events. The source events are stored in <code>events</code>, |
| * which is also where polled events will be placed. |
| * |
| * @param events The events to poll. This buffer is also the destination |
| * for events read from the queue. |
| * @param nevents The number of events to poll (that is, the number of |
| * events in the <code>events</code> buffer). |
| * @param nout The maximum number of events that may be returned. |
| * @param timeout The timeout. A timeout of -1 returns immediately; a timeout |
| * of 0 waits indefinitely. |
| * @return The number of events read. |
| */ |
| private static native int kevent(int kq, ByteBuffer events, int nevents, |
| int nout, long timeout); |
| |
| /** |
| * Fetch a polled key from a native state buffer. For each kevent key we |
| * create, we put the native state info (one or more <code>struct |
| * kevent</code>s) in that key's {@link KqueueSelectionKeyImpl#nstate} |
| * buffer, and place the pointer of the key in the <code>udata</code> field |
| * of that structure. This method fetches that pointer from the given |
| * buffer (assumed to be a <code>struct kqueue</code>) and returns it. |
| * |
| * @param nstate The buffer containing the <code>struct kqueue</code> to read. |
| * @return The key object. |
| */ |
| private static native int fetch_key(ByteBuffer nstate); |
| |
| /** |
| * Fetch the ready ops of the associated native state. That is, this |
| * inspects the first argument as a <code>struct kevent</code>, looking |
| * at its operation (the input is assumed to have been returned via a |
| * previous call to <code>kevent</code>), and translating that to the |
| * appropriate Java bit set, based on the second argument. |
| * |
| * @param nstate The native state. |
| * @param interestOps The enabled operations for the key. |
| * @return The bit set representing the ready operations. |
| */ |
| private static native int ready_ops(ByteBuffer nstate, int interestOps); |
| |
| /** |
| * Check if kevent returned EV_EOF for a selection key. |
| * |
| * @param nstate The native state. |
| * @return True if the kevent call returned EOF. |
| */ |
| private static native boolean check_eof(ByteBuffer nstate); |
| } |