neo4j / neo4j-dotnet-driver

Neo4j Bolt driver for .NET
Apache License 2.0
229 stars 69 forks source link

Error when disposing unclosed transaction in session #643

Closed kzhui125 closed 1 year ago

kzhui125 commented 1 year ago

Neo4j Version: 4.4.6 Enterprise
Neo4j Mode: Single instance
Driver version: .NET driver 5.0.0 dotnet: 6.0

Steps to reproduce

  1. Run a query with await session.ExecuteReadAsync(Work).
  2. when the function returns, because of the await using session = statement, so DisposeAsync is called.
  3. I get exception
  4. I can't reproduce now.

Actual behavior

I get exception: "Error when disposing unclosed transaction in session: Cannot rollback this transaction, because it has already been committed."

Why does the driver try to rollback the committed transaction when dispose the session?

When the transaction is committed, the session state in AsyncTransaction should be Committed, _transaction in AsyncSession should be null.

So I don't know how does this exception happen, is there a bug in the driver?

Neo4j.Driver.ClientException: Error when disposing unclosed transaction in session: Cannot rollback this transaction, because it has already been committed.
 ---> Neo4j.Driver.TransactionClosedException: Cannot rollback this transaction, because it has already been committed.
   at Neo4j.Driver.Internal.AsyncTransaction.CommittedState.RollbackAsync(IConnection connection, IBoltProtocol protocol, IBookmarksTracker tracker, IState& nextState)
   at Neo4j.Driver.Internal.AsyncTransaction.RollbackAsync()
   at Neo4j.Driver.Internal.AsyncTransaction.RollbackAsync()
   at Neo4j.Driver.Internal.AsyncSession.DisposeTransactionAsync()
   --- End of inner exception stack trace ---
   at Neo4j.Driver.Internal.AsyncSession.DisposeTransactionAsync()
   at Neo4j.Driver.Internal.AsyncSession.<CloseAsync>b__50_0()
   at Neo4j.Driver.Internal.AsyncSession.<CloseAsync>b__50_0()
   at Neo4j.Driver.Internal.Logging.DriverLoggerUtil.TryExecuteAsync(ILogger logger, Func`1 func, String message)
   at Neo4j.Driver.Internal.AsyncSession.DisposeAsyncCore()
   at Neo4j.Driver.Internal.AsyncQueryRunner.DisposeAsync()
   at 
image
kzhui125 commented 1 year ago

another exception

Neo4j.Driver.ClientException: Error when disposing unclosed transaction in session: Message 'COMMIT' cannot be handled by a session in the READY state.
 ---> Neo4j.Driver.ProtocolException: Message 'COMMIT' cannot be handled by a session in the READY state.
   at Neo4j.Driver.Internal.AsyncTransaction.TransactionConnection.OnErrorAsync(Exception error)
   at Neo4j.Driver.Internal.Connector.DelegatedConnection.TaskWithErrorHandling(Func`1 task)
   at Neo4j.Driver.Internal.Protocol.BoltProtocolV3.RollbackTransactionAsync(IConnection connection)
   at Neo4j.Driver.Internal.AsyncTransaction.RollbackAsync()
   at Neo4j.Driver.Internal.AsyncTransaction.RollbackAsync()
   at Neo4j.Driver.Internal.AsyncSession.DisposeTransactionAsync()
   --- End of inner exception stack trace ---
   at Neo4j.Driver.Internal.AsyncSession.DisposeTransactionAsync()
   at Neo4j.Driver.Internal.AsyncSession.<CloseAsync>b__50_0()
   at Neo4j.Driver.Internal.AsyncSession.<CloseAsync>b__50_0()
   at Neo4j.Driver.Internal.Logging.DriverLoggerUtil.TryExecuteAsync(ILogger logger, Func`1 func, String message)
   at Neo4j.Driver.Internal.AsyncSession.DisposeAsyncCore()
   at Neo4j.Driver.Internal.AsyncQueryRunner.DisposeAsync()
kzhui125 commented 1 year ago
async ExecuteAsync()
{
    await using session = xx

    var tasksQuery = xxx
        .AsEnumerable()
        .Select(async xx => await session.ExecuteReadAsync xx);

    foreach (var task in tasksQuery)
    {
        // this break cause bug
        if (linkedToken.IsCancellationRequested)
            break;

        var taskResult = await task;
    }
}

because the break line, cause session dispose before transaction dispose

it's not neo4j driver bug...