diff --git a/src/main/java/org/springframework/data/redis/core/ScanCursor.java b/src/main/java/org/springframework/data/redis/core/ScanCursor.java index dc2a8fe2e..c319d0281 100644 --- a/src/main/java/org/springframework/data/redis/core/ScanCursor.java +++ b/src/main/java/org/springframework/data/redis/core/ScanCursor.java @@ -21,13 +21,14 @@ import java.util.NoSuchElementException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * Redis client agnostic {@link Cursor} implementation continuously loading additional results from Redis server until * reaching its starting point {@code zero}.
* Note: Please note that the {@link ScanCursor} has to be initialized ({@link #open()} prior to usage. + * Any failures during scanning will {@link #close() close} the cursor and release any associated resources such as + * connections. * * @author Christoph Strobl * @author Thomas Darimont @@ -85,8 +86,16 @@ public abstract class ScanCursor implements Cursor { private void scan(long cursorId) { - ScanIteration result = doScan(cursorId, this.scanOptions); - processScanResult(result); + try { + processScanResult(doScan(cursorId, this.scanOptions)); + } catch (RuntimeException e) { + try { + close(); + } catch (RuntimeException nested) { + e.addSuppressed(nested); + } + throw e; + } } /** diff --git a/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java b/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java index 0b67aaa1f..b59239436 100644 --- a/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java @@ -233,6 +233,21 @@ class ScanCursorUnitTests { assertThat(cursor.isClosed()).isTrue(); } + @Test // GH-2414 + void shouldCloseCursorOnScanFailure() { + + KeyBoundCursor cursor = new KeyBoundCursor("foo".getBytes(), 0, null) { + @Override + protected ScanIteration doScan(byte[] key, long cursorId, ScanOptions options) { + throw new IllegalStateException(); + } + }; + + assertThatIllegalStateException().isThrownBy(cursor::open); + assertThat(cursor.isOpen()).isFalse(); + assertThat(cursor.isClosed()).isTrue(); + } + private CapturingCursorDummy initCursor(Queue> values) { CapturingCursorDummy cursor = new CapturingCursorDummy(values); cursor.open();