com.github.ltsopensource.nio.loop.NioSelectorLoop Maven / Gradle / Ivy
package com.github.ltsopensource.nio.loop;
import com.github.ltsopensource.core.constant.Constants;
import com.github.ltsopensource.core.logger.Logger;
import com.github.ltsopensource.core.logger.LoggerFactory;
import com.github.ltsopensource.nio.NioException;
import com.github.ltsopensource.nio.channel.NioChannel;
import com.github.ltsopensource.nio.processor.NioProcessor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author Robert HG ([email protected]) on 1/17/16.
*/
public class NioSelectorLoop {
private static final Logger LOGGER = LoggerFactory.getLogger(NioSelectorLoop.class);
private static final int SELECTOR_AUTO_REBUILD_THRESHOLD = 512;
private static final int MIN_PREMATURE_SELECTOR_RETURNS = 3;
private Selector selector;
private SelectorWorker selectorWorker;
private volatile boolean running = false;
private static boolean isLinuxPlatform = false;
static {
if (Constants.OS_NAME != null && Constants.OS_NAME.toLowerCase().contains("linux")) {
isLinuxPlatform = true;
}
/**
* Selector.open() can throw a NPE in java6 because of missing synchronization.
* http://bugs.java.com/view_bug.do?bug_id=6427854
*/
String key = "sun.nio.ch.bugLevel";
try {
String bugLevel = System.getProperty(key);
if (bugLevel == null) {
System.setProperty(key, "");
}
} catch (SecurityException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Unable to get/set System Property: {}", key, e);
}
}
}
public NioSelectorLoop(String name, NioProcessor processor) {
this.selector = openSelector();
this.selectorWorker = new SelectorWorker(name, processor);
}
private static boolean isLinuxPlatform() {
return isLinuxPlatform;
}
private Selector openSelector() {
Selector result = null;
// 在linux平台,尽量启用epoll实现
if (isLinuxPlatform()) {
try {
final Class> providerClazz = Class.forName("sun.nio.ch.EPollSelectorProvider");
if (providerClazz != null) {
final Method method = providerClazz.getMethod("provider");
if (method != null) {
final SelectorProvider selectorProvider = (SelectorProvider) method.invoke(null);
if (selectorProvider != null) {
result = selectorProvider.openSelector();
}
}
}
} catch (final Exception ignored) {
}
}
if (result == null) {
try {
result = SelectorProvider.provider().openSelector();
} catch (IOException e) {
throw new NioException("open selector error:" + e.getMessage(), e);
}
}
return result;
}
public Selector selector() {
return selector;
}
public void start() {
running = true;
selectorWorker.start();
}
public void shutdown() {
running = false;
}
private void select() throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentNanoTime = System.nanoTime();
long selectDeadLineNanos = currentNanoTime + TimeUnit.SECONDS.toNanos(1);
for (; ; ) {
long timeoutMillis = (selectDeadLineNanos - currentNanoTime + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
int selectedKeys = selector.select(timeoutMillis);
selectCnt++;
if (selectedKeys != 0) {
break;
}
if (selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
LOGGER.warn("Selector.select() returned prematurely {} times in a row; rebuilding selector.", selectCnt);
rebuildSelector();
selector = this.selector;
// 重新select,填充 selectedKeys
selector.selectNow();
selectCnt = 1;
break;
}
currentNanoTime = System.nanoTime();
}
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Selector.select() returned prematurely {} times in a row.", selectCnt - 1);
}
}
} catch (CancelledKeyException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector - JDK bug?", e);
}
}
}
/**
* 重新创建一个新的Selector, 来解决 java nio 在linux下 epoll CPU 100% 的bug
* 官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该bug发生概率降低了一些而已,它并没有被根本解决
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719
*/
private void rebuildSelector() {
final Selector oldSelector = this.selector;
final Selector newSelector;
if (oldSelector == null) {
return;
}
try {
newSelector = openSelector();
} catch (Exception e) {
LOGGER.warn("Failed to create a new Selector.", e);
return;
}
// 注册所有的channels到新的Selector.
int nChannels = 0;
for (; ; ) {
try {
for (SelectionKey key : oldSelector.keys()) {
Object a = key.attachment();
try {
if (key.channel().keyFor(newSelector) != null) {
continue;
}
int interestOps = key.interestOps();
key.cancel();
key.channel().register(newSelector, interestOps, a);
nChannels++;
} catch (Exception e) {
LOGGER.warn("Failed to re-register a Channel to the new Selector.", e);
}
}
} catch (ConcurrentModificationException e) {
// Probably due to concurrent modification of the key set.
continue;
}
break;
}
selector = newSelector;
try {
// time to close the old selector as everything else is registered to the new one
oldSelector.close();
} catch (Throwable t) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Failed to close the old Selector.", t);
}
}
LOGGER.info("Migrated " + nChannels + " channel(s) to the new Selector.");
}
private class SelectorWorker extends Thread {
private NioProcessor processor;
public SelectorWorker(String name, NioProcessor processor) {
super(name);
setDaemon(true);
this.processor = processor;
}
@Override
public void run() {
while (running) {
try {
select();
Set selectionKeys = selector.selectedKeys();
if (selectionKeys.isEmpty()) {
continue;
}
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
final SelectionKey key = iterator.next();
iterator.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
doAccept(key);
}
if (key.isConnectable()) {
doConnect(key);
}
if (key.isValid() && key.isReadable()) {
doRead(key);
}
if (key.isValid() && key.isWritable()) {
doWrite(key);
}
}
} catch (Throwable t) {
LOGGER.warn("Unexpected exception in the selector loop.", t);
// 睡眠1S, 防止连续的异常导致cpu消耗
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {
}
}
}
}
private void doAccept(SelectionKey key) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("doAccept:" + key.toString());
}
processor.accept(key);
}
private void doConnect(SelectionKey key) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("doConnect:" + key.toString());
}
processor.connect(key);
}
private void doRead(SelectionKey key) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("doRead:" + key.toString());
}
NioChannel channel = (NioChannel) key.attachment();
processor.read(channel);
}
private void doWrite(SelectionKey key) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("doWrite:" + key.toString());
}
NioChannel channel = (NioChannel) key.attachment();
processor.flush(channel);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy