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();