上一篇文章中我们了解了netty的启动过程,初步了解了NioEventLoop,本篇文章讲解NioEventLoop最核心的功能run()方法
run()方法怎么被调用?
由NioEventLoop的run()方法向前追溯我们看到是SingleThreadEventExecutor中调用了run()方法,我们在这一行代码打上一个断点,然后看下其调用链即可。
点开第二行,看到了熟悉的excute()方法
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
在点开第三行,竟然看看到了register方法
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
可以看到AbstractChannel#AbstractUnsafe.register方法中调用了execute方法,这是因为整个代码都是在主线程中运行的因此在下面的register方法中 eventLoop.inEventLoop() 就为 false, 于是进入到 else 分支, 在这个分支中调用了 eventLoop.execute
。eventLoop 是一个 NioEventLoop 的实例, 而 NioEventLoop 没有实现 execute 方法, 因此调用的是 SingleThreadEventExecutor.execute,继而调用startThread方法完成NioEventLoop所对应的线程的启动。
上面找到了startThread调用的调用链哈,这里看下startThread()方法
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
上面startThread方法中的 STATE_UPDATER 是 SingleThreadEventExecutor 内部维护的一个属性, 它的作用是标识当前的 thread 的状态. 在初始的时候, STATE_UPDATER == ST_NOT_STARTED
, 因此第一次调用 startThread() 方法时, 就会进入到 if 语句内, 进而调用到 thread.start()完成NioEventLoop所对应的 Java线程的启动.
run()方法怎么获得就绪的事件
言归正传,接下来就是该方法的主体,本文重点部分
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
for(;;)是一个无条件的死循环, 这里就是 NioEventLoop 事件循环的核心。其基本逻辑为: 首先调用selectStrategy()方法判断当前任务队列taskQueue是否为空。
有两种情况, 一种是有任务,则调用selectNow,一种是无任务,则调用select(wakenUp.getAndSet(false));
,这selectNow和select()两者都是获取已经准备好的连接,不同的是select(oldWakeUp)会产生阻塞
.
为什么会根据taskQueue是否有任务来调用不同的select方法呢?
答案:如前面所说在 Netty 中, 一个 EventLoop 需要负责两个工作, 第一个是作为 IO 线程, 负责相应的 IO 操作; 第二个是作为任务线程, 执行 taskQueue 中的任务
。基于EventLoo的职责所在因此当taskQueue无任务的时候Netty可以选择阻塞的等待IO就绪事件,不影响其他的东西(因此此时taskQueue为空,没有任务需要执行,是吧),而当taskQueue有任务的时候如果选择阻塞的等待IO就绪事件,则就导致taskQueue中的任务不能尽快得到执行,是吧,因此当taskQueue有任务的时候就会调用非阻塞的selectNow方法,以保证taskQueue中的任务可以尽快执行
。
我们一一讲解下面几个重点方法
1.calculateStrategy方法
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
如果有任务,直接调用selectSupplier.get()
,没有任务则反回SelectStrategy.SELECT,那么selectSupplier.get()
做了什么呢
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
};
int selectNow() throws IOException {
try {
return selector.selectNow();
} finally {
// restore wakeup state if needed
if (wakenUp.get()) {
selector.wakeup();
}
}
}
可以看到如果队列中有任务,最终调用selectNow方法;那么如果没有任务看这段代码
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
发现调用了select方法,接下来就主要将这两个方法
2 selectNow方法
int selectNow() throws IOException {
int var1;
try {
var1 = this.selector.selectNow();
} finally {
if (this.wakenUp.get()) {
this.selector.wakeup();
}
}
return var1;
}
该方法调用了selector.selectNow(),这个 selector 字段正是 Java NIO 中的多路复用器 Selector。
selector.select()函数功能:选择一些I/O操作已经准备好的管道。每个管道对应着一个key
。这个方法 是一个阻塞的选择操作。当至少有一个通道被选择时才返回。当这个方法被执行时,当前线程是允许被中断的。
进一步查看源码
public int selectNow() throws IOException {
return this.lockAndDoSelect(0L);
}
private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
if (!this.isOpen()) {//检查这个Selector是否打开
throw new ClosedSelectorException();
} else {
int var10000;
//双重锁
synchronized(this.publicKeys) {
synchronized(this.publicSelectedKeys) {
var10000 = this.doSelect(var1);
}
}
return var10000;
}
}
}
上面首先检查了这个Selector是否打开,如果没有打开,则抛异常,否则利用双重锁定调用了doSelect(var1)这个方法。
protected int doSelect(long var1) throws IOException {
if (this.channelArray == null) {
throw new ClosedSelectorException();
} else {
this.timeout = var1;
//processDeregisterQueue方法会清理那些已经cancelled的SelectionKey
this.processDeregisterQueue();
if (this.interruptTriggered) {
this.resetWakeupSocket();
return 0;
} else {
this.adjustThreadsCount();
this.finishLock.reset();
this.startLock.startThreads();
try {
this.begin();
try {
this.subSelector.poll();
} catch (IOException var7) {
this.finishLock.setException(var7);
}
if (this.threads.size() > 0) {
this.finishLock.waitForHelperThreads();
}
} finally {
this.end();
}
this.finishLock.checkForException();
this.processDeregisterQueue();
int var3 = this.updateSelectedKeys();
this.resetWakeupSocket();
return var3;
}
}
}
其中 subSelector.poll() 是select的核心,由native函数poll0实现,readFds、writeFds 和exceptFds数组用来保存底层select的结果,数组的第一个位置都是存放发生事件的socket的总数,其余位置存放发生事件的socket句柄fd。
private final int[] readFds = new int [MAX_SELECTABLE_FDS + 1];
private final int[] writeFds = new int [MAX_SELECTABLE_FDS + 1];
private final int[] exceptFds = new int [MAX_SELECTABLE_FDS + 1];
private int poll() throws IOException{ // poll for the main thread
return poll0(pollWrapper.pollArrayAddress,
Math.min(totalChannels, MAX_SELECTABLE_FDS),
readFds, writeFds, exceptFds, timeout);
}
执行 selector.select() ,poll0函数把指向socket句柄和事件的内存地址传给底层函数。
- 如果之前没有发生事件,程序就阻塞在select处,当然不会一直阻塞,因为epoll在timeout时间内如果没有事件,也会返回。
- 一旦有对应的事件发生,poll0方法就会返回。
- processDeregisterQueue方法会清理那些已经cancelled的SelectionKey
- updateSelectedKeys方法统计有事件发生的SelectionKey数量,并把符合条件发生事件的SelectionKey添加到selectedKeys哈希表中,提供给后续使用。
在早期的JDK1.4和1.5 update10版本之前,Selector基于select/poll模型实现,是基于IO复用技术的非阻塞IO,不是异步IO。在JDK1.5 update10和linux core2.6以上版本,sun优化了Selctor的实现,底层使用epoll替换了select/poll。
3 select 方法
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
// If a task was submitted when wakenUp value was true, the task didn't get a chance to call
// Selector#wakeup. So we need to check task queue again before executing select operation.
// If we don't, the task might be pended until select operation was timed out.
// It might be pended until idle timeout if IdleStateHandler existed in pipeline.
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
// - Selected something,
// - waken up by user, or
// - the task queue has a pending task.
// - a scheduled task is ready for processing
break;
}
if (Thread.interrupted()) {
// Thread was interrupted so reset selected keys and break so we not run into a busy loop.
// As this is most likely a bug in the handler of the user or it's client library we will
// also log it.
//
// See https://github.com/netty/netty/issues/2426
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because " +
"Thread.currentThread().interrupt() was called. Use " +
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
selectCnt = 1;
break;
}
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// timeoutMillis elapsed without anything selected.
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// The selector returned prematurely many times in a row.
// Rebuild the selector to work around the problem.
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
rebuildSelector();
selector = this.selector;
// Select again to populate selectedKeys.
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
}
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
selector, e);
}
// Harmless exception - log anyway
}
}
selectNow()方法是通过调用selector.selectNow()立即返回不会阻塞当前线程,而在这个 select 方法中, 调用了 selector.select(timeoutMillis), 而这个调用是会阻塞住当前线程的, timeoutMillis 是阻塞的超时时间。
下面逐步分析关键方法:
1、delayNanos(currentTimeNanos)
protected long delayNanos(long currentTimeNanos) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null) {
return SCHEDULE_PURGE_INTERVAL;
}
return scheduledTask.delayNanos(currentTimeNanos);
}
public long delayNanos(long currentTimeNanos) {
return Math.max(0, deadlineNanos() - (currentTimeNanos - START_TIME));
}
public long deadlineNanos() {
return deadlineNanos;
}
该方法主要是计算延迟任务队列中第一个任务的到期执行时间(即最晚还能延迟执行的时间)。注意:每个SingleThreadEventExecutor都持有一个延迟执行任务的优先队列:final Queue<ScheduledFutureTask<?>> delayedTaskQueue = new PriorityQueue<ScheduledFutureTask<?>>())
,在启动线程的时候会往队列中加入一个任务)。最终的结果:{创建该任务指定的延迟时间-(当前时间-delayedTask创建的时间)}。如果队列中没有任何任务,则默认返回1秒钟。
在前面我们介绍启动线程的startThread()方法(代码如下)时,就添加了一个定时任务,该任务所做的事情为:将定时任务队列中的已取消任务从队列中移除,该任务每间隔1秒执行1次 ,即这个任务的deadlineNanos = SCHEDULE_PURGE_INTERVAL = 1s。
private void startThread() {
if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
delayedTaskQueue.add(new ScheduledFutureTask<Void>(
this, delayedTaskQueue, Executors.<Void>callable(new PurgeTask(), null),
ScheduledFutureTask.deadlineNanos(SCHEDULE_PURGE_INTERVAL), -SCHEDULE_PURGE_INTERVAL));
thread.start();
}
}
}
2、从如下的代码可以得到:如果延迟任务队列中第一个任务的最晚还能延迟执行的时间小于500000纳秒,且selectCnt == 0(selectCnt 用来记录selector.select方法的执行次数和标识是否执行过selector.selectNow()),则执行selector.selectNow()方法并立即返回。
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
3.如果在wakenUp值为true时提交了任务,则该任务没有机会调用Selector#wakeup。因此,我们需要在执行选择操作之前再次检查任务队列。如果不这样做,则可能会挂起任务,直到选择操作超时。 如果管道中存在IdleStateHandler,则可能要等到空闲超时为止。
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
4、如果大于500000纳秒,则执行selector.select(timeoutMillis),这个方法在等待有通道被选择时至多会阻塞timeoutMillis毫秒。
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
5.selector.select(timeoutMillis)执行完毕之后,则开始执行下面的代码:
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
// - Selected something,
// - waken up by user, or
// - the task queue has a pending task.
// - a scheduled task is ready for processing
break;
}
if (Thread.interrupted()) {
// Thread was interrupted so reset selected keys and break so we not run into a busy loop.
// As this is most likely a bug in the handler of the user or it's client library we will
// also log it.
//
// See https://github.com/netty/netty/issues/2426
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because " +
"Thread.currentThread().interrupt() was called. Use " +
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
selectCnt = 1;
break;
}
从上面的代码可知:如果存在以下几种情况则退出for循环:
1)已经ready的selectionKey,
2)selector被唤醒,
3)任务taskQueue不为空,
4)scheduledTaskQueue不为空。
5)线程被中断
6、如果time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos,即说明selector.select(timeoutMillis)等待了timeoutMillis长的时间,将selectCnt置为1,便于下次循环时退出循环。
7.如果 selectCnt 没达到阈值SELECTOR_AUTO_REBUILD_THRESHOLD(默认512),则继续进行for循环。其中 currentTimeNanos 在select操作之后会重新赋值当前时间,如果selector.select(timeoutMillis)行为真的阻塞了timeoutMillis,第二次的timeoutMillis肯定等于0,此时selectCnt 为1,所以会直接退出for循环。
8、如果selectCnt达到阈值,则说明,执行了这一段for循环达512次还没有break,这是什么原因呢?这是java早期的nio bug会导致cpu 100%, 此时select(timeout)根本不会阻塞(如果阻塞了则不能达到阈值哈)而直接返回0, 在netty中判断是否发生了此bug的方式为:为在很短时间内(小于1秒)完成了多次(默认512)select(timeout),则发生了该bug,此时进行rebuildSelector来消除bug
。即如果selectCnt 达到阈值SELECTOR_AUTO_REBUILD_THRESHOLD,则会进行重建。
else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// The selector returned prematurely many times in a row.
// Rebuild the selector to work around the problem.
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
rebuildSelector();
selector = this.selector;
// Select again to populate selectedKeys.
selector.selectNow();
selectCnt = 1;
break;
}
就如下面rebuildSelector()方法上的注释所言:重建的目的就是消灭epoll 100% cpu bug。
/**
* 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() {
if (!inEventLoop()) {
execute(new Runnable() {
@Override
public void run() {
rebuildSelector0();
}
});
return;
}
rebuildSelector0();
}
rebuildSelector()方法的代码如下,比较长,但主要干了如下2件事:
1、通过调用openSelector()方法创建一个新的selector。
2、将old selector的selectionKey执行cancel,并将oldSelector中的channel重新register到newSelector上。
9.在重建完成之后,再一次调用方法selectNow,检查是否有已ready的selectionKey,为后面的处理做准备
处理I/O事件
到这里就分析了NioEventLoop类中run方法的前面一部分:通过 select/selectNow 调用查询当前是否有就绪的 IO 事件。下面继续run方法的后半部分:通过select/selectNow查询有 IO 事件就绪时, 如何处理这些 IO 事件
。
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
在 Netty 中, 一个 EventLoop 有两个职责, 第一个是作为 IO 线程, 负责相应的 IO 操作; 第二个是作为任务线程, 执行 taskQueue 中的任务。
每个职责对应的一个处理函数,在上面的代码中反应了出来,第一个是 processSelectedKeys()函数负责处理第一步select/selectNow查询所得到的就绪的 IO 事件,第二个是 runAllTasks()函数负责处理taskQueue中的任务。
既然这个EventLoop所对应的Java 本地线程需要处理这两种任务,因此就会有一定的时间分配,因此代码中的 ioRatio 变量表示的就是此线程分配给 IO 操作所占的时间比(即 运行processSelectedKeys 耗时在整个循环中所占用的时间),例如:ioRatio默认为 50 ,表示的就是 执行 IO 操作和执行 tash 任务 所占的时间比值为1:1。
当知道了 IO 操作耗时和它所占用的时间比, 那么执行 task 的时间就可以很方便的计算出来了:
设 IO 操作耗时为 ioTime, ioTime 占的时间比例为 ioRatio, 则:
ioTime / ioRatio = taskTime / taskRatio
taskRatio = 100 - ioRatio
=> taskTime = ioTime * (100 - ioRatio) / ioRatio
当 ioRatio 为 100 时, Netty 就不考虑 IO 耗时的占比, 而是分别调用 processSelectedKeys()、runAllTasks(); 而当 ioRatio 不为 100时, 则执行到 else 分支, 在这个分支中, 首先记录下 processSelectedKeys() 所执行的时间(即 IO 操作的耗时), 然后根据公式, 计算出执行 task 所占用的时间, 然后以此为参数, 调用 runAllTasks()。
1、processSelectedKeys() 分析
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
先看processSelectedKeysOptimized方法,processSelectedKeysPlain方法与该方法类似,不再赘述
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
// null out entry in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
//方便GC
selectedKeys.keys[i] = null;
final Object a = k.attachment();
// key关联两种不同类型的对象,一种是AbstractNioChannel,一 种是NioTask
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
// 如果需要重新select则重置当前数据
if (needsToSelectAgain) {
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
迭代 selectedKeys 获取就绪的 IO 事件, 然后为每个事件都调用 processSelectedKey 来处理它.
上面的处理过程中有一个needsToSelectAgain,什么情况下会触发这个条件呢。当多个channel从selector中撤销注册时,由于很多数据无效了(默认为256),需要重新处理,设置needsToSelectAgain=true的函数如下:
void cancel(SelectionKey key) {
key.cancel();
cancelledKeys ++;
if (cancelledKeys >= CLEANUP_INTERVAL) {
cancelledKeys = 0;
needsToSelectAgain = true;
}
}
接下来看下processSelectedKey
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
//检查该SelectionKey是否有效,如果无效,则关闭channel
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
// If the channel implementation throws an exception because there is no event loop, we ignore this
// because we are only trying to determine if ch is registered to this event loop and thus has authority
// to close ch.
return;
}
// Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop
// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
// still healthy and should not be closed.
// See https://github.com/netty/netty/issues/5125
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// 如果是OP_CONNECT,则需要移除OP_CONNECT否则Selector.select(timeout)将立即返回不会有任何阻塞,这样可能会出现cpu 100%
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// 如果准备好了WRITE则将缓冲区中的数据发送出去,如果缓冲区中数据都发送完成,则清除之前关注的OP_WRITE标记
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// 如果准备好READ或ACCEPT则触发unsafe.read() ,检查是否为0,如上面的源码英文注释所说:解决JDK可能会产生死循环的一个bug。
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
该函数主要SelectionKey k进行了检查,然后如下几种不同的情况
1)OP_READ, 可读事件, 即 Channel 中收到了新数据可供上层读取。
2)OP_WRITE, 可写事件, 即上层可以向 Channel 写入数据。
3)OP_CONNECT, 连接建立事件, 即 TCP 连接已经建立, Channel 处于 active 状态.
2、runAllTasks()分析
protected boolean runAllTasks() {
assert inEventLoop();
boolean fetchedAll;
boolean ranAtLeastOne = false;
do {
fetchedAll = fetchFromScheduledTaskQueue();
if (runAllTasksFrom(taskQueue)) {
ranAtLeastOne = true;
}
} while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.
if (ranAtLeastOne) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
afterRunningAllTasks();
return ranAtLeastOne;
}
从任务队列中拉取所有的任务,然后运行这些任务的run方法来执行他们。如果至少有一个任务被执行就返回true。
1.fetchFromDelayedQueue
通过调用如下的fetchFromDelayedQueue函数从延时任务队列拉取已延迟任务到任务队列;
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
//返回延时的任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
while (scheduledTask != null) {
//放入taskQueue中,失败就循环放入
if (!taskQueue.offer(scheduledTask)) {
// No space left in the task queue add it back to the scheduledTaskQueue so we pick it up again.
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
2 runAllTasksFrom
通过调用如下的runAllTasksFrom()执行完任务队列中所有的任务。
protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
Runnable task = pollTaskFrom(taskQueue);
if (task == null) {
return false;
}
for (;;) {
safeExecute(task);
task = pollTaskFrom(taskQueue);
if (task == null) {
return true;
}
}
}
通过pollTaskFrom获取任务队列的任务
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
for (;;) {
Runnable task = taskQueue.poll();
if (task == WAKEUP_TASK) {
continue;
}
return task;
}
}
循环调用safeExecute方法执行任务
protected static void safeExecute(Runnable task) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception. Task: {}", task, t);
}
}
至此,我们以NioEventLoop的run方法为切入点,知道了NioEventLoop是如何处理IO操作和task任务的。