Skip to content

Commit 247ca1a

Browse files
committed
Make git_threads_shutdown run after git finalizers
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
1 parent a6c1571 commit 247ca1a

File tree

1 file changed

+18
-15
lines changed

1 file changed

+18
-15
lines changed

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Reflection;
55
using System.Runtime.CompilerServices;
6+
using System.Runtime.ConstrainedExecution;
67
using System.Runtime.InteropServices;
78
using LibGit2Sharp.Core.Handles;
89

@@ -13,6 +14,21 @@ internal static class NativeMethods
1314
{
1415
public const uint GIT_PATH_MAX = 4096;
1516
private const string libgit2 = "git2";
17+
private static readonly LibraryLifetimeObject lifetimeObject;
18+
19+
/// <summary>
20+
/// Internal hack to ensure that the call to git_threads_shutdown is called after all handle finalizers
21+
/// have run to completion ensuring that no dangling git-related finalizer runs after git_threads_shutdown.
22+
/// There should never be more than one instance of this object per AppDomain.
23+
/// </summary>
24+
private sealed class LibraryLifetimeObject : CriticalFinalizerObject
25+
{
26+
// Ensure mono can JIT the .cctor and adjust the PATH before trying to load the native library.
27+
// See https://github.com/libgit2/libgit2sharp/pull/190
28+
[MethodImpl(MethodImplOptions.NoInlining)]
29+
public LibraryLifetimeObject() { NativeMethods.git_threads_init(); }
30+
~LibraryLifetimeObject() { NativeMethods.git_threads_shutdown(); }
31+
}
1632

1733
static NativeMethods()
1834
{
@@ -29,21 +45,8 @@ static NativeMethods()
2945
String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable)));
3046
}
3147

32-
GitInit();
33-
AppDomain.CurrentDomain.ProcessExit += ThreadsShutdown;
34-
}
35-
36-
[MethodImpl(MethodImplOptions.NoInlining)]
37-
private static void GitInit()
38-
{
39-
// keep this in a separate method so mono can JIT the .cctor
40-
// and adjust the PATH before trying to load the native library
41-
git_threads_init();
42-
}
43-
44-
private static void ThreadsShutdown(object sender, EventArgs e)
45-
{
46-
git_threads_shutdown();
48+
// See LibraryLifetimeObject description.
49+
lifetimeObject = new LibraryLifetimeObject();
4750
}
4851

4952
public static string ProcessorArchitecture

0 commit comments

Comments
 (0)