servlet3.1规范随笔--异步上下文AsyncContext的超时机制探究

servlet3.0之后引入的异步处理给予开发者新的解决方案。与同步上下文ServletContext对应的异步上下文是AsyncContext。值得注意的是,异步请求会有超时处理,所以AsyncContext有个方法是setTimeout。然后就有个疑问,servlet是如何做到超时设置呢?
通过debug,我们可以追踪到AbstractProcessor.java的action方法。这个方法我们之后会持续提到,它是ActionHook类的实现方法,包括了servlet容器到连接器connter的所有操作。我们需要的超时设置代码如下:

case ASYNC_SETTIMEOUT: {
    if (param == null) {
        return;
    }
    long timeout = ((Long) param).longValue();
    setAsyncTimeout(timeout);
    break;
}

emmmm....看起来没什么东西对吧,仅仅就是set一下这个timeout值。在set方法的不远处,就看到了对应的get方法。然后继续追踪get方法的调用,到timeoutAsync方法:

public void timeoutAsync(long now) {
    if (now < 0) {
        doTimeoutAsync();
    } else {
        long asyncTimeout = getAsyncTimeout();
        if (asyncTimeout > 0) {
            long asyncStart = asyncStateMachine.getLastAsyncStart();
            if ((now - asyncStart) > asyncTimeout) {
                doTimeoutAsync();
            }
        } else if (!asyncStateMachine.isAvailable()) {
            // 如果关联的application关闭了 也要做超时处理
            doTimeoutAsync();
        }
    }
}

是一些对于超时情况的处理。我们要追踪的是如何高效地实现超时机制,所以这里也不是我们想要的,那么就继续追踪这个方法的调用情况。终于在AbstractProtocol抽象类中找到了关于超时的处理机制:

public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
        logPortOffset();
    }

    endpoint.start();
    monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
            new Runnable() {
                @Override
                public void run() {
                    if (!isPaused()) {
                        startAsyncTimeout();
                    }
                }
            }, 0, 60, TimeUnit.SECONDS);
}

protected void startAsyncTimeout() {
    if (asyncTimeoutFuture == null || (asyncTimeoutFuture != null && asyncTimeoutFuture.isDone())) {
        if (asyncTimeoutFuture != null && asyncTimeoutFuture.isDone()) {
            // There was an error executing the scheduled task, get it and log it
            try {
                asyncTimeoutFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                getLog().error(sm.getString("abstractProtocolHandler.asyncTimeoutError"), e);
            }
        }
        asyncTimeoutFuture = getUtilityExecutor().scheduleAtFixedRate(
                new Runnable() {
                    @Override
                    public void run() {
                        long now = System.currentTimeMillis();
                        for (Processor processor : waitingProcessors) {
                            processor.timeoutAsync(now);
                        }
                    }
                }, 1, 1, TimeUnit.SECONDS);
    }
}

endpoint是tomcat提供基础网络服务的,此处不进行展开。从这段代码可以看出,异步请求的超时判断由一个延时一秒启动、间隔一秒执行的定时器循环扫描,在定时器外层又有一个间隔60秒执行的定时器,这个定时器存在的原因是捕获异常时使用了future.get()这个阻塞方法。
本文基于springboot 2.1.6版本中内置的tomcat-embed-core 9.0.21。更多关于tomcat和servlet的内容请参照:
https://www.ibm.com/developerworks/cn/java/j-lo-servlet/

你可能感兴趣的:(servlet3.1规范随笔--异步上下文AsyncContext的超时机制探究)