New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deadlock in the GC Finalizer thread [DNET382] #387
Comments
Commented by: Daniel Presser (danielpresser) I was having the exact same problem (quite frequently in my case), but it looks like DNET316 [1], which addresses some lock issues, fixed this. [1] DNET316 Here are the callstacks from the locked threads (using WinDbg) in version 2.6. 0:015> !threads 00ecf90c 04b8447d FirebirdSql.Data.Client.Managed.Version10.GdsStatement.Free(Int32) 00ecfc58 68f8ec61 [DebuggerU2MCatchHandlerFrame: 00ecfc58] |
Commented by: @cincuranet That's good to know. Thanks. |
Modified by: @cincuranetstatus: Open [ 1 ] => Closed [ 6 ] resolution: Fixed [ 1 ] Fix Version: 2.7 [ 10431 ] |
Submitted by: Fernando Nájera (fernandonajera)
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 ();
}
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.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):
> 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.
The text was updated successfully, but these errors were encountered: