1. 서론 

http://bugs.sun.com/view_bug.do?bug_id=6427854

http://bugs.sun.com/view_bug.do?bug_id=6527572



jdk5, jdk6에서 Chartset.forName() 또는 Selector.open() 호출시 random(rarely)하게 NullPointerException(NPE)가 발생되는 문제가 있었다. 이 이유는 Selector.open() 호출시 thread safe하지 않으면서 생긴 문제이다. 



java.lang.NullPointerException
   at sun.nio.ch.Util.atBugLevel(Util.java:290)
   at sun.nio.ch.SelectorImpl.<init>(SelectorImpl.java:40)
   at sun.nio.ch.WindowsSelectorImpl.<init>(WindowsSelectorImpl.java:104)
   at sun.nio.ch.WindowsSelectorProvider.openSelector(WindowsSelectorProvider.java:26)
   at java.nio.channels.Selector.open(Selector.java:209)



오픈소스쪽은 어떻게 구현되어 있는지 살펴보고, 앞으로 만들 솔루션들을 미리 대비해본다.



2. Zookeeper 사례

이런 문제를 해결하기 위해서 Zookeeper에서는 다음과 같이 해결했다. 이미 열고 닫음으로서 미리 초기화를 시켜 thread safe로 인한 문제가 없도록 했다. tokyotyrant도 비슷하게 사용.



<NIOServerCnxnFactory.java>


    static {

      .... 

        /**

         * this is to avoid the jvm bug:

         * NullPointerException in Selector.open()

         * http://bugs.sun.com/view_bug.do?bug_id=6427854

         */

        try {

            Selector.open().close();

        } catch(IOException ie) {

            LOG.error("Selector failed to open", ie);

        }

    }




3. Tomcat 사례

Tomcat은 synchronized(Selector.class)를 사용해서 thread safe하도록 했다. 


<NioSelectorPool.java >
protected Selector getSharedSelector() throws IOException {
        if (SHARED && SHARED_SELECTOR == null) {
            synchronized ( NioSelectorPool.class ) {
                if ( SHARED_SELECTOR == null )  {
                    synchronized (Selector.class) {
                        // Selector.open() isn't thread safe
                        // http://bugs.sun.com/view_bug.do?bug_id=6427854
                        // Affects 1.6.0_29, fixed in 1.7.0_01
                        SHARED_SELECTOR = Selector.open();
                    }
                    log.info("Using a shared selector for servlet write/read");
                }
            }
        }
        return  SHARED_SELECTOR;
    }

 @SuppressWarnings("resource") // s is closed in put()
    public Selector get() throws IOException{
        if ( SHARED ) {
            return getSharedSelector();
        }
        if ( (!enabled) || active.incrementAndGet() >= maxSelectors ) {
            if ( enabled ) active.decrementAndGet();
            return null;
        }
        Selector s = null;
        try {
            s = selectors.size()>0?selectors.poll():null;
            if (s == null) {
                synchronized (Selector.class) {
                    // Selector.open() isn't thread safe
                    // http://bugs.sun.com/view_bug.do?bug_id=6427854
                    // Affects 1.6.0_29, fixed in 1.7.0_01
                    s = Selector.open();
                }
            }
            else spare.decrementAndGet();

        }catch (NoSuchElementException x ) {
            try {
                synchronized (Selector.class) {
                    // Selector.open() isn't thread safe
                    // http://bugs.sun.com/view_bug.do?bug_id=6427854
                    // Affects 1.6.0_29, fixed in 1.7.0_01
                    s = Selector.open();
                }
            } catch (IOException iox) {
            }
        } finally {
            if ( s == null ) active.decrementAndGet();//we were unable to find a selector
        }
        return s;
    }



3, terracotta

dso-common이란 패키지안에서 Selector.open() 에서 발생하는 NPE를 catch해서 계속 retry하도록 코딩되어 있다. 



CoreNIOServices.java

private Selector createSelector() {
    Selector selector1 = null;

    final int tries = 3;

    for (int i = 0; i < tries; i++) {
      try {
        selector1 = Selector.open();
        return selector1;
      } catch (IOException ioe) {
        throw new RuntimeException(ioe);
      } catch (NullPointerException npe) {
        if (i < tries && NIOWorkarounds.selectorOpenRace(npe)) {
          System.err.println("Attempting to work around sun bug 6427854 (attempt " + (i + 1) + " of " + tries + ")");
          try {
            Thread.sleep(new Random().nextInt(20) + 5);
          } catch (InterruptedException ie) {
            //
          }
          continue;
        }
        throw npe;
      }
    }

    return selector1;
  }



4. Netty 및 JBoss 및 기타 오픈소스.

실행 스크립트에  -Dsun.nio.ch.bugLevel="" 를 추가해서 NPE가 안나게 했다.
Netty의 경우는 소스 레벨에서 이런 작업을 추가했다.

<SelectorUtil.java>

   static {
46          String key = "sun.nio.ch.bugLevel";
47          try {
48              String buglevel = System.getProperty(key);
49              if (buglevel == null) {
50                  System.setProperty(key, "");
51              }
52          } catch (SecurityException e) {
53              if (logger.isDebugEnabled()) {
54                  logger.debug("Unable to get/set System Property '" + key + '\'', e);
55              }
56          }
57          if (logger.isDebugEnabled()) {
58              logger.debug("Using select timeout of " + SELECT_TIMEOUT);
59              logger.debug("Epoll-bug workaround enabled = " + EPOLL_BUG_WORKAROUND);
60          }
61      }



Posted by 김용환 '김용환'

댓글을 달아 주세요