Skip to content

Commit 4df153a

Browse files
authored
Fix QueryBatchCursor.ResourceManager.close (#1440)
This is a backport of #1440 to 4.11.x JAVA-5516
1 parent 86a847b commit 4df153a

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

driver-core/src/main/com/mongodb/internal/operation/QueryBatchCursor.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.mongodb.annotations.ThreadSafe;
2727
import com.mongodb.connection.ConnectionDescription;
2828
import com.mongodb.connection.ServerType;
29+
import com.mongodb.internal.VisibleForTesting;
2930
import com.mongodb.internal.binding.ConnectionSource;
3031
import com.mongodb.internal.connection.Connection;
3132
import com.mongodb.internal.connection.QueryResult;
@@ -58,6 +59,7 @@
5859
import static com.mongodb.assertions.Assertions.isTrueArgument;
5960
import static com.mongodb.assertions.Assertions.notNull;
6061
import static com.mongodb.internal.Locks.withLock;
62+
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
6163
import static com.mongodb.internal.operation.CursorHelper.getNumberToReturn;
6264
import static com.mongodb.internal.operation.DocumentHelper.putIfNotNull;
6365
import static com.mongodb.internal.operation.SyncOperationHelper.getMoreCursorDocumentToQueryResult;
@@ -359,7 +361,8 @@ private BsonDocument getPostBatchResumeTokenFromResponse(final BsonDocument resu
359361
* others are not and rely on the total order mentioned above.
360362
*/
361363
@ThreadSafe
362-
private final class ResourceManager {
364+
@VisibleForTesting(otherwise = PRIVATE)
365+
final class ResourceManager {
363366
private final Lock lock;
364367
private volatile State state;
365368
@Nullable
@@ -416,7 +419,8 @@ <R> R execute(final String exceptionMessageIfClosed, final Supplier<R> operation
416419
* If {@linkplain #operable() closed}, then returns false, otherwise completes abruptly.
417420
* @throws IllegalStateException Iff another operation is in progress.
418421
*/
419-
private boolean tryStartOperation() throws IllegalStateException {
422+
@VisibleForTesting(otherwise = PRIVATE)
423+
boolean tryStartOperation() throws IllegalStateException {
420424
return withLock(lock, () -> {
421425
State localState = state;
422426
if (!localState.operable()) {
@@ -435,7 +439,8 @@ private boolean tryStartOperation() throws IllegalStateException {
435439
/**
436440
* Thread-safe.
437441
*/
438-
private void endOperation() {
442+
@VisibleForTesting(otherwise = PRIVATE)
443+
void endOperation() {
439444
boolean doClose = withLock(lock, () -> {
440445
State localState = state;
441446
if (localState == State.OPERATION_IN_PROGRESS) {
@@ -459,7 +464,7 @@ private void endOperation() {
459464
void close() {
460465
boolean doClose = withLock(lock, () -> {
461466
State localState = state;
462-
if (localState == State.OPERATION_IN_PROGRESS) {
467+
if (localState.inProgress()) {
463468
state = State.CLOSE_PENDING;
464469
return false;
465470
} else if (localState != State.CLOSED) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.internal.operation;
17+
18+
import com.mongodb.ServerAddress;
19+
import com.mongodb.ServerCursor;
20+
import com.mongodb.internal.binding.ConnectionSource;
21+
import com.mongodb.internal.connection.QueryResult;
22+
import org.bson.BsonDocument;
23+
import org.bson.codecs.BsonDocumentCodec;
24+
import org.junit.jupiter.api.Test;
25+
26+
import static java.util.Collections.emptyList;
27+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
28+
import static org.mockito.Mockito.mock;
29+
import static org.mockito.Mockito.when;
30+
31+
final class QueryBatchCursorResourceManagerTest {
32+
@Test
33+
void doubleCloseExecutedConcurrentlyWithOperationBeingInProgressShouldNotFail() {
34+
ConnectionSource connectionSourceMock = mock(ConnectionSource.class);
35+
when(connectionSourceMock.retain()).thenReturn(connectionSourceMock);
36+
when(connectionSourceMock.release()).thenReturn(1);
37+
ServerAddress serverAddress = new ServerAddress();
38+
try (QueryBatchCursor<BsonDocument> cursor = new QueryBatchCursor<>(
39+
new QueryResult<>(null, emptyList(), 0, serverAddress),
40+
1, 1, new BsonDocumentCodec())) {
41+
QueryBatchCursor<?>.ResourceManager cursorResourceManager = cursor.new ResourceManager(
42+
connectionSourceMock, null, new ServerCursor(1, serverAddress));
43+
cursorResourceManager.tryStartOperation();
44+
try {
45+
assertDoesNotThrow(() -> {
46+
cursorResourceManager.close();
47+
cursorResourceManager.close();
48+
cursorResourceManager.setServerCursor(null);
49+
});
50+
} finally {
51+
cursorResourceManager.endOperation();
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)