Deadlock in the GC Finalizer thread
-----------------------------------

                 Key: DNET-382
                 URL: http://tracker.firebirdsql.org/browse/DNET-382
             Project: .NET Data provider
          Issue Type: Bug
          Components: ADO.NET Provider
    Affects Versions: 2.6
         Environment: Not important
            Reporter: Fernando Nájera
            Assignee: Jiri Cincura
            Priority: Blocker


The .NET Provider 2.6.0 sometimes deadlocks with the GC. After a lot of 
research I have found the problem: sometimes the GC is triggered in a "bad 
moment" causing the deadlock.

My case: I have an FbConnection and FbTransaction. I create a FbCommand, 
execute it, but forget to call .Dispose(). Then I call FbTransaction.Commit(), 
but during its execution, the GC is triggered (remember that GC can be 
triggered at any moment, and apparently I got the worst one). If the timing is 
bad enough, a deadlock happens.

Reproducing this issue is normally very difficult, but I have managed to create 
a test case that will always deadlock:

1. In FirebirdSql.Data.FirebirdClient, modify AssemblyInfo.cs and add:

[assembly: InternalsVisibleTo ("FirebirdSql.Data.UnitTests, 
PublicKey=0024000004800000940000000602000000240000525341310004000001000100efef0d6d73af0b39be7ad5932256d0dbcce6e4bfec20d3697f52d9057e61b9b432d026bce894d519ee4c4d8bfa0853b88c779c4718cf0f8cd070fddb62e9835113d334f9105456692a459c4de434e49b7a789b6785a49febf71d6fb0efffd58945e906ce1442fca026064610d9e89aa4cf15625b0c468b650db8e222cc2e37c3")]

2. In FirebirdSql.Data.UnitTests, change the project properties and make the 
dll signed using the same key as FirebirdSql.Data.FirebirdClient.

These two steps are required so the tests can access the field added in step 3.

3. In FirebirdSql.Data.FirebirdClient, modify 
FirebirdSql.Data.Client.Managed.Version10.GdsStatement to add a new field and a 
call inside TransactionUpdated.

// ...
// new field used by the tests to force a GC call exactly in the moment where 
it hurts the most
public static Action DEBUG_WhenTransactionUpdated;

protected override void TransactionUpdated(object sender, EventArgs e)
{
    // call the method set by the tests
    if (DEBUG_WhenTransactionUpdated != null) { DEBUG_WhenTransactionUpdated 
(); }
    lock (this)
    {
        if (this.Transaction != null && this.TransactionUpdate != null)
        {
// ...

Note that this change won't affect the DLL in any way as 
DEBUG_WhenTransactionUpdated is null by default.
But it provides a nice "injection point" for our test, used in step 4.

4. In FirebirdSql.Data.UnitTests, create a new unit test and add this test:

[Test]
public void TestHang ()
{
    FbConnectionStringBuilder csb = base.BuildConnectionStringBuilder ();

    try
    {
        using (FbConnection cnn = new FbConnection (csb.ToString ()))
        {
            cnn.Open ();
            using (FbTransaction tx = cnn.BeginTransaction ())
            {

                // NOTE: not "using" here
                FbCommand cmd = new FbCommand ("SELECT * FROM TEST", cnn, tx);
                cmd.ExecuteNonQuery ();
                cmd = null; // important - make GC think that we are finished 
with the command, and it can be gathered

                // GC can be triggered at any point - in this test, we trigger 
it exactly when we are inside the WhenTransactionUpdated event
                
global::FirebirdSql.Data.Client.Managed.Version10.GdsStatement.DEBUG_WhenTransactionUpdated
 = delegate
                {
                    // to be realistic, we will invoke the GC from yet a 
different thread
                    Thread th = new Thread (new ThreadStart (delegate
                    {
                        // simulate, in a different thread, a GC pass
                        GC.Collect ();
                        GC.WaitForPendingFinalizers ();
                        GC.Collect ();
                    })) { IsBackground = true };
                    th.Start ();

                    // give some time to the thread to run and cause the 
damage, then continue with the "TransactionUpdated" code
                    Thread.Sleep (2000);
                };

                // this will hang...
                tx.Commit ();
            }
        }
    }
    finally
    {
        // clean up... although this test will hang, so this code doesn't get a 
chance...
        
global::FirebirdSql.Data.Client.Managed.Version10.GdsStatement.DEBUG_WhenTransactionUpdated
 = null;
    }
}

5. Run the test. It will hang (never complete).

If you debug the situation, you will see this:

a) TestRunnerThread stack (in **inverse** order):

        
FirebirdSql.Data.UnitTests.DLL!FirebirdSql.Data.UnitTests.DeadlockWithGCTests.TestHang()
 Line 60 + 0xb bytes    C#
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.FirebirdClient.FbTransaction.Commit()
 Line 169 + 0xc bytes C#
            which takes lock on FbTransaction
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.Client.Managed.Version10.GdsTransaction.Commit()
 Line 174 + 0x2f bytes     C#
            which takes lock on database.SyncObject
>       
> FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.Client.Managed.Version10.GdsStatement.TransactionUpdated(object
>  sender = {FirebirdSql.Data.Client.Managed.Version10.GdsTransaction}, 
> System.EventArgs e = {System.EventArgs}) Line 659 + 0x11 bytes        C#
            which deadlocks while trying to lock on the GdsStatement
        mscorlib.dll!System.Threading.Monitor.Enter(object obj, ref bool 
lockTaken) + 0x14 bytes        


b) GC Finalizer thread (in **inverse** order):

        System.dll!System.ComponentModel.Component.Finalize() + 0x18 bytes      
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.FirebirdClient.FbCommand.Dispose(bool
 disposing = false) Line 396 + 0x8 bytes      C#
            which takes lock on the FbCommand
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.FirebirdClient.FbCommand.Release()
 Line 842 + 0xe bytes    C#
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.Common.StatementBase.Dispose()
 Line 177 + 0x10 bytes       C#
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.Client.Managed.Version10.GdsStatement.Dispose(bool
 disposing = true) Line 183 + 0xa bytes  C#
            which takes lock on the GdsStatement
        
FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.Common.StatementBase.Release()
 Line 261 + 0x10 bytes       C#
>       
> FirebirdSql.Data.FirebirdClient.DLL!FirebirdSql.Data.Client.Managed.Version11.GdsStatement.Free(int
>  option = 2) Line 203 + 0x21 bytes   C#
            which deadlocks while trying to lock the database.SyncObject
        mscorlib.dll!System.Threading.Monitor.Enter(object obj, ref bool 
lockTaken) + 0x14 bytes        

As forced as this situation might seem by the looks of the test, I have to say 
that this is not a border case at all: this bug is affecting my software and I 
have seen this deadlock happening in several ocassions in several computers.

I am going to change my code to ensure that I call FbCommand.Dispose() before 
losing the variable reference, which should prevent this deadlock.

However, considering that it is the GC thread then one that deadlocks (apart 
from the program itself), and that it is "easy" to forget to call .Dispose in 
every single object that implements IDisposable, it might be worth trying to 
fix this issue. Following the instructions of the Bug Tracker, this should be a 
Blocker Priority Issue as it effectively blocks the program when it happens - 
even if it happens very rarely.


-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: 
http://tracker.firebirdsql.org/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

       

------------------------------------------------------------------------------
vRanger cuts backup time in half-while increasing security.
With the market-leading solution for virtual backup and recovery, 
you get blazing-fast, flexible, and affordable data protection.
Download your free trial now. 
http://p.sf.net/sfu/quest-d2dcopy1
_______________________________________________
Firebird-net-provider mailing list
Firebird-net-provider@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/firebird-net-provider

Reply via email to