Spring Batch在Fargate任务停止时出现EntityManagerFactory已关闭错误
解决Spring Batch异步作业完成后关闭Fargate任务时的EntityManagerFactory错误
问题根源
使用异步JobLauncher提交作业时,若在JobCompletionListener里直接触发任务停止,会导致Spring上下文被提前强制关闭,而异步作业的残留线程此时仍在尝试访问已关闭的EntityManagerFactory,最终抛出错误。核心矛盾是监听器触发时机早于异步作业资源的完全释放。
正确实现方案
1. 等待异步作业完全结束后再关闭应用
不要直接在监听器里停止任务,而是先确保所有异步作业线程执行完毕,再优雅关闭Spring上下文。可以通过监听作业执行事件+线程池同步的方式实现:
@Configuration public class BatchJobShutdownConfig { @Autowired private JobLauncher asyncJobLauncher; @Autowired private ApplicationContext applicationContext; @EventListener public void onJobCompletion(JobExecutionEvent event) { if (BatchStatus.COMPLETED.equals(event.getJobExecution().getStatus())) { waitForAsyncTasks(); SpringApplication.exit(applicationContext, () -> 0); } } private void waitForAsyncTasks() { AsyncTaskExecutor executor = ((SimpleJobLauncher) asyncJobLauncher).getTaskExecutor(); if (executor instanceof ThreadPoolTaskExecutor threadPool) { threadPool.shutdown(); try { if (!threadPool.awaitTermination(5, TimeUnit.MINUTES)) { threadPool.shutdownNow(); } } catch (InterruptedException e) { threadPool.shutdownNow(); Thread.currentThread().interrupt(); } } } }
2. 开启Spring Boot优雅关闭配置
在application.properties中配置优雅关闭参数,保证容器关闭时资源能有序释放:
# 给关闭阶段设置超时时间,确保资源释放完成 spring.lifecycle.timeout-per-shutdown-phase=30s # 启用优雅关闭模式 spring.main.shutdown=graceful
3. 依赖ECS自动停止任务,而非手动调用API
Fargate任务的主进程退出后,ECS会自动终止任务,因此无需在应用内调用StopTask API。只要保证Spring Boot应用优雅退出,ECS会自动处理任务停止。
如果必须手动触发任务停止,需在异步作业完全结束后再调用AWS SDK:
@Autowired private AmazonECS ecsClient; private void stopCurrentEcsTask() { String taskArn = System.getenv("ECS_TASK_ARN"); StopTaskRequest request = new StopTaskRequest() .withTask(taskArn) .withReason("Batch job completed successfully"); ecsClient.stopTask(request); }
注意:需给ECS任务角色添加ecs:StopTask权限。
4. 调整JobCompletionListener逻辑(备选方案)
若坚持使用监听器,可通过信号量让主线程等待作业完成后再关闭应用:
@Component public class JobCompletionSignalListener implements JobCompletionListener { private final CountDownLatch shutdownLatch = new CountDownLatch(1); @Override public void afterJob(JobExecution jobExecution) { if (BatchStatus.COMPLETED.equals(jobExecution.getStatus())) { shutdownLatch.countDown(); } } public void awaitShutdown() throws InterruptedException { shutdownLatch.await(); } }
在主类中等待信号:
@SpringBootApplication public class BatchApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(BatchApplication.class, args); JobCompletionSignalListener listener = context.getBean(JobCompletionSignalListener.class); try { listener.awaitShutdown(); SpringApplication.exit(context, () -> 0); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
关键注意事项
- 异步JobLauncher的线程池需合理配置核心线程数、最大线程数,确保作业完成后线程能被正确回收。
- 绝对不能在作业执行过程中强制关闭Spring上下文,必须等待所有异步任务的资源释放完成。
- 优先依赖ECS的进程退出自动终止机制,减少手动调用API带来的上下文冲突风险。
内容的提问来源于stack exchange,提问作者shoaib-jpeg
相关产品推荐
相关产品推荐

