[JAVA-2651] Unable to exit from hasNext() for tailable cursor if cursor was closed from another thread Created: 03/Nov/17  Updated: 29/Oct/23  Resolved: 10/Nov/17

Status: Closed
Project: Java Driver
Component/s: Query Operations
Affects Version/s: 3.5.0
Fix Version/s: 3.6.0

Type: Bug Priority: Major - P3
Reporter: Igor Assignee: Jeffrey Yemin
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

WIndows 10, Java 1.8


Issue Links:
Depends
is depended on by DRIVERS-421 Cursor iteration should complete (abn... Closed
Related
related to SERVER-21710 Allow pinned ClientCursors to be kill... Closed
Backwards Compatibility: Fully Compatible

 Description   

Unable to exit from hasNext() for tailable cursor if cursor was closed from another thread
My proposal is to add another check for "closed" inside

        while (serverCursor != null) {
            getMore();
            if (nextBatch != null) {
                return true;
            }
        }

before getMore(). So if cursor was closed from another thread, hasNext() will exit. But this should be done in thread-safe way.



 Comments   
Comment by Githook User [ 10/Nov/17 ]

Author:

{'name': 'Jeff Yemin', 'username': 'jyemin', 'email': 'jeff.yemin@10gen.com'}

Message: JAVA-2651: Ensure that cursor iteration eventually stops when the cursor is closed
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/cbf948f5b51b9a71d1f3479840bee42765981d01

Comment by Jeffrey Yemin [ 06/Nov/17 ]

Further investigation shows that SERVER-21710 is the underlying cause of this, so linking the two issues.

I still think, as suggested, that the driver could check itself for a cursor that's been closed, so I'll leave this issue open as well.

Comment by Igor [ 06/Nov/17 ]

Here is the example where I can reproduce it:

import com.mongodb.CursorType;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CreateCollectionOptions;
import org.bson.Document;
 
public class mainClass {
 
    public static void main(String[] args) throws Exception {
 
        MongoClient client = new MongoClient();
        MongoDatabase database = client.getDatabase("db");
        MongoCollection<Document> tailableCollection = database.getCollection("tailable");
        tailableCollection.drop();
        database.createCollection("tailable",
                new CreateCollectionOptions().capped(true).sizeInBytes(10000));
 
        tailableCollection.insertOne(new Document());
 
        final MongoCursor<Document> cursor = tailableCollection.find().cursorType(CursorType.TailableAwait).iterator();
 
        tailableCollection.insertOne(new Document());
        tailableCollection.insertOne(new Document());
 
        Runnable runGet = new Runnable() {
            public void run() {
                try {
                    while (cursor.hasNext()) {
                        System.out.println(cursor.next());
                    }
                    System.out.println("exiting cursor");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
 
        Thread thread = new Thread(runGet);
 
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                try {
                    cursor.close();
                    System.out.println("closing cursor");
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        thread.start();
    }
}

When exiting (Ctrl+C) you will se next

Document{{_id=5a003d0cb1dc2632ec6d7806}}
Document{{_id=5a003d0cb1dc2632ec6d7807}}
Document{{_id=5a003d0cb1dc2632ec6d7808}}
closing cursor

When waiting for exit, if you will insert new document(e.g. from Robo 3T), you will get

java.lang.IllegalStateException: Cursor has been closed
	at com.mongodb.operation.QueryBatchCursor.hasNext(QueryBatchCursor.java:93)
	at com.mongodb.MongoBatchCursorAdapter.hasNext(MongoBatchCursorAdapter.java:46)
	at com.mongodb.MongoBatchCursorAdapter.next(MongoBatchCursorAdapter.java:51)
	at mainClass$1.run(mainClass.java:33)
	at java.lang.Thread.run(Thread.java:748)
 
Process finished with exit code 1

which signals that hasNext() has reacted on cursor.close() only after new document was inserted during shutdown.

Comment by Jeffrey Yemin [ 06/Nov/17 ]

My tests show that hasNext() will eventually exit if the cursor is closed, as the getMore will fail. The exception, though, is not what I expected:

java.lang.NullPointerException
	at com.mongodb.operation.QueryHelper.translateCommandException(QueryHelper.java:27)
	at com.mongodb.operation.QueryBatchCursor.getMore(QueryBatchCursor.java:215)
	at com.mongodb.operation.QueryBatchCursor.hasNext(QueryBatchCursor.java:105)
	at com.mongodb.MongoBatchCursorAdapter.hasNext(MongoBatchCursorAdapter.java:46)
	at org.mongodb.test.three_six.JAVA2651.lambda$main$0(JAVA2651.java:50)
	at java.lang.Thread.run(Thread.java:748)

Can you verify that you're seeing different behavior? Here's my test program for reference:

        MongoClient client = new MongoClient();
        MongoDatabase database = client.getDatabase("test");
        MongoCollection<Document> tailableCollection = database.getCollection("tailable");
        tailableCollection.drop();
        database.createCollection("tailable",
                new CreateCollectionOptions().capped(true).sizeInBytes(10000));
 
        tailableCollection.insertOne(new Document());
 
        final MongoCursor<Document> cursor = tailableCollection.find().cursorType(CursorType.TailableAwait).iterator();
 
        new Thread(() -> {
            try {
                while (cursor.hasNext()) {
                    System.out.println(cursor.next());
                }
                System.out.println("exiting cursor");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
 
        tailableCollection.insertOne(new Document());
        Thread.sleep(1000);
 
        tailableCollection.insertOne(new Document());
        Thread.sleep(1000);
 
        cursor.close();

One caveat: if I add less than two documents after starting the tailable cursor but before closing it, I do get the hanging behavior that you observe, so perhaps that's what's happening. This appears to be a server bug.

Generated at Thu Feb 08 08:57:45 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.