17
17
package com .mongodb .internal .operation ;
18
18
19
19
import com .mongodb .MongoCommandException ;
20
+ import com .mongodb .MongoException ;
20
21
import com .mongodb .MongoNamespace ;
22
+ import com .mongodb .MongoSocketException ;
21
23
import com .mongodb .ReadPreference ;
22
24
import com .mongodb .ServerAddress ;
23
25
import com .mongodb .ServerCursor ;
26
+ import com .mongodb .annotations .ThreadSafe ;
27
+ import com .mongodb .connection .ConnectionDescription ;
24
28
import com .mongodb .connection .ServerType ;
25
29
import com .mongodb .internal .VisibleForTesting ;
26
30
import com .mongodb .internal .binding .ConnectionSource ;
27
31
import com .mongodb .internal .connection .Connection ;
28
- import com .mongodb .internal .diagnostics .logging .Logger ;
29
- import com .mongodb .internal .diagnostics .logging .Loggers ;
30
- import com .mongodb .internal .validator .NoOpFieldNameValidator ;
32
+ import com .mongodb .internal .connection .OperationContext ;
31
33
import com .mongodb .lang .Nullable ;
32
34
import org .bson .BsonDocument ;
33
35
import org .bson .BsonTimestamp ;
34
36
import org .bson .BsonValue ;
35
- import org .bson .FieldNameValidator ;
37
+ import org .bson .codecs . BsonDocumentCodec ;
36
38
import org .bson .codecs .Decoder ;
37
39
38
40
import java .util .List ;
39
41
import java .util .NoSuchElementException ;
42
+ import java .util .concurrent .locks .StampedLock ;
43
+ import java .util .function .Consumer ;
44
+ import java .util .function .Supplier ;
40
45
41
46
import static com .mongodb .assertions .Assertions .assertNotNull ;
47
+ import static com .mongodb .assertions .Assertions .assertTrue ;
42
48
import static com .mongodb .assertions .Assertions .notNull ;
43
49
import static com .mongodb .internal .VisibleForTesting .AccessModifier .PRIVATE ;
44
50
import static com .mongodb .internal .operation .CommandBatchCursorHelper .FIRST_BATCH ;
45
51
import static com .mongodb .internal .operation .CommandBatchCursorHelper .MESSAGE_IF_CLOSED_AS_CURSOR ;
46
52
import static com .mongodb .internal .operation .CommandBatchCursorHelper .MESSAGE_IF_CLOSED_AS_ITERATOR ;
47
53
import static com .mongodb .internal .operation .CommandBatchCursorHelper .NEXT_BATCH ;
54
+ import static com .mongodb .internal .operation .CommandBatchCursorHelper .NO_OP_FIELD_NAME_VALIDATOR ;
55
+ import static com .mongodb .internal .operation .CommandBatchCursorHelper .getKillCursorsCommand ;
56
+ import static com .mongodb .internal .operation .CommandBatchCursorHelper .getCommandCursorResult ;
48
57
import static com .mongodb .internal .operation .CommandBatchCursorHelper .getMoreCommandDocument ;
49
- import static com .mongodb .internal .operation .QueryHelper .translateCommandException ;
50
- import static java .lang .String .format ;
58
+ import static com .mongodb .internal .operation .CommandBatchCursorHelper .translateCommandException ;
51
59
52
60
class CommandBatchCursor <T > implements AggregateResponseBatchCursor <T > {
53
- private static final Logger LOGGER = Loggers .getLogger ("operation" );
54
- private static final FieldNameValidator NO_OP_FIELD_NAME_VALIDATOR = new NoOpFieldNameValidator ();
55
61
56
62
private final MongoNamespace namespace ;
57
63
private final int limit ;
@@ -61,11 +67,11 @@ class CommandBatchCursor<T> implements AggregateResponseBatchCursor<T> {
61
67
private final BsonValue comment ;
62
68
private final int maxWireVersion ;
63
69
private final boolean firstBatchEmpty ;
64
- private final CursorResourceManager resourceManager ;
70
+ private final ResourceManager resourceManager ;
65
71
66
72
private int batchSize ;
67
73
private CommandCursorResult <T > commandCursorResult ;
68
- private int count ;
74
+ private int count = 0 ;
69
75
@ Nullable
70
76
private List <T > nextBatch ;
71
77
@@ -76,26 +82,26 @@ class CommandBatchCursor<T> implements AggregateResponseBatchCursor<T> {
76
82
@ Nullable final BsonValue comment ,
77
83
final ConnectionSource connectionSource ,
78
84
final Connection connection ) {
79
- this . commandCursorResult = initFromCommandCursorDocument ( connection .getDescription (). getServerAddress (),
80
- FIRST_BATCH , commandCursorDocument );
85
+ ConnectionDescription connectionDescription = connection .getDescription ();
86
+ this . commandCursorResult = toCommandCursorResult ( connectionDescription . getServerAddress (), FIRST_BATCH , commandCursorDocument );
81
87
this .namespace = commandCursorResult .getNamespace ();
82
88
this .limit = limit ;
83
89
this .batchSize = batchSize ;
84
90
this .maxTimeMS = maxTimeMS ;
85
91
this .decoder = notNull ("decoder" , decoder );
86
92
this .comment = comment ;
87
- this .maxWireVersion = connection . getDescription () .getMaxWireVersion ();
93
+ this .maxWireVersion = connectionDescription .getMaxWireVersion ();
88
94
this .firstBatchEmpty = commandCursorResult .getResults ().isEmpty ();
89
95
90
96
Connection connectionToPin = null ;
91
97
boolean releaseServerAndResources = false ;
92
98
if (limitReached ()) {
93
99
releaseServerAndResources = true ;
94
- } else if (connectionSource . getServerDescription (). getType () == ServerType .LOAD_BALANCER ) {
100
+ } else if (connectionDescription . getServerType () == ServerType .LOAD_BALANCER ) {
95
101
connectionToPin = connection ;
96
102
}
97
103
98
- resourceManager = new CursorResourceManager (namespace , connectionSource , connectionToPin , commandCursorResult .getServerCursor ());
104
+ resourceManager = new ResourceManager (namespace , connectionSource , connectionToPin , commandCursorResult .getServerCursor ());
99
105
if (releaseServerAndResources ) {
100
106
resourceManager .releaseServerAndClientResources (connection );
101
107
}
@@ -115,7 +121,7 @@ private boolean doHasNext() {
115
121
return false ;
116
122
}
117
123
118
- while (resourceManager .serverCursor () != null ) {
124
+ while (resourceManager .getServerCursor () != null ) {
119
125
getMore ();
120
126
if (!resourceManager .operable ()) {
121
127
throw new IllegalStateException (MESSAGE_IF_CLOSED_AS_CURSOR );
@@ -194,7 +200,7 @@ private boolean tryHasNext() {
194
200
return false ;
195
201
}
196
202
197
- if (resourceManager .serverCursor () != null ) {
203
+ if (resourceManager .getServerCursor () != null ) {
198
204
getMore ();
199
205
}
200
206
@@ -207,8 +213,7 @@ public ServerCursor getServerCursor() {
207
213
if (!resourceManager .operable ()) {
208
214
throw new IllegalStateException (MESSAGE_IF_CLOSED_AS_ITERATOR );
209
215
}
210
-
211
- return resourceManager .serverCursor ();
216
+ return resourceManager .getServerCursor ();
212
217
}
213
218
214
219
@ Override
@@ -241,11 +246,11 @@ public int getMaxWireVersion() {
241
246
}
242
247
243
248
private void getMore () {
244
- ServerCursor serverCursor = assertNotNull (resourceManager .serverCursor ());
249
+ ServerCursor serverCursor = assertNotNull (resourceManager .getServerCursor ());
245
250
resourceManager .executeWithConnection (connection -> {
246
251
ServerCursor nextServerCursor ;
247
252
try {
248
- initFromCommandCursorDocument (connection .getDescription ().getServerAddress (), NEXT_BATCH ,
253
+ this . commandCursorResult = toCommandCursorResult (connection .getDescription ().getServerAddress (), NEXT_BATCH ,
249
254
assertNotNull (
250
255
connection .command (namespace .getDatabaseName (),
251
256
getMoreCommandDocument (serverCursor .getId (), connection .getDescription (), namespace ,
@@ -265,21 +270,123 @@ private void getMore() {
265
270
});
266
271
}
267
272
268
- private CommandCursorResult <T > initFromCommandCursorDocument (final ServerAddress serverAddress , final String fieldNameContainingBatch ,
273
+ private CommandCursorResult <T > toCommandCursorResult (final ServerAddress serverAddress , final String fieldNameContainingBatch ,
269
274
final BsonDocument commandCursorDocument ) {
270
- this . commandCursorResult = new CommandCursorResult <> (serverAddress , fieldNameContainingBatch , commandCursorDocument );
275
+ CommandCursorResult < T > commandCursorResult = getCommandCursorResult (serverAddress , fieldNameContainingBatch , commandCursorDocument );
271
276
this .nextBatch = commandCursorResult .getResults ().isEmpty () ? null : commandCursorResult .getResults ();
272
277
this .count += commandCursorResult .getResults ().size ();
273
- if (LOGGER .isDebugEnabled ()) {
274
- LOGGER .debug (format ("Received batch of %d documents with cursorId %d from server %s" , commandCursorResult .getResults ().size (),
275
- commandCursorResult .getCursorId (), commandCursorResult .getServerAddress ()));
276
- }
277
278
return commandCursorResult ;
278
279
}
279
280
280
281
private boolean limitReached () {
281
282
return Math .abs (limit ) != 0 && count >= Math .abs (limit );
282
283
}
283
284
285
+ @ ThreadSafe
286
+ private static final class ResourceManager extends CursorResourceManager <ConnectionSource , Connection > {
287
+
288
+ ResourceManager (
289
+ final MongoNamespace namespace ,
290
+ final ConnectionSource connectionSource ,
291
+ @ Nullable final Connection connectionToPin ,
292
+ @ Nullable final ServerCursor serverCursor ) {
293
+ super (new StampedLock ().asWriteLock (), namespace , connectionSource , connectionToPin , serverCursor );
294
+ }
295
+
296
+ /**
297
+ * Thread-safe.
298
+ * Executes {@code operation} within the {@link #tryStartOperation()}/{@link #endOperation()} bounds.
299
+ *
300
+ * @throws IllegalStateException If {@linkplain CommandBatchCursor#close() closed}.
301
+ */
302
+ @ Nullable
303
+ <R > R execute (final String exceptionMessageIfClosed , final Supplier <R > operation ) throws IllegalStateException {
304
+ if (!tryStartOperation ()) {
305
+ throw new IllegalStateException (exceptionMessageIfClosed );
306
+ }
307
+ try {
308
+ return operation .get ();
309
+ } finally {
310
+ endOperation ();
311
+ }
312
+ }
284
313
314
+ @ Override
315
+ void markAsPinned (final Connection connectionToPin , final Connection .PinningMode pinningMode ) {
316
+ connectionToPin .markAsPinned (pinningMode );
317
+ }
318
+
319
+ @ Override
320
+ void doClose () {
321
+ if (isSkipReleasingServerResourcesOnClose ()) {
322
+ unsetServerCursor ();
323
+ }
324
+ try {
325
+ if (getServerCursor () != null ) {
326
+ // Don't handle corrupted connections
327
+ Connection connection = getConnection ();
328
+ try {
329
+ releaseServerResources (connection );
330
+ } finally {
331
+ connection .release ();
332
+ }
333
+ }
334
+ } catch (MongoException e ) {
335
+ // ignore exceptions when releasing server resources
336
+ } finally {
337
+ // guarantee that regardless of exceptions, `serverCursor` is null and client resources are released
338
+ unsetServerCursor ();
339
+ releaseClientResources ();
340
+ }
341
+ }
342
+
343
+ void executeWithConnection (final Consumer <Connection > action ) {
344
+ Connection connection = getConnection ();
345
+ try {
346
+ action .accept (connection );
347
+ } catch (MongoSocketException e ) {
348
+ onCorruptedConnection (connection , e );
349
+ throw e ;
350
+ } finally {
351
+ connection .release ();
352
+ }
353
+ }
354
+
355
+ private Connection getConnection () {
356
+ assertTrue (getState () != State .IDLE );
357
+ Connection pinnedConnection = getPinnedConnection ();
358
+ if (pinnedConnection == null ) {
359
+ return assertNotNull (getConnectionSource ()).getConnection ();
360
+ } else {
361
+ return pinnedConnection .retain ();
362
+ }
363
+ }
364
+
365
+ private void releaseServerAndClientResources (final Connection connection ) {
366
+ try {
367
+ releaseServerResources (assertNotNull (connection ));
368
+ } finally {
369
+ releaseClientResources ();
370
+ }
371
+ }
372
+
373
+ private void releaseServerResources (final Connection connection ) {
374
+ try {
375
+ ServerCursor localServerCursor = getServerCursor ();
376
+ if (localServerCursor != null ) {
377
+ killServerCursor (getNamespace (), localServerCursor , connection );
378
+ }
379
+ } finally {
380
+ unsetServerCursor ();
381
+ }
382
+ }
383
+
384
+ private void killServerCursor (final MongoNamespace namespace , final ServerCursor localServerCursor ,
385
+ final Connection localConnection ) {
386
+ OperationContext operationContext = assertNotNull (getConnectionSource ()).getOperationContext ();
387
+ localConnection .command (namespace .getDatabaseName (), getKillCursorsCommand (namespace , localServerCursor ),
388
+ NO_OP_FIELD_NAME_VALIDATOR , ReadPreference .primary (), new BsonDocumentCodec (),
389
+ operationContext );
390
+ }
391
+ }
285
392
}
0 commit comments