-
Notifications
You must be signed in to change notification settings - Fork 899
Finalizer can crash process when native libgit2 failed to load #1436
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
Comments
Hmm, interesting. Am I reading correctly that the "inner" exception is the Putting aside the correctness of the utility of the finalizer, I'm not sure that I understand why the Obviously I'm not understanding something. Sorry, this is just musing, I haven't stepped through this to understand why I'm wrong. But I will unless unless somebody hits me with the clue stick before I get to this. |
It appears the static finalizer is getting invoked when the app domain is coming down, however since the static constructor was never invoked, the runtime is "helpfully" invoking it in the call to the finalizer. If I'm correct, this is seriously putting the cart before the horse. @AArnott the finalizer is only closing a handle, this is exactly what they're for. It is the runtime's decision to call the c'tor during finalization that is mind-blowing. One bit I would add here is that the method is attributed with (from: https://msdn.microsoft.com/en-us/library/ms228973(v=vs.110).aspx)
Given the CLR rules about CER, the finalizer will always be called and should never throw. Seems like there might be a bug... |
I don't think there's any such thing as a "static finalizer". It's an (instance) finalizer. And this finalizer breaks the rules of avoiding all other reference types that I mentioned initially. One problem with breaking this rule is during AppDomain shutdown, other reference types are in an unknown state (since appdomain finalization does not happen in dependency order so the ref types you reference may have already been destroyed). This finalizer calls
Now particularly interesting is that this BTW, in my experience I find it's really bad for a cctor to throw. The documented CLR rules guarantee that a cctor has executed before any static or instance members are invoked on it, but it doesn't document how much earlier it runs than when the last moment it must run. In particular, from this documentation:
In other words, the cctor's could be run long before they are needed (or perhaps before other state like env vars that they implicitly depend on has occurred). So I find it's best to keep cctor's very simple, if present at all. So my advice for fixing this would be any or all of the following:
Any one of these should be sufficient to solve the problem. And the last one looks by far the easiest and wouldn't introduce a breaking change. |
The pattern is generally more like:
Not sure how we get from here to there, but making all of this stuff non-static could be a start. |
The official dispose pattern is closer to this: ~TypeName() { Dispose(false); }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing)
{
// dispose of managed resources.
}
// Dispose of native resources.
IntPtr handle;
if ((handle = Interlocked.Exchange(ref memberHandle, IntPtr.Zero)) != IntPtr.Zero) {
CloseHandle(handle); // where CloseHandle is a direct p/invoke call that doesn't require ref types.
}
} |
It seems to me that there are two issues here; the static constructor shouldn't be throwing and there are issues with the finalizer. Let's tackle the cctor first and squirrel away the load failures, or simply catch them and let subsequent pinvokes fail to load also. |
That usual pattern is how to make 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 You can make the finalizer trivial ( 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 I'll simplify the static constructor and the finalizer and just make then call |
Oh, that's exactly what you said, @AArnott :
Whoops. Reading comprehension fail on my part. |
|
You're confusing ctor with cctor I think. The exception isn't thrown due to an allocation error. Look at the Inner exception: it throws because it cannot find the native binary. |
I don't think that @whoisj meant allocation in terms of |
In @whoisj's event sequence, the finalizer invokes a ctor. It does not. The callstack appears to suggest the cctor is invoked, but that doesn't happen either. In fact the Finalizer never really runs, because the CLR sees that it would need a cctor to have run but it failed in the past so it just rethrows right then and there without having tried anything. I'd be interested in what @whoisj means by "static instance" as well -- that sounds like a contradiction in terms and I'm not sure whether you mean the NativeMethods static class (which has no instance) or the nested class (which seems to have exactly one instance, but is not itself static). |
Just to remind you folks that the failure we're seeing is in Mono Runtime, not the CLR. I'm sure most of the above still holds, just another caveat to keep in mind. |
@KirillOsenkov Sorry, which failure are you talking about? |
The one in this bug - the crash Andrew reported is from Mono runtime. This is when trying to build a project on a Mac that uses NerdBank.GitVersioning, which in turn uses libgit2sharp, which in turn fails to find or load the native git binary (git2-1196807). |
Oh, I see what you're saying. It seemed to behave identically in the CLR and in Mono for me when the native library couldn't be loaded. (And #1438 similarly seems to correct it in both.) |
Oh, how wonderful, there is a fix pending? You guys don't disappoint! |
Fixed via #1438 |
I'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:
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.
The text was updated successfully, but these errors were encountered: