Skip to content

Interfaces are released twice after commit(), rollback(), close(), free() and detach() #70

Description

@nLeonardt95

fb-cpp releases a Firebird interface a second time after the calls that end the life of a Firebird object. For example, Transaction::commit() calls ITransaction::commit() and then handle.reset():

handle->commit(&statusWrapper);
handle.reset();

But on success these Firebird calls already release the interface. doc/Using_OO_API.md says so for IResultSet::close(), IStatement::free(), IAttachment::detach() and dropDatabase(), IBlob::close() and cancel(), and IService::detach() ("On success releases interface"), and a test shows the same for ITransaction::commit() and rollback(). The reset() that follows is a second release of an object that is already gone.

The same pattern is in:

  • Transaction::commit(), Transaction::rollback()
  • Statement::free(), and the result set that is closed when a statement is executed again
  • Attachment::disconnect(), Attachment::dropDatabase()
  • Blob::close(), Blob::cancel()
  • ServiceManager (IService::detach())

How to reproduce

The program holds two extra references around each call and then looks at the reference count; one release is expected.

#include <firebird/Interface.h>
#include <fb-cpp/fb-cpp.h>

#include <iostream>
#include <string>

using namespace Firebird;

extern "C" IMaster* ISC_EXPORT fb_get_master_interface();

// Holds two extra references around a call; released() tells how many
// references the call gave back. Expected: 1.
class Probe
{
public:
    explicit Probe(IReferenceCounted* p)
        : ptr(p)
    {
        ptr->addRef();
        before = ptr->release();
        ptr->addRef();
        ptr->addRef();
    }

    int released()
    {
        const int after = ptr->release();
        if (after > 0)
            ptr->release();
        return before + 1 - after;
    }

private:
    IReferenceCounted* ptr;
    int before = 0;
};

int main(int argc, char** argv)
{
    const std::string database = argc > 1 ? argv[1] : "localhost:/tmp/fbcpp-release.fdb";
    fbcpp::Client client{ fb_get_master_interface() };
    fbcpp::Attachment attachment{ client, database, fbcpp::AttachmentOptions()
        .setUserName("SYSDBA").setPassword("masterkey").setCreateDatabase(true) };
    {
        fbcpp::Transaction transaction{ attachment };
        IReferenceCounted* handle = transaction.getHandle().get();
        Probe probe(handle);
        transaction.commit();
        std::cout << "Transaction::commit(): " << probe.released() << " releases\n";
    }
    {
        fbcpp::Transaction transaction{ attachment };
        fbcpp::Statement statement{ attachment, transaction, "select 1 from rdb$database" };
        statement.execute(transaction);
        // the handles first: getHandle() returns a temporary reference
        IReferenceCounted* resultSetHandle = statement.getResultSetHandle().get();
        IReferenceCounted* statementHandle = statement.getStatementHandle().get();
        Probe resultSet(resultSetHandle);
        Probe stmt(statementHandle);
        statement.free();
        std::cout << "Statement::free(), IResultSet: " << resultSet.released() << " releases\n";
        std::cout << "Statement::free(), IStatement: " << stmt.released() << " releases\n";
        IReferenceCounted* transactionHandle = transaction.getHandle().get();
        Probe tra(transactionHandle);
        transaction.rollback();
        std::cout << "Transaction::rollback(): " << tra.released() << " releases\n";
    }
    fbcpp::Attachment second{ client, database, fbcpp::AttachmentOptions()
        .setUserName("SYSDBA").setPassword("masterkey") };
    {
        IReferenceCounted* attachmentHandle = second.getHandle().get();
        Probe probe(attachmentHandle);
        second.disconnect();
        std::cout << "Attachment::disconnect(): " << probe.released() << " releases\n";
    }
    attachment.dropDatabase();
    return 0;
}

Output, the same with the client libraries of Firebird 5.0.4 and 3.0.11:

Transaction::commit(): 2 releases
Statement::free(), IResultSet: 2 releases
Statement::free(), IStatement: 2 releases
Transaction::rollback(): 2 releases
Attachment::disconnect(): 2 releases

Measured the same way, the Firebird calls alone (ITransaction::commit(), rollback(), IResultSet::close(), IStatement::free(), IAttachment::detach()) release exactly once, with both client libraries; each interface has one reference before the call.

Checked with main at 30a341b (v1.0.0), a Firebird 5.0.4 server, on Linux with g++ 13.3.

Possible fix

After a successful call, forget the pointer without releasing it, for example with a member of FbRef that works like std::unique_ptr::release(). When the call fails, the interface is not released by Firebird, so the reference stays and is released as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions