View Javadoc

1   /*
2    * JBoss, Home of Professional Open Source
3    *
4    * Copyright 2008, Red Hat Middleware LLC, and individual contributors
5    * by the @author tags. See the COPYRIGHT.txt in the distribution for a
6    * full listing of individual contributors.
7    *
8    * This is free software; you can redistribute it and/or modify it
9    * under the terms of the GNU Lesser General Public License as
10   * published by the Free Software Foundation; either version 2.1 of
11   * the License, or (at your option) any later version.
12   *
13   * This software is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   * Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public
19   * License along with this software; if not, write to the Free
20   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
22   */
23  package org.jboss.netty.channel.socket.nio;
24  
25  import java.io.IOException;
26  import java.net.InetSocketAddress;
27  import java.nio.channels.ClosedChannelException;
28  import java.nio.channels.SelectionKey;
29  import java.nio.channels.Selector;
30  import java.nio.channels.ServerSocketChannel;
31  import java.nio.channels.spi.SelectorProvider;
32  import java.util.Set;
33  import java.util.concurrent.BlockingQueue;
34  import java.util.concurrent.ExecutorService;
35  import java.util.concurrent.Executors;
36  import java.util.concurrent.LinkedBlockingQueue;
37  import java.util.concurrent.TimeUnit;
38  
39  import org.jboss.netty.logging.InternalLogger;
40  import org.jboss.netty.logging.InternalLoggerFactory;
41  import org.jboss.netty.util.SystemPropertyUtil;
42  import org.jboss.netty.util.ThreadRenamingRunnable;
43  
44  /**
45   * Provides information which is specific to a NIO service provider
46   * implementation.
47   *
48   * @author The Netty Project (netty-dev@lists.jboss.org)
49   * @author Trustin Lee (tlee@redhat.com)
50   *
51   * @version $Rev: 452 $, $Date: 2008-11-07 22:04:56 +0900 (Fri, 07 Nov 2008) $
52   *
53   */
54  class NioProviderMetadata {
55      static final InternalLogger logger =
56          InternalLoggerFactory.getInstance(NioProviderMetadata.class);
57  
58      private static final String CONSTRAINT_LEVEL_PROPERTY =
59          "java.nio.channels.spi.constraintLevel";
60      
61      private static final long AUTODETECTION_TIMEOUT = 7000L;
62  
63      /**
64       * 0 - no need to wake up to get / set interestOps (most cases)
65       * 1 - no need to wake up to get interestOps, but need to wake up to set.
66       * 2 - need to wake up to get / set interestOps    (old providers)
67       */
68      static final int CONSTRAINT_LEVEL;
69  
70      static {
71          int constraintLevel = -1;
72  
73          // Use the system property if possible.
74          try {
75              String value = SystemPropertyUtil.get(CONSTRAINT_LEVEL_PROPERTY, "-1");
76              constraintLevel = Integer.parseInt(value);
77              if (constraintLevel < 0 || constraintLevel > 2) {
78                  constraintLevel = -1;
79              } else {
80                  logger.debug(
81                          "Using the specified NIO constraint level: " +
82                          constraintLevel);
83              }
84          } catch (Exception e) {
85              // format error
86          }
87  
88          if (constraintLevel < 0) {
89              constraintLevel = detectConstraintLevelFromSystemProperties();
90  
91              if (constraintLevel < 0) {
92                  logger.debug(
93                          "Couldn't get the NIO constraint level from the system properties.");
94                  ConstraintLevelAutodetector autodetector =
95                      new ConstraintLevelAutodetector();
96                  
97                  try {
98                      constraintLevel = autodetector.autodetectWithTimeout();
99                  } catch (Exception e) {
100                     // Probably because of security manager - try again without
101                     // creating a new thread directly.
102                     constraintLevel = autodetector.autodetectWithoutTimeout();
103                 }
104             }
105 
106             if (constraintLevel < 0) {
107                 constraintLevel = 2;
108                 logger.warn(
109                         "Failed to autodetect the NIO constraint level; " +
110                         "using the safest level (2)");
111             } else if (constraintLevel != 0) {
112                 logger.info(
113                         "Using the autodetected NIO constraint level: " +
114                         constraintLevel +
115                         " (Use better NIO provider for better performance)");
116             } else {
117                 logger.debug(
118                         "Using the autodetected NIO constraint level: " +
119                         constraintLevel);
120             }
121         }
122 
123         CONSTRAINT_LEVEL = constraintLevel;
124 
125         if (CONSTRAINT_LEVEL < 0 || CONSTRAINT_LEVEL > 2) {
126             throw new Error(
127                     "Unexpected NIO constraint level: " +
128                     CONSTRAINT_LEVEL + ", please report this error.");
129         }
130     }
131 
132     private static int detectConstraintLevelFromSystemProperties() {
133         String version = SystemPropertyUtil.get("java.specification.version");
134         String os = SystemPropertyUtil.get("os.name");
135         String vendor = SystemPropertyUtil.get("java.vm.vendor");
136         String provider;
137         try {
138             provider = SelectorProvider.provider().getClass().getName();
139         } catch (Exception e) {
140             // Perhaps security exception.
141             provider = null;
142         }
143 
144         if (version == null || os == null || vendor == null || provider == null) {
145             return -1;
146         }
147 
148         os = os.toLowerCase();
149         vendor = vendor.toLowerCase();
150 
151         // Sun JVM
152         if (vendor.indexOf("sun") >= 0) {
153             // Linux
154             if (os.indexOf("linux") >= 0) {
155                 if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
156                     provider.equals("sun.nio.ch.PollSelectorProvider")) {
157                     return 0;
158                 }
159 
160             // Windows
161             } else if (os.indexOf("windows") >= 0) {
162                 if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
163                     return 0;
164                 }
165 
166             // Solaris
167             } else if (os.indexOf("sun") >= 0 || os.indexOf("solaris") >= 0) {
168                 if (provider.equals("sun.nio.ch.DevPollSelectorProvider")) {
169                     return 0;
170                 }
171             }
172         // Apple JVM
173         } else if (vendor.indexOf("apple") >= 0) {
174             // Mac OS
175             if (os.indexOf("mac") >= 0 && os.indexOf("os") >= 0) {
176                 if (provider.equals("sun.nio.ch.KQueueSelectorProvider")) {
177                     return 0;
178                 }
179             }
180         // IBM
181         } else if (vendor.indexOf("ibm") >= 0) {
182             // Linux
183             if (os.indexOf("linux") >= 0) {
184                 if (version.equals("1.5") || version.matches("^1\\.5\\D.*$")) {
185                     if (provider.equals("sun.nio.ch.PollSelectorProvider")) {
186                         return 1;
187                     }
188                 } else if (version.equals("1.6") || version.matches("^1\\.6\\D.*$")) {
189                     if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
190                         provider.equals("sun.nio.ch.PollSelectorProvider")) {
191                         return 2;
192                     }
193                 }
194 
195             // AIX
196             } if (os.indexOf("aix") >= 0) {
197                 if (version.equals("1.5") || version.matches("^1\\.5\\D.*$")) {
198                     if (provider.equals("sun.nio.ch.PollSelectorProvider")) {
199                         return 1;
200                     }
201                 } else if (version.equals("1.6") || version.matches("^1\\.6\\D.*$")) {
202                     if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
203                         provider.equals("sun.nio.ch.PollSelectorProvider")) {
204                         return 2;
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         }
224 
225         // Others (untested)
226         return -1;
227     }
228     
229 
230     private static class ConstraintLevelAutodetector {
231 
232         ConstraintLevelAutodetector() {
233             super();
234         }
235 
236         int autodetectWithTimeout() {
237             final BlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<Integer>();
238             Runnable detector = new ThreadRenamingRunnable(new Runnable() {
239                 public void run() {
240                     int level = -1;
241                     try {
242                         level = autodetectWithoutTimeout();
243                     } finally {
244                         resultQueue.offer(Integer.valueOf(level));
245                     }
246                 }
247             }, "NIO constraint level detector");
248             
249             Thread detectorThread = new Thread(detector);
250             detectorThread.start();
251 
252             for (;;) {
253                 try {
254                     Integer result = resultQueue.poll(AUTODETECTION_TIMEOUT, TimeUnit.MILLISECONDS);
255                     if (result == null) {
256                         logger.warn("NIO constraint level autodetection timed out.");
257                         return -1;
258                     } else {
259                         return result.intValue();
260                     }
261                 } catch (InterruptedException e) {
262                     // Ignored
263                 }
264             }
265         }
266 
267         int autodetectWithoutTimeout() {
268             // TODO Code cleanup - what a mess.
269             final int constraintLevel;
270             ExecutorService executor = Executors.newCachedThreadPool();
271             boolean success;
272             long startTime;
273             int interestOps;
274 
275             ServerSocketChannel ch = null;
276             SelectorLoop loop = null;
277 
278             try {
279                 // Open a channel.
280                 ch = ServerSocketChannel.open();
281 
282                 // Configure the channel
283                 try {
284                     ch.socket().bind(new InetSocketAddress(0));
285                     ch.configureBlocking(false);
286                 } catch (IOException e) {
287                     logger.warn("Failed to configure a temporary socket.", e);
288                     return -1;
289                 }
290 
291                 // Prepare the selector loop.
292                 try {
293                     loop = new SelectorLoop();
294                 } catch (IOException e) {
295                     logger.warn("Failed to open a temporary selector.", e);
296                     return -1;
297                 }
298 
299                 // Register the channel
300                 try {
301                     ch.register(loop.selector, 0);
302                 } catch (ClosedChannelException e) {
303                     logger.warn("Failed to register a temporary selector.", e);
304                     return -1;
305                 }
306 
307                 SelectionKey key = ch.keyFor(loop.selector);
308 
309                 // Start the selector loop.
310                 executor.execute(loop);
311 
312                 // Level 0
313                 success = true;
314                 for (int i = 0; i < 10; i ++) {
315 
316                     // Increase the probability of calling interestOps
317                     // while select() is running.
318                     do {
319                         while (!loop.selecting) {
320                             Thread.yield();
321                         }
322 
323                         // Wait a little bit more.
324                         try {
325                             Thread.sleep(50);
326                         } catch (InterruptedException e) {
327                             // Ignore
328                         }
329                     } while (!loop.selecting);
330 
331                     startTime = System.nanoTime();
332                     key.interestOps(key.interestOps() | SelectionKey.OP_ACCEPT);
333                     key.interestOps(key.interestOps() & ~SelectionKey.OP_ACCEPT);
334 
335                     if (System.nanoTime() - startTime >= 500000000L) {
336                         success = false;
337                         break;
338                     }
339                 }
340 
341                 if (success) {
342                     constraintLevel = 0;
343                 } else {
344                     // Level 1
345                     success = true;
346                     for (int i = 0; i < 10; i ++) {
347 
348                         // Increase the probability of calling interestOps
349                         // while select() is running.
350                         do {
351                             while (!loop.selecting) {
352                                 Thread.yield();
353                             }
354 
355                             // Wait a little bit more.
356                             try {
357                                 Thread.sleep(50);
358                             } catch (InterruptedException e) {
359                                 // Ignore
360                             }
361                         } while (!loop.selecting);
362 
363                         startTime = System.nanoTime();
364                         interestOps = key.interestOps();
365                         synchronized (loop) {
366                             loop.selector.wakeup();
367                             key.interestOps(interestOps | SelectionKey.OP_ACCEPT);
368                             key.interestOps(interestOps & ~SelectionKey.OP_ACCEPT);
369                         }
370 
371                         if (System.nanoTime() - startTime >= 500000000L) {
372                             success = false;
373                             break;
374                         }
375                     }
376                     if (success) {
377                         constraintLevel = 1;
378                     } else {
379                         constraintLevel = 2;
380                     }
381                 }
382             } catch (IOException e) {
383                 return -1;
384             } finally {
385                 if (ch != null) {
386                     try {
387                         ch.close();
388                     } catch (IOException e) {
389                         logger.warn("Failed to close a temporary socket.", e);
390                     }
391                 }
392 
393                 if (loop != null) {
394                     loop.done = true;
395                     executor.shutdownNow();
396                     try {
397                         for (;;) {
398                             loop.selector.wakeup();
399                             try {
400                                 if (executor.awaitTermination(1, TimeUnit.SECONDS)) {
401                                     break;
402                                 }
403                             } catch (InterruptedException e) {
404                                 // Ignore
405                             }
406                         }
407                     } catch (Exception e) {
408                         // Perhaps security exception.
409                     }
410 
411                     try {
412                         loop.selector.close();
413                     } catch (IOException e) {
414                         logger.warn("Failed to close a temporary selector.", e);
415                     }
416                 }
417             }
418 
419             return constraintLevel;
420         }
421     }
422 
423     private static class SelectorLoop implements Runnable {
424         final Selector selector;
425         volatile boolean done;
426         volatile boolean selecting; // Just an approximation
427 
428         SelectorLoop() throws IOException {
429             selector = Selector.open();
430         }
431 
432         public void run() {
433             while (!done) {
434                 synchronized (this) {
435                     // Guard
436                 }
437                 try {
438                     selecting = true;
439                     try {
440                         selector.select(1000);
441                     } finally {
442                         selecting = false;
443                     }
444 
445                     Set<SelectionKey> keys = selector.selectedKeys();
446                     for (SelectionKey k: keys) {
447                         k.interestOps(0);
448                     }
449                     keys.clear();
450                 } catch (IOException e) {
451                     logger.warn("Failed to wait for a temporary selector.", e);
452                 }
453             }
454         }
455     }
456 }