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 org.jboss.netty.logging.InternalLogger;
19  import org.jboss.netty.logging.InternalLoggerFactory;
20  import org.jboss.netty.util.internal.SystemPropertyUtil;
21  
22  import java.io.IOException;
23  import java.net.InetSocketAddress;
24  import java.nio.channels.SelectionKey;
25  import java.nio.channels.Selector;
26  import java.nio.channels.ServerSocketChannel;
27  import java.nio.channels.spi.SelectorProvider;
28  import java.util.Map.Entry;
29  import java.util.Set;
30  import java.util.concurrent.ExecutorService;
31  import java.util.concurrent.Executors;
32  import java.util.concurrent.TimeUnit;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
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.getInt(CONSTRAINT_LEVEL_PROPERTY, -1);
62          if (constraintLevel < 0 || constraintLevel > 2) {
63              // Try the old property.
64              constraintLevel = SystemPropertyUtil.getInt(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.contains("sun")) {
145             // Linux
146             if (os.contains("linux")) {
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.contains("windows")) {
154                 if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
155                     return 0;
156                 }
157 
158             // Solaris
159             } else if (os.contains("sun") || os.contains("solaris")) {
160                 if (provider.equals("sun.nio.ch.DevPollSelectorProvider")) {
161                     return 0;
162                 }
163             }
164         // Apple JVM
165         } else if (vendor.contains("apple")) {
166             // Mac OS
167             if (os.contains("mac") && os.contains("os")) {
168                 if (provider.equals("sun.nio.ch.KQueueSelectorProvider")) {
169                     return 0;
170                 }
171             }
172         // IBM
173         } else if (vendor.contains("ibm")) {
174             // Linux or AIX
175             if (os.contains("linux") || os.contains("aix")) {
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.contains("bea") || vendor.contains("oracle")) {
210             // Linux
211             if (os.contains("linux")) {
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.contains("windows")) {
219                 if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
220                     return 0;
221                 }
222             }
223         // Apache Software Foundation
224         } else if (vendor.contains("apache")) {
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 }