If you have a finalizer, watch your ctor
We recently got into a very bad state with our build server. We had a few dozens failing tests in an area of the code that didn’t change in months. That was annoying, to say the least. I tried to run this on my machine, to see what was going on, and we got an even worse state, a process crash.
The good thing was that this is managed code, so you get stack traces, and we were able to figure out that the issue is with the SqlClient assembly. It looks like a recent change meant that loading it by reflection will give you the netstadnard 2.0 release, which is basically just a filler that always throw PlatformNotSupported. That is perfectly fine, we’ll change the way we do things to load the direct reference.
What killed us was this:
This will kill the process. How is that?
Well, DbConnection inherits from Component, and Component has a finalizer. Care to guess how what will happen when that is called?
So here is what will happen, the ctor throws, so no one can have an instance of this class. But the finalizer is already tracking it, and will call the finalizer. The finalizer will call the Dispose method, and that will end up throwing, and an exception during finalization is fatal to the entire process. Bye!
The general idea is that when you are working with a throwing ctor, you either make sure to call GC.SupprressFinalize or you cleanup in such a way that you can safely finalize things.
I took the liberty of reporting this to corefx.
If you are always going to throw in the constructor, why throw in the finalizer ? The only options are: 1. The constructor runs until it throws the PlatformNotSupportedException. No need to run the finalizer here - the object wasn't fully initialized anyway. 2. Some other finalizer is thrown prior to the PlatformNotSupportedException. Should work the same as case 1.
What does throwing in the finalizer get you here?
Nothing, it's just a bug.
Why didn't you report this issue right after tackling down the cause? Just wondering why Svick had to report it.
The object is actually instantiated before the constructor is called and therefore will call the finalizer anyway
There is a typo in: netstadnard 2.0 release
Dom, I assumed that this was intentional and that the issue was us picking up the wrong assembly via reflection.