We encountered an error in FB 2.1.0 and 2.5.1 in some web applications:

================================================

Message: Object reference not set to an instance of an object. 

Source: FirebirdSql.Data.FirebirdClient Stack trace: at 
FirebirdSql.Data.FirebirdClient.FbCommand.RollbackImplicitTransaction() at 
FirebirdSql.Data.FirebirdClient.FbCommand.CommitImplicitTransaction() at 
FirebirdSql.Data.FirebirdClient.FbCommand.DiscardImplicitTransaction() at 
FirebirdSql.Data.FirebirdClient.FbCommand.ExecuteReader(CommandBehavior 
behavior) at 
FirebirdSql.Data.FirebirdClient.FbCommand.ExecuteDbDataReader(CommandBehavior 
behavior) at 
System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior
 behavior) at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, 
DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, 
IDbCommand command, CommandBehavior behavior) at 
System.Data.Common.DbDataAdapter.Fill(DataTable[] dataTables, Int32 
startRecord, Int32 maxRecords, IDbCommand command, CommandBehavior behavior) at 
System.Data.Common.DbDataAdapter.Fill(DataTable dataTable) at <et-cetera> 
==================================================

Similar errors were occurring too frequently from various Firebird queries (all 
SELECT; no updates, deletions or insertions), some very simple, some very 
complex. We tried adding explicit transactions and committing in lieu of 
implicit transaction control, to no avail.

We determined that the exception originated in the following Firebird code:

public FbDataReader ExecuteReader(CommandBehavior behavior) { 

lock (this) 

{ 

this.CheckCommand(); 

try 

{ 

this.ExecuteCommand(behavior, true); 

} 

catch (IscException exception) 

{ 

this.DiscardImplicitTransaction(); 

throw new FbException(exception.Message, exception); 

} 

catch 

{ 

this.DiscardImplicitTransaction(); 

throw; 

} 

} 

this.activeReader = new FbDataReader(this, this.connection, behavior); 

return this.activeReader;

} 

Specifically, the line "this.ExecuteCommand(behavior,true);". Because this line 
is enclosed within a try/catch block, the exception is handled. However, this 
alters program flow to the catch block. Inside the catch block, the 
DiscardImplicitTransaction() method is called before the Exception is thrown. 
Unfortunately, that method calls CommitImplicitTransaction: 

internal void CommitImplicitTransaction() { 

if ((this.HasImplicitTransaction && (this.transaction != null)) && 
(this.transaction.Transaction != null)) 

{ 

try 

{ 

this.transaction.Commit(); 

} 

catch (Exception) 

{ 

this.RollbackImplicitTransaction(); 

throw; 

} 

finally 

{ 

if (this.transaction != null) 

{ 

this.transaction.Dispose(); 

this.transaction = null; 

this.implicitTransaction = false; 

} 

if (this.statement != null) 

{ 

this.statement.Transaction = null; 

} 

} 

}

} 

 

>From the stack trace we know the line "this.transaction.Commit()" throws a new 
>Exception. Because the line is encased in a try/catch block, this exception is 
>caught and program flow is redirected to the catch block. The catch block 
>calls "this.RollbackImplicitTransaction()" before throwing the exception up 
>the call stack. However, before the exception can be rethrown, 
>this.RollbackImplicitTransation() throws the NullReferenceException. Here is 
>the RollbackImplicitTransaction method: 

internal void RollbackImplicitTransaction() { 

if ((this.HasImplicitTransaction && (this.transaction != null)) && 
(this.transaction.Transaction != null)) 

{ 

int transactionCount = 
this.Connection.InnerConnection.Database.TransactionCount; 

try 

{ 

this.transaction.Rollback(); 

} 

catch 

{ 

if (this.Connection.InnerConnection.Database.TransactionCount == 
transactionCount) 

{ 

IDatabase database = this.Connection.InnerConnection.Database; 

database.TransactionCount--; 

} 

} 

finally 

{ 

this.transaction.Dispose(); 

this.transaction = null; 

this.implicitTransaction = false; 

if (this.statement != null) 

{ 

this.statement.Transaction = null; 

} 

} 

}

} 

The NullReferenceException is not handled by the Firebird DLL and is passed up 
the call stack to our DLL. Consequently, we don't see the two original 
exceptions, making it next to impossible to infer what caused them.

But I was able to finally figure it out. I was able to reproduce the error in a 
multithreaded environment. When I locked the code which called the DLL which 
uses Firebird, the error went away. Without the lock, the error also did not 
occur when our SQL DLL (issuing Firebird queries) was called serially 
(sequential calls rather than parallel). But when a bunch of threads were fired 
up all at once, dann war die Hoelle los. That is to say, one thread would bomb 
and take the rest with it. Ugly.

This gave me a hunch as to what the problem was. I tried commenting out the 
"FbConnection.ClearAllPools()" statement, and voila! End of error.

We had started clearing all pools a few years ago because we were seeing 
persistent connections on our servers, because the .NET Data Provider pools 
connections even after they are closed, so that they can be reused on the next 
call to the database. The pooled connection stays "alive" on the server until 
the application is closed; unfortunately, since these were web applications, 
they were never truly closed. So, when any thread issues a "clear all pools" 
command, it knocks the slats right out from under all the other threads sharing 
the connection pool.

The solution was simply to change our connection string from "Pooling=True" to 
"Pooling=False". That fixed it. It rendered the "clear all pools" statement 
moot, although I commented it out for good measure, even though testing showed 
that it didn't hurt anything to leave it active. The small amount of overhead 
incurred by not pooling is much preferable to bombing.

Thus, it appears that connection pooling in Firebird is mighty dangerous in a 
multithreading environment. You can, to be sure, use a lock block around all 
code which issues SQL commands to Firebird, and this will also prevent the 
error; but it will also create a bottleneck. Better just to turn connection 
pooling off.

Comments and additional insight would be welcome and appreciated.
                                          
_________________________________________________________________
Your E-mail and More On-the-Go. Get Windows Live Hotmail Free.
http://clk.atdmt.com/GBL/go/201469229/direct/01/
------------------------------------------------------------------------------
SOLARIS 10 is the OS for Data Centers - provides features such as DTrace,
Predictive Self Healing and Award Winning ZFS. Get Solaris 10 NOW
http://p.sf.net/sfu/solaris-dev2dev
_______________________________________________
Firebird-net-provider mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/firebird-net-provider

Reply via email to