[JAVA-3978] Introduce self-managing reference counting to AsyncConnection and AsyncConnectionSource Created: 31/Jan/21  Updated: 14/Sep/23

Status: Backlog
Project: Java Driver
Component/s: Internal
Affects Version/s: None
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Valentin Kavalenka Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: tech-debt
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Issue split
split from JAVA-3907 AsyncQueryBatchCursor does not releas... Closed
Related
related to JAVA-4496 Refactor usages of ReferenceCounted s... Backlog
related to JAVA-3964 Improve the documentation of the Refe... Closed
Epic Link: JAVA-3703
Backwards Compatibility: Fully Compatible
Documentation Changes: Not Needed

 Description   

The idea of reference counting breaks when a resource may be used without the need to have a reference to it. For example, consider the following code

asyncConnection.commandAsync((result, exception) ->
    //process result without touching the connection
)

On one hand, the callback does not store a reference to asyncConnection, so the caller of the commandAsync method does not see a reason to retain asyncConnection;
on the other hand, the asyncConnection should not be released until the asynchronous logic completes, otherwise we may never receive a result.
Clearly, asyncConnection must be retained by someone, but not by the caller who cannot deduce this based only on thinking about references (and this is all he should be thinking about to preserve mental health).

To cope with the situation, we can put the burden of retaining and releasing asyncConnection on the connection itself.

This improvement does not have effects observable to our users and is intended to make our code more robust by reducing the reference counting burden that lies on the code writer.

Currently, we have to write the following code:

/* In this example the code that creates/borrows connectionSource
 * and the code that releases connectionSource
 * is not executed by the same method,
 * and may be even be located in different classes
 * depending on how we create the callback.
 * This makes the code less readable and more error-prone.*/
AsyncConnectionSource connectionSource = ...;//count is 1
connectionSource.getConnectionAsync((connection, problem) -> {
    try {
        connectionSource.release();//count is 0; connectionSource must have stayed retained until its asynchronous logic that obtains a connection completes
    } finally {
        if (problem == null) {
            //handle the problem
        } else {
            //use connection and release it
        }
    }
});

it would have saved us some trouble if we were not have to remember to keep connectionSource retained when we call getConnectionAsync, and if AsyncConnectionSource itself retained it and released when it's no longer needed:

/* In this example the code that creates/borrows connectionSource
 * and the code that releases connectionSource
 * is executed by and located in the same method.
 * This simplifies reading/writing the code, thus making it more robust.*/
AsyncConnectionSource connectionSource = ...;//count is 1
try {
    connectionSource.getConnectionAsync((connection, problem) -> {
        //connectionSource.count is either 0 or 1 depending on the execution, but eventually it is guaranteed to be 0
        if (problem == null) {
            //handle the problem
        } else {
            //use connection and release it
        }
    });
} finally {
    connectionSource.release();//count is either 0 or 1 depending on the execution, but eventually it is guaranteed to be 0
}


Generated at Thu Feb 08 09:00:56 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.