Description
Bug description
I am developing an application that executes jobs by executing the TaskExecutorJobLauncher#run(job, jobParameters) method within a RESTful service.
The first time a job is executed, it works as expected.
However, when the job is executed the second time or later,
it is executed with the StepContribution instance and ChunkContext instance from the first execution passed to the tasklet (e.g. the jobInstanceId from the first execution is referenced from the second time or later).
The jobInstanceId value of JobListener and StepListener changes each time a job is executed.
After my investigation, I found that the cause was AbstractMethodInvokingDelegator.java and MethodInvokingTaskletAdapter.java.
https://github.com/spring-projects/spring-batch/blob/v5.2.2/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapter.java
There is a problem with the following code in MethodInvokingTaskletAdapter.java.
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
if (getArguments() == null) {
setArguments(new Object[] { contribution, chunkContext });
}
contribution.setExitStatus(mapResult(invokeDelegateMethod()));
return RepeatStatus.FINISHED;
}
getArguments() returns null for the first execution of a job, so getArguments() == null become true at the time.
When stepContribution and chunkContext are passed to MethodInvokingTaskletAdapter#execute,
setArguments is called in the first execution of a job, but MethodInvokingTaskletAdapter.arguments is not cleared to null after invokeDelegateMethod() is completed.
From the second job execution onwards, getArguments() no longer returns null, and the a stepContribution instance and a chunkContext instance of the first execution continues to be passed as two arguments to Tacklet#execute implementation.
Are there ways to resolve this issue? Please advise me.
By the way, it also seems that there is a problem with the current execute(StepContribution, ChunkContext) in MethodInvokingTaskletAdapter.java for me.
Shouldn't null be passed to the setArguments method immediately after executing invokeDelegateMethod() as follows to initialize the arguments field of MethodInvokingTaskletAdapter?
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
if (getArguments() == null) {
setArguments(new Object[] { contribution, chunkContext });
}
Object obj = null;
try {
obj = invokeDelegateMethod();
} finally {
setArguments(null);
}
contribution.setExitStatus(mapResult(obj));
return RepeatStatus.FINISHED;
}
As it stands now, there seems to be an issue where the first job's MethodInvokingTaskletAdapter.arguments continues to reference the instance, preventing it from being released by GC.
Because we need to proceed with development, as a workaround, we have temporarily changed the implementation to one where the StepExecution is temporarily added to a static Cache using the job name and step name as keys in StepListener#beforeStep,
and information equivalent to StepContext is obtained from the StepExecution instance temporarily pushed to Cache,
rather than using the information of chunkContext.getStepContext() passed to the execute method.
(The StepExecution is deleted from the static Cache immediately after the tasklet is executed.)
Environment
Please provide as many details as possible: Spring Batch version, Java version, which database you use if any, etc
Spring Framework version 6.2.5
Spring Batch version 5.2.2
Java version: 17.0.11 (Red_Hat-17.0.11.0+9-1)
Java Distribution: Red Hat build of OpenJDK
JEE AP Server: JBoss EAP 8.0 Update 4.1 (WildFly Core 21.0.11.Final-redhat-00001)
DataBase: Oracle DB express:21.3.0-xe
OS: Windows 11
Steps to reproduce
Steps to reproduce the issue.
Run a job once, then run the same job while changing the timestamp argument.
Expected behavior
A clear and concise description of what you expected to happen.
I would like Tacklet#execute to be passed stepContribution and chunkContext as arguments when each job is executed, just like JobListener and StepListener.
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.
I cannot reveal it at current time as it would violate our confidentiality.
However, if it is absolutely necessary, I will provide a sample app without any confidential information.
I'm sorry, it will take some long time.