1 /*
   2  * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * KQueueSelectorImpl.java
  28  * Implementation of Selector using FreeBSD / Mac OS X kqueues
  29  * Derived from Sun's DevPollSelectorImpl
  30  */
  31 
  32 package sun.nio.ch;
  33 
  34 import java.io.IOException;
  35 import java.io.FileDescriptor;
  36 import java.nio.channels.*;
  37 import java.nio.channels.spi.*;
  38 import java.util.*;
  39 
  40 class KQueueSelectorImpl
  41     extends SelectorImpl
  42 {
  43     // File descriptors used for interrupt
  44     protected int fd0;
  45     protected int fd1;
  46 
  47     // The kqueue manipulator
  48     KQueueArrayWrapper kqueueWrapper;
  49 
  50     // Count of registered descriptors (including interrupt)
  51     private int totalChannels;
  52 
  53     // Map from a file descriptor to an entry containing the selection key
  54     private HashMap<Integer,MapEntry> fdMap;
  55 
  56     // True if this Selector has been closed
  57     private boolean closed = false;
  58 
  59     // Lock for interrupt triggering and clearing
  60     private Object interruptLock = new Object();
  61     private boolean interruptTriggered = false;
  62 
  63     // used by updateSelectedKeys to handle cases where the same file
  64     // descriptor is polled by more than one filter
  65     private long updateCount;
  66 
  67     // Used to map file descriptors to a selection key and "update count"
  68     // (see updateSelectedKeys for usage).
  69     private static class MapEntry {
  70         SelectionKeyImpl ski;
  71         long updateCount;
  72         MapEntry(SelectionKeyImpl ski) {
  73             this.ski = ski;
  74         }
  75     }
  76 
  77     /**
  78      * Package private constructor called by factory method in
  79      * the abstract superclass Selector.
  80      */
  81     KQueueSelectorImpl(SelectorProvider sp) {
  82         super(sp);
  83         long fds = IOUtil.makePipe(false);
  84         fd0 = (int)(fds >>> 32);
  85         fd1 = (int)fds;
  86         try {
  87             kqueueWrapper = new KQueueArrayWrapper();
  88             kqueueWrapper.initInterrupt(fd0, fd1);
  89             fdMap = new HashMap<>();
  90             totalChannels = 1;
  91         } catch (Throwable t) {
  92             try {
  93                 FileDispatcherImpl.closeIntFD(fd0);
  94             } catch (IOException ioe0) {
  95                 t.addSuppressed(ioe0);
  96             }
  97             try {
  98                 FileDispatcherImpl.closeIntFD(fd1);
  99             } catch (IOException ioe1) {
 100                 t.addSuppressed(ioe1);
 101             }
 102             throw t;
 103         }
 104     }
 105 
 106 
 107     protected int doSelect(long timeout)
 108         throws IOException
 109     {
 110         int entries = 0;
 111         if (closed)
 112             throw new ClosedSelectorException();
 113         processDeregisterQueue();
 114         try {
 115             begin();
 116             entries = kqueueWrapper.poll(timeout);
 117         } finally {
 118             end();
 119         }
 120         processDeregisterQueue();
 121         return updateSelectedKeys(entries);
 122     }
 123 
 124     /**
 125      * Update the keys whose fd's have been selected by kqueue.
 126      * Add the ready keys to the selected key set.
 127      * If the interrupt fd has been selected, drain it and clear the interrupt.
 128      */
 129     private int updateSelectedKeys(int entries)
 130         throws IOException
 131     {
 132         int numKeysUpdated = 0;
 133         boolean interrupted = false;
 134 
 135         // A file descriptor may be registered with kqueue with more than one
 136         // filter and so there may be more than one event for a fd. The update
 137         // count in the MapEntry tracks when the fd was last updated and this
 138         // ensures that the ready ops are updated rather than replaced by a
 139         // second or subsequent event.
 140         updateCount++;
 141 
 142         for (int i = 0; i < entries; i++) {
 143             int nextFD = kqueueWrapper.getDescriptor(i);
 144             if (nextFD == fd0) {
 145                 interrupted = true;
 146             } else {
 147                 MapEntry me = fdMap.get(Integer.valueOf(nextFD));
 148 
 149                 // entry is null in the case of an interrupt
 150                 if (me != null) {
 151                     int rOps = kqueueWrapper.getReventOps(i);
 152                     SelectionKeyImpl ski = me.ski;
 153                     if (selectedKeys.contains(ski)) {
 154                         // first time this file descriptor has been encountered on this
 155                         // update?
 156                         if (me.updateCount != updateCount) {
 157                             if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
 158                                 numKeysUpdated++;
 159                                 me.updateCount = updateCount;
 160                             }
 161                         } else {
 162                             // ready ops have already been set on this update
 163                             ski.channel.translateAndUpdateReadyOps(rOps, ski);
 164                         }
 165                     } else {
 166                         ski.channel.translateAndSetReadyOps(rOps, ski);
 167                         if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
 168                             selectedKeys.add(ski);
 169                             numKeysUpdated++;
 170                             me.updateCount = updateCount;
 171                         }
 172                     }
 173                 }
 174             }
 175         }
 176 
 177         if (interrupted) {
 178             // Clear the wakeup pipe
 179             synchronized (interruptLock) {
 180                 IOUtil.drain(fd0);
 181                 interruptTriggered = false;
 182             }
 183         }
 184         return numKeysUpdated;
 185     }
 186 
 187 
 188     protected void implClose() throws IOException {
 189         if (!closed) {
 190             closed = true;
 191 
 192             // prevent further wakeup
 193             synchronized (interruptLock) {
 194                 interruptTriggered = true;
 195             }
 196 
 197             FileDispatcherImpl.closeIntFD(fd0);
 198             FileDispatcherImpl.closeIntFD(fd1);
 199             if (kqueueWrapper != null) {
 200                 kqueueWrapper.close();
 201                 kqueueWrapper = null;
 202                 selectedKeys = null;
 203 
 204                 // Deregister channels
 205                 Iterator<SelectionKey> i = keys.iterator();
 206                 while (i.hasNext()) {
 207                     SelectionKeyImpl ski = (SelectionKeyImpl)i.next();
 208                     deregister(ski);
 209                     SelectableChannel selch = ski.channel();
 210                     if (!selch.isOpen() && !selch.isRegistered())
 211                         ((SelChImpl)selch).kill();
 212                     i.remove();
 213                 }
 214                 totalChannels = 0;
 215             }
 216             fd0 = -1;
 217             fd1 = -1;
 218         }
 219     }
 220 
 221 
 222     protected void implRegister(SelectionKeyImpl ski) {
 223         if (closed)
 224             throw new ClosedSelectorException();
 225         int fd = IOUtil.fdVal(ski.channel.getFD());
 226         fdMap.put(Integer.valueOf(fd), new MapEntry(ski));
 227         totalChannels++;
 228         keys.add(ski);
 229     }
 230 
 231 
 232     protected void implDereg(SelectionKeyImpl ski) throws IOException {
 233         int fd = ski.channel.getFDVal();
 234         fdMap.remove(Integer.valueOf(fd));
 235         kqueueWrapper.release(ski.channel);
 236         totalChannels--;
 237         keys.remove(ski);
 238         selectedKeys.remove(ski);
 239         deregister((AbstractSelectionKey)ski);
 240         SelectableChannel selch = ski.channel();
 241         if (!selch.isOpen() && !selch.isRegistered())
 242             ((SelChImpl)selch).kill();
 243     }
 244 
 245 
 246     public void putEventOps(SelectionKeyImpl ski, int ops) {
 247         if (closed)
 248             throw new ClosedSelectorException();
 249         kqueueWrapper.setInterest(ski.channel, ops);
 250     }
 251 
 252 
 253     public Selector wakeup() {
 254         synchronized (interruptLock) {
 255             if (!interruptTriggered) {
 256                 kqueueWrapper.interrupt();
 257                 interruptTriggered = true;
 258             }
 259         }
 260         return this;
 261     }
 262 }