Skip to content

Unable to correctly save task metadata when registering JobExecutionShutdownHook #5307

@XhstormR

Description

@XhstormR

Bug description
By registering a JVM exit callback using JobExecutionShutdownHook, and then using kill to shut down the JVM program during task execution, you will see an exception indicating that the database operation failed.

Environment
openjdk 25.0.2 2026-01-20 LTS
spring batch 6.0.2
spring boot 4.0.3

Steps to reproduce
registering a JVM exit callback, and execute command kill 62989

    val hook = JobExecutionShutdownHook(jobExecution, jobOperator)
    Runtime.getRuntime().addShutdownHook(hook)

I also tried SpringApplication.getShutdownHandlers().add(hook), but it still couldn't save the work to the DB.

The exception log shows that this line threw a database operation exception:

org.springframework.batch.core.launch.support.SimpleJobOperator.stop(SimpleJobOperator.java:349)

full log:

2026-03-01T15:19:45.071+08:00  INFO 34906 --- [sast-web3-consumer] [      Thread-12] o.s.b.c.l.s.JobExecutionShutdownHook     : Received JVM shutdown signal
2026-03-01T15:19:45.071+08:00  INFO 34906 --- [sast-web3-consumer] [      Thread-12] o.s.b.c.l.s.JobExecutionShutdownHook     : Attempting to gracefully stop job execution 228
2026-03-01T15:19:45.072+08:00  INFO 34906 --- [sast-web3-consumer] [      Thread-12] o.s.b.c.l.s.TaskExecutorJobOperator      : Stopping job execution: JobExecution: id=228, version=1, startTime=2026-03-01T15:19:09.782276, endTime=null, lastUpdated=2026-03-01T15:19:09.782424, status=STARTED, exitStatus=exitCode=UNKNOWN;exitDescription=, job=[JobInstance: id=225, version=0, Job=[contractAnalysisJob]], jobParameters=[{JobParameter{name='task', value=ContractScanTask(analyzer=Semgrep, analysisId=1190, filePath=null, objKey=adds, extra=null, contractAddress=x faf88wf aaw a]odwdded 9aw d, chainType=ETH, dataType=SRC), type=class io.github.xhstormr.sast.model.contract.ContractScanTask, identifying=true}}]
2026-03-01T15:19:45.578+08:00  INFO 34906 --- [sast-web3-consumer] [ionShutdownHook] i.g.x.s.d.c.handler.ApplicationLogger    : Application is destroyed!
2026-03-01T15:19:45.589+08:00  WARN 34906 --- [sast-web3-consumer] [      Thread-12] com.zaxxer.hikari.pool.ProxyConnection   : HikariPool-1 - Connection org.postgresql.jdbc.PgConnection@665f7826 marked as broken because of SQLSTATE(08006), ErrorCode(0)

org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:456)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:387)
	at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:998)
	at org.postgresql.jdbc.PgConnection.commit(PgConnection.java:1020)
	at com.zaxxer.hikari.pool.ProxyConnection.commit(ProxyConnection.java:378)
	at com.zaxxer.hikari.pool.HikariProxyConnection.commit(HikariProxyConnection.java)
	at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:84)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:242)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:89)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:553)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:794)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:757)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:687)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:408)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy248.update(Unknown Source)
	at org.springframework.batch.core.launch.support.SimpleJobOperator.stop(SimpleJobOperator.java:349)
	at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.stop(TaskExecutorJobOperator.java:139)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:133)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy266.stop(Unknown Source)
	at org.springframework.batch.core.launch.support.JobExecutionShutdownHook.run(JobExecutionShutdownHook.java:55)
Caused by: java.net.SocketException: Socket closed
	at java.base/sun.nio.ch.NioSocketImpl.endRead(NioSocketImpl.java:242)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:321)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:354)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:798)
	at java.base/java.net.Socket$SocketInputStream.implRead(Socket.java:974)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:964)
	at org.postgresql.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:192)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:159)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:144)
	at org.postgresql.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:76)
	at org.postgresql.core.PGStream.receiveChar(PGStream.java:477)
	at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2314)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:429)
	... 32 common frames omitted

Exception in thread "Thread-12" java.lang.RuntimeException: Unable to gracefully stop job execution 228
	at org.springframework.batch.core.launch.support.JobExecutionShutdownHook.run(JobExecutionShutdownHook.java:62)
Caused by: org.springframework.dao.DataAccessResourceFailureException: Hibernate transaction: Unable to commit against JDBC Connection; An I/O error occurred while sending to the backend.
	at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:152)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:102)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:111)
	at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:144)
	at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.convertHibernateAccessException(HibernateExceptionTranslator.java:131)
	at org.springframework.orm.jpa.hibernate.HibernateExceptionTranslator.translateExceptionIfPossible(HibernateExceptionTranslator.java:105)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:557)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:794)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:757)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:687)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:408)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy248.update(Unknown Source)
	at org.springframework.batch.core.launch.support.SimpleJobOperator.stop(SimpleJobOperator.java:349)
	at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.stop(TaskExecutorJobOperator.java:139)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:133)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy266.stop(Unknown Source)
	at org.springframework.batch.core.launch.support.JobExecutionShutdownHook.run(JobExecutionShutdownHook.java:55)
Caused by: org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:456)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:387)
	at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:998)
	at org.postgresql.jdbc.PgConnection.commit(PgConnection.java:1020)
	at com.zaxxer.hikari.pool.ProxyConnection.commit(ProxyConnection.java:378)
	at com.zaxxer.hikari.pool.HikariProxyConnection.commit(HikariProxyConnection.java)
	at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:84)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:242)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:89)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:553)
	... 22 more
Caused by: java.net.SocketException: Socket closed
	at java.base/sun.nio.ch.NioSocketImpl.endRead(NioSocketImpl.java:242)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:321)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:354)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:798)
	at java.base/java.net.Socket$SocketInputStream.implRead(Socket.java:974)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:964)
	at org.postgresql.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:192)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:159)
	at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:144)
	at org.postgresql.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:76)
	at org.postgresql.core.PGStream.receiveChar(PGStream.java:477)
	at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2314)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:429)
	... 32 more

Expected behavior
Able to save task metadata and gracefully exit, no exception occurs.

Minimal Complete Reproducible example
Please provide a failing test or a minimal complete verifiable example that reproduces the issue.
Bug reports that are reproducible will take priority in resolution over reports that are not reproducible.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions