hhh
关于NioEventLoop对于线程的run()方法的实现,是真正对于Selector的轮询操作
protected void run() {
while(true) {
while(true) {
try {
switch(this.selectStrategy.calculateStrategy(this.selectNowSupplier, this.hasTasks())) {
case -2:
continue;
case -1:
this.select(this.wakenUp.getAndSet(false));
if (this.wakenUp.get()) {
this.selector.wakeup();
}
default:
this.cancelledKeys = 0;
this.needsToSelectAgain = false;
int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
this.processSelectedKeys();
} finally {
this.runAllTasks();
}
} else {
long ioStartTime = System.nanoTime();
boolean var13 = false;
try {
var13 = true;
this.processSelectedKeys();
var13 = false;
} finally {
if (var13) {
long ioTime = System.nanoTime() - ioStartTime;
this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
}
}
long ioTime = System.nanoTime() - ioStartTime;
this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
}
}
} catch (Throwable var21) {
handleLoopException(var21);
}
try {
if (this.isShuttingDown()) {
this.closeAll();
if (this.confirmShutdown()) {
return;
}
}
} catch (Throwable var18) {
handleLoopException(var18);
}
}
}
}
run()方法死循环,进入switch块,调用选择策略,this.selectNowSupplier在NioEventLoop创建时就被创建了
private final IntSupplier selectNowSupplier = new IntSupplier() {
public int get() throws Exception {
return NioEventLoop.this.selectNow();
}
};
调用了selectNow()方法:
int selectNow() throws IOException {
int var1;
try {
var1 = this.selector.selectNow();
} finally {
if (this.wakenUp.get()) {
this.selector.wakeup();
}
}
return var1;
}
关于选择策略:
public interface SelectStrategy {
int SELECT = -1;
int CONTINUE = -2;
int calculateStrategy(IntSupplier var1, boolean var2) throws Exception;
}
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : -1;
}
hasTasks根据hasTasks()方法判断,hasTasks()方法判断任务队列是否为空,在一开始进行初始化,任务队列必为空,这里calculateStrategy返回值即为-1,
回到run()里,case的-1条件成立,执行this.select(this.wakenUp.getAndSet(false)),其中wakenUp是一个原子化的Boolean,用来表示是需要唤醒Selector的轮询阻塞,初始化是为true,这里通过CAS操作设置为false代表不需要唤醒,后面在select执行完后,又判断wakenUp是否需要唤醒,说明在select中对Selector的阻塞进行了检查,若是需要唤醒,就通过Selector的原生API完成唤醒
select实现:
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + this.delayNanos(currentTimeNanos);
while(true) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0L) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
if (this.hasTasks() && this.wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
int selectedKeys = selector.select(timeoutMillis);
++selectCnt;
if (selectedKeys != 0 || oldWakenUp || this.wakenUp.get() || this.hasTasks() || this.hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
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) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, selector);
this.rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
if (selectCnt > 3 && logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.", selectCnt - 1, selector);
}
} catch (CancelledKeyException var13) {
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?", selector, var13);
}
}
}
这个方法的核心即判断这个存放任务的阻塞队列是否还有文件,若有,调用Selector的selectNow方法获取就绪的文件描述符,若是没有就绪的文件描述符该方法也会立即返回,如果阻塞队列中没有任务,就调用Selector的select(timeout)方法,尝试在超时时间内取获取就绪的文件描述符。
由于如今在执行NioEventLoopGroup的创建,并没有Channel的注册,所以没有轮询到任何文件描述符就绪,轮询结束后回到run()方法,进入default块。
ioRatio是执行IO操作和执行任务队列的任务用时比率,默认是50。
若ioRatio设置为100,就必须等到tasks阻塞队列中的所有任务执行完毕才再次进行轮询。若是小于100,那么就根据(100 - ioRatio) / ioRatio的比值乘以ioTime计算出的超时时间让所有任务尝试在超时时间内执行完毕,若是到达超时时间还没执行完毕,就在下一轮的轮询中执行。
关于processSelectedKeys()方法即为获取Selector轮询的SelectedKeys结果:
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
这里selectedKeys在openSelector时被初始化了,若在openSelector中出现异常selectedKeys值才会为null;
processSelectedKeysOptimized()方法:
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
selectedKeys.keys[i] = null;
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
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;
}
}
}
遍历openSelector中注入到Selector的SelectedKeys,得到SelectionKey对象。
这里Netty巧妙通过SelectionKey的attachment附件,将jdk中的Channel和Netty中的Channel联系起来。根据得到的附件Channel的类型,执行不同的processSelectedKey方法处理IO操作。
processSelectedKey(SelectionKey k, AbstractNioChannel ch)方法:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
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) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
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
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
这里核心在于根据SelectedKey的readyOps值判断,处理不同的就绪事件,情况如下:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
对应状态及上面的判断相对应的状态即为:连接就绪、写就绪、侦听或读就绪(缺省状态0),交给Netty的AbstractNioChannel的NioUnsafe去处理不同事件的byte数据,NioUnsafe会将数据再交由ChannelPipeline双向链表去处理。
接下来processSelectedKey(SelectionKey k, NioTask task)这个方法的实现细节需要由使用者实现NioTask接口就省略不提。
回到processSelectedKeys方法,在this.selectedKeys等于null的情况下:
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
// check if the set is empty and if so just return to not create garbage by
// creating a new Iterator every time even if there is nothing to process.
// See https://github.com/netty/netty/issues/597
if (selectedKeys.isEmpty()) {
return;
}
Iterator<SelectionKey> i = selectedKeys.iterator();
for (;;) {
final SelectionKey k = i.next();
final Object a = k.attachment();
i.remove();
//IO事件由Netty框架处理
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
//IO事件由用户自定义处理
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (!i.hasNext()) {
break;
}
if (needsToSelectAgain) {
selectAgain();
selectedKeys = selector.selectedKeys();
// Create the iterator again to avoid ConcurrentModificationException
if (selectedKeys.isEmpty()) {
break;
} else {
i = selectedKeys.iterator();
}
}
}
}
遍历selectedKeys进行事件处理,先遍历查看有误事件待处理的key列表,直接返回,再获得迭代器,循环迭代每个待处理的key,进行处理,主要遍历选择键,对于选择键处理有两种方式:Netty框架处理以及用户自定义处理。
再回到run()方法,调用完processSelectedKeys方法后,就开始调用runAlllTasks处理任务队列中的任务。
runAllTasks()方法:
protected boolean runAllTasks(long timeoutNanos) {
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
// XXX: Hard-coded value - will make it configurable if it is really a problem.
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
首先调用fetchFromScheduledTaskQueue()方法:
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
Runnable scheduledTask = pollScheduledTask(nanoTime);
while (scheduledTask != null) {
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;
}
拿出线程池中定时任务到期的任务,若没有需要执行的定时任务,为null直接返回,若有需要执行的定时任务需要被执行,将其放入taskQueue队列,如果队列满了则将任务放入延时队列,等等再拿出来放入队列。
一直执行while循环,直到没有定时任务拿出为止。
pollScheduledTask()方法:
protected final Runnable pollScheduledTask(long nanoTime) {
assert inEventLoop();
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
ScheduledFutureTask<?> scheduledTask = scheduledTaskQueue == null ? null : scheduledTaskQueue.peek();
if (scheduledTask == null) {
return null;
}
if (scheduledTask.deadlineNanos() <= nanoTime) {
scheduledTaskQueue.remove();
return scheduledTask;
}
return null;
}
这里,到期的定时任务会返回并且从定时任务队列里删除,最快到期的定时任务会在最前面。从延时任务队列中获取队首的任务scheduledTask,若是scheduledTask的deadlineNanos小于等于nanoTime,说明该任务到期。
回到runAllTasks,pollTask(),其作用从任务队列头部取出一个任务
protected Runnable pollTask() {
assert inEventLoop();
return pollTaskFrom(taskQueue);
}
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
for (;;) {
Runnable task = taskQueue.poll();
if (task == WAKEUP_TASK) {
continue;
}
return task;
}
}
用WAKEUP_TASK来控制循环
private static final Runnable WAKEUP_TASK = new Runnable() {
@Override
public void run() {
// Do nothing.
}
};
在回到runAllTasks,safeExecute方法来执行任务:实际上就是执行Runnable 的run方法。
protected static void safeExecute(Runnable task) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception. Task: {}", task, t);
}
}
再次回到runAllTasks,当所有到期任务执行完毕后,最后调用afterRunningAllTasks方法,runAllTasks到此结束。
再回到run()方法,在轮询完毕,并且执行完任务后,通过isShuttingDown判断当前状态,在之前的CAS操作中,state已经变为了3,所以isShuttingDown成立,就需要调用closeAll方法:
private void closeAll() {
selectAgain();
Set<SelectionKey> keys = selector.keys();
Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
for (SelectionKey k: keys) {
Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
channels.add((AbstractNioChannel) a);
} else {
k.cancel();
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
invokeChannelUnregistered(task, k, null);
}
}
for (AbstractNioChannel ch: channels) {
ch.unsafe().close(ch.unsafe().voidPromise());
}
}
首先调用selectAgain()方法进行一次轮询:
private void selectAgain() {
needsToSelectAgain = false;
try {
selector.selectNow();
} catch (Throwable t) {
logger.warn("Failed to update SelectionKeys.", t);
}
}
这次轮询后,将当前仍有事件就绪的JDK的SelectionKey中绑定的Netty的Channel添加到channels集合中,遍历这个集合,通过unsafe的close方法关闭Netty的Channel。
然后在回到run()中,之后调用confirmShutdown()方法:
protected boolean confirmShutdown() {
if (!isShuttingDown()) {
return false;
}
if (!inEventLoop()) {
throw new IllegalStateException("must be invoked from an event loop");
}
cancelScheduledTasks();
if (gracefulShutdownStartTime == 0) {
gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();
}
if (runAllTasks() || runShutdownHooks()) {
if (isShutdown()) {
// Executor shut down - no new tasks anymore.
return true;
}
// There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period or
// terminate if the quiet period is 0.
// See https://github.com/netty/netty/issues/4241
if (gracefulShutdownQuietPeriod == 0) {
return true;
}
wakeup(true);
return false;
}
final long nanoTime = ScheduledFutureTask.nanoTime();
if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
return true;
}
if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
// Check if any tasks were added to the queue every 100ms.
// TODO: Change the behavior of takeTask() so that it returns on timeout.
wakeup(true);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Ignore
}
return false;
}
// No tasks were added for last quiet period - hopefully safe to shut down.
// (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
return true;
}
这里首先调用cancelScheduledTasks取消所有的延时任务:
protected void cancelScheduledTasks() {
assert inEventLoop();
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
if (isNullOrEmpty(scheduledTaskQueue)) {
return;
}
final ScheduledFutureTask<?>[] scheduledTasks =
scheduledTaskQueue.toArray(new ScheduledFutureTask<?>[0]);
for (ScheduledFutureTask<?> task: scheduledTasks) {
task.cancelWithoutRemove(false);
}
scheduledTaskQueue.clearIgnoringIndexes();
}
遍历scheduledTasks 延时任务对立中所有的任务,通过cancelWithoutRemove将该任务取消,到这里轮询的整个生命周期完成。