Skip to content

Commit 978ad37

Browse files
committed
[JAVA-293]: Cursor is not closed after limit is fully fetched from server.
This turned out to be more of a refactoring than hoped, to clean up code while fixing issue. Solution: - positive batchsize tells server the ideal size of a batch returned - negative batchsize tells server to return 1 batch of maximum that size, or whatever fits in a 4MB batch, and close the cursor - positive limit does limit the total number of documents to return on cursor. Once limit is reached, cursor should be called automatically by driver. - negative limit is only supported for legacy reason. It sets the batchSize to that negative value.
1 parent 893d342 commit 978ad37

File tree

6 files changed

+132
-100
lines changed

6 files changed

+132
-100
lines changed

src/main/com/mongodb/DB.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public CommandResult command( DBObject cmd )
149149
public CommandResult command( DBObject cmd , int options )
150150
throws MongoException {
151151

152-
Iterator<DBObject> i = getCollection( "$cmd" ).__find( cmd , new BasicDBObject() , 0 , -1 , options );
152+
Iterator<DBObject> i = getCollection("$cmd").__find(cmd, new BasicDBObject(), 0, -1, 0, options);
153153
if ( i == null || ! i.hasNext() )
154154
return null;
155155

@@ -260,7 +260,7 @@ public Set<String> getCollectionNames()
260260
if (namespaces == null)
261261
throw new RuntimeException("this is impossible");
262262

263-
Iterator<DBObject> i = namespaces.__find(new BasicDBObject(), null, 0, 0, getOptions());
263+
Iterator<DBObject> i = namespaces.__find(new BasicDBObject(), null, 0, 0, 0, getOptions());
264264
if (i == null)
265265
return new HashSet<String>();
266266

src/main/com/mongodb/DBApiLayer.java

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,29 @@ static final boolean willTrace(){
5050
static final void trace( String s ){
5151
TRACE_LOGGER.log( TRACE_LEVEL , s );
5252
}
53-
53+
54+
static int chooseBatchSize(int batchSize, int limit, int fetched) {
55+
int bs = Math.abs(batchSize);
56+
int remaining = limit > 0 ? limit - fetched : 0;
57+
int res = 0;
58+
if (bs == 0 && remaining > 0)
59+
res = remaining;
60+
else if (bs > 0 && remaining == 0)
61+
res = bs;
62+
else
63+
res = Math.min(bs, remaining);
64+
65+
if (batchSize < 0) {
66+
// force close
67+
res = -res;
68+
}
69+
70+
if (res == 1) {
71+
// optimization: use negative batchsize to close cursor
72+
res = -1;
73+
}
74+
return res;
75+
}
5476

5577
/**
5678
* @param mongo the Mongo instance
@@ -260,15 +282,15 @@ public WriteResult remove( DBObject o , com.mongodb.WriteConcern concern )
260282
}
261283

262284
@Override
263-
Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int options )
285+
Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize, int limit , int options )
264286
throws MongoException {
265287

266288
if ( ref == null )
267289
ref = new BasicDBObject();
268290

269291
if ( willTrace() ) trace( "find: " + _fullNameSpace + " " + JSON.serialize( ref ) );
270292

271-
OutMessage query = OutMessage.query( _mongo , options , _fullNameSpace , numToSkip , batchSize , ref , fields );
293+
OutMessage query = OutMessage.query( _mongo , options , _fullNameSpace , numToSkip , chooseBatchSize(batchSize, limit, 0) , ref , fields );
272294

273295
Response res = _connector.call( _db , this , query , null , 2 );
274296

@@ -282,7 +304,7 @@ Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int
282304
throw e;
283305
}
284306

285-
return new Result( this , res , batchSize , options );
307+
return new Result( this , res , batchSize, limit , options );
286308
}
287309

288310
@Override
@@ -329,28 +351,34 @@ public void createIndex( final DBObject keys, final DBObject options )
329351

330352
class Result implements Iterator<DBObject> {
331353

332-
Result( MyCollection coll , Response res , int numToReturn , int options ){
333-
init( res );
354+
Result( MyCollection coll , Response res , int batchSize, int limit , int options ){
334355
_collection = coll;
335-
_numToReturn = numToReturn;
356+
_batchSize = batchSize;
357+
_limit = limit;
336358
_options = options;
337359
_host = res._host;
360+
init( res );
338361
}
339362

340363
private void init( Response res ){
341364
_totalBytes += res._len;
342365
_curResult = res;
343366
_cur = res.iterator();
344367
_sizes.add( res.size() );
368+
_numFetched += res.size();
345369

346370
if ( ( res._flags & Bytes.RESULTFLAG_CURSORNOTFOUND ) > 0 ){
347371
throw new MongoException.CursorNotFound();
348372
}
373+
374+
if (res._cursor > 0 && _limit > 0 && _limit - _numFetched <= 0) {
375+
// fetched all docs within limit, close cursor server-side
376+
killCursor();
377+
}
349378
}
350379

351380
public DBObject next(){
352381
if ( _cur.hasNext() ) {
353-
_numSeen++;
354382
return _cur.next();
355383
}
356384

@@ -382,7 +410,7 @@ private void _advance(){
382410

383411
m.writeInt( 0 );
384412
m.writeCString( _collection._fullNameSpace );
385-
m.writeInt( _numToReturn - _numSeen ); // num to return
413+
m.writeInt( chooseBatchSize(_batchSize, _limit, _numFetched) );
386414
m.writeLong( _curResult.cursor() );
387415

388416
Response res = _connector.call( DBApiLayer.this , _collection , m , _host );
@@ -394,12 +422,12 @@ public void remove(){
394422
throw new RuntimeException( "can't remove this way" );
395423
}
396424

397-
public int getNumberToReturn(){
398-
return _numToReturn;
425+
public int getBatchSize(){
426+
return _batchSize;
399427
}
400428

401-
public void setNumberToReturn(int num){
402-
_numToReturn = num;
429+
public void setBatchSize(int size){
430+
_batchSize = size;
403431
}
404432

405433
public String toString(){
@@ -439,39 +467,47 @@ List<Integer> getSizes(){
439467
void close(){
440468
// not perfectly thread safe here, may need to use an atomicBoolean
441469
if (_curResult != null) {
442-
long curId = _curResult.cursor();
470+
killCursor();
443471
_curResult = null;
444472
_cur = null;
445-
446-
if (curId > 0) {
447-
List<Long> l = new ArrayList<Long>();
448-
l.add(curId);
449-
450-
try {
451-
killCursors(_host, l);
452-
} catch (Throwable t) {
453-
Bytes.LOGGER.log(Level.WARNING, "can't clean 1 cursor", t);
454-
_deadCursorIds.add(new DeadCursor(curId, _host));
455-
}
456-
}
457473
}
458474
}
459475

476+
void killCursor() {
477+
if (_curResult == null)
478+
return;
479+
long curId = _curResult.cursor();
480+
if (curId < 0)
481+
return;
482+
483+
List<Long> l = new ArrayList<Long>();
484+
l.add(curId);
485+
486+
try {
487+
killCursors(_host, l);
488+
} catch (Throwable t) {
489+
Bytes.LOGGER.log(Level.WARNING, "can't clean 1 cursor", t);
490+
_deadCursorIds.add(new DeadCursor(curId, _host));
491+
}
492+
_curResult._cursor = 0;
493+
}
494+
460495
public ServerAddress getServerAddress() {
461496
return _host;
462497
}
463498

464499
Response _curResult;
465500
Iterator<DBObject> _cur;
466-
int _numToReturn;
501+
int _batchSize;
502+
int _limit;
467503
final MyCollection _collection;
468504
final int _options;
469505
final ServerAddress _host; // host where first went. all subsequent have to go there
470506

471507
private long _totalBytes = 0;
472508
private int _numGetMores = 0;
473509
private List<Integer> _sizes = new ArrayList<Integer>();
474-
private int _numSeen = 0;
510+
private int _numFetched = 0;
475511

476512
} // class Result
477513

src/main/com/mongodb/DBCollection.java

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -199,64 +199,44 @@ public WriteResult remove( DBObject o )
199199

200200

201201
/**
202-
* Finds an object from the database
203-
* @param ref query used to search
204-
* @param fields the fields of matching objects to return
205-
* @param numToSkip will not return the first <tt>numToSkip</tt> matches
206-
* @param batchSize if positive, represents the size of each batch of objects retrieved. If negative, it limits the total number of objects retrieved.
207-
* @param options - see Bytes QUERYOPTION_*
208-
* @return the objects, if found
209-
* @dochub find
202+
* Finds objects
210203
*/
211-
abstract Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException ;
204+
abstract Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options ) throws MongoException ;
212205

213206
/**
214-
* Finds an object.
215207
* Calls {@link DBCollection#find(com.mongodb.DBObject, com.mongodb.DBObject, int, int)} and applies the query options
216-
* @param ref query used to search
208+
* @param query query used to search
217209
* @param fields the fields of matching objects to return
218-
* @param numToSkip will not return the first <tt>numToSkip</tt> matches
219-
* @param batchSize if positive, represents the size of each batch of objects retrieved. If negative, it limits the total number of objects retrieved.
210+
* @param numToSkip number of objects to skip
211+
* @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
220212
* @param options - see Bytes QUERYOPTION_*
221-
* @return the objects, if found
213+
* @return the cursor
222214
* @throws MongoException
223215
* @dochub find
224216
*/
225-
public final DBCursor find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException{
226-
return find(ref, fields, numToSkip, batchSize).addOption(options);
217+
public final DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException{
218+
return find(query, fields, numToSkip, batchSize).addOption(options);
227219
}
228220

229221

230222
/**
231-
* Finds an object.
232-
* @param ref query used to search
223+
* Finds objects from the database that match a query.
224+
* A DBCursor object is returned, that can be iterated to go through the results.
225+
*
226+
* @param query query used to search
233227
* @param fields the fields of matching objects to return
234-
* @param numToSkip will not return the first <tt>numToSkip</tt> matches
235-
* @param batchSize if positive, represents the size of each batch of objects retrieved. If negative, it limits the total number of objects retrieved.
236-
* @return the objects, if found
228+
* @param numToSkip number of objects to skip
229+
* @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
230+
* @param options - see Bytes QUERYOPTION_*
231+
* @return the cursor
232+
* @throws MongoException
237233
* @dochub find
238234
*/
239235
public final DBCursor find( DBObject ref , DBObject fields , int numToSkip , int batchSize ) {
240236
DBCursor cursor = find(ref, fields).skip(numToSkip).batchSize(batchSize);
241-
if ( batchSize < 0 )
242-
cursor.limit( Math.abs(batchSize) );
243237
return cursor;
244238
}
245239

246-
/**
247-
* Finds an object.
248-
* @param ref query used to search
249-
* @param fields the fields of matching objects to return
250-
* @param numToSkip will not return the first <tt>numToSkip</tt> matches
251-
* @param batchSize if positive, is the # of objects per batch sent back from the db. all objects that match will be returned. if batchSize < 0, its a hard limit, and only 1 batch will either batchSize or the # that fit in a batch
252-
* @return the objects, if found
253-
* @dochub find
254-
*/
255-
Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize )
256-
throws MongoException {
257-
return __find( ref , fields , numToSkip , batchSize , getOptions() );
258-
}
259-
260240
// ------
261241

262242
/**
@@ -282,7 +262,7 @@ public final DBObject findOne( Object obj )
282262
* @dochub find
283263
*/
284264
public final DBObject findOne( Object obj, DBObject fields ) {
285-
Iterator<DBObject> iterator = __find(new BasicDBObject("_id", obj), fields, 0, -1, getOptions() );
265+
Iterator<DBObject> iterator = __find(new BasicDBObject("_id", obj), fields, 0, -1, 0, getOptions() );
286266
return (iterator != null ? iterator.next() : null);
287267
}
288268

@@ -573,7 +553,7 @@ public final DBObject findOne( DBObject o )
573553
* @dochub find
574554
*/
575555
public final DBObject findOne( DBObject o, DBObject fields ) {
576-
Iterator<DBObject> i = __find( o , fields , 0 , -1 , getOptions() );
556+
Iterator<DBObject> i = __find( o , fields , 0 , -1 , 0, getOptions() );
577557
if ( i == null || ! i.hasNext() )
578558
return null;
579559
return i.next();

0 commit comments

Comments
 (0)