Spring Boot embedded Tomcat session timeout issue

Spring Boot embedded Tomcat recently let the session timeout issues to pit one.

In applications need to set the session timeout, then you get used to the settings in the configuration file as follows application.properties,

server.session.timeout=90

Here the shorter timeout, mainly want to see in the end there is no work (can not set the value of 30min and then look at it, as too inhumane). The results did not work, at Baidu Spring Boot found after 2, the configuration becomes as follows,

server.servlet.session.timeout=90

But the result still does not work, then it is intermittent ignorant forced to find a cause of the problem, a variety of Baidu, google, and finally feel it or see the source code, by the way is also under study.

1. Since it is Session timeout issues, then look at the implementation of the Session - StandardSession

There isValid () method

    /**
     * Return the <code>isValid</code> flag for this session.
     */
    @Override
    public boolean isValid() {

        if (!this.isValid) {
            return false;
        }

        if (this.expiring) {
            return true;
        }

        if (ACTIVITY_CHECK && accessCount.get() > 0) {
            return true;
        }

        if (maxInactiveInterval > 0) {
            int timeIdle = (int) (getIdleTimeInternal() / 1000L);
            if (timeIdle >= maxInactiveInterval) {
                expire(true);
            }
        }

        return this.isValid;
    }

Under read, here's  timeIdle> = maxInactiveInterval is triggered to determine session time-out to meet the calls expire (true). So the question becomes, when calling isValid ()?

2. Background certainly have regularly call isValid () thread

View isValid () call related classes as follows, StandardManager and ManagerBase into the discernment.

StandardManager the annotation is used to indicate that all surviving session expired, should be called when the web container destroyed, so we look ManagerBase

        // Expire all active sessions
        Session sessions[] = findSessions();
        for (int i = 0; i < sessions.length; i++) {
            Session session = sessions[i];
            try {
                if (session.isValid()) {
                    session.expire();
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
            } finally {
                // Measure against memory leaking if references to the session
                // object are kept in a shared field somewhere
                session.recycle();
            }
        }

ManagerBase, notes show what we want, then look calls processExpires () class. Or ManagerBase.

    /**
     * Invalidate all sessions that have expired.
     */
    public void processExpires() {

        long timeNow = System.currentTimeMillis();
        Session sessions[] = findSessions();
        int expireHere = 0 ;

        if(log.isDebugEnabled())
            log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
        for (int i = 0; i < sessions.length; i++) {
            if (sessions[i]!=null && !sessions[i].isValid()) {
                expireHere++;
            }
        }
        long timeEnd = System.currentTimeMillis();
        if(log.isDebugEnabled())
             log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
        processingTime += ( timeEnd - timeNow );

    }

Call processExpires ()

    /**
     * Frequency of the session expiration, and related manager operations.
     * Manager operations will be done once for the specified amount of
     * backgroundProcess calls (ie, the lower the amount, the most often the
     * checks will occur).
     */
    protected int processExpiresFrequency = 6;
    /**
     * {@inheritDoc}
     * <p>
     * Direct call to {@link #processExpires()}
     */
    @Override
    public void backgroundProcess() {
        count = (count + 1) % processExpiresFrequency;
        if (count == 0)
            processExpires();
    }

See backgroundProcess () method name to know not far from the truth. Which call is the following, in StandardContext class,

    @Override
    public void backgroundProcess() {

        if (!getState().isAvailable())
            return;

        Loader loader = getLoader();
        if (loader != null) {
            try {
                loader.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString(
                        "standardContext.backgroundProcess.loader", loader), e);
            }
        }
        Manager manager = getManager();
        if (manager != null) {
            try {
                manager.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString(
                        "standardContext.backgroundProcess.manager", manager),
                        e);
            }
        }
        WebResourceRoot resources = getResources();
        if (resources != null) {
            try {
                resources.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString(
                        "standardContext.backgroundProcess.resources",
                        resources), e);
            }
        }
        InstanceManager instanceManager = getInstanceManager();
        if (instanceManager instanceof DefaultInstanceManager) {
            try {
                ((DefaultInstanceManager)instanceManager).backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString(
                        "standardContext.backgroundProcess.instanceManager",
                        resources), e);
            }
        }
        super.backgroundProcess();
    }

But we have not seen the thread creation, continue to view the call, ContainerBase.ContainerBackgroundProcessor

    /**
     * Private thread class to invoke the backgroundProcess method
     * of this container and its children after a fixed delay.
     */
    protected class ContainerBackgroundProcessor implements Runnable 
                while (!threadDone) {
                    try {
                        Thread.sleep(backgroundProcessorDelay * 1000L);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                    if (!threadDone) {
                        processChildren(ContainerBase.this);
                    }
                }

See the dawn! It seems every background thread  backgroundProcessorDelay * processExpiresFrequency (S) to determine whether the session expired.

Defaults:

backgroundProcessorDelay  = 30s

ServerProperties.class
     /**
         * Delay between the invocation of backgroundProcess methods. If a duration suffix
         * is not specified, seconds will be used.
         */
        @DurationUnit(ChronoUnit.SECONDS)
        private Duration backgroundProcessorDelay = Duration.ofSeconds(30);

processExpiresFrequency = 6

So by default every 3min background thread to judge whether the overtime session. So I set before server.servlet.session.timeout = 90s, no way to see the effect.

Also note that background processing for timeout in min units, ie 90s in the background will be considered for 1min.

TomcatServletWebServerFactory.class

    private long getSessionTimeoutInMinutes() {
        Duration sessionTimeout = getSession().getTimeout();
        if (isZeroOrLess(sessionTimeout)) {
            return 0;
        }
        return Math.max(sessionTimeout.toMinutes(), 1);
    }

 

Guess you like

Origin www.cnblogs.com/hello-yz/p/10993707.html