<시작하며..>
apache mina에서 cpu 100% 치는 현상이 발생된 적이 있다. JDK 1.6.0_22 and mina 2.0.2에서는 더 이상 cpu가 치는 현상이 없었지만, old한 jdk 에서 동작하는 apache mina를 위해서 코드를 패치하여 해결했다고 한 상태이다. (2.0.3 패치)
NioProcessor 100% CPU usage on Linux (epoll selector bug)
https://issues.apache.org/jira/browse/DIRMINA-678
이 문제는 Jetty (http://jira.codehaus.org/browse/JETTY-937), jdk(http://bugs.sun.com/view_bug.do?bug_id=6693490, http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933) 와도 밀접한 연관이 있으므로 알아두면 좋을 것 같다.
참고 jdk 1.6.0_22 의 release note(http://www.oracle.com/technetwork/java/javase/6u22releasenotes-176121.html)를 봐도 특별한 내용은 없다. 그러나, 1.6.0_23의 버그 fix (http://www.oracle.com/technetwork/java/javase/2col/6u23bugfixes-191074.html) 의 내용 중 “6728542”은 관련 내용이 있다. 그 내용(http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6728542)은 다음과 같다. 아마도 이 부분을 수정한 것이 아닌가 싶다. Epoll implementation of java.nio.channels.Selector |
<내용>
jdk의 버그라 할지라도 문제를 해결했는지에 대한 것은 다른 이슈이다. 수많은 버그를 피해서 만들어둔 코드가 멋지기로 하다. (ibm jike 버그 회피 코드를 잘 짰었다.. ㅋ)
Mina 의 버그질라 및 Jetty, jdk 이슈는 아래 thread dump를 근거로 이야기를 하고 있다. select 하면서 loop에 빠지면서 cpu가 100%가 되는 현상이다. 버그질라에 나온 영어로는 spinning in tight loop 이다.
java.lang.Thread.State: RUNNABLE at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:215) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65) at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69) - locked <0x00002aaab3dd8760> (a sun.nio.ch.Util$1) - locked <0x00002aaab3dd83a0> (a java.util.Collections$UnmodifiableSet) - locked <0x00002aaab3dd82e8> (a sun.nio.ch.EPollSelectorImpl) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80) |
<자바 및 jvm 소스>
java.net에 있는 jdk 6 update 22 의 linux 소스를 바탕으로 작성한다.
(다운로드 받을 수 있는 곳 : http://download.java.net/jdk6/6u10/archive/)
java.nio.channels.Selector클래스의 select() 메서드가 호출되는 tree를 쫓아가본다.
public abstract class Selector { public abstract int select(long timeout) throws IOException; } |
->
abstract class SelectorImpl extends AbstractSelector { private int lockAndDoSelect(long timeout) throws IOException { protected abstract int doSelect(long timeout) throws IOException; |
->
class EPollSelectorImpl extends SelectorImpl { …. // The poll object protected int doSelect(long timeout) throws IOException } |
->
/** int poll(long timeout) throws IOException { private native int epollWait(long pollAddress, int numfds, long timeout, |
->
EPollArrayWrapper.c
typedef union epoll_data { struct epoll_event { JNIEXPORT jint JNICALL if (timeout <= 0) { /* Indefinite or no wait */ static int iepoll(int epfd, struct epoll_event *events, int numfds, jlong timeout) gettimeofday(&t, NULL); for (;;) { |
<mina쪽 소스>
mina쪽에서 어떻게 해서 무한 루프가 되는 시나리오는 다음과 같다.
select(timeout)를 호출하지만, 바로 jvm 코드에 의해 selected가 리턴된다. isDisposing() 메서드는 종료여부를 알린다. selector와 바인드된 Channel 이 종료되면 disposing 값은 true가 된다.
jvm 내부의 버그로 종료되지 못한 channel 또는 처리해야 할 channel이 존재함으로서, select()에서는 이벤트가 계속 반복되는 현상이 일어나. 계속 무한 루프가 도는 현상이 발생하게 된다.
# Apache Mina 2.0.0-RC1 소스
public abstract class AbstractPollingIoProcessor<T extends AbstractIoSession> implements IoProcessor<T> { private static final long SELECT_TIMEOUT = 1000L; private class Processor implements Runnable { for (;;) { nSessions += handleNewSessions(); if (selected > 0) { long currentTime = System.currentTimeMillis(); if (nSessions == 0) { if (isDisposing()) { } /** }
public final class NioProcessor extends AbstractPollingIoProcessor<NioSession> { @Override
|
그래서 2011년 Apache Mina 2.0.3 에서는 old jvm을 위한 패치가 이루어졌다.
Mina 버그질라 (https://issues.apache.org/jira/browse/DIRMINA-678)의 Attachment에 있는
diff 파일을 참조할 수 있다.
핵심 코드만 따로 보면, 아래 소스를 보면 된다.
Selector 안에 Channel이 문제가 있는 것(connection이 broken)일 때는 정리하면 되고,
이미 연결되어 있어 있는 Channel에 대해서는 selector를 새거로 바꾸고 예전꺼는 정리하는 구조
(NioProcessor.registerNewSelector())로 바꿔놨다.
특별히 이 버그에 대해서 또다른 내용이 등록되어 있지 않아서 잘 동작되는 것 같다.
# Apache Mina 2.0.4 (수정된 패치 내역이 있는 부분)
public abstract class AbstractPollingIoProcessor<T extends AbstractIoSession> implements IoProcessor<T> { protected AtomicBoolean wakeupCalled = new AtomicBoolean(false); private class Processor implements Runnable { int nSessions = 0; for (;;) { if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) { // we can reselect immediately continue; // Set back the flag to false // and continue the loop // Manage newly created session first updateTrafficMask(); // Now, if we have had some incoming or outgoing events, // Write the pending requests // And manage removed sessions // Last, not least, send Idle events to the idle sessions // Get a chance to exit the infinite loop if there are no // Disconnect all sessions immediately if disposal has been wakeup(); try { |
public final class NioProcessor extends AbstractPollingIoProcessor<NioSession> { @Override // Open a new selector // Loop on all the registered keys, and register them on the new selector // Don't forget to attache the session, and back ! // Now we can close the old selector and switch it |
마치며..
jvm의 버그로 인해서 회피하는 Apache mina 코드와 연관된 jvm 소스를 살펴보았다. 원천적으로 jvm 코드의 수정이 됨으로서 문제가 해결되었지만, jvm 버그가 언제 고쳐질지 모르거나 old jvm을 쓰면서 이슈가 생기는 것을 막기 위해서 회피하는 코드를 만들었다. 책임감이 강한 멋쟁이 mina commitor.. 잘 되기를 바란다.
'java core' 카테고리의 다른 글
Oracle에서 jdk 7 부터는 G1를 디폴트로 사용한다고 했었는데.. 적용되고 있지 않다. (0) | 2012.02.10 |
---|---|
JDK 버그-classloader가 array 타입의 클래스 로딩시 ClassNotFoundException발생 (2012.2.현재) (0) | 2012.02.10 |
JDK 1.6.0_22에서 JMX memory leak 이슈 해결 (0) | 2012.02.03 |
jdk 1.7 변화 - OperatingSystemMXBean api 추가 (0) | 2011.12.02 |
jni vs jna (4) | 2011.12.02 |