引言
Jetty的NIO可以有效地减少请求的线程数,但是对于servlet而言,依然是每个servlet需要一个线程,Continuation的出现有效地解决了servlet中耗时操作占用请求线程的境况。
Continuation用于处理异步请求,可以在请求处理的上下文交给应用线程去处理并挂起当前请求,这样减少了请求线程的数量,最后当应用线程处理完成后恢复该请求。比如使用该功能可以解决ajax长轮询服务端的境况,异步长连接的功能可见一斑。
Jetty中的Continunation官方文档:http://wiki.eclipse.org/Jetty/Feature/Continuations
Servlet3中规范:http://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html
而Jetty8已经将Continuation集成到了整个框架中,虽然目前并没有很大的应用场景,但是了解该功能亦能无障碍地阅读Jetty的源码。
Continunation结构
1、Continunation的类图
究其名字就是异步长连接的含义,首先了解下其体系结构
蓝色的留作拓展;红色的是恢复请求的基础;黄色的持有Continunation,拥有每个阶段的状态,因此可以有效地控制请求的流转。
2、Continunation的状态
private static final int __IDLE=0; // Idle request
private static final int __DISPATCHED=1; // Request dispatched to filter/servlet
private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container
private static final int __REDISPATCHING=3;// resumed while dispatched
private static final int __ASYNCWAIT=4; // Suspended and parked
private static final int __REDISPATCH=5; // Has been scheduled
private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
private static final int __COMPLETING=7; // complete while dispatched
private static final int __UNCOMPLETED=8; // Request is completable
private static final int __COMPLETED=9; // Request is complete
图中不同颜色对应这请求不同的流程,理解该图对于本节的总结至关重要。
1)蓝色的表示正常请求流程
2)红色的表示请求被挂起,并交出线程
3)橙色的表示请求先是被挂起,交出线程前,又被resume恢复了,因此会重复被server去handle。
4)黑色的表示被挂起的请求在调用resume或者超时的情况下被重新恢复。
5)绿色有两层涵义:线程挂起后交出线程前和交出线程之后。可以看到,无论哪种情况走的流程都是一样的。但是区别于resume,complete正如其含义:“完成,结束”,请求是不会再次被递交给server去执行的,因此只适用于交出线程前需要提前结束的时候调用(交出线程之后就算超时了被再次调用也不会被server去执行,因此没有意义)
6)为了简要,图中状态图并不全,但是对于理解Continunation已经足够了。
Jetty中的Continunation
对于Jetty中Continunation的理解其实只要理解到挂起的Request如何管理和Continunation在Jetty请求的处理流程中的地位即可。
1、AbstractHttpConnection
protected void handleRequest() throws IOException
{
boolean error = false;
String threadName=null;
Throwable async_exception=null;
try
{
if (LOG.isDebugEnabled())
{
threadName=Thread.currentThread().getName();
Thread.currentThread().setName(threadName+" - "+_uri);
}
final Server server=_server;
//标志服务器处理请求了,修改状态位
boolean handling=_request._async.handling() && server!=null && server.isRunning();
while (handling)
{
_request.setHandled(false);
String info=null;
try
{
_uri.getPort();
String path = null;
try
{
path = _uri.getDecodedPath();
}
catch (Exception e)
{
LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
LOG.ignore(e);
path = _uri.getDecodedPath(StringUtil.__ISO_8859_1);
}
info=URIUtil.canonicalPath(path);
if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT))
{
if (_uri.getScheme()!=null && _uri.getHost()!=null)
info="/";
else
throw new HttpException(400);
}
_request.setPathInfo(info);
if (_out!=null)
_out.reopen();
if (_request._async.isInitial())
{
_request.setDispatcherType(DispatcherType.REQUEST);
_connector.customize(_endp, _request);
//应用的入口
server.handle(this);
}
else
{
_request.setDispatcherType(DispatcherType.ASYNC);
server.handleAsync(this);
}
}
catch (ContinuationThrowable e)
{
LOG.ignore(e);
}
catch (EofException e)
{
async_exception=e;
LOG.debug(e);
error=true;
_request.setHandled(true);
if (!_response.isCommitted())
_generator.sendError(500, null, null, true);
}
catch (RuntimeIOException e)
{
async_exception=e;
LOG.debug(e);
error=true;
_request.setHandled(true);
}
catch (HttpException e)
{
LOG.debug(e);
error=true;
_request.setHandled(true);
_response.sendError(e.getStatus(), e.getReason());
}
catch (Throwable e)
{
async_exception=e;
LOG.warn(String.valueOf(_uri),e);
error=true;
_request.setHandled(true);
_generator.sendError(info==null?400:500, null, null, true);
}
finally
{
//标志服务器已经处理完请求了,修改状态位并判断是不是需要再次被Server处理一次,主要适用于一次请求线程暂时被挂起又被resume了。
handling = !_request._async.unhandle() && server.isRunning() && _server!=null;
}
}
}
finally
{
if (threadName!=null)
Thread.currentThread().setName(threadName);
if (_request._async.isUncompleted())
{
//请求处理的最后一步,主要是记录一些错误信息
_request._async.doComplete(async_exception);
if (_expect100Continue)
{
LOG.debug("100 continues not sent");
_expect100Continue = false;
if (!_response.isCommitted())
_generator.setPersistent(false);
}
if(_endp.isOpen())
{
if (error)
{
_endp.shutdownOutput();
_generator.setPersistent(false);
if (!_generator.isComplete())
_response.complete();
}
else
{
if (!_response.isCommitted() && !_request.isHandled())
_response.sendError(HttpServletResponse.SC_NOT_FOUND);
_response.complete();
if (_generator.isPersistent())
_connector.persist(_endp);
}
}
else
{
_response.complete();
}
_request.setHandled(true);
}
}
}
2、在请求处理完了之后服务器会判断是不是有被挂起的标记,即ASYNCSTARTED,如果有,则加入超时队列中。
protected boolean unhandle()
{
synchronized (this)
{
switch(_state)
{
case __ASYNCSTARTED:
_initial=false;
_state=__ASYNCWAIT;
scheduleTimeout(); // could block and change state.
if (_state==__ASYNCWAIT)
return true;
else if (_state==__COMPLETING)
{
_state=__UNCOMPLETED;
return true;
}
_initial=false;
_state=__REDISPATCHED;
return false;
}
}
}
随后注册到SelectSet的TimeOut队列中,在每次doSelect的时候被检查。
SelectSet在进行doSelect时的检查逻辑:
now=System.currentTimeMillis();
_timeout.setNow(now);
Task task = _timeout.expired();
while (task!=null)
{
if (task instanceof Runnable)
dispatch((Runnable)task);
task = _timeout.expired();
}