3

I have a .net test application which creates table, uses them, then drops them, many times.

When I run this application against a Firebird 3.0.2 database with Firebird ADO.Net Data Provider 5.9.1, it fails at soon as it has to create a table with the same name than a previously dropped one: table does already exist!

Restarting the application dodge the trouble, but I do not want to restart it after each test.

This question is very similar but is hanging at the drop instead, and use directly the Firebird isql tool instead of a .Net application.

Is there a way to actually drop the tables in Firebird without restarting the application?

This application tests many other databases, without this issue: SQL-Server, SQL-Server Compact Edition, MySql, SQLite, Oracle, PostgreSQL.

Here is a MCVE failing for Firebird. Replace the two first lines with suitable code for having a connection string. All other code is just Firebird ADO.Net data provider and NUnit. It does not fail exactly as my actual application, but I think it is the same underlying trouble.

[Test]
public void CreateSelectDrop()
{
    var cfg = TestConfigurationHelper.GetDefaultConfiguration();
    var cnxStr = cfg.Properties[Environment.ConnectionString];
    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "create table test (id int not null primary key)";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "insert into test (id) values (1)";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "select id from test";
                using (var reader = cmd.ExecuteReader())
                {
                    Assert.IsTrue(reader.Read());
                    Assert.AreEqual(1, reader.GetInt32(0));
                    Assert.IsFalse(reader.Read());
                }
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "delete from test";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "drop table test";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }
}

Only selecting from the table was not enough. The trouble appeared only after I have added the delete to the test. It fails at the last transaction commit, the one of the drop, with message:

FirebirdSql.Data.FirebirdClient.FbException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
  ----> FirebirdSql.Data.Common.IscException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
   at FirebirdSql.Data.FirebirdClient.FbTransaction.Commit()
   at NHibernate.Test.DialectTest.FirebirdDialectFixture.CreateSelectDrop()

According to Nathan Brown in a Github discussion, this trouble seems bounded to Firebird ADO.Net Data provider only. He has narrowed it to switching from its 2.7.7 version to 3.0.0.

Frédéric
  • 8,372
  • 2
  • 51
  • 102
  • Please show some code, it sounds like you didn't commit the transaction. Tables are dropped on commit of the transaction, not on execute of the drop statement; depending on the isolation level and other concurrent transactions this may fail or wait on commit if the table is in use. – Mark Rotteveel Jun 04 '17 at 12:00
  • @MarkRotteveel, [here is the code](https://github.com/nhibernate/nhibernate-core/tree/master/src/NHibernate.Test). This is the NHibernate test project. The creates and drops are issued by its hbm2ddl tool. As this is an ORM, the concrete connections and commands are supplied by providers, with some abstraction layer for having an agnostic API (database vendor independent). So showing the code in such case would be a bit moot. The fact is, schema manipulations do not look transacted at all. But only Firebird seems to have issues with that. – Frédéric Jun 04 '17 at 17:28
  • Anyway, if it was a missing (auto?) commit case, and if Firebird ADO.Net Data Provider was compliant with ADO.Net contracts, it then should rollback those transactions as soon as the connection is returned to the pool. Instead of committing them when the pool is cleared. – Frédéric Jun 04 '17 at 17:32
  • Well, I have just tested what it gives to transact that tool, without clearing the pool: still failing at recreate table with same name after its drop, this time with `FbException : lock conflict on no wait transaction, unsuccessful metadata update - object TABLE "EMPLOYEE" is in use`. Although of course I have committed the drop transaction before trying the next create table. Only clearing the connection pool works, with or without transactions. But with transacted creates/drops, it must additionally be cleared before trying to drop the table. The tests I have used are themselves transacted. – Frédéric Jun 04 '17 at 17:55
  • Now I have added a MCVE in this question. One more detail: Firebird is already tested by NHibernate builds, but it is tested with Firebird v2.5 and in embedded mode. Now I am migrating that to Firebird v3 and using an actual Firebird server. And with that setup, it fails as written in this question. – Frédéric Jun 04 '17 at 18:56
  • Interesting; it could also be caused by prepared statements being kept in cache, although I'm not sure the Firebird ADO.net provider does that. I'm going to investigate, but it might take me a while. Note that Firebird embedded and Firebird server behave exactly the same (as they use the same database code), so you could possibly also have hit a bug in Firebird 3. – Mark Rotteveel Jun 05 '17 at 07:12
  • `application which creates table, uses them, then drops them` - do you really need that sequence? Maybe GTTs would be good enough, only dropping data in the tables, not the schema ? – Arioch 'The Jun 05 '17 at 08:28
  • "Only clearing the connection pool works, with or without transactions" - looks like some connections are still there. Do you use SYSDBA user to connect? If so, use mon$attachment or Trace API to see if all other connecitons were really let go before drop attempt. https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-appx05-montables.html and https://www.firebirdsql.org/file/community/conference-2014/pcisar/ and https://dba.stackexchange.com/questions/29919/ and http://www.firebirdfaq.org/faq95/ – Arioch 'The Jun 05 '17 at 08:34
  • @Arioch'The Sure connections are still there. The provider handles a connections pool, a feature meant to maintain the connection actually opened when it is released (to the pool), thus allowing a quicker re-opening of it. Although not actually closed, connection release to the pool should do required cleanup (like rollbacking pending transaction) for ensuring they do not hold database ressource (especially locks on objects) once released to the pool. – Frédéric Jun 05 '17 at 12:12
  • So either there are some bug in the data provider causing it to put back in pool connections still holding locks or similar things, or Firebird does not supports pooling at all because it would not permit to cleanup connection in another way than closing them. If it is the later, the Firebird ADO.Net Data provider should then remove its connection pool feature, but that will badly hurt performances. – Frédéric Jun 05 '17 at 12:14
  • > they do not hold database resource (especially locks on objects) They hold on to database caches. Just like the pool you talk about caches the connections despite it does not need them this very moment, exactly the same way Firebird instance(s) cache meta-data information. And unless this cache is dropped (with the connection), it exists. Firebird is not designed as .Net sidekick and perhaps is supporting connection pooling not in exactly the same way as .Net expects and implies side-effects of pooling to behave. But modifying database scheme is not a standard operation anyway. – Arioch 'The Jun 05 '17 at 12:19
  • Changing the very structure of database while other programs are working with that structure is quest for glory. Due to versioned foundation of FB it usually tolerates incrementing changes, adding objects. Without demanding you to get a monopolistic hold of the database. But with deleting objects it is different. If they are cached - then they are cached. Firebird benefits performance-wise from caching no less than .Net do. Additionally, Firebird can operate as uniprocess "super server" or multi-process "classic server" adapting to different loads, which further complicates cache sync possible – Arioch 'The Jun 05 '17 at 12:22
  • @Arioch'The, what I do there is a very common pattern for test projects working on test databases, for continuous integration validations. It is a bit off-topic to move the debate on whether we should do that or not. – Frédéric Jun 05 '17 at 12:32
  • Well, there are many common patterns out there, including copying user input into SQL text verbatim. And you are correct, estimation to which extent some pattern is common or unusual in some niche seems off-topic. I explain you the logic of Firebird core. If you are interested. If not - not. "continuous integration validations" to me sounds like program upgrades and database upgrades, that is what "continuous" is about. And program updates should be done in monopolistic mode. – Arioch 'The Jun 05 '17 at 12:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145875/discussion-between-frederic-and-arioch-the). – Frédéric Jun 05 '17 at 12:46

1 Answers1

2

It appears the table drops are delayed until the connections having used them are actually closed, not only returned to the pool. Clearing the connection pool causes those delayed drops to be actually done.

So adding something like following code before table drops solves this issue in my case:

using (var connection = GetConnection())
{
    FbConnection.ClearPool(connection);
}

This solution was found here in an isolated test among more than 5 000.

It looks like there is another option, calling FbConnection.ClearAllPool() instead. Although I have not checked it, the former is likely to clear only the connection pool for supplied connection connection-string, while the later clears the pool of all connections, regardless of their connection string.

Since it is a test application with some generic logic and also some specificity, the code I will actually use as a solution will be:

// Firebird will pool each connection created during the test and will 
// marked as used any table referenced by queries. It will delays those
// tables drop until connections are actually closed.
// This results in other tests failing when they try to create tables with
// same name.
// By clearing the connection pool the tables will get dropped. This is done
// by the following code.
// Moved from NH1908 test case, contributed by Amro El-Fakharany.
var clearConnection = Sfi.ConnectionProvider.GetConnection();
try
{
    var fbConnectionType = clearConnection.GetType();
    var clearPool = fbConnectionType.GetMethod("ClearPool");
    clearPool.Invoke(null, new object[] {clearConnection});
}
finally
{
    Sfi.ConnectionProvider.CloseConnection(clearConnection);
}
Frédéric
  • 8,372
  • 2
  • 51
  • 102
  • Sometimes FB3 does not disconnect, when you asker it for, until quite a delay passes. See Fb3 Release Notes on the >>“Linger” Database Closure for Superserver<< topic. – Arioch 'The Jun 05 '17 at 08:44
  • BTW, did you tried another ADO.Net provider - http://ibprovider.com/eng ? – Arioch 'The Jun 05 '17 at 12:26
  • No, I have not tried another provider. We, at NHibernate, favor testing what seems to be the default configuration. Since Firebird official site points to its data provider, that is the one we use for our test. If there are evidences that it is not the mainstream .Net provider for Firebird, why not switching, but we do not know of such evidences. Since currently no one ever asked for including that another data provider in NHibernate default providers, contrary to the one we currently use, it does not look like this other one is much used by our users. – Frédéric Jun 05 '17 at 12:36
  • There are two providers, one FLOSS and one commercial. Users are free to choose what they like more. So are you. However if you would argue to Jiri http://cincura.net that his provider works wrong unless it would implement special support for nHibernate test patters, then showing that other FB providers added this support (if they do) could be an extra argument. – Arioch 'The Jun 05 '17 at 12:40
  • I am not here for arguing, I have posted here in case there was some better solution than bluntly clearing the pool. Changing the provider does not look as an option to me either, as this will miss testing what looks like being the most common case. – Frédéric Jun 05 '17 at 12:44
  • Do not delete meta-data objects while there are live connections that are/were using it. Control which connections use which objects and close them before deleting metadata. Try to minimize creating/deleting metadata (to avoid name clashing, when different connections might want to create same named object, if nothing else), with regard to tables you may use GTTs that have persistent scheme and volatile data. All in all, Firebird was not created for concurrent and parallel database restructurings processes. Best of all would be to rethink the program, so it would reach its goals without that. – Arioch 'The Jun 05 '17 at 12:50
  • This is not an option. This is a test program with more than 5 000 tests. We will not do such a change for one database which would be unable to support a common pattern supported by all other databases we test. We will rather drop such database from our tests. – Frédéric Jun 05 '17 at 13:07
  • You may try to switch between firebird SS/CS modes, this may change connections cache semantics. You may try to use firebird only with no connections pool mode. Or you may let this task for integrating those programs together for those users in the future who would need it, if any. – Arioch 'The Jun 05 '17 at 13:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145881/discussion-between-frederic-and-arioch-the). – Frédéric Jun 05 '17 at 13:29