5.4 KiB
7. The failure that does not look like a failure: rejected partitions
← Previous | README | Next: Restart reruns only the failed partition →
TaskExecutorPartitionHandler submits every partition's worker step to its TaskExecutor up
front, then waits for all of them. What happens if the executor cannot accept all of those
submissions?
Reproducing it
The reject Spring profile swaps in a deliberately undersized ThreadPoolTaskExecutor:
corePoolSize/maxPoolSize both 1, queueCapacity 0, and
java.util.concurrent.ThreadPoolExecutor.AbortPolicy as the rejection handler (Spring's own
default, CallerRunsPolicy, would instead silently run the "extra" partitions on the submitting
thread one at a time — serializing them rather than rejecting them, which hides this problem
instead of surfacing it). Four shard files, pool size one:
$ java -jar target/spring-batch-partitioning-1.0.0.jar --spring.profiles.active=reject \
--partition.shards-dir=./data/shards-small --partition.grid-size=4 --partition.reject.pool-size=1
Only one partition ever logs "Executing step." The job fails with
JobExecutionException: Partition handler returned an unsuccessful step — a message that
never mentions rejection, threads, or pools. Full transcript:
docs/output/06-rejected-partitions-stuck.txt.
What actually happened, found by querying the job repository directly
Nothing in the console log says TaskRejectedException anywhere.
TaskExecutorPartitionHandler.doHandle catches whatever the executor throws on submission and
folds it into the corresponding StepExecution's failure exceptions rather than logging it, so
the only way to see it is to look at the repository's own tables:
$ java -cp h2-2.4.240.jar org.h2.tools.Shell -url jdbc:h2:file:./data/rejecttest -user sa -password "" \
-sql "SELECT STEP_EXECUTION_ID, STEP_NAME, STATUS, EXIT_CODE FROM BATCH_STEP_EXECUTION ORDER BY STEP_EXECUTION_ID;"
STEP_EXECUTION_ID | STEP_NAME | STATUS | EXIT_CODE
1 | ordersManagerStep | FAILED | FAILED
2 | ordersWorkerStep:partition3 | STARTING | EXECUTING
3 | ordersWorkerStep:partition2 | STARTING | EXECUTING
4 | ordersWorkerStep:partition1 | COMPLETED | COMPLETED
5 | ordersWorkerStep:partition0 | STARTING | EXECUTING
The manager step and the JobExecution both reach a clean FAILED. The three rejected worker
StepExecutions do not. They are permanently parked at STARTING/EXECUTING — not failed,
not running, just stuck — because nothing ever calls back into the job repository to close
them out; the executor rejected them before Spring Batch's own bookkeeping around that
StepExecution ever started.
Why this is worse than an ordinary failure
An ordinary failed step is exactly what restart exists for. This is not that — see chapter 8 for what happens when you try to restart a job in this state, and chapter 9 for the Spring Batch 6.0 feature that exists specifically to get out of it.
Going deeper
ThreadPoolTaskExecutor's rejection handler options (AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy) and their very different failure characters:ThreadPoolExecutorJavadoc (rel="nofollow").- Sizing a pool correctly relative to gridSize in the first place: chapter 3.