8000 Make git_threads_shutdown run after git finalizers by niik · Pull Request #249 · libgit2/libgit2sharp · GitHub
[go: up one dir, main page]

Skip to content

Make git_threads_shutdown run after git finalizers #249

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

Merged
merged 1 commit into from
Nov 22, 2012

Conversation

niik
Copy link
Contributor
@niik niik commented Nov 19, 2012

Problem

If you initiate a repository in order to extract a diff between the last commit and the working directory and 'forget'/ignore to properly dispose of the Repository instance you'll get an AccessViolationException ("Attempted to read or write protected memory. This is often an indication that other memory is corrupt.") on application shutdown.

The problem is that Repository destructor attempts to call git_repository_free after git_threads_shutdown has been called which I'm guessing means that thread local state which is potentially required as part of a shutdown isn't available.

git_threads_shutdown is supposed to be called right before "shutting down" the library. See global.c L16-40. At the moment the call to git_threads_shutdown is done from the AppDomain.ProcessExit event.

The problem is that the MSDN doesn't guarantee that AppDomain.ProcessExit runs after finalizers (on my machine it doesn't and I'm guessing that's actually the expected behavior).

Reproduce it

Here's an example of how to reproduce it:

static void Main(string[] args) {
    // path to repo which contains changes in the working directory.
    var repo = new Repository(@"e:\code\misc\libgit2sharp\");
    var changes = repo.Diff.Compare();
}

This will throw an AccessViolationException in git_repository_free when the GC calls the Repository destructor. The obvious and correct way to fix this is to simply make sure that the repo instance gets disposed first:

static void Main(string[] args) {
    using(var repo = new Repository(@"e:\code\misc\libgit2sharp\"))
        var changes = repo.Diff.Compare();
}

Possible solution

One way to tackle this problem would be to ignore it and just clearly state the importance of properly disposing your repositories but since libgit2sharp seems to be doing a lot of work to shut down cleanly in finalizers today I thought we should at least discuss it.

My hackish workaround is to create an object which derives from CriticalFinalizerObject which calls git_threads_init in the constructor and git_threads_shutdown in the destructor. Since this object is a CriticalFinalizerObject its destructor will run after any 'regular' destructors such as the Repository destructor thus ensuring that git_threads_shutdown is the last thing to happen on the PInvoke-side before the AppDomain is completely done.

From the blog of Chris Brumme:

All normal finalizable objects are either executed or discarded without finalization, before any critical finalizers are executed.

@niik
Copy link
Contributor Author
niik commented Nov 19, 2012

ping @tclem, @aroben: this is the finalization problem we were chasing 1.5 weeks ago

@niik
Copy link
Contributor Author
niik commented Nov 19, 2012

I should add that I'm not overly excited about this workaround. It feels a wee bit dirty but hopefully it's a starting point.

@tclem
Copy link
Member
tclem commented Nov 19, 2012

Nice patch. Man I wish we didn't have to do this. I wonder if @nulltoken has any clever ideas :)

@ethomson
Copy link
Member

There's been some interest in adding a non-global context to libgit2. This would mean that there are no more calls to git_threads_init() and git_threads_shutdown(). If this proceeds as planned, I think that this is the likely long-term solution here.

@niik
Copy link
Contributor Author
niik commented Nov 21, 2012

@dahlbyk It would make it very obvious that it shouldn't be instantiated by anyone else. I wasn't sure about the coding conventions when I wrote it. I'll fix.

@ethomson I agree that would be the optimal solution. Any gut feeling as to when we might see progress in that area?

As I stated earlier this feels like a very low priority issue since it's kind of self-inflicted when you don't dipose. On the other hand, from reading the code, the library amibition seems to be nice cleanup even without dispose and the AVException is a bit intimidating. Looking around at some SO-questions and such it's not uncommon to see code which doesn't dispose properly. Especially for people writing one-off "script-ish" console applications.

@nulltoken
Copy link
Member

@markus-olsson Looks like a very well thought PR!

I just wonder how well that would play on Mono considering 3627335

/cc @joncham

@niik
Copy link
Contributor Author
niik commented Nov 21, 2012

@nulltoken yep, I saw that commit. It didn't occur to me at the time that constructor calls would ever get inlined but a quick glance at the mono_method_check_inlining in the mono jitter (btw; how awesome is it that I can do that!) suggests there's no opt-outs for normal constructors, only classes with static constructors.

I'm guessing there's two ways of solving this, either we just decorate the constructor with a MethodImpl attribute to prevent any inlining or we keep the call to git_threads_init in a private static method of the NativeMethods class and just use the lifetime object for finalizing. My knee jerk reaction would be to decorate the constructor call. It's not like it's gonna hurt if the method isn't inlined.

I'm adding the attribute now and then I'll revert it if we decide otherwise

8000

@nulltoken
Copy link
Member

@markus-olsson Looks fine.

Last nitpick: could you please squash the commits into a nice cohesive one and remove the code beautification changes?

@niik
Copy link
Contributor Author
niik commented Nov 22, 2012

@nulltoken I had a hunch you'd want me to :) Sure. And I'll re-include the comment about why to prevent inlining in the first place since I seem to have missed that.

Darn it, the hotkey for formatting a document is muscle memory by now but it wasn't meant to go in the repo. I'll fix.

When initiating a repository in order to extract a diff between the last
commit and the working directory and 'forget'/ignore to properly dispose
of the Repository instance you'll get an AccessViolationException on
application shutdown.

The problem is that the finalizer for Repository attempts to call
git_repository_free after git_threads_shutdown has been called meaning
that TLS-state which is potentially required as part of a shutdown isn't
available.

git_threads_shutdown is supposed to be called right before "shutting
down" the library. See [global.c L16-40][1]. At the moment the call to
git_threads_shutdown is done from the [AppDomain.ProcessExit][2] event.

The problem is that the MSDN doesn't guarantee that
[AppDomain.ProcessExit][2] runs after finalizers (on my machine it never
does and I'm guessing that's actually the expected behavior).

Here's an example of how to reproduce it:

```c#
static void Main(string[] args) {
    // path to repo which contains changes in the working directory.
    var repo = new Repository(@"e:\code\misc\libgit2sharp\");
    var changes = repo.Diff.Compare();
}
```

This will throw an AccessViolationException in git_repository_free. The
obvious and correct way to fix this is to simply make sure that the repo
instance gets disposed.

```c#
static void Main(string[] args) {
    // path to repo which contains changes in the working directory.
    using(var repo = new Repository(@"e:\code\misc\libgit2sharp\"))
        var changes = repo.Diff.Compare();
}
```

One way to tackle this problem would be to clearly state that you really
have to dispose your repositories but since libgit2sharp seems to be doing
a lot of work to shut down cleanly in finalizers today I thought we should
at least discuss it.

My hackish workaround is to create an object which derives from
[CriticalFinalizerObject][3] which calls git_threads_init in the
constructor and git_threads_shutdown in the destructor. Since this object
is a CriticalFinalizerObject its destructor will run after any 'regular'
destructors such as the Repository destructor thus ensuring that
git_threads_shutdown is the last thing to happen on the PInvoke-side
before the AppDomain is completely done.

From the blog of [Chris Brumme][4]:

> All normal finalizable objects are either executed or discarded without
> finalization, before any critical finalizers are executed.

[1]: https://github.com/libgit2/libgit2/blob/development/src/global.c#L16-L40
[2]: http://msdn.microsoft.com/en-us/library/system.appdomain.processexit.aspx
[3]: http://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.criticalfinalizerobject.aspx
[4]: http://blogs.msdn.com/b/cbrumme/archive/2004/02/20/77460.aspx
@niik
8000
Copy link
Contributor Author
niik commented Nov 22, 2012

@nulltoken there you go.

@nulltoken nulltoken merged commit 247ca1a into libgit2:vNext Nov 22, 2012
@nulltoken
Copy link
Member

@markus-olsson Awesome troubleshooting and very clean fix! ✨

Merged in vNext

@jbialobr
Copy link
Contributor

I still get AV on exit from GitExtensions buiild from commit d7aecc0.
LibGit2Sharp.Core.LibraryLifetimeObject finalizer is called before RepositorySafeHandle get disposed.

Steps to reproduce:

  1. Run GitExtensions
  2. Change repository a few times.
  3. Close GitExtensions.

gitextensions/gitextensions#1429

@nulltoken
Copy link
Member

It looks like this happens when

  • The Repository is not explicitly disposed
  • The finalizer of RepositorySafeHandle runs after the finalizer of LibraryLifetimeObject

AFAIK even if the finalizer of LibraryLifetimeObject was deriving from CriticalFinalizerObject, I still think we wouldn't be completely protected as the order of execution is not guaranteed.

I can think of two options and I sincerely hope there are others because none of them really appeal to me:

  • Do no call git_threads_shutdown (cf. Paul's comment)
  • Throw a YouShouldHaveCalledDisposeException from every SafeHandle finalizer

Thoughts?

/cc @xpaulbettsx @sharwell @dahlbyk @phkelley

@sharwell
Copy link
Contributor

I agree with Paul's statement. You should take a very close look at the behavior of git_threads_shutdown and determine if failing to call this method, but successfully cleaning up every other object via SafeHandle-derived handles, will ever leave the repository in a bad state. The application itself will never be corrupted by this because it's already closing.

Last I checked, the git_threads_shutdown documentation didn't even hint at the information necessary to make a properly-informed decision on an issue like this, so I'll be watching for other comments as well.

@KindDragon
Copy link
Contributor

Why you not implement finalizer for Repository? I think finalizer for Repository should call Dispose(false).

@sharwell
Copy link
Contributor

Finalizers in C#/.NET don't work the way (most) people expect. If you don't have a rigorously justified reason for creating one for a very specific purpose where CriticalFinalizerObject (or derived) types aren't usable, then you shouldn't be adding one.

@KindDragon
Copy link
Contributor

Ok, but at least finalizer for Repository fix this crash for us.

@sharwell
Copy link
Contributor

Omitting the call to git_threads_shutdown has the following behavior:

  • Does not call TlsFree (Win32 API) or pthread_key_delete (pthreads)
    • Not a problem, the process is exiting anyway
  • Does not call git_mutex_free(&git__mwindow_mutex)
    • Not a problem, the process is exiting anyway
  • Does not call git_hash_global_shutdown()
    • This is a noop anyway for everything except the Win32 hashing
    • For Win32 hashing, CryptReleaseContext will not be called (not a problem)
    • For Win32 hashing, BCryptCloseAlgorithmProvider will not be called (not a problem)

@niik
Copy link
Contributor Author
niik commented Feb 28, 2013

So in conclusion no side effects at all from removing the call completely? In that case I'm all for it! 👍

@sharwell
Copy link
Contributor

For normal usage, definitely not a problem to remove it. For a specific case where someone is creating multiple new AppDomain, and loading libgit2sharp in those domains, then I think you could end up with a memory leak if the libgit2 library is loaded multiple times as a result. I wouldn't guess that P/Invoke allows reloading a native library multiple times though. If it's only loaded once, then multiple attempts to load libgit2sharp would actually be fine because calling git_threads_init multiple times is a nop.

@niik
Copy link
Contributor Author
niik commented Feb 28, 2013

Yes. AppDomains! I knew that there was something on my mind when I investigated this for the CFO hack. I wouldn't have thought it was even possible to load native libraries multiple times from a process so I guess we don't need to worry about that.

Is the nop-behavior of git_threads_init documented or is it an implementation detail?

@sharwell
Copy link
Contributor

It's an implementation detail, but probably wouldn't affect a decision on this issue anyway

@jbialobr
Copy link
Contributor

Thoughts?

  1. Track all repositories with weakreferences and dispose them before calling git_threads_shutdown.
    or
  2. Don't call to SafeHandleBase base.Dispose after git_threads_shutdown was called. If they weren't disposed explicitly, lets treat them as leaks.

@jbialobr
Copy link
Contributor

Omitting the call to git_threads_shutdown has the following behavior

But you don't know what else git_threads_shutdown will be doing in the future.

@sharwell
Copy link
Contributor

Track all repositories with weakreferences and dispose them before calling git_threads_shutdown.

git_threads_shutdown is called from a finalizer, so you aren't allowed to call WeakReference.Target for any sort of check like this.

Don't call to SafeHandleBase base.Dispose after git_threads_shutdown was called. If they weren't disposed explicitly, lets treat them as leaks

SafeHandle.Release is always called by the runtime, just not in a particular order if it's inside finalizers during application shutdown.

@sharwell
Copy link
Contributor

We could request that libgit2 document git_threads_shutdown as an optionally called method.

@jbialobr
Copy link
Contributor
jbialobr commented Mar 1, 2013

git_threads_shutdown is called from a finalizer, so you aren't allowed to call WeakReference.Target for any sort of check like this.

Could it be called from AppDomain.ProcessExit?

@jbialobr
Copy link
Contributor
jbialobr commented Mar 1, 2013

or
3. The last turns off the light.

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

AppDomain.ProcessExit is not guaranteed to be called, and adds another layer of confusion to the shutdown process. Not sure what you mean by 3.

I think removing the call and getting official documentation that the call to git_threads_shutdown is not required at process exit (to prevent future modifications to libgit2 from breaking libgit2sharp's assumptions) is by far the cleanest option.

@jbialobr
Copy link
Contributor
jbialobr commented Mar 1, 2013

I think removing the call and getting official documentation that the call to git_threads_shutdown is not required at process exit (to prevent future modifications to libgit2 from breaking libgit2sharp's assumptions) is by far the cleanest option.

I don't like this idea at all. I would never accept such a request.

@jbialobr
Copy link
Contributor
jbialobr commented Mar 1, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0