Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terminates the job if a flag is set on afterStep #4455

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

umbum
Copy link

@umbum umbum commented Sep 23, 2023

As-is : Even if the terminateOnly flag is set on afterStep, It is ignored and the next step is executed. There is no way to terminate a job gracefully without using the on(ExitStatus) method when building the job.

To-be : If the terminateOnly flag is set, the job terminates gracefully.

It looks similar to #1213 but It's different.
If developers want to explicitly terminate a job, they should be able to do so.

@fmbenhassine
Copy link
Contributor

Thank you for opening this PR. I am not sure "isTerminateOnly" was designed to be used in a step execution listener. My thinking is that in the afterStep, the step has already ended successfully or with a failure, so it is too late to set that flag at that point. The issue more is about stopping the surrounding job (ass mentioned by the javadocs). And this is where things become tricky, which I admit can be confusing.

There is a key difference between a SimpleJob and a FlowJob: Unlike FlowJob, SimpleJob is designed only for sequential executions of steps until the last step succeeds or one of the steps fail. So this implementation was not designed to be stopped programmatically.

Here is a quick example:

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.support.JdbcTransactionManager;

import javax.sql.DataSource;

@Configuration
@EnableBatchProcessing
public class HelloWorldJobConfiguration {

	public static void main(String[] args) throws Exception {
		ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldJobConfiguration.class);
		JobLauncher jobLauncher = context.getBean(JobLauncher.class);
		Job job = context.getBean(Job.class);
		jobLauncher.run(job, new JobParameters());
	}

	@Bean
	public Step step1(JobRepository jobRepository, JdbcTransactionManager transactionManager) {
		return new StepBuilder("step1", jobRepository)
				.tasklet((contribution, chunkContext) -> {
			System.out.println("Hello");
			return RepeatStatus.FINISHED;
		}, transactionManager)
				.listener(new StepExecutionListener() {
					@Override
					public ExitStatus afterStep(StepExecution stepExecution) {
						stepExecution.setTerminateOnly();
						return stepExecution.getExitStatus();
					}
				})
				.build();
	}

	@Bean
	public Step step2(JobRepository jobRepository, JdbcTransactionManager transactionManager) {
		return new StepBuilder("step2", jobRepository).tasklet((contribution, chunkContext) -> {
			System.out.println("world!");
			return RepeatStatus.FINISHED;
		}, transactionManager).build();
	}

	@Bean
	public Job job(JobRepository jobRepository, Step step1, Step step2) {
		return new JobBuilder("job", jobRepository)
				.start(step1)
				.next(step2)
				.build();
	}

	// infra beans

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
				.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
				.addScript("/org/springframework/batch/samples/common/business-schema-hsqldb.sql")
				.generateUniqueName(true)
				.build();
	}

	@Bean
	public JdbcTransactionManager transactionManager(DataSource dataSource) {
		return new JdbcTransactionManager(dataSource);
	}

}

In this case, step2 runs even if step1 was flagged as terminateOnly. However, if I only change the job type to FlowJob, things behave as expected:

@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2) {
   return new JobBuilder("job", jobRepository)
				.flow(step1)
				.next(step2)
				.build()
				.build();
}

Do you see the difference?

If developers want to explicitly terminate a job, they should be able to do so.

BTW, I find that stopping a job with a listener is implicit rather than explicit. The more explicit way would be to create a clear execution flow through the Java DSL or the XML namespace.

@fmbenhassine fmbenhassine added status: waiting-for-reporter Issues for which we are waiting for feedback from the reporter and removed pr-for: feature labels Feb 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core status: waiting-for-reporter Issues for which we are waiting for feedback from the reporter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants