View Javadoc

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