[JAVA-3060] The Java driver ignores exceptions in user code. Created: 24/Oct/18  Updated: 27/Oct/23  Resolved: 25/Oct/18

Status: Closed
Project: Java Driver
Component/s: Error Handling
Affects Version/s: 3.8.2
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Joan Karadimov Assignee: Jeffrey Yemin
Resolution: Works as Designed Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

The async driver ignores exceptions in user code. But judging from the stack trace it might be relevant for the sync driver too.

We have an implementation of `org.bson.codecs.Codec`. Its `encode` method calls some [potentially 3rd party] code that ends up throwing a runtime exception.

This is the relevant part of the stack trace:

        .............. user code ..............
        at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
        at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
        at com.mongodb.operation.BulkWriteBatch$WriteRequestEncoder.encode(BulkWriteBatch.java:387)
        at com.mongodb.operation.BulkWriteBatch$WriteRequestEncoder.encode(BulkWriteBatch.java:377)
        at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
        at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
        at com.mongodb.internal.connection.BsonWriterHelper.writeDocument(BsonWriterHelper.java:75)
        at com.mongodb.internal.connection.BsonWriterHelper.writePayload(BsonWriterHelper.java:59)
        at com.mongodb.internal.connection.CommandMessage.encodeMessageBodyWithMetadata(CommandMessage.java:143)
        at com.mongodb.internal.connection.RequestMessage.encode(RequestMessage.java:138)
        at com.mongodb.internal.connection.CommandMessage.encode(CommandMessage.java:57)
        at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceiveAsync(InternalStreamConnection.java:319)
        at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceiveAsync(UsageTrackingInternalConnection.java:114)
        at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceiveAsync(DefaultConnectionPool.java:455)
        at com.mongodb.internal.connection.CommandProtocolImpl.executeAsync(CommandProtocolImpl.java:78)
        at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.executeAsync(DefaultServer.java:215)
        at com.mongodb.internal.connection.DefaultServerConnection.executeProtocolAsync(DefaultServerConnection.java:285)
        at com.mongodb.internal.connection.DefaultServerConnection.commandAsync(DefaultServerConnection.java:156)
        at com.mongodb.operation.MixedBulkWriteOperation.executeCommandAsync(MixedBulkWriteOperation.java:427)
        at com.mongodb.operation.MixedBulkWriteOperation.executeBatchesAsync(MixedBulkWriteOperation.java:334)
        at com.mongodb.operation.MixedBulkWriteOperation.access$900(MixedBulkWriteOperation.java:67)
        at com.mongodb.operation.MixedBulkWriteOperation$2$1.call(MixedBulkWriteOperation.java:234)
        at com.mongodb.operation.OperationHelper.validateWriteRequests(OperationHelper.java:177)
        at com.mongodb.operation.MixedBulkWriteOperation$2.call(MixedBulkWriteOperation.java:217)
        at com.mongodb.operation.OperationHelper$7.onResult(OperationHelper.java:517)
        at com.mongodb.operation.OperationHelper$7.onResult(OperationHelper.java:514)
        at com.mongodb.internal.connection.DefaultServer$1.onResult(DefaultServer.java:109)
        at com.mongodb.internal.connection.DefaultServer$1.onResult(DefaultServer.java:98)
        at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
        at com.mongodb.internal.connection.DefaultConnectionPool.openAsync(DefaultConnectionPool.java:192)
        at com.mongodb.internal.connection.DefaultConnectionPool.getAsync(DefaultConnectionPool.java:149)
        at com.mongodb.internal.connection.DefaultServer.getConnectionAsync(DefaultServer.java:98)
        at com.mongodb.binding.AsyncClusterBinding$AsyncClusterBindingConnectionSource.getConnection(AsyncClusterBinding.java:131)
        at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:514)
        at com.mongodb.operation.OperationHelper.access$100(OperationHelper.java:63)
        at com.mongodb.operation.OperationHelper$AsyncCallableWithConnectionAndSourceCallback.onResult(OperationHelper.java:534)
        at com.mongodb.operation.OperationHelper$AsyncCallableWithConnectionAndSourceCallback.onResult(OperationHelper.java:522)
        at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
        at com.mongodb.binding.AsyncClusterBinding$1.onResult(AsyncClusterBinding.java:105)
        at com.mongodb.binding.AsyncClusterBinding$1.onResult(AsyncClusterBinding.java:99)
        at com.mongodb.internal.connection.BaseCluster$ServerSelectionRequest.onResult(BaseCluster.java:433)
        at com.mongodb.internal.connection.BaseCluster.handleServerSelectionRequest(BaseCluster.java:297)
        at com.mongodb.internal.connection.BaseCluster.selectServerAsync(BaseCluster.java:157)
        at com.mongodb.internal.connection.SingleServerCluster.selectServerAsync(SingleServerCluster.java:41)
        at com.mongodb.binding.AsyncClusterBinding.getAsyncClusterBindingConnectionSource(AsyncClusterBinding.java:99)
        at com.mongodb.binding.AsyncClusterBinding.getWriteConnectionSource(AsyncClusterBinding.java:94)
        at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:473)
        at com.mongodb.operation.MixedBulkWriteOperation.executeAsync(MixedBulkWriteOperation.java:211)
        at com.mongodb.async.client.OperationExecutorImpl$2.onResult(OperationExecutorImpl.java:112)
        at com.mongodb.async.client.OperationExecutorImpl$2.onResult(OperationExecutorImpl.java:104)
        at com.mongodb.async.client.ClientSessionHelper$2.onResult(ClientSessionHelper.java:80)
        at com.mongodb.async.client.ClientSessionHelper$2.onResult(ClientSessionHelper.java:73)
        at com.mongodb.internal.connection.BaseCluster$ServerSelectionRequest.onResult(BaseCluster.java:433)
        at com.mongodb.internal.connection.BaseCluster.handleServerSelectionRequest(BaseCluster.java:297)
        at com.mongodb.internal.connection.BaseCluster.selectServerAsync(BaseCluster.java:157)
        at com.mongodb.internal.connection.SingleServerCluster.selectServerAsync(SingleServerCluster.java:41)
        at com.mongodb.async.client.ClientSessionHelper.createClientSession(ClientSessionHelper.java:68)
        at com.mongodb.async.client.ClientSessionHelper.withClientSession(ClientSessionHelper.java:51)
        at com.mongodb.async.client.OperationExecutorImpl.execute(OperationExecutorImpl.java:104)
        at com.mongodb.async.client.MongoCollectionImpl.executeInsertMany(MongoCollectionImpl.java:524)
        at com.mongodb.async.client.MongoCollectionImpl.insertMany(MongoCollectionImpl.java:506)
        .............. user code ..............

The exception seems to be ignored here:
`com.mongodb.internal.connection.BaseCluster$ServerSelectionRequest.onResult(BaseCluster.java:433)`

This prevents us from seeing/handling the original cause.



 Comments   
Comment by Jeffrey Yemin [ 25/Oct/18 ]

OK, thanks for following up.

Comment by Joan Karadimov [ 25/Oct/18 ]

Ah, yes, my bad. There was another piece of code that did not handle a throwable in a callback. This bug is invalid.

Comment by Jeffrey Yemin [ 24/Oct/18 ]

I had a further look at `com.mongodb.internal.connection.BaseCluster$ServerSelectionRequest.onResult(BaseCluster.java:433)`.  The reason it's written that way is because there's nothing the driver can do about an exception thrown from an application-provided callback.   That catch would only be triggered by code like this:

        client.getDatabase("test").getCollection("test")
                .insertOne(new Document(), (result, t) -> {
                    // throw some exception
                });
 

The most the driver could do here is log the application-throw exception, as there is nowhere for it to bubble up to, so to speak.

Comment by Jeffrey Yemin [ 24/Oct/18 ]

Do you have a simple reproducer for this?  I tried with the following test program:

        MongoClientSettings settings =
                MongoClientSettings.builder()
                .codecRegistry(
                        CodecRegistries.fromRegistries(
                                CodecRegistries.fromCodecs(new DocumentCodec()),
                                MongoClientSettings.getDefaultCodecRegistry()
                        )
                ).build();
 
        MongoClient client = MongoClients.create(settings);
 
 
        // I observe the exception passed through to insertOne callback with or without waiting
        // for an initial operation to complete.
        // Thread.sleep(5000);
 
        client.getDatabase("test").getCollection("test")
                .insertOne(new Document(), (result, t) -> {
                    t.printStackTrace();
                    System.out.println(result);
                });
 
        Thread.sleep(Long.MAX_VALUE);
 

and with or without the sleep before the insertOne call, I observe the exception in the callback to insertOne

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