Finalizer can crash process when native libgit2 failed to load
See original GitHub issueI’ve hit this myself and worked around it by helping libgit2sharp find the native binary. But for those times when it still fails it should be permissible for the caller to catch the exception regarding not finding the native binary and move on. But in this case, libgit2sharp has a finalizer which ends up re-throwing the exception. And when finalizers throw, the process goes down:
[ERROR] FATAL UNHANDLED EXCEPTION: System.TypeInitializationException: The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception. ---> System.DllNotFoundException: git2-1196807
at (wrapper managed-to-native) LibGit2Sharp.Core.NativeMethods:git_libgit2_init ()
at LibGit2Sharp.Core.NativeMethods+LibraryLifetimeObject..ctor () [0x00006] in <48079e5da5364f5db01fd1de5fb12b0d>:0
at LibGit2Sharp.Core.NativeMethods..cctor () [0x00054] in <48079e5da5364f5db01fd1de5fb12b0d>:0
--- End of inner exception stack trace ---
at LibGit2Sharp.Core.NativeMethods+LibraryLifetimeObject.Finalize () [0x00000] in <48079e5da5364f5db01fd1de5fb12b0d
By the way, this is too much work for a finalizer to do. The only safe thing for a finalizer (at least during appdomain shutdown) is access structs. Reference types like LibraryLifetimeObject
aren’t safe to assume they can be used.
So at least the Finalize method should catch this exception and swallow it. But preferably, it should invoke native methods and access its own struct fields only.
Issue Analytics
- State:
- Created 6 years ago
- Comments:18 (12 by maintainers)
Top GitHub Comments
That usual pattern is how to make
IDisposable
interact nicely with finalizers, so that you don’t wind up invoking the native cleanup in both. We don’t have anythingIDisposable
here, we only do finalizers, so havingDispose
methods wouldn’t have utility for us, and we should just do our native cleanup code.However I do want to point out that I think that the title of this issue is misleading - despite being in the stack trace, it’s not the finalizer that’s throwing here, it’s the static constructor. Which will fail with a
TypeInitializationException
since there was an exception in the static constructor. That the finalizer is in the stack trace is a bit odd, I concede. I suppose what’s actually going on is that the finalizer just rethrows the initializer’s exception?You can make the finalizer trivial (
int i = 1+1;
) to convince yourself that it’s not doing anything unsafe and it will still be blamed in the stack trace.Now having said that, we don’t need this reference counting magic anymore. I’m not entirely clear why this would have been useful in the first place. It was so that the
SafeHandleBase
could ensure that it ran finalizers before callinggit_libgit2_shutdown
… but I don’t understand how theLibraryLifetimeObject
’s finalizer could be called beforeNativeMethods
is “finalized”, which is not deterministic but definitely cannot happen before it’s used last. IOW, nothing that callsNativeMethods
can outlast this finalizer… So we don’t need to refcount. (And in fact we don’t; we don’t have objects that extendSafeHandle
and do this reference counting anymore, so it’s only ever incremented in the cctor and decremented in the finalizer.)I’ll simplify the static constructor and the finalizer and just make then call
git_libgit2_init
andgit_libgit2_shtudown
, which is all they’re doing anyway.Fixed via #1438