NOTE
Tomcat Request Processing: Source View
Sequence diagram and source tracing from NioEndpoint accept/poll processing through SocketProcessor, Processor, CoyoteAdapter, valves, filters, and servlet invocation.
This is a historical learning note and may contain outdated or incomplete understanding.
Sequence Diagram

Source Tracing
According to Startup Flow, Tomcat startup has two main steps: one is init, and the other is start.
The main work of init is binding the port (bind), while the main work of start is listening for client connections (accept).
The code that listens for client connections is in the following logic.
NioEndpoint
startInternal
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create a thread pool as workers to process requests whose connections have already been established
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// Need to understand the relationship between poller and acceptor
// Connections accepted by acceptor are wrapped and handed to poller;
// poller calls workers in the thread pool to process the connection
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// Acceptor threads are used to receive client connection requests
// org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThreads
startAcceptorThreads();
}
}
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
// The created Acceptor is a Runnable instance
// org.apache.tomcat.util.net.NioEndpoint.Acceptor
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();// Start this Runnable
}
}
Acceptor
Acceptor.run
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
countUpOrAwaitConnection();
SocketChannel socket = null;
try {
// Block and wait for a client connection
socket = serverSock.accept();
} catch (IOException ioe) {
// We didn't get a socket
countDownConnection();
if (running) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {
break;
}
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (running && !paused) {
// Hand the socket to poller for processing
//getPoller0().register(channel);
if (!setSocketOptions(socket)) {
closeSocket(socket);
}
} else {
closeSocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}
state = AcceptorState.ENDED;
}
}
Set a breakpoint in org.apache.tomcat.util.net.NioEndpoint.Acceptor.run. Any client connection that arrives passes through this logic.
After that, the socket is handed to Poller for processing. We continue by studying Poller.
Poller
Poller is also a Runnable. It continuously checks whether new events have arrived and processes them if there are any.
Poller.run
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
//if we are here, means we have other stuff to do
//do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
if (close) {
events();
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
}
break;
}
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error("",x);
continue;
}
//either we timed out or we woke up, process events first
if ( keyCount == 0 ) hasEvents = (hasEvents | events());
// Traverse all events
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
// Process it
processKey(sk, attachment);
}
}//while
//process timeouts
timeout(keyCount,hasEvents);
}//while
getStopLatch().countDown();
}
processKey
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
try {
if ( close ) {
cancelledKey(sk);
} else if ( sk.isValid() && attachment != null ) {
if (sk.isReadable() || sk.isWritable() ) {
if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment, false);
} else {
unreg(sk, attachment, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
// Process a read event
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
// Process a write event
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk);
}
}
}
} else {
//invalid key
cancelledKey(sk);
}
} catch ( CancelledKeyException ckx ) {
cancelledKey(sk);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("",t);
}
}
processSocket
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> sc = processorCache.pop();
if (sc == null) {
// Wrap the socket event as SocketProcessorBase
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
// Use the thread pool to process SocketProcessorBase
executor.execute(sc);
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
SocketProcessorBase
This is also a Runnable. Look at org.apache.tomcat.util.net.NioEndpoint.SocketProcessor#doRun.
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
int handshake = -1;
try {
if (key != null) {
// Related to the TCP handshake
if (socket.isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
// The important part is this call to org.apache.coyote.AbstractProtocol.ConnectionHandler#process
// Then it calls org.apache.coyote.AbstractProcessorLight#process
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key);
}
} else if (handshake == -1 ) {
getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);
close(socket, key);
} else if (handshake == SelectionKey.OP_READ){
socketWrapper.registerReadInterest();
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest();
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error("", t);
socket.getPoller().cancelledKey(key);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused) {
processorCache.push(this);
}
}
}
Processor
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
throws IOException {
//...
// Call org.apache.coyote.http11.Http11Processor#service
// Parse the request into HTTP request and response
// Then call getAdapter().service(request, response);
// That is, hand request and response to the Container through the Adapter
state = service(socketWrapper);
//...
}
CoyoteAdapter
service
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
// The key code is this section.
// First call org.apache.catalina.core.StandardEngineValve#invoke
// then org.apache.catalina.core.StandardHostValve#invoke
// then org.apache.catalina.core.StandardContextValve#invoke
// and finally org.apache.catalina.core.StandardWrapperValve#invoke
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
StandardWrapperValve
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Finally, the familiar Servlet appears
Servlet servlet = null;
if (!unavailable) {
// Create the servlet instance through reflection
// Also calls servlet.init(facade);
servlet = wrapper.allocate();
// Wrap servlet and filter together
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// Call both servlet and filter
// Eventually calls org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
filterChain.doFilter(request.getRequest(),
response.getResponse());
// Put it back into the Servlet pool and wake it up?? Rather than calling destroy?
wrapper.deallocate(servlet);
}
ApplicationFilterChain
internalDoFilter
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call filter
filter.doFilter(request, response, this);
// Finally call the servlet's service method
// It calls javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
// javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
// Then calls the doXXX method
servlet.service(request, response);
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub