Reputation: 10989
I have successfully halted a job from a before step with this method.
public class FirstListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
boolean shouldRun = shouldJobRun();
if (!shouldRun) {
// listeners will still work, but any other step logic (reader, processor, writer) will not happen
stepExecution.setTerminateOnly();
stepExecution.setExitStatus(new ExitStatus("STOPPED", "Job should not be run right now."));
LOGGER.warn(duplicate_message);
}
}
Code is a trimmed for brevity/clarity, but that's the gist. Calling stepExecution.setTerminateOnly()
and stepExecution.setExitStatus()
is enough to get Spring Batch to halt the job and not execute any subsequent steps. The status is logged correctly in the BATCH_JOB_EXECUTION
tables
EXIT_MESSAGE STATUS
org.springframework.batch.core.JobInterruptedException STOPPED
However, the same approach in the afterStep
method gets rolled over and goes unrecognized. The status gets recorded as COMPLETED and all subsequent steps go along their merry way (eventually failing in their own horrible ways because the failure detection in the afterStep is detecting faults so that they don't have to).
public class SecondListener implements StepExecutionListener {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (stepExecution.getExitStatus().getExitCode().equals(ExitStatus.STOPPED.getExitCode())) {
return stepExecution.getExitStatus();
}
if (everythingIsOkay()) {
return stepExecution.getExitStatus();
}
String failureMessage = "Something bad happened.";
LOGGER.error(failureMessage);
ExitStatus exitStatus = new ExitStatus(ExitStatus.FAILED.getExitCode(), failureMessage);
stepExecution.setExitStatus(exitStatus);
stepExecution.setTerminateOnly();
return exitStatus;
}
Here's the only wrinkle I can think of: both listeners are on the same step using a composite listener.
@Bean(name = "org.springframework.batch.core.StepExecutionListener-compositeListener")
@StepScope
public StepExecutionListener compositeListener() {
CompositeStepExecutionListener listener = new CompositeStepExecutionListener();
List<StepExecutionListener> listeners = Lists.newArrayList(secondListener());
if (jobShouldHaveFirstListener()) {
listeners.add(0, firstListener()); // prepend; delegates are called in order
}
listener.setListeners(listeners.toArray());
return listener;
}
public Step firstStep() {
return stepBuilderFactory.get("firstStep")
.listener(compositeListener)
// Small batch size for frequency capping, which happens in the writer, before analytics get written
.<Recipient, Recipient>chunk(500)
.reader(rawRecipientInputFileItemReader)
.processor(recipientItemProcessor)
.writer(recipientWriter)
.throttleLimit(2)
.build();
}
@Bean(name = "org.springframework.batch.core.Job-delivery")
public Job deliveryJob() {
return jobs.get("delivery")
.preventRestart()
.start(firstStep)
.next(deliveryStep)
.next(handleSentStep)
.listener(failedCleanupListener)
.build();
}
Is there anything else I can do to get this execution to halt correctly?
Upvotes: 2
Views: 9352
Reputation: 10989
After much experimentation, I found that the following flow definition will allow the job to be halted correctly from the StepListener without executing steps after the first step.
return jobs.get("delivery")
.preventRestart()
.listener(failedCleanupListener)
.flow(firstStep)
.next(deliveryStep)
.next(handleSentStep)
.end()
.build();
The key difference is changing the start()
to flow()
and adding the FlowBuilder.end()
method call to the end of the builder chain. The SimpleJobBuilder
class that gets returned from the .start
method does not expose a similar end()
method.
I have no idea why this makes such a difference in the internals of the job execution, and I would gladly bounty some points to someone who can illuminate what the actual difference is and why it step execution status codes are ignored using the SimpleJobBuilder. But I found something that works and that's what matters for now.
Upvotes: 4