Andrey
Andrey

Reputation: 311

Spring boot embedded tomcat application session does not invalidate

Recently we ported our application from the web application running in tomcat to spring boot application with embedded tomcat.

After running the app for several days, memory and cpu usage have reached 100%. In heap dump analysis it comes out that there was a bunch of http session objects which where not destroyed.

I can see in the debug that sessions created with configured timeout value, lets say, 5 minutes. But after this time the invalidation is not triggered. It invoked only if I do request again after the timeout period.

I have compared this behavior with app running in tomcat and I can see that session invalidation is triggered by ContainerBackgroungProcessor thread [StandardManager(ManagerBase).processExpires()]

I do not see this background thread in spring boot application.

What was done following some suggestions found:

  1. session timeout set in application.properties: server.session.timout=300 or in EmbeddedServletContainerCustomizer @Bean: factory.setSessionTimout(5, TimeUnit.MINUTES)

  2. Added HttpSessionEventPublisher and SessionRegistry beans

Nothing helps, sessions just not invalidated at the expiration time.

Some clue about this?

Upvotes: 5

Views: 2885

Answers (3)

Thiago
Thiago

Reputation: 1

Try:

server.servlet.session.persistent=false

Upvotes: 0

Akkave
Akkave

Reputation: 473

Andrey answer is self explanatory.

While implementing this in spring boot 2.0.2, I got some finding which made my application works as expected.

Embedded tomcat used backgroundProcessorDelay to control tomcat thread.

We can control tomcat thread behaviour as mitigation by injecting bean.

@Bean        
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> containerCustomizer() {
    
    Integer threasholdTime = backgroundProcessorDelay + 1; //second
    Integer threadInterruptionTime = threasholdTime + 2; // second
    
    return factory -> {                 
        StuckThreadDetectionValve stuckThreadDetectionValve = new StuckThreadDetectionValve();
        stuckThreadDetectionValve.setThreshold(threasholdTime);                 
        stuckThreadDetectionValve.setInterruptThreadThreshold(threadInterruptionTime);
        factory.addEngineValves(stuckThreadDetectionValve);             
    };
}

In spring boot 2.0 , backgroundProcessorDelay default value is 30 second.

Important thing to consider for its working.

backgroundProcessorDelay < threasholdTime < threadInterruptionTime

Upvotes: 0

Andrey
Andrey

Reputation: 311

After some more debugging and documentation reading this is the reason and solution:

In tomcat, there is a thread spawned on behalf of the root container which scans periodically container and its child containers session pools and invalidates them. Each container/child container may be configured to have its own background processor to do the job or to rely on its host's background processor. This controlled by context.backgroundProcessorDelay

Apache Tomcat 8 Configuration Reference

backgroundProcessorDelay -
This value represents the delay in seconds between the invocation of the backgroundProcess method on this engine and its child containers, including all hosts and contexts. Child containers will not be invoked if their delay value is not negative (which would mean they are using their own processing thread). Setting this to a positive value will cause a thread to be spawn. After waiting the specified amount of time, the thread will invoke the backgroundProcess method on this engine and all its child containers. If not specified, the default value for this attribute is 10, which represent a 10 seconds delay.

In spring boot application with embedded tomcat there is TomcatEmbeddedServletContainerFactory.configureEngine() which sets this property -1 for the StandardEngine[Tomcat], which is the root container in tomcat hierarchy, as I understand. All the child containers including web app also have this parameter set to -1. And this means they all rely on someone else to do the job. Spring do not do it, no-one do it.

The solution for me was to set this parameter for the app context:

@Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory factory = (TomcatEmbeddedServletContainerFactory) container;
                TomcatContextCustomizer contextCustomizer = new TomcatContextCustomizer() {
                    
                    @Override
                    public void customize(Context context) {
                        context.setBackgroundProcessorDelay(10);
                    }
                };
                List<TomcatContextCustomizer> contextCustomizers = new ArrayList<TomcatContextCustomizer>();
                contextCustomizers.add(contextCustomizer);
                factory.setTomcatContextCustomizers(contextCustomizers);
                customizeTomcat(factory);
            }
        }

Upvotes: 6

Related Questions