servlet3.1規(guī)范隨筆--異步上下文AsyncContext的超時(shí)機(jī)制探究

servlet3.0之后引入的異步處理給予開發(fā)者新的解決方案。與同步上下文ServletContext對應(yīng)的異步上下文是AsyncContext。值得注意的是,異步請求會(huì)有超時(shí)處理,所以AsyncContext有個(gè)方法是setTimeout。然后就有個(gè)疑問,servlet是如何做到超時(shí)設(shè)置呢?
通過debug,我們可以追蹤到AbstractProcessor.java的action方法。這個(gè)方法我們之后會(huì)持續(xù)提到,它是ActionHook類的實(shí)現(xiàn)方法,包括了servlet容器到連接器connter的所有操作。我們需要的超時(shí)設(shè)置代碼如下:

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

emmmm....看起來沒什么東西對吧,僅僅就是set一下這個(gè)timeout值。在set方法的不遠(yuǎn)處,就看到了對應(yīng)的get方法。然后繼續(xù)追蹤get方法的調(diào)用,到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()) {
            // 如果關(guān)聯(lián)的application關(guān)閉了 也要做超時(shí)處理
            doTimeoutAsync();
        }
    }
}

是一些對于超時(shí)情況的處理。我們要追蹤的是如何高效地實(shí)現(xiàn)超時(shí)機(jī)制,所以這里也不是我們想要的,那么就繼續(xù)追蹤這個(gè)方法的調(diào)用情況。終于在AbstractProtocol抽象類中找到了關(guān)于超時(shí)的處理機(jī)制:

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提供基礎(chǔ)網(wǎng)絡(luò)服務(wù)的,此處不進(jìn)行展開。從這段代碼可以看出,異步請求的超時(shí)判斷由一個(gè)延時(shí)一秒啟動(dòng)、間隔一秒執(zhí)行的定時(shí)器循環(huán)掃描,在定時(shí)器外層又有一個(gè)間隔60秒執(zhí)行的定時(shí)器,這個(gè)定時(shí)器存在的原因是捕獲異常時(shí)使用了future.get()這個(gè)阻塞方法。
本文基于springboot 2.1.6版本中內(nèi)置的tomcat-embed-core 9.0.21。更多關(guān)于tomcat和servlet的內(nèi)容請參照:
https://www.ibm.com/developerworks/cn/java/j-lo-servlet/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。