在上一篇文章里我们主要介绍了 tomcat NIO 中响应数据的写入,在这里我们主要介绍 BlockPoller 线程。
tomcat NIO 架构中会有一个 BlockPoller 线程,该线程主要处理阻塞的读写操作。对于请求体数据读取,根据以前文章一般由 tomcat io 线程进行,当数据不可读的时候的时候(客户端数据未发送完毕),会注册封装的 OPEN_READ 事件到 BlockPoller 线程中,然后阻塞当前线程(一般为tomcat io线程)。对于响应数据的写入,根据以前文章一般也由 tomcat io 线程进行,当数据不可写的时候(原始 socket 发送缓冲区满),会注册封装的 OPEN_WRITE 事件对象到 BlockPoller 线程中,然后阻塞当前线程。
BlockPoller 实例都有一个 NIO selector 对象,主要用于监测注册在原始 scoket 上的事件是否发生。该实例有事件队列 SynchronizedQueue<PollerEvent>,用来存放发生的事件,一般该事件队列的元素由前一篇文章介绍的 tomcat io 线程放入(当请求体不可读或者响应数据不可写的时候)。对于该 block poller thread ,主要包含如下 :
启动 block poller 线程
添加事件到事件队列
对原始 socket 注册事件
block poller 线程核心逻辑
block poller 线程的等待与唤醒
启动BlockPoller线程
该线程的启动主要在以前文章中介绍的架构类 NioBlockingSelector 的 open() 方法中完成,相关核心源代码如下:
public void open(String name, Selector selector) {
sharedSelector = selector;
poller = new BlockPoller();
poller.selector = sharedSelector;
poller.setDaemon(true);
poller.setName(name + "-BlockPoller");
poller.start();
}
该代码基于 tomcat v 9.0.21,其中 block poller thread 数目始终为 1。
该代码启动 block poller thread ,并设置其为后台线程。
添加事件到事件队列
该工作主要由 BlockPoller 类的 add() 方法完成,核心源码如下:
protected Selector selector = null;
protected final SynchronizedQueue<Runnable> events = new SynchronizedQueue<>();
protected final AtomicInteger wakeupCounter = new AtomicInteger(0);
public void add(final NioSocketWrapper key, final int ops, final KeyReference ref) {
if (key == null) {
return;
}
NioChannel nch = key.getSocket();
final SocketChannel ch = nch.getIOChannel();
if (ch == null) {
return;
}
Runnable r = new RunnableAdd(ch, key, ops, ref);
events.offer(r);
wakeup();
}
public void wakeup() {
if (wakeupCounter.addAndGet(1)==0) {
selector.wakeup();
}
}
该实例有SynchronizedQueue<PollerEvent> 队列,主要用来存放发生的事件,事件类型为 RunnableAdd 。
add() 方法一般由以前文章介绍的 tomcat io 线程调用(当请求体不可读或者响应数据不可写的时候)。
add() 方法直接调用队列对象 offer() 方法,将元素加入队列。同时根据 wakeupCounter 增加之后的值是否为 0 来决定是否唤醒 selector (详细会在 poller 线程的等待与唤醒部分讲解)。
对原始socket注册事件
该工作主要由 BlockPoller 类的 events() 方法和队列元素 RunnableAdd 类的 run() 方法完成,核心源码如下:
//BlockPoller
public boolean events() {
Runnable r = null;
int size = events.size();
for (int i = 0; i < size && (r = events.poll()) != null; i++) {
r.run();
}
return (size > 0);
}
//RunnableAdd
public void run() {
SelectionKey sk = ch.keyFor(selector);
try {
if (sk == null) {
sk = ch.register(selector, ops, key);
ref.key = sk;
} else if (!sk.isValid()) {
cancel(sk, key, ops);
} else {
sk.interestOps(sk.interestOps() | ops);
}
} catch (CancelledKeyException cx) {
cancel(sk, key, ops);
} catch (ClosedChannelException cx) {
cancel(null, key, ops);
}
}
BlockPoller 的 event() 方法会遍历实例中事件队列中的所有 RunnableAdd 事件对象,然后依次调用该对象的 run() 方法。
RunnableAdd 的 run() 方法会把原始 socket 感兴趣的事件注册到 selector 对象中。
BlockPoller线程核心逻辑
该工作主要由 BlockPoller 对象实例的 run() 方法和 countDown() 方法完成,核心源码如下:
public void run() {
while (run) {
try {
events();
int keyCount = 0;
try {
int i = wakeupCounter.get();
if (i > 0) {
keyCount = selector.selectNow();
} else {
wakeupCounter.set(-1);
keyCount = selector.select(1000);
}
wakeupCounter.set(0);
if (!run) {
break;
}
} catch (NullPointerException x) {
// sun bug 5076772 on windows JDK 1.5
if (selector == null) {
throw x;
}
if (log.isDebugEnabled()) {
log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5", x);
}
continue;
} catch (CancelledKeyException x) {
// sun bug 5076772 on windows JDK 1.5
if (log.isDebugEnabled()) {
log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5", x);
}
continue;
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("nioBlockingSelector.selectError"), x);
continue;
}
Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (run && iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
try {
iterator.remove();
sk.interestOps(sk.interestOps() & (~sk.readyOps()));
if (sk.isReadable()) {
countDown(socketWrapper.getReadLatch());
}
if (sk.isWritable()) {
countDown(socketWrapper.getWriteLatch());
}
} catch (CancelledKeyException ckx) {
sk.cancel();
countDown(socketWrapper.getReadLatch());
countDown(socketWrapper.getWriteLatch());
}
}
} catch (Throwable t) {
log.error(sm.getString("nioBlockingSelector.processingError"), t);
}
}
events.clear();
if (selector.isOpen()) {
try {
selector.selectNow();
} catch (Exception ignore) {
if (log.isDebugEnabled())
log.debug("", ignore);
}
}
try {
selector.close();
} catch (Exception ignore) {
if (log.isDebugEnabled())
log.debug("", ignore);
}
}
public void countDown(CountDownLatch latch) {
if (latch == null) {
return;
}
latch.countDown();
}
run() 方法是一个循环来处理整个 block poller 线程的逻辑。
如果线程循环逻辑结束会把事件队列清空,同时也把 selector 关闭。
run() 方法会先调用前面介绍的 event() 方法来对队列中所有原始 socket 注册事件。
然后调用 NIO API 中的 selector.selectNow() 和 selector.select() 方法来监听是否有 event() 方法注册的事件发生(这里会涉及到 BlockPoller 线程的阻塞与唤醒这种比较优雅的设计,详细在后面文章单独讲解)。
如果 selector 有检测到事件发生,并且原始 socket 可读或者可写,则对调用 countDown() 方法。
在countDown() 方法里,调用和以前文章介绍的 NioSocketWrapper 对象实例关联的 readLatch 或者 writeLatch 的 countDown() 方法。
根据 tomcat 请求数据读取和响应数据写入相关文章的介绍,数据不可读或者不可写的时候,会调用 readLatch 或者 writeLatch 的 await() 方法来阻塞当前线程(一般阻塞 tomcat io线程)。当数据可读或可写的时候,调用 readLatch 或者 writeLatch 的 countDown() 方法,由 poller thread 唤醒阻塞的线程(一般为 tomcat io线程),继续处理数据的读写。
目前先写到这里,下一篇文章里我们继续介绍 tomcat NIO 中的 block poller 线程的阻塞与唤醒。