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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With