Skip to content

Commit 3807cb2

Browse files
KMGeonfmbenhassine
authored andcommitted
Fix SimpleStepExecutionSplitter to restart COMPLETED partitions
Remove redundant BatchStatus.COMPLETED check before shouldStart() so that allowStartIfComplete and same JobExecution cases work correctly. Resolves #5238 Signed-off-by: mugeon <pos04167@kakao.com>
1 parent bf87da9 commit 3807cb2

2 files changed

Lines changed: 105 additions & 4 deletions

File tree

spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2025 the original author or authors.
2+
* Copyright 2006-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -143,8 +143,7 @@ public Set<StepExecution> split(StepExecution stepExecution, int gridSize) throw
143143
set.add(currentStepExecution);
144144
}
145145
else { // restart
146-
if (lastStepExecution.getStatus() != BatchStatus.COMPLETED
147-
&& shouldStart(allowStartIfComplete, stepExecution, lastStepExecution)) {
146+
if (shouldStart(allowStartIfComplete, stepExecution, lastStepExecution)) {
148147
StepExecution currentStepExecution = jobRepository.createStepExecution(stepName, jobExecution);
149148
currentStepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
150149
jobRepository.updateExecutionContext(currentStepExecution);

spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitterTests.java

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2025 the original author or authors.
2+
* Copyright 2008-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -201,6 +201,108 @@ void testAbandonedStatus() throws Exception {
201201
}
202202
}
203203

204+
@Test
205+
void testCompletedPartitionsSkippedByDefault() throws Exception {
206+
SimpleStepExecutionSplitter provider = new SimpleStepExecutionSplitter(jobRepository, step.getName(),
207+
new SimplePartitioner());
208+
Set<StepExecution> split = provider.split(stepExecution, 2);
209+
assertEquals(2, split.size());
210+
211+
stepExecution = update(split, stepExecution, BatchStatus.COMPLETED, false);
212+
213+
Set<StepExecution> restartSplit = provider.split(stepExecution, 2);
214+
assertEquals(0, restartSplit.size());
215+
}
216+
217+
@Test
218+
void testCompletedPartitionsRestartWithAllowStartIfComplete() throws Exception {
219+
SimpleStepExecutionSplitter provider = new SimpleStepExecutionSplitter(jobRepository, step.getName(),
220+
new SimplePartitioner());
221+
provider.setAllowStartIfComplete(true);
222+
223+
Set<StepExecution> split = provider.split(stepExecution, 2);
224+
assertEquals(2, split.size());
225+
226+
stepExecution = update(split, stepExecution, BatchStatus.COMPLETED, false);
227+
228+
Set<StepExecution> restartSplit = provider.split(stepExecution, 2);
229+
assertEquals(2, restartSplit.size());
230+
}
231+
232+
@Test
233+
void testCompletedPartitionsRestartInSameJobExecution() throws Exception {
234+
SimpleStepExecutionSplitter provider = new SimpleStepExecutionSplitter(jobRepository, step.getName(),
235+
new SimplePartitioner());
236+
237+
Set<StepExecution> split = provider.split(stepExecution, 2);
238+
assertEquals(2, split.size());
239+
240+
stepExecution = update(split, stepExecution, BatchStatus.COMPLETED, true);
241+
242+
Set<StepExecution> restartSplit = provider.split(stepExecution, 2);
243+
assertEquals(2, restartSplit.size());
244+
}
245+
246+
@Test
247+
void testMixedStatusPartitionsRestartWithAllowStartIfComplete() throws Exception {
248+
SimpleStepExecutionSplitter provider = new SimpleStepExecutionSplitter(jobRepository, step.getName(),
249+
new SimplePartitioner());
250+
provider.setAllowStartIfComplete(true);
251+
252+
Set<StepExecution> split = provider.split(stepExecution, 2);
253+
assertEquals(2, split.size());
254+
255+
StepExecution restartStepExecution = updateMixedStatus(split, stepExecution);
256+
257+
Set<StepExecution> restartSplit = provider.split(restartStepExecution, 2);
258+
assertEquals(2, restartSplit.size());
259+
}
260+
261+
@Test
262+
void testMixedStatusPartitionsRestartWithoutAllowStartIfComplete() throws Exception {
263+
SimpleStepExecutionSplitter provider = new SimpleStepExecutionSplitter(jobRepository, step.getName(),
264+
new SimplePartitioner());
265+
// allowStartIfComplete = false (default)
266+
267+
Set<StepExecution> split = provider.split(stepExecution, 2);
268+
assertEquals(2, split.size());
269+
270+
StepExecution restartStepExecution = updateMixedStatus(split, stepExecution);
271+
272+
// Only FAILED partition should restart, COMPLETED should be skipped
273+
Set<StepExecution> restartSplit = provider.split(restartStepExecution, 2);
274+
assertEquals(1, restartSplit.size());
275+
}
276+
277+
private StepExecution updateMixedStatus(Set<StepExecution> split, StepExecution stepExecution) throws Exception {
278+
boolean first = true;
279+
for (StepExecution child : split) {
280+
child.setEndTime(LocalDateTime.now());
281+
child.setStatus(first ? BatchStatus.COMPLETED : BatchStatus.FAILED);
282+
jobRepository.update(child);
283+
first = false;
284+
}
285+
286+
stepExecution.setEndTime(LocalDateTime.now());
287+
stepExecution.setStatus(BatchStatus.FAILED);
288+
jobRepository.update(stepExecution);
289+
290+
JobExecution jobExecution = stepExecution.getJobExecution();
291+
jobExecution.setStatus(BatchStatus.FAILED);
292+
jobExecution.setEndTime(LocalDateTime.now());
293+
jobRepository.update(jobExecution);
294+
295+
JobInstance jobInstance = jobExecution.getJobInstance();
296+
JobExecution newJobExecution = jobRepository.createJobExecution(jobInstance, jobExecution.getJobParameters(),
297+
jobExecution.getExecutionContext());
298+
StepExecution newStepExecution = jobRepository.createStepExecution(stepExecution.getStepName(),
299+
newJobExecution);
300+
newStepExecution.setExecutionContext(stepExecution.getExecutionContext());
301+
jobRepository.updateExecutionContext(newStepExecution);
302+
303+
return newStepExecution;
304+
}
305+
204306
private StepExecution update(Set<StepExecution> split, StepExecution stepExecution, BatchStatus status)
205307
throws Exception {
206308
return update(split, stepExecution, status, true);

0 commit comments

Comments
 (0)