View Javadoc

    1   /*
    2    * Copyright 2009 Red Hat, Inc.
    3    *
    4    * Red Hat licenses this file to you under the Apache License, version 2.0
    5    * (the "License"); you may not use this file except in compliance with the
    6    * License.  You may obtain a copy of the License at:
    7    *
    8    *    http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
    13   * License for the specific language governing permissions and limitations
    14   * under the License.
    15   */
    16  package org.jboss.netty.channel.socket.nio;
    17
    18  import java.io.IOException;
    19  import java.net.InetSocketAddress;
    20  import java.nio.channels.SelectionKey;
    21  import java.nio.channels.Selector;
    22  import java.nio.channels.ServerSocketChannel;
    23  import java.nio.channels.spi.SelectorProvider;
    24  import java.util.Set;
    25  import java.util.Map.Entry;
    26  import java.util.concurrent.ExecutorService;
    27  import java.util.concurrent.Executors;
    28  import java.util.concurrent.TimeUnit;
    29  import java.util.regex.Matcher;
    30  import java.util.regex.Pattern;
    31
    32  import org.jboss.netty.logging.InternalLogger;
    33  import org.jboss.netty.logging.InternalLoggerFactory;
    34  import org.jboss.netty.util.internal.SystemPropertyUtil;
    35
    36  /**
    37   * Provides information which is specific to a NIO service provider
    38   * implementation.
    39   *
    40   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
    41   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
    42   *
    43   * @version $Rev: 2162 $, $Date: 2010-02-18 11:23:51 +0900 (Thu, 18 Feb 2010) $
    44   *
    45   */
    46  class NioProviderMetadata {
    47      static final InternalLogger logger =
    48          InternalLoggerFactory.getInstance(NioProviderMetadata.class);
    49
    50      private static final String CONSTRAINT_LEVEL_PROPERTY =
    51          "org.jboss.netty.channel.socket.nio.constraintLevel";
    52
    53      private static final String OLD_CONSTRAINT_LEVEL_PROPERTY =
    54          "java.nio.channels.spi.constraintLevel";
    55
    56      /**
    57       * 0 - no need to wake up to get / set interestOps (most cases)
    58       * 1 - no need to wake up to get interestOps, but need to wake up to set.
    59       * 2 - need to wake up to get / set interestOps    (old providers)
    60       */
    61      static final int CONSTRAINT_LEVEL;
    62
    63      static {
    64          int constraintLevel = -1;
    65
    66          // Use the system property if possible.
    67          constraintLevel = SystemPropertyUtil.get(CONSTRAINT_LEVEL_PROPERTY, -1);
    68          if (constraintLevel < 0 || constraintLevel > 2) {
    69              // Try the old property.
    70              constraintLevel = SystemPropertyUtil.get(OLD_CONSTRAINT_LEVEL_PROPERTY, -1);
    71              if (constraintLevel < 0 || constraintLevel > 2) {
    72                  constraintLevel = -1;
    73              } else {
    74                  logger.warn(
    75                          "System property '" +
    76                          OLD_CONSTRAINT_LEVEL_PROPERTY +
    77                          "' has been deprecated.  Use '" +
    78                          CONSTRAINT_LEVEL_PROPERTY + "' instead.");
    79              }
    80          }
    81
    82          if (constraintLevel >= 0) {
    83              logger.debug(
    84                      "Setting the NIO constraint level to: " + constraintLevel);
    85          }
    86
    87          if (constraintLevel < 0) {
    88              constraintLevel = detectConstraintLevelFromSystemProperties();
    89
    90              if (constraintLevel < 0) {
    91                  constraintLevel = 2;
    92                  logger.debug(
    93                          "Couldn't determine the NIO constraint level from " +
    94                          "the system properties; using the safest level (2)");
    95              } else if (constraintLevel != 0) {
    96                  logger.info(
    97                          "Using the autodetected NIO constraint level: " +
    98                          constraintLevel +
    99                          " (Use better NIO provider for better performance)");
    100             } else {
    101                 logger.debug(
    102                         "Using the autodetected NIO constraint level: " +
    103                         constraintLevel);
    104             }
    105         }
    106
    107         CONSTRAINT_LEVEL = constraintLevel;
    108
    109         if (CONSTRAINT_LEVEL < 0 || CONSTRAINT_LEVEL > 2) {
    110             throw new Error(
    111                     "Unexpected NIO constraint level: " +
    112                     CONSTRAINT_LEVEL + ", please report this error.");
    113         }
    114     }
    115
    116     private static int detectConstraintLevelFromSystemProperties() {
    117         String version = SystemPropertyUtil.get("java.specification.version");
    118         String vminfo = SystemPropertyUtil.get("java.vm.info", "");
    119         String os = SystemPropertyUtil.get("os.name");
    120         String vendor = SystemPropertyUtil.get("java.vm.vendor");
    121         String provider;
    122         try {
    123             provider = SelectorProvider.provider().getClass().getName();
    124         } catch (Exception e) {
    125             // Perhaps security exception.
    126             provider = null;
    127         }
    128
    129         if (version == null || os == null || vendor == null || provider == null) {
    130             return -1;
    131         }
    132
    133         os = os.toLowerCase();
    134         vendor = vendor.toLowerCase();
    135
    136 //        System.out.println(version);
    137 //        System.out.println(vminfo);
    138 //        System.out.println(os);
    139 //        System.out.println(vendor);
    140 //        System.out.println(provider);
    141
    142         // Sun JVM
    143         if (vendor.indexOf("sun") >= 0) {
    144             // Linux
    145             if (os.indexOf("linux") >= 0) {
    146                 if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
    147                     provider.equals("sun.nio.ch.PollSelectorProvider")) {
    148                     return 0;
    149                 }
    150
    151             // Windows
    152             } else if (os.indexOf("windows") >= 0) {
    153                 if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
    154                     return 0;
    155                 }
    156
    157             // Solaris
    158             } else if (os.indexOf("sun") >= 0 || os.indexOf("solaris") >= 0) {
    159                 if (provider.equals("sun.nio.ch.DevPollSelectorProvider")) {
    160                     return 0;
    161                 }
    162             }
    163         // Apple JVM
    164         } else if (vendor.indexOf("apple") >= 0) {
    165             // Mac OS
    166             if (os.indexOf("mac") >= 0 && os.indexOf("os") >= 0) {
    167                 if (provider.equals("sun.nio.ch.KQueueSelectorProvider")) {
    168                     return 0;
    169                 }
    170             }
    171         // IBM
    172         } else if (vendor.indexOf("ibm") >= 0) {
    173             // Linux or AIX
    174             if (os.indexOf("linux") >= 0 || os.indexOf("aix") >= 0) {
    175                 if (version.equals("1.5") || version.matches("^1\\.5\\D.*$")) {
    176                     if (provider.equals("sun.nio.ch.PollSelectorProvider")) {
    177                         return 1;
    178                     }
    179                 } else if (version.equals("1.6") || version.matches("^1\\.6\\D.*$")) {
    180                     // IBM JDK 1.6 has different constraint level for different
    181                     // version.  The exact version can be determined only by its
    182                     // build date.
    183                     Pattern datePattern = Pattern.compile(
    184                             "(?:^|[^0-9])(" +
    185                             "[2-9][0-9]{3}" +              // year
    186                             "(?:0[1-9]|1[0-2])" +          // month
    187                             "(?:0[1-9]|[12][0-9]|3[01])" + // day of month
    188                             ")(?:$|[^0-9])");
    189
    190                     Matcher dateMatcher = datePattern.matcher(vminfo);
    191                     if (dateMatcher.find()) {
    192                         long dateValue = Long.parseLong(dateMatcher.group(1));
    193                         if (dateValue < 20081105L) {
    194                             // SR0, 1, and 2
    195                             return 2;
    196                         } else {
    197                             // SR3 and later
    198                             if (provider.equals("sun.nio.ch.EPollSelectorProvider")) {
    199                                 return 0;
    200                             } else if (provider.equals("sun.nio.ch.PollSelectorProvider")) {
    201                                 return 1;
    202                             }
    203                         }
    204                     }
    205                 }
    206             }
    207         // BEA
    208         } else if (vendor.indexOf("bea") >= 0 || vendor.indexOf("oracle") >= 0) {
    209             // Linux
    210             if (os.indexOf("linux") >= 0) {
    211                 if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
    212                     provider.equals("sun.nio.ch.PollSelectorProvider")) {
    213                     return 0;
    214                 }
    215
    216             // Windows
    217             } else if (os.indexOf("windows") >= 0) {
    218                 if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
    219                     return 0;
    220                 }
    221             }
    222         // Apache Software Foundation
    223         } else if (vendor.indexOf("apache") >= 0) {
    224             if (provider.equals("org.apache.harmony.nio.internal.SelectorProviderImpl")) {
    225                 return 1;
    226             }
    227         }
    228
    229         // Others (untested)
    230         return -1;
    231     }
    232
    233     private static final class ConstraintLevelAutodetector {
    234
    235         ConstraintLevelAutodetector() {
    236             super();
    237         }
    238
    239         int autodetect() {
    240             final int constraintLevel;
    241             ExecutorService executor = Executors.newCachedThreadPool();
    242             boolean success;
    243             long startTime;
    244             int interestOps;
    245
    246             ServerSocketChannel ch = null;
    247             SelectorLoop loop = null;
    248
    249             try {
    250                 // Open a channel.
    251                 ch = ServerSocketChannel.open();
    252
    253                 // Configure the channel
    254                 try {
    255                     ch.socket().bind(new InetSocketAddress(0));
    256                     ch.configureBlocking(false);
    257                 } catch (Throwable e) {
    258                     logger.warn("Failed to configure a temporary socket.", e);
    259                     return -1;
    260                 }
    261
    262                 // Prepare the selector loop.
    263                 try {
    264                     loop = new SelectorLoop();
    265                 } catch (Throwable e) {
    266                     logger.warn("Failed to open a temporary selector.", e);
    267                     return -1;
    268                 }
    269
    270                 // Register the channel
    271                 try {
    272                     ch.register(loop.selector, 0);
    273                 } catch (Throwable e) {
    274                     logger.warn("Failed to register a temporary selector.", e);
    275                     return -1;
    276                 }
    277
    278                 SelectionKey key = ch.keyFor(loop.selector);
    279
    280                 // Start the selector loop.
    281                 executor.execute(loop);
    282
    283                 // Level 0
    284                 success = true;
    285                 for (int i = 0; i < 10; i ++) {
    286
    287                     // Increase the probability of calling interestOps
    288                     // while select() is running.
    289                     do {
    290                         while (!loop.selecting) {
    291                             Thread.yield();
    292                         }
    293
    294                         // Wait a little bit more.
    295                         try {
    296                             Thread.sleep(50);
    297                         } catch (InterruptedException e) {
    298                             // Ignore
    299                         }
    300                     } while (!loop.selecting);
    301
    302                     startTime = System.nanoTime();
    303                     key.interestOps(key.interestOps() | SelectionKey.OP_ACCEPT);
    304                     key.interestOps(key.interestOps() & ~SelectionKey.OP_ACCEPT);
    305
    306                     if (System.nanoTime() - startTime >= 500000000L) {
    307                         success = false;
    308                         break;
    309                     }
    310                 }
    311
    312                 if (success) {
    313                     constraintLevel = 0;
    314                 } else {
    315                     // Level 1
    316                     success = true;
    317                     for (int i = 0; i < 10; i ++) {
    318
    319                         // Increase the probability of calling interestOps
    320                         // while select() is running.
    321                         do {
    322                             while (!loop.selecting) {
    323                                 Thread.yield();
    324                             }
    325
    326                             // Wait a little bit more.
    327                             try {
    328                                 Thread.sleep(50);
    329                             } catch (InterruptedException e) {
    330                                 // Ignore
    331                             }
    332                         } while (!loop.selecting);
    333
    334                         startTime = System.nanoTime();
    335                         interestOps = key.interestOps();
    336                         synchronized (loop) {
    337                             loop.selector.wakeup();
    338                             key.interestOps(interestOps | SelectionKey.OP_ACCEPT);
    339                             key.interestOps(interestOps & ~SelectionKey.OP_ACCEPT);
    340                         }
    341
    342                         if (System.nanoTime() - startTime >= 500000000L) {
    343                             success = false;
    344                             break;
    345                         }
    346                     }
    347                     if (success) {
    348                         constraintLevel = 1;
    349                     } else {
    350                         constraintLevel = 2;
    351                     }
    352                 }
    353             } catch (Throwable e) {
    354                 return -1;
    355             } finally {
    356                 if (ch != null) {
    357                     try {
    358                         ch.close();
    359                     } catch (Throwable e) {
    360                         logger.warn("Failed to close a temporary socket.", e);
    361                     }
    362                 }
    363
    364                 if (loop != null) {
    365                     loop.done = true;
    366                     try {
    367                         executor.shutdownNow();
    368                     } catch (NullPointerException ex) {
    369                         // Some JDK throws NPE here, but shouldn't.
    370                     }
    371
    372                     try {
    373                         for (;;) {
    374                             loop.selector.wakeup();
    375                             try {
    376                                 if (executor.awaitTermination(1, TimeUnit.SECONDS)) {
    377                                     break;
    378                                 }
    379                             } catch (InterruptedException e) {
    380                                 // Ignore
    381                             }
    382                         }
    383                     } catch (Throwable e) {
    384                         // Perhaps security exception.
    385                     }
    386
    387                     try {
    388                         loop.selector.close();
    389                     } catch (Throwable e) {
    390                         logger.warn("Failed to close a temporary selector.", e);
    391                     }
    392                 }
    393             }
    394
    395             return constraintLevel;
    396         }
    397     }
    398
    399     private static final class SelectorLoop implements Runnable {
    400         final Selector selector;
    401         volatile boolean done;
    402         volatile boolean selecting; // Just an approximation
    403
    404         SelectorLoop() throws IOException {
    405             selector = Selector.open();
    406         }
    407
    408         public void run() {
    409             while (!done) {
    410                 synchronized (this) {
    411                     // Guard
    412                 }
    413                 try {
    414                     selecting = true;
    415                     try {
    416                         selector.select(1000);
    417                     } finally {
    418                         selecting = false;
    419                     }
    420
    421                     Set<SelectionKey> keys = selector.selectedKeys();
    422                     for (SelectionKey k: keys) {
    423                         k.interestOps(0);
    424                     }
    425                     keys.clear();
    426                 } catch (IOException e) {
    427                     logger.warn("Failed to wait for a temporary selector.", e);
    428                 }
    429             }
    430         }
    431     }
    432
    433     public static void main(String[] args) throws Exception {
    434         for (Entry<Object, Object> e: System.getProperties().entrySet()) {
    435             System.out.println(e.getKey() + ": " + e.getValue());
    436         }
    437         System.out.println();
    438         System.out.println("Hard-coded Constraint Level: " + CONSTRAINT_LEVEL);
    439         System.out.println(
    440                 "Auto-detected Constraint Level: " +
    441                 new ConstraintLevelAutodetector().autodetect());
    442     }
    443
    444     private NioProviderMetadata() {
    445         // Unused
    446     }
    447 }