5

I have a problem with a spring boot application that closes the EntityManager/session on shutdown before @Async tasks (that use the EntityManager) are finished.

There are 2 classes relevant for this problem:

Scheduler

The scheduled method reserves a limited number of jobs and calls an @Async method on XYJobProcessor that does the actual work.

@Component
public class XYJobProcessingTimer {

    private final XYJobService      xyJobService;
    private final XYJobProcessor    xyJobProcessor;

    //constructor skipped

    @Scheduled(initialDelayString = "${initial_delay}", fixedDelayString = "${delay}")
    public void performXYJobProcessing() {
        final String ticket = UUID.randomUUID().toString();
        final int reservedJobs = xyJobService.findAndReserveReadyXYJobs(ticket);

        if (reservedJobs > 0) {
            final Collection<XYJob> xyJobs = xyJobService.readReservedJobs(ticket);
            xyJobProcessor.process(xyJobs);
        }
    }

}

Async Processor

The @Async annotated method calls the service that accesses the EntityManager.

@Service
public class XYJobProcessor {

    private final XYJobService  xyJobService;

    // constructor skipped

    @Async("xyJobProcessorExecutor")
    public void process(final Collection<XYJob> jobs) {
        // This service uses the EntityManager and takes some time depending o job count.
        xyJobService.createXYJobsAndDelete(jobs);
    }

}

Configuration

The configuration of the Executor that runs the @Async tasks. The created Threads are non-daemon.

@Configuration
public class AsyncExecutorConfiguration {

    @Bean(name = "xyJobProcessorExecutor")
    public Executor xyJobProcessorExecutor() {
        final SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.setConcurrencyLimit(10);
        executor.setThreadNamePrefix("Hasselhoff-");
        return executor;
    }

}

The Problem

When I shut down the application, spring closes the EntityManager Session immediately - before all @Async tasks have finished. This leads to the following Exception:

2017-08-31 16:10:54.212 ERROR 12663 --- [Hasselhoff-12] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected error occurred invoking async method 'public void de.xy.services.XYJobProcessor.process(java.util.Collection)'.org.springframework.orm.jpa.JpaSystemException: Session is closed!; nested exception is org.hibernate.SessionException: Session is closed!
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:333)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
        at de.xy.services.XYJobService$$EnhancerBySpringCGLIB$$6b9cb1ae.createXYJobsAndDelete(<generated>)
        at de.xy.services.XYJobProcessor.process(XYJobProcessor.java:24)
        at de.xy.services.XYJobProcessor$$FastClassBySpringCGLIB$$ccc40c8f.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
        at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at org.springframework.core.task.SimpleAsyncTaskExecutor$ConcurrencyThrottlingRunnable.run(SimpleAsyncTaskExecutor.java:268)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.hibernate.SessionException: Session is closed!
        at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:132)
        at org.hibernate.internal.SessionImpl.getPersistenceContext(SessionImpl.java:2088)
        at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:340)
        at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
        at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
        at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465)
        at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963)
        at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339)
        at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
        at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
        at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
        ... 17 common frames omitted

Is there a way to make Spring wait for @Async executions to finish before it closes the EntityManager? Or is this a bug?

  • Maybe this thread could help: [how-to-shutdown-a-spring-boot-application-in-a-correct-way](https://stackoverflow.com/questions/26547532/how-to-shutdown-a-spring-boot-application-in-a-correct-way) – Patrick Sep 05 '17 at 08:49
  • Thanks for the tip, but I am already shutting down the application gracefully. The problem is that spring closes the EntityManager before (still running) @Async methods have finished their work, resulting in the exception. – Christoph Giesche Sep 05 '17 at 08:54
  • 2
    Use a different executor like the `ThreadPoolTaskExecutor` and set the `waitForTasksToCompleteOnShutdown` property to `true`. – M. Deinum Sep 05 '17 at 09:17
  • Is this thread of any help ? https://github.com/spring-projects/spring-boot/issues/4657 – PaulNUK Sep 05 '17 at 09:22
  • @M.Deinum I already tried the {ThreadPoolTaskExecutor}, but it behaves the same as the {SimpleAsyncTaskExecutor} when {waitForTasksToCompleteOnShutdown} is set to {true}. But: Because of your comment, I re-checked other available settings on the {ThreadPoolTaskExecutor} and found the exact solution for my problem. Thank you for pointing me to the right direction. (I will add an answer). – Christoph Giesche Sep 05 '17 at 09:30

1 Answers1

1

Thanks to @M.Deinum, I found out how to avoid the problem:

Using a ThreadPoolTaskExecutor (instead of SimpleAsyncTaskExecutor) and setting it's awaitTerminationSeconds property in combination with waitForTasksToCompleteOnShutdown set to true fixes my issue.

From the JavaDoc of setAwaitTerminationSeconds:

Set the maximum number of seconds that this executor is supposed to block on shutdown in order to wait for remaining tasks to complete their execution before the rest of the container continues to shut down. This is particularly useful if your remaining tasks are likely to need access to other resources that are also managed by the container.

This is the exact description and solution for my problem.