Skip to content

Commit 01aa6c4

Browse files
committed
Improve failure-safety of Cursor.
Any failures during opening the cursor or scanning now close the cursor and release associated resources. Closes #2414
1 parent 94f48ff commit 01aa6c4

File tree

2 files changed

+27
-3
lines changed

2 files changed

+27
-3
lines changed

src/main/java/org/springframework/data/redis/core/ScanCursor.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121

2222
import org.springframework.dao.InvalidDataAccessApiUsageException;
2323
import org.springframework.lang.Nullable;
24-
import org.springframework.util.Assert;
2524
import org.springframework.util.CollectionUtils;
2625

2726
/**
2827
* Redis client agnostic {@link Cursor} implementation continuously loading additional results from Redis server until
2928
* reaching its starting point {@code zero}. <br />
3029
* <strong>Note:</strong> Please note that the {@link ScanCursor} has to be initialized ({@link #open()} prior to usage.
30+
* Any failures during scanning will {@link #close() close} the cursor and release any associated resources such as
31+
* connections.
3132
*
3233
* @author Christoph Strobl
3334
* @author Thomas Darimont
@@ -85,8 +86,16 @@ public ScanCursor(long cursorId, @Nullable ScanOptions options) {
8586

8687
private void scan(long cursorId) {
8788

88-
ScanIteration<T> result = doScan(cursorId, this.scanOptions);
89-
processScanResult(result);
89+
try {
90+
processScanResult(doScan(cursorId, this.scanOptions));
91+
} catch (RuntimeException e) {
92+
try {
93+
close();
94+
} catch (RuntimeException nested) {
95+
e.addSuppressed(nested);
96+
}
97+
throw e;
98+
}
9099
}
91100

92101
/**

src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,21 @@ void streamingCursorShouldForwardClose() {
233233
assertThat(cursor.isClosed()).isTrue();
234234
}
235235

236+
@Test // GH-2414
237+
void shouldCloseCursorOnScanFailure() {
238+
239+
KeyBoundCursor<String> cursor = new KeyBoundCursor<String>("foo".getBytes(), 0, null) {
240+
@Override
241+
protected ScanIteration<String> doScan(byte[] key, long cursorId, ScanOptions options) {
242+
throw new IllegalStateException();
243+
}
244+
};
245+
246+
assertThatIllegalStateException().isThrownBy(cursor::open);
247+
assertThat(cursor.isOpen()).isFalse();
248+
assertThat(cursor.isClosed()).isTrue();
249+
}
250+
236251
private CapturingCursorDummy initCursor(Queue<ScanIteration<String>> values) {
237252
CapturingCursorDummy cursor = new CapturingCursorDummy(values);
238253
cursor.open();

0 commit comments

Comments
 (0)