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  */
  30 
  31 package sun.nio.ch;
  32 
  33 import java.io.IOException;
  34 import java.nio.channels.ClosedSelectorException;
  35 import java.nio.channels.SelectableChannel;
  36 import java.nio.channels.SelectionKey;
  37 import java.nio.channels.Selector;
  38 import java.nio.channels.spi.SelectorProvider;
  39 import java.util.HashMap;
  40 import java.util.Iterator;
  41 
  42 class KQueueSelectorImpl
  43     extends SelectorImpl
  44 {
  45     // File descriptors used for interrupt
  46     private final int fd0;
  47     private final int fd1;
  48 
  49     // The kqueue manipulator
  50     private final KQueueArrayWrapper kqueueWrapper;
  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;
  57 
  58     // Lock for interrupt triggering and clearing
  59     private final Object interruptLock = new Object();
  60     private boolean interruptTriggered;
  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         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) throws IOException {
  81         super(sp);
  82         long fds = IOUtil.makePipe(false);
  83         fd0 = (int)(fds >>> 32);
  84         fd1 = (int)fds;
  85         try {
  86             kqueueWrapper = new KQueueArrayWrapper(fd0, fd1);
  87             fdMap = new HashMap<>();
  88         } catch (Throwable t) {
  89             try {
  90                 FileDispatcherImpl.closeIntFD(fd0);
  91             } catch (IOException ioe0) {
  92                 t.addSuppressed(ioe0);
  93             }
  94             try {
  95                 FileDispatcherImpl.closeIntFD(fd1);
  96             } catch (IOException ioe1) {
  97                 t.addSuppressed(ioe1);
  98             }
  99             throw t;
 100         }
 101     }
 102 
 103     private void ensureOpen() {
 104         if (closed)
 105             throw new ClosedSelectorException();
 106     }
 107 
 108     @Override
 109     protected int doSelect(long timeout)
 110         throws IOException
 111     {
 112         ensureOpen();
 113         int numEntries;
 114         processDeregisterQueue();
 115         try {
 116             begin();
 117             numEntries = kqueueWrapper.poll(timeout);
 118         } finally {
 119             end();
 120         }
 121         processDeregisterQueue();
 122         return updateSelectedKeys(numEntries);
 123     }
 124 
 125     /**
 126      * Update the keys whose fd's have been selected by kqueue.
 127      * Add the ready keys to the selected key set.
 128      * If the interrupt fd has been selected, drain it and clear the interrupt.
 129      */
 130     private int updateSelectedKeys(int numEntries)
 131         throws IOException
 132     {
 133         int numKeysUpdated = 0;
 134         boolean interrupted = false;
 135 
 136         // A file descriptor may be registered with kqueue with more than one
 137         // filter and so there may be more than one event for a fd. The update
 138         // count in the MapEntry tracks when the fd was last updated and this
 139         // ensures that the ready ops are updated rather than replaced by a
 140         // second or subsequent event.
 141         updateCount++;
 142 
 143         for (int i = 0; i < numEntries; i++) {
 144             int nextFD = kqueueWrapper.getDescriptor(i);
 145             if (nextFD == fd0) {
 146                 interrupted = true;
 147             } else {
 148                 MapEntry me = fdMap.get(Integer.valueOf(nextFD));
 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             clearInterrupt();
 178         }
 179         return numKeysUpdated;
 180     }
 181 
 182     @Override
 183     protected void implClose() throws IOException {
 184         if (!closed) {
 185             closed = true;
 186 
 187             // prevent further wakeup
 188             synchronized (interruptLock) {
 189                 interruptTriggered = true;
 190             }
 191 
 192             kqueueWrapper.close();
 193             FileDispatcherImpl.closeIntFD(fd0);
 194             FileDispatcherImpl.closeIntFD(fd1);
 195 
 196             // Deregister channels
 197             Iterator<SelectionKey> i = keys.iterator();
 198             while (i.hasNext()) {
 199                 SelectionKeyImpl ski = (SelectionKeyImpl)i.next();
 200                 deregister(ski);
 201                 SelectableChannel selch = ski.channel();
 202                 if (!selch.isOpen() && !selch.isRegistered())
 203                     ((SelChImpl)selch).kill();
 204                 i.remove();
 205             }
 206         }
 207     }
 208 
 209     @Override
 210     protected void implRegister(SelectionKeyImpl ski) {
 211         ensureOpen();
 212         int fd = IOUtil.fdVal(ski.channel.getFD());
 213         fdMap.put(Integer.valueOf(fd), new MapEntry(ski));
 214         keys.add(ski);
 215     }
 216 
 217     @Override
 218     protected void implDereg(SelectionKeyImpl ski) throws IOException {
 219         int fd = ski.channel.getFDVal();
 220         fdMap.remove(Integer.valueOf(fd));
 221         kqueueWrapper.release(ski.channel);
 222         keys.remove(ski);
 223         selectedKeys.remove(ski);
 224         deregister(ski);
 225         SelectableChannel selch = ski.channel();
 226         if (!selch.isOpen() && !selch.isRegistered())
 227             ((SelChImpl)selch).kill();
 228     }
 229 
 230     @Override
 231     public void putEventOps(SelectionKeyImpl ski, int ops) {
 232         ensureOpen();
 233         kqueueWrapper.setInterest(ski.channel, ops);
 234     }
 235 
 236     @Override
 237     public Selector wakeup() {
 238         synchronized (interruptLock) {
 239             if (!interruptTriggered) {
 240                 kqueueWrapper.interrupt();
 241                 interruptTriggered = true;
 242             }
 243         }
 244         return this;
 245     }
 246 
 247     private void clearInterrupt() throws IOException {
 248         synchronized (interruptLock) {
 249             IOUtil.drain(fd0);
 250             interruptTriggered = false;
 251         }
 252     }
 253 }