Skip to content

Commit 58a4134

Browse files
hpoettkerfmbenhassine
authored andcommitted
Explicit handling of nested splits in SplitState
Resolves #3857
1 parent 5e849c6 commit 58a4134

File tree

3 files changed

+51
-13
lines changed

3 files changed

+51
-13
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java

+11-11
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ else if (input instanceof Flow) {
301301
return result;
302302
}
303303

304-
private SplitState createState(Collection<Flow> flows, TaskExecutor executor) {
304+
private SplitState createState(Collection<Flow> flows, TaskExecutor executor, SplitState parentSplit) {
305305
if (!states.containsKey(flows)) {
306-
states.put(flows, new SplitState(flows, prefix + "split" + (splitCounter++)));
306+
states.put(flows, new SplitState(flows, prefix + "split" + (splitCounter++), parentSplit));
307307
}
308308
SplitState result = (SplitState) states.get(flows);
309309
if (executor != null) {
@@ -627,23 +627,23 @@ public SplitBuilder(FlowBuilder<Q> parent, TaskExecutor executor) {
627627
public FlowBuilder<Q> add(Flow... flows) {
628628
Collection<Flow> list = new ArrayList<>(Arrays.asList(flows));
629629
String name = "split" + (parent.splitCounter++);
630-
int counter = 0;
631630
State one = parent.currentState;
632-
Flow flow = null;
631+
632+
if (one instanceof SplitState) {
633+
parent.currentState = parent.createState(list, executor, (SplitState) one);
634+
return parent;
635+
}
636+
633637
if (!(one == null || one instanceof FlowState)) {
634-
FlowBuilder<Flow> stateBuilder = new FlowBuilder<>(name + "_" + (counter++));
638+
FlowBuilder<Flow> stateBuilder = new FlowBuilder<>(name + "_0");
635639
stateBuilder.currentState = one;
636-
flow = stateBuilder.build();
640+
list.add(stateBuilder.build());
637641
}
638642
else if (one instanceof FlowState && parent.states.size() == 1) {
639643
list.add(((FlowState) one).getFlows().iterator().next());
640644
}
641645

642-
if (flow != null) {
643-
list.add(flow);
644-
}
645-
State next = parent.createState(list, executor);
646-
parent.currentState = next;
646+
parent.currentState = parent.createState(list, executor, null);
647647
return parent;
648648
}
649649

spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package org.springframework.batch.core.job.flow.support.state;
1717

1818
import java.util.ArrayList;
19+
import java.util.Arrays;
1920
import java.util.Collection;
21+
import java.util.Collections;
2022
import java.util.concurrent.ExecutionException;
2123
import java.util.concurrent.Future;
2224
import java.util.concurrent.FutureTask;
@@ -31,6 +33,7 @@
3133
import org.springframework.core.task.SyncTaskExecutor;
3234
import org.springframework.core.task.TaskExecutor;
3335
import org.springframework.core.task.TaskRejectedException;
36+
import org.springframework.lang.Nullable;
3437

3538
/**
3639
* A {@link State} implementation that splits a {@link Flow} into multiple parallel
@@ -44,6 +47,8 @@ public class SplitState extends AbstractState implements FlowHolder {
4447

4548
private final Collection<Flow> flows;
4649

50+
private final SplitState parentSplit;
51+
4752
private TaskExecutor taskExecutor = new SyncTaskExecutor();
4853

4954
private final FlowExecutionAggregator aggregator = new MaxValueFlowExecutionAggregator();
@@ -53,8 +58,18 @@ public class SplitState extends AbstractState implements FlowHolder {
5358
* @param name the name of the state.
5459
*/
5560
public SplitState(Collection<Flow> flows, String name) {
61+
this(flows, name, null);
62+
}
63+
64+
/**
65+
* @param flows collection of {@link Flow} instances.
66+
* @param name the name of the state.
67+
* @param parentSplit the parent {@link SplitState}.
68+
*/
69+
public SplitState(Collection<Flow> flows, String name, @Nullable SplitState parentSplit) {
5670
super(name);
5771
this.flows = flows;
72+
this.parentSplit = parentSplit;
5873
}
5974

6075
/**
@@ -101,6 +116,8 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception
101116

102117
}
103118

119+
FlowExecutionStatus parentSplitStatus = parentSplit == null ? null : parentSplit.handle(executor);
120+
104121
Collection<FlowExecution> results = new ArrayList<>();
105122

106123
// Could use a CompletionService here?
@@ -120,7 +137,11 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception
120137
}
121138
}
122139

123-
return doAggregation(results, executor);
140+
FlowExecutionStatus flowExecutionStatus = doAggregation(results, executor);
141+
if (parentSplitStatus != null) {
142+
return Collections.max(Arrays.asList(flowExecutionStatus, parentSplitStatus));
143+
}
144+
return flowExecutionStatus;
124145
}
125146

126147
protected FlowExecutionStatus doAggregation(Collection<FlowExecution> results, FlowExecutor executor) {

spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 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.
@@ -38,6 +38,7 @@
3838
import org.springframework.batch.core.job.flow.Flow;
3939
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
4040
import org.springframework.batch.core.job.flow.JobExecutionDecider;
41+
import org.springframework.batch.core.job.flow.support.SimpleFlow;
4142
import org.springframework.batch.core.launch.JobLauncher;
4243
import org.springframework.batch.core.repository.JobRepository;
4344
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
@@ -171,6 +172,22 @@ void testBuildSplit() {
171172
assertEquals(2, execution.getStepExecutions().size());
172173
}
173174

175+
@Test
176+
void testNestedSplitsWithSingleThread() {
177+
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
178+
taskExecutor.setConcurrencyLimit(1);
179+
180+
FlowBuilder<SimpleFlow> flowBuilder = new FlowBuilder<>("flow");
181+
FlowBuilder.SplitBuilder<SimpleFlow> splitBuilder = flowBuilder.split(taskExecutor);
182+
splitBuilder.add(new FlowBuilder<Flow>("subflow1").from(step1).end());
183+
splitBuilder.add(new FlowBuilder<Flow>("subflow2").from(step2).end());
184+
Job job = new JobBuilder("job").repository(jobRepository).start(flowBuilder.build()).end().build();
185+
job.execute(execution);
186+
187+
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
188+
assertEquals(2, execution.getStepExecutions().size());
189+
}
190+
174191
@Test
175192
void testBuildSplitUsingStartAndAdd_BATCH_2346() {
176193
Flow subflow1 = new FlowBuilder<Flow>("subflow1").from(step2).end();

0 commit comments

Comments
 (0)