1 /*
   2  * Copyright (c) 2011, 2018, 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.nio.channels.*;
  36 import java.nio.channels.spi.*;
  37 import java.util.*;
  38 
  39 class KQueueSelectorImpl
  40     extends SelectorImpl
  41 {
  42     // File descriptors used for interrupt
  43     protected int fd0;
  44     protected int fd1;
  45 
  46     // The kqueue manipulator
  47     KQueueArrayWrapper kqueueWrapper;
  48 
  49     // Count of registered descriptors (including interrupt)
  50     private int totalChannels;
  51 
  52     // Map from a file descriptor to an entry containing the selection key
  53     private final HashMap<Integer,MapEntry> fdMap;
  54 
  55     // True if this Selector has been closed
  56     private boolean closed = false;
  57 
  58     // Lock for interrupt triggering and clearing
  59     private final Object interruptLock = new Object();
  60     private boolean interruptTriggered = false;
  61 
  62     // used by updateSelectedKeys to handle cases where the same file
  63     // descriptor is polled by more than one filter
  64     private long updateCount;
  65 
  66     // Used to map file descriptors to a selection key and "update count"
  67     // (see updateSelectedKeys for usage).
  68     private static class MapEntry {
  69         final SelectionKeyImpl ski;
  70         long updateCount;
  71         MapEntry(SelectionKeyImpl ski) {
  72             this.ski = ski;
  73         }
  74     }
  75 
  76     /**
  77      * Package private constructor called by factory method in
  78      * the abstract superclass Selector.
  79      */
  80     KQueueSelectorImpl(SelectorProvider sp) {
  81         super(sp);
  82         long fds = IOUtil.makePipe(false);
  83         fd0 = (int)(fds >>> 32);
  84         fd1 = (int)fds;
  85         try {
  86             kqueueWrapper = new KQueueArrayWrapper();
  87             kqueueWrapper.initInterrupt(fd0, fd1);
  88             fdMap = new HashMap<>();
  89             totalChannels = 1;
  90         } catch (Throwable t) {
  91             try {
  92                 FileDispatcherImpl.closeIntFD(fd0);
  93             } catch (IOException ioe0) {
  94                 t.addSuppressed(ioe0);
  95             }
  96             try {
  97                 FileDispatcherImpl.closeIntFD(fd1);
  98             } catch (IOException ioe1) {
  99                 t.addSuppressed(ioe1);
 100             }
 101             throw t;
 102         }
 103     }
 104 
 105 
 106     protected int doSelect(long timeout)
 107         throws IOException
 108     {
 109         int entries = 0;
 110         if (closed)
 111             throw new ClosedSelectorException();
 112         processDeregisterQueue();
 113         try {
 114             begin();
 115             entries = kqueueWrapper.poll(timeout);
 116         } finally {
 117             end();
 118         }
 119         processDeregisterQueue();
 120         return updateSelectedKeys(entries);
 121     }
 122 
 123     /**
 124      * Update the keys whose fd's have been selected by kqueue.
 125      * Add the ready keys to the selected key set.
 126      * If the interrupt fd has been selected, drain it and clear the interrupt.
 127      */
 128     private int updateSelectedKeys(int entries)
 129         throws IOException
 130     {
 131         int numKeysUpdated = 0;
 132         boolean interrupted = false;
 133 
 134         // A file descriptor may be registered with kqueue with more than one
 135         // filter and so there may be more than one event for a fd. The update
 136         // count in the MapEntry tracks when the fd was last updated and this
 137         // ensures that the ready ops are updated rather than replaced by a
 138         // second or subsequent event.
 139         updateCount++;
 140 
 141         for (int i = 0; i < entries; i++) {
 142             int nextFD = kqueueWrapper.getDescriptor(i);
 143             if (nextFD == fd0) {
 144                 interrupted = true;
 145             } else {
 146                 MapEntry me = fdMap.get(Integer.valueOf(nextFD));
 147 
 148                 // entry is null in the case of an interrupt
 149                 if (me != null) {
 150                     int rOps = kqueueWrapper.getReventOps(i);
 151                     SelectionKeyImpl ski = me.ski;
 152                     if (selectedKeys.contains(ski)) {
 153                         // first time this file descriptor has been encountered on this
 154                         // update?
 155                         if (me.updateCount != updateCount) {
 156                             if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
 157                                 numKeysUpdated++;
 158                                 me.updateCount = updateCount;
 159                             }
 160                         } else {
 161                             // ready ops have already been set on this update
 162                             ski.channel.translateAndUpdateReadyOps(rOps, ski);
 163                         }
 164                     } else {
 165                         ski.channel.translateAndSetReadyOps(rOps, ski);
 166                         if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
 167                             selectedKeys.add(ski);
 168                             numKeysUpdated++;
 169                             me.updateCount = updateCount;
 170                         }
 171                     }
 172                 }
 173             }
 174         }
 175 
 176         if (interrupted) {
 177             // Clear the wakeup pipe
 178             synchronized (interruptLock) {
 179                 IOUtil.drain(fd0);
 180                 interruptTriggered = false;
 181             }
 182         }
 183         return numKeysUpdated;
 184     }
 185 
 186 
 187     protected void implClose() throws IOException {
 188         if (!closed) {
 189             closed = true;
 190 
 191             // prevent further wakeup
 192             synchronized (interruptLock) {
 193                 interruptTriggered = true;
 194             }
 195 
 196             FileDispatcherImpl.closeIntFD(fd0);
 197             FileDispatcherImpl.closeIntFD(fd1);
 198             if (kqueueWrapper != null) {
 199                 kqueueWrapper.close();
 200                 kqueueWrapper = null;
 201                 selectedKeys.clear();
 202 
 203                 // Deregister channels
 204                 Iterator<SelectionKey> i = keys.iterator();
 205                 while (i.hasNext()) {
 206                     SelectionKeyImpl ski = (SelectionKeyImpl)i.next();
 207                     deregister(ski);
 208                     SelectableChannel selch = ski.channel();
 209                     if (!selch.isOpen() && !selch.isRegistered())
 210                         ((SelChImpl)selch).kill();
 211                     i.remove();
 212                 }
 213                 totalChannels = 0;
 214             }
 215             fd0 = -1;
 216             fd1 = -1;
 217         }
 218     }
 219 
 220 
 221     protected void implRegister(SelectionKeyImpl ski) {
 222         if (closed)
 223             throw new ClosedSelectorException();
 224         int fd = IOUtil.fdVal(ski.channel.getFD());
 225         fdMap.put(Integer.valueOf(fd), new MapEntry(ski));
 226         totalChannels++;
 227         keys.add(ski);
 228     }
 229 
 230 
 231     protected void implDereg(SelectionKeyImpl ski) throws IOException {
 232         int fd = ski.channel.getFDVal();
 233         fdMap.remove(Integer.valueOf(fd));
 234         kqueueWrapper.release(ski.channel);
 235         totalChannels--;
 236         keys.remove(ski);
 237         selectedKeys.remove(ski);
 238         deregister(ski);
 239         SelectableChannel selch = ski.channel();
 240         if (!selch.isOpen() && !selch.isRegistered())
 241             ((SelChImpl)selch).kill();
 242     }
 243 
 244 
 245     public void putEventOps(SelectionKeyImpl ski, int ops) {
 246         if (closed)
 247             throw new ClosedSelectorException();
 248         kqueueWrapper.setInterest(ski.channel, ops);
 249     }
 250 
 251 
 252     public Selector wakeup() {
 253         synchronized (interruptLock) {
 254             if (!interruptTriggered) {
 255                 kqueueWrapper.interrupt();
 256                 interruptTriggered = true;
 257             }
 258         }
 259         return this;
 260     }
 261 }