Add I/O ratio parameter to NioEventLoop

- Add SingleThreadEventExecutor.runAllTasks(timeout)
- Add NioEventLoop.ioRatio property
- Merge SelectorUtil into NioEventLoop
This commit is contained in:
Trustin Lee 2013-03-14 06:49:08 +09:00
parent 97b2feedec
commit 8dcb1387e3
4 changed files with 214 additions and 167 deletions

View File

@ -170,7 +170,13 @@ public abstract class SingleThreadEventExecutor extends AbstractEventExecutor {
*/ */
protected Runnable pollTask() { protected Runnable pollTask() {
assert inEventLoop(); assert inEventLoop();
return taskQueue.poll(); for (;;) {
Runnable task = taskQueue.poll();
if (task == WAKEUP_TASK) {
continue;
}
return task;
}
} }
/** /**
@ -230,27 +236,65 @@ public abstract class SingleThreadEventExecutor extends AbstractEventExecutor {
/** /**
* Poll all tasks from the task queue and run them via {@link Runnable#run()} method. * Poll all tasks from the task queue and run them via {@link Runnable#run()} method.
*
* @return {@code true} if and only if at least one task was run
*/ */
protected boolean runAllTasks() { protected boolean runAllTasks() {
boolean ran = false; Runnable task = pollTask();
for (;;) {
final Runnable task = pollTask();
if (task == null) { if (task == null) {
break; return false;
}
if (task == WAKEUP_TASK) {
continue;
} }
for (;;) {
try { try {
task.run(); task.run();
ran = true;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("A task raised an exception.", t); logger.warn("A task raised an exception.", t);
} }
task = pollTask();
if (task == null) {
return true;
} }
return ran; }
}
/**
* Poll all tasks from the task queue and run them via {@link Runnable#run()} method. This method stops running
* the tasks in the task queue and returns if it ran longer than {@code timeoutNanos}.
*/
protected boolean runAllTasks(long timeoutNanos) {
Runnable task = pollTask();
if (task == null) {
return false;
}
final long deadline = System.nanoTime() + timeoutNanos;
long runTasks = 0;
for (;;) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception.", t);
}
runTasks ++;
// Check timeout every 64 tasks because System.nanoTime() is relatively expensive.
// XXX: Hard-coded value - will make it configurable if it is really a problem.
if ((runTasks & 0x40) == 0) {
if (System.nanoTime() >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
break;
}
}
return true;
} }
/** /**

View File

@ -18,10 +18,11 @@ package io.netty.channel.nio;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelException; import io.netty.channel.ChannelException;
import io.netty.util.concurrent.TaskScheduler;
import io.netty.channel.EventLoopException; import io.netty.channel.EventLoopException;
import io.netty.channel.SingleThreadEventLoop; import io.netty.channel.SingleThreadEventLoop;
import io.netty.channel.nio.AbstractNioChannel.NioUnsafe; import io.netty.channel.nio.AbstractNioChannel.NioUnsafe;
import io.netty.util.concurrent.TaskScheduler;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
@ -39,6 +40,7 @@ import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
@ -54,8 +56,36 @@ public final class NioEventLoop extends SingleThreadEventLoop {
private static final InternalLogger logger = private static final InternalLogger logger =
InternalLoggerFactory.getInstance(NioEventLoop.class); InternalLoggerFactory.getInstance(NioEventLoop.class);
static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization. private static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization.
private static final boolean EPOLL_BUG_WORKAROUND =
SystemPropertyUtil.getBoolean("io.netty.epollBugWorkaround", false);
private static final long SELECT_TIMEOUT = SystemPropertyUtil.getLong("io.netty.selectTimeout", 500);
private static final long SELECT_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(SELECT_TIMEOUT);
// Workaround for JDK NIO bug.
//
// See:
// - http://bugs.sun.com/view_bug.do?bug_id=6427854
// - https://github.com/netty/netty/issues/203
static {
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);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using select timeout of " + SELECT_TIMEOUT);
logger.debug("Epoll-bug workaround enabled = " + EPOLL_BUG_WORKAROUND);
}
}
/** /**
* The NIO {@link Selector}. * The NIO {@link Selector}.
*/ */
@ -71,6 +101,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
*/ */
private final AtomicBoolean wakenUp = new AtomicBoolean(); private final AtomicBoolean wakenUp = new AtomicBoolean();
private volatile int ioRatio = 50;
private int cancelledKeys; private int cancelledKeys;
private boolean cleanedCancelledKeys; private boolean cleanedCancelledKeys;
@ -148,6 +179,28 @@ public final class NioEventLoop extends SingleThreadEventLoop {
} }
} }
/**
* Returns the percentage of the desired amount of time spent for I/O in the event loop.
*/
public int getIoRatio() {
return ioRatio;
}
/**
* Sets the percentage of the desired amount of time spent for I/O in the event loop. The default value is
* {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks.
*/
public void setIoRatio(int ioRatio) {
if (ioRatio <= 0 || ioRatio >= 100) {
throw new IllegalArgumentException("ioRatio: " + ioRatio + " (expected: 0 < ioRatio < 100)");
}
this.ioRatio = ioRatio;
}
/**
* Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
* around the infamous epoll 100% CPU bug.
*/
public void rebuildSelector() { public void rebuildSelector() {
if (!inEventLoop()) { if (!inEventLoop()) {
execute(new Runnable() { execute(new Runnable() {
@ -224,20 +277,22 @@ public final class NioEventLoop extends SingleThreadEventLoop {
@Override @Override
protected void run() { protected void run() {
// use 80% of the timeout for measure
final long minSelectTimeout = SELECT_TIMEOUT_NANOS / 100 * 80;
Selector selector = this.selector; Selector selector = this.selector;
int selectReturnsImmediately = 0; int selectReturnsImmediately = 0;
// use 80% of the timeout for measure
long minSelectTimeout = SelectorUtil.SELECT_TIMEOUT_NANOS / 100 * 80;
for (;;) { for (;;) {
wakenUp.set(false); wakenUp.set(false);
try { try {
if (hasTasks()) {
selectNow();
} else {
long beforeSelect = System.nanoTime(); long beforeSelect = System.nanoTime();
int selected = SelectorUtil.select(selector); int selected = select();
if (EPOLL_BUG_WORKAROUND) {
if (SelectorUtil.EPOLL_BUG_WORKAROUND) {
if (selected == 0) { if (selected == 0) {
long timeBlocked = System.nanoTime() - beforeSelect; long timeBlocked = System.nanoTime() - beforeSelect;
if (timeBlocked < minSelectTimeout) { if (timeBlocked < minSelectTimeout) {
@ -296,13 +351,17 @@ public final class NioEventLoop extends SingleThreadEventLoop {
if (wakenUp.get()) { if (wakenUp.get()) {
selector.wakeup(); selector.wakeup();
} }
}
cancelledKeys = 0; cancelledKeys = 0;
final long ioStartTime = System.nanoTime();
processSelectedKeys(); processSelectedKeys();
selector = this.selector; selector = this.selector;
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(); final int ioRatio = this.ioRatio;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
selector = this.selector; selector = this.selector;
if (isShutdown()) { if (isShutdown()) {
@ -312,8 +371,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
} }
} }
} catch (Throwable t) { } catch (Throwable t) {
logger.warn( logger.warn("Unexpected exception in the selector loop.", t);
"Unexpected exception in the selector loop.", t);
// Prevent possible consecutive immediate failures that lead to // Prevent possible consecutive immediate failures that lead to
// excessive CPU consumption. // excessive CPU consumption.
@ -341,7 +399,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
if (cancelledKeys >= CLEANUP_INTERVAL) { if (cancelledKeys >= CLEANUP_INTERVAL) {
cancelledKeys = 0; cancelledKeys = 0;
cleanedCancelledKeys = true; cleanedCancelledKeys = true;
SelectorUtil.cleanupKeys(selector); cleanupKeys();
} }
} }
@ -475,7 +533,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
} }
private void closeAll() { private void closeAll() {
SelectorUtil.cleanupKeys(selector); cleanupKeys();
Set<SelectionKey> keys = selector.keys(); Set<SelectionKey> keys = selector.keys();
Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size()); Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
for (SelectionKey k: keys) { for (SelectionKey k: keys) {
@ -521,4 +579,27 @@ public final class NioEventLoop extends SingleThreadEventLoop {
} }
} }
} }
private int select() throws IOException {
try {
return selector.select();
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(
CancelledKeyException.class.getSimpleName() +
" raised by a Selector - JDK bug?", e);
}
// Harmless exception - log anyway
}
return -1;
}
void cleanupKeys() {
try {
selector.selectNow();
} catch (Throwable t) {
logger.warn("Failed to update SelectionKeys.", t);
}
}
} }

View File

@ -16,9 +16,9 @@
package io.netty.channel.nio; package io.netty.channel.nio;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.util.concurrent.TaskScheduler;
import io.netty.util.concurrent.EventExecutor;
import io.netty.channel.MultithreadEventLoopGroup; import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.TaskScheduler;
import java.nio.channels.Selector; import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider; import java.nio.channels.spi.SelectorProvider;
@ -62,6 +62,16 @@ public class NioEventLoopGroup extends MultithreadEventLoopGroup {
super(nThreads, threadFactory, selectorProvider); super(nThreads, threadFactory, selectorProvider);
} }
/**
* Sets the percentage of the desired amount of time spent for I/O in the child event loops. The default value is
* {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks.
*/
public void setIoRatio(int ioRatio) {
for (EventExecutor e: children()) {
((NioEventLoop) e).setIoRatio(ioRatio);
}
}
/** /**
* Replaces the current {@link Selector}s of the child event loops with newly created {@link Selector}s to work * Replaces the current {@link Selector}s of the child event loops with newly created {@link Selector}s to work
* around the infamous epoll 100% CPU bug. * around the infamous epoll 100% CPU bug.

View File

@ -1,88 +0,0 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.nio;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.Selector;
import java.util.concurrent.TimeUnit;
/**
* Utility class for operate on a {@link Selector}
*/
final class SelectorUtil {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(SelectorUtil.class);
static final long DEFAULT_SELECT_TIMEOUT = 500;
static final long SELECT_TIMEOUT =
SystemPropertyUtil.getLong("io.netty.selectTimeout", DEFAULT_SELECT_TIMEOUT);
static final long SELECT_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(SELECT_TIMEOUT);
static final boolean EPOLL_BUG_WORKAROUND =
SystemPropertyUtil.getBoolean("io.netty.epollBugWorkaround", false);
// Workaround for JDK NIO bug.
//
// See:
// - http://bugs.sun.com/view_bug.do?bug_id=6427854
// - https://github.com/netty/netty/issues/203
static {
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);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using select timeout of " + SELECT_TIMEOUT);
logger.debug("Epoll-bug workaround enabled = " + EPOLL_BUG_WORKAROUND);
}
}
static int select(Selector selector) throws IOException {
try {
return selector.select(SELECT_TIMEOUT);
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(
CancelledKeyException.class.getSimpleName() +
" raised by a Selector - JDK bug?", e);
}
// Harmless exception - log anyway
}
return -1;
}
static void cleanupKeys(Selector selector) {
try {
selector.selectNow();
} catch (Throwable t) {
logger.warn("Failed to update SelectionKeys.", t);
}
}
private SelectorUtil() {
// Unused
}
}