View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19
    20  package org.apache.mina.transport.nio;
    21
    22  import java.io.IOException;
    23  import java.nio.ByteBuffer;
    24  import java.nio.channels.ClosedChannelException;
    25  import java.nio.channels.SelectableChannel;
    26  import java.nio.channels.SelectionKey;
    27  import java.nio.channels.Selector;
    28  import java.util.Iterator;
    29  import java.util.Queue;
    30  import java.util.concurrent.ConcurrentLinkedQueue;
    31
    32  import org.slf4j.Logger;
    33  import org.slf4j.LoggerFactory;
    34
    35  /**
    36   * This class holds a Selector and handle all the incoming events for the sessions registered on this selector.ALl the
    37   * events will be processed by some dedicated thread, taken from a pool. It will loop forever, untill the instance is
    38   * stopped.
    39   *
    40   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    41   */
    42  public class NioSelectorLoop implements SelectorLoop {
    43      /** The logger for this class */
    44      private static final Logger LOG = LoggerFactory.getLogger(NioSelectorLoop.class);
    45
    46      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
    47
    48      /** the selector managed by this class */
    49      private Selector selector;
    50
    51      /** Read buffer for all the incoming bytes (default to 64Kb) */
    52      private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(64 * 1024);
    53
    54      /** The queue containing the channels to register on the selector */
    55      private final Queue<Registration> registrationQueue = new ConcurrentLinkedQueue<Registration>();
    56
    57      /**
    58       * Creates an instance of the SelectorLoop.
    59       *
    60       * @param prefix
    61       * @param index
    62       */
    63      public NioSelectorLoop(final String prefix) {
    64          this(prefix, -1);
    65      }
    66
    67      /**
    68       * Creates an instance of the SelectorLoop.
    69       *
    70       * @param prefix
    71       * @param index
    72       */
    73      public NioSelectorLoop(final String prefix, final int index) {
    74          String workerName = "SelectorWorker " + prefix;
    75
    76          if (index >= 0) {
    77              workerName += "-" + index;
    78          }
    79
    80          SelectorWorker worker = new SelectorWorker(workerName);
    81
    82          try {
    83              if (IS_DEBUG) {
    84                  LOG.debug("open a selector");
    85              }
    86
    87              selector = Selector.open();
    88          } catch (final IOException ioe) {
    89              LOG.error("Impossible to open a new NIO selector, O/S is out of file descriptor ?");
    90              throw new IllegalStateException("Impossible to open a new NIO selector, O/S is out of file descriptor ?",
    91                      ioe);
    92          }
    93
    94          if (IS_DEBUG) {
    95              LOG.debug("starting worker thread");
    96          }
    97
    98          worker.start();
    99
    100     }
    101
    102     /**
    103      * {@inheritDoc}
    104      */
    105     @Override
    106     public void register(boolean accept, boolean connect, boolean read, boolean write, SelectorListener listener,
    107             SelectableChannel channel, RegistrationCallback callback) {
    108         if (IS_DEBUG) {
    109             LOG.debug("registering : {} for accept : {}, connect: {}, read : {}, write : {}, channel : {}",
    110                     new Object[] { listener, accept, connect, read, write, channel });
    111         }
    112
    113         int ops = 0;
    114
    115         if (accept) {
    116             ops |= SelectionKey.OP_ACCEPT;
    117         }
    118
    119         if (connect) {
    120             ops |= SelectionKey.OP_CONNECT;
    121         }
    122
    123         if (read) {
    124             ops |= SelectionKey.OP_READ;
    125         }
    126
    127         if (write) {
    128             ops |= SelectionKey.OP_WRITE;
    129         }
    130
    131         // TODO : if it's the same selector/worker, we don't need to do that we could directly enqueue
    132         registrationQueue.add(new Registration(ops, channel, listener, callback));
    133
    134         // Now, wakeup the selector in order to let it update the selectionKey status
    135         wakeup();
    136     }
    137
    138     /**
    139      * {@inheritDoc}
    140      */
    141     @Override
    142     public void modifyRegistration(boolean accept, boolean read, boolean write, final SelectorListener listener,
    143             SelectableChannel channel, boolean wakeup) {
    144         if (IS_DEBUG) {
    145             LOG.debug("modifying registration : {} for accept : {}, read : {}, write : {}, channel : {}", new Object[] {
    146                                     listener, accept, read, write, channel });
    147         }
    148
    149         final SelectionKey key = channel.keyFor(selector);
    150
    151         if (key == null) {
    152             LOG.error("Trying to modify the registration of a not registered channel");
    153             return;
    154         }
    155
    156         int ops = 0;
    157
    158         if (accept) {
    159             ops |= SelectionKey.OP_ACCEPT;
    160         }
    161
    162         if (read) {
    163             ops |= SelectionKey.OP_READ;
    164         }
    165
    166         if (write) {
    167             ops |= SelectionKey.OP_WRITE;
    168         }
    169
    170         key.interestOps(ops);
    171
    172         // we need to wakeup for the registration to be modified (TODO : not needed if we are in the worker thread)
    173         if (wakeup) {
    174             wakeup();
    175         }
    176     }
    177
    178     /**
    179      * {@inheritDoc}
    180      */
    181     @Override
    182     public void unregister(final SelectorListener listener, final SelectableChannel channel) {
    183         if (IS_DEBUG) {
    184             LOG.debug("unregistering : {}", listener);
    185         }
    186
    187         final SelectionKey key = channel.keyFor(selector);
    188
    189         if (key == null) {
    190             LOG.error("Trying to modify the registration of a not registered channel");
    191             return;
    192         }
    193         key.cancel();
    194         key.attach(null);
    195
    196         if (IS_DEBUG) {
    197             LOG.debug("unregistering : {} done !", listener);
    198         }
    199     }
    200
    201     /**
    202      * The worker processing incoming session creation, session destruction requests, session write and reads. It will
    203      * also bind new servers.
    204      */
    205     private class SelectorWorker extends Thread {
    206
    207         public SelectorWorker(String name) {
    208             super(name);
    209             setDaemon(true);
    210         }
    211
    212         @Override
    213         public void run() {
    214
    215             for (;;) {
    216                 try {
    217                     if (IS_DEBUG) {
    218                         LOG.debug("selecting...");
    219                     }
    220
    221                     final int readyCount = selector.select();
    222
    223                     if (IS_DEBUG) {
    224                         LOG.debug("... done selecting : {} events", readyCount);
    225                     }
    226
    227                     if (readyCount > 0) {
    228                         final Iterator<SelectionKey> it = selector.selectedKeys().iterator();
    229
    230                         while (it.hasNext()) {
    231                             final SelectionKey key = it.next();
    232                             final SelectorListener listener = (SelectorListener) key.attachment();
    233                             int ops = key.readyOps();
    234                             boolean isAcceptable = (ops & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
    235                             boolean isConnectable = (ops & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
    236                             boolean isReadable = (ops & SelectionKey.OP_READ) == SelectionKey.OP_READ;
    237                             boolean isWritable = (ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
    238                             listener.ready(isAcceptable, isConnectable, isReadable, isReadable ? readBuffer : null,
    239                                     isWritable);
    240                             // if you don't remove the event of the set, the selector will present you this event again
    241                             // and again
    242                             if (IS_DEBUG) {
    243                                 LOG.debug("remove");
    244                             }
    245
    246                             it.remove();
    247                         }
    248                     }
    249
    250                     // new registration
    251                     while (!registrationQueue.isEmpty()) {
    252                         final Registration reg = registrationQueue.poll();
    253
    254                         try {
    255                             SelectionKey selectionKey = reg.channel.register(selector, reg.ops, reg.listener);
    256
    257                             if (reg.getCallback() != null) {
    258                                 reg.getCallback().done(selectionKey);
    259                             }
    260                         } catch (final ClosedChannelException ex) {
    261                             // dead session..
    262                             LOG.error("socket is already dead", ex);
    263                         }
    264                     }
    265                 } catch (final Exception e) {
    266                     LOG.error("Unexpected exception : ", e);
    267                 }
    268             }
    269         }
    270     }
    271
    272     @Override
    273     public void wakeup() {
    274         selector.wakeup();
    275     }
    276
    277     private static class Registration {
    278
    279         public Registration(int ops, SelectableChannel channel, SelectorListener listener, RegistrationCallback callback) {
    280             this.ops = ops;
    281             this.channel = channel;
    282             this.listener = listener;
    283             this.callback = callback;
    284         }
    285
    286         private final int ops;
    287
    288         private final SelectableChannel channel;
    289
    290         private final SelectorListener listener;
    291
    292         private final RegistrationCallback callback;
    293
    294         public RegistrationCallback getCallback() {
    295             return callback;
    296         }
    297
    298         @Override
    299         public String toString() {
    300             StringBuilder sb = new StringBuilder();
    301
    302             sb.append("Registration : [");
    303
    304             boolean hasOp = false;
    305
    306             if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
    307                 sb.append("OP_READ");
    308                 hasOp = true;
    309             }
    310
    311             if ((ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
    312                 if (hasOp) {
    313                     sb.append("|");
    314                 }
    315
    316                 sb.append("OP_WRITE");
    317                 hasOp = true;
    318             }
    319
    320             if ((ops & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
    321                 if (hasOp) {
    322                     sb.append("|");
    323                 }
    324
    325                 sb.append("OP_ACCEPT");
    326                 hasOp = true;
    327             }
    328
    329             if ((ops & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) {
    330                 if (hasOp) {
    331                     sb.append("|");
    332                 }
    333
    334                 sb.append("OP_CONNECT");
    335                 hasOp = true;
    336             }
    337
    338             if (channel != null) {
    339                 sb.append(", ").append(channel);
    340             }
    341
    342             return sb.toString();
    343         }
    344     }
    345 }