Skip to content

Commit 17cfc2f

Browse files
committed
idempotent close cursor (DE-727, #528)
1 parent a0175ac commit 17cfc2f

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

Diff for: core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,14 @@ public CursorEntity<T> next(final String id, final String nextBatchId) {
213213

214214
@Override
215215
public void close(final String id) {
216-
executorSync().execute(queryCloseRequest(id, options), Void.class, hostHandle);
216+
try {
217+
executorSync().execute(queryCloseRequest(id, options), Void.class, hostHandle);
218+
} catch (final ArangoDBException e) {
219+
// ignore errors Response: 404, Error: 1600 - cursor not found
220+
if (!matches(e, 404, 1600)) {
221+
throw e;
222+
}
223+
}
217224
}
218225
};
219226

Diff for: core/src/main/java/com/arangodb/internal/cursor/ArangoCursorAsyncImpl.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.arangodb.internal.cursor;
22

33
import com.arangodb.ArangoCursorAsync;
4+
import com.arangodb.ArangoDBException;
45
import com.arangodb.entity.CursorEntity;
56
import com.arangodb.internal.ArangoDatabaseAsyncImpl;
67
import com.arangodb.internal.InternalArangoCursor;
78
import com.arangodb.internal.net.HostHandle;
89

910
import java.util.NoSuchElementException;
1011
import java.util.concurrent.CompletableFuture;
12+
import java.util.concurrent.CompletionException;
13+
14+
import static com.arangodb.internal.ArangoErrors.matches;
1115

1216
public class ArangoCursorAsyncImpl<T> extends InternalArangoCursor<T> implements ArangoCursorAsync<T> {
1317

@@ -41,7 +45,19 @@ public CompletableFuture<ArangoCursorAsync<T>> nextBatch() {
4145
@Override
4246
public CompletableFuture<Void> close() {
4347
if (getId() != null && (allowRetry() || Boolean.TRUE.equals(hasMore()))) {
44-
return executorAsync().execute(this::queryCloseRequest, Void.class, hostHandle);
48+
return executorAsync().execute(this::queryCloseRequest, Void.class, hostHandle)
49+
.exceptionally(err -> {
50+
Throwable e = err instanceof CompletionException ? err.getCause() : err;
51+
if (e instanceof ArangoDBException) {
52+
ArangoDBException aEx = (ArangoDBException) e;
53+
// ignore errors Response: 404, Error: 1600 - cursor not found
54+
if (matches(aEx, 404, 1600)) {
55+
return null;
56+
}
57+
}
58+
throw ArangoDBException.of(e);
59+
})
60+
.thenApply(__ -> null);
4561
} else {
4662
return CompletableFuture.completedFuture(null);
4763
}

Diff for: driver/src/test/java/com/arangodb/ArangoDatabaseAsyncTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,23 @@ void queryClose(ArangoDBAsync arangoDB) throws ExecutionException, InterruptedEx
911911
assertThat(ex.getMessage()).contains("cursor not found");
912912
}
913913

914+
@ParameterizedTest(name = "{index}")
915+
@MethodSource("asyncArangos")
916+
void queryCloseShouldBeIdempotent(ArangoDBAsync arangoDB) throws ExecutionException, InterruptedException {
917+
ArangoCursorAsync<Integer> cursor = arangoDB.db().query("for i in 1..2 return i", Integer.class,
918+
new AqlQueryOptions().batchSize(1)).get();
919+
cursor.close().get();
920+
cursor.close().get();
921+
}
922+
923+
@ParameterizedTest(name = "{index}")
924+
@MethodSource("asyncArangos")
925+
void queryCloseOnCursorWithoutId(ArangoDBAsync arangoDB) throws ExecutionException, InterruptedException {
926+
ArangoCursorAsync<Integer> cursor = arangoDB.db().query("return 1", Integer.class).get();
927+
cursor.close().get();
928+
cursor.close().get();
929+
}
930+
914931
@ParameterizedTest(name = "{index}")
915932
@MethodSource("asyncDbs")
916933
void queryNoResults(ArangoDatabaseAsync db) throws ExecutionException, InterruptedException {

Diff for: driver/src/test/java/com/arangodb/ArangoDatabaseTest.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,23 @@ void queryClose(ArangoDB arangoDB) throws IOException {
986986
assertThat(count).hasValue(1);
987987
}
988988

989+
@ParameterizedTest(name = "{index}")
990+
@MethodSource("arangos")
991+
void queryCloseShouldBeIdempotent(ArangoDB arangoDB) throws IOException {
992+
ArangoCursor<Integer> cursor = arangoDB.db().query("for i in 1..2 return i", Integer.class,
993+
new AqlQueryOptions().batchSize(1));
994+
cursor.close();
995+
cursor.close();
996+
}
997+
998+
@ParameterizedTest(name = "{index}")
999+
@MethodSource("arangos")
1000+
void queryCloseOnCursorWithoutId(ArangoDB arangoDB) throws IOException {
1001+
ArangoCursor<Integer> cursor = arangoDB.db().query("return 1", Integer.class);
1002+
cursor.close();
1003+
cursor.close();
1004+
}
1005+
9891006
@ParameterizedTest(name = "{index}")
9901007
@MethodSource("dbs")
9911008
void queryNoResults(ArangoDatabase db) throws IOException {
@@ -1153,7 +1170,7 @@ void getCurrentlyRunningQueries(ArangoDatabase db) throws InterruptedException {
11531170
assertThat(queryEntity.getBindVars()).isEmpty();
11541171
assertThat(queryEntity.getStarted()).isInThePast();
11551172
assertThat(queryEntity.getRunTime()).isPositive();
1156-
if(isAtLeastVersion(3,11)){
1173+
if (isAtLeastVersion(3, 11)) {
11571174
assertThat(queryEntity.getPeakMemoryUsage()).isNotNull();
11581175
}
11591176
assertThat(queryEntity.getState()).isEqualTo(QueryExecutionState.EXECUTING);
@@ -1213,7 +1230,7 @@ void getAndClearSlowQueries(ArangoDatabase db) {
12131230
assertThat(queryEntity.getBindVars()).isEmpty();
12141231
assertThat(queryEntity.getStarted()).isInThePast();
12151232
assertThat(queryEntity.getRunTime()).isPositive();
1216-
if(isAtLeastVersion(3,11)){
1233+
if (isAtLeastVersion(3, 11)) {
12171234
assertThat(queryEntity.getPeakMemoryUsage()).isNotNull();
12181235
}
12191236
assertThat(queryEntity.getState()).isEqualTo(QueryExecutionState.FINISHED);

0 commit comments

Comments
 (0)