Skip to content

Fault-tolerant step: retry(Class) traverses exception causes, skip(Class) does not #5127

@kzander91

Description

@kzander91

Bug description
skip(Class) and retry(Class) behave inconsistently in that skip(SkippableException.class) does not cause throw new RuntimeException(new SkippableException()) to be skipped, but retry(SkippableException.class) does inspect the cause and causes the same expression to be retried.

The expectation would be that exception matching in both RetryPolicy and SkipPolicy behave consistently (ideally aligned with RetryPolicy, in that causes are traversed).

The underlying reason for that is the switch to the new retry support from Framework, which always traverses causes (as it happens, a feature that I have requested myself 🙃): spring-projects/spring-framework#35583

Environment
Spring Batch 6.0.0

Steps to reproduce

  1. Configure fault-tolerant step that skips and retries the same exception type.
  2. Throw another exception with a skippable exception as the cause.

Expected behavior
The exception is both retried and then skipped (after retry exhaustion).

Minimal Complete Reproducible example
Reproducer: demo14.zip
Run with ./mvnw test

The project has a step like this:

@Bean
Step step() {
    return new StepBuilder("step", jobRepository)
            .chunk(5)
            .transactionManager(transactionManager)
            .faultTolerant()
            .retry(SkippableException.class)
            .retryLimit(1)
            .skip(SkippableException.class)
            .skipLimit(1)
            .reader(new ListItemReader<>(List.of("item")))
            .writer(_ -> {
                throw new RuntimeException(new SkippableException());
            })
            .build();
}

static class SkippableException extends RuntimeException {

}

A test then launches and expects successful completion and a skip count of 1.


With Spring Batch 5, I was using a LimitCheckingItemSkipPolicy with a BinaryExceptionClassifier (from spring-retry) that was configured to traverse causes. However this is now deprecated and no equivalent replacement exists (apart from fully reimplementing my own SkipPolicy.


While debugging this, I found another, likely related issue:

@Override
public boolean shouldSkip(Throwable t, long skipCount) throws SkipLimitExceededException {
if (skipCount < 0) {
return !isSkippable(t);
}

Note how the logic is inverted in the case of skipCount < 0 (which is the case when the SkipPolicy is queried directly after a retryable exception happend). In that case, non-skippable exceptions are classified as skippable, due to !isSkippable(t).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions