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 }