-
Notifications
You must be signed in to change notification settings - Fork 899
Enable thread affinity for life of Repository #203
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
Conversation
@dahlbyk Hmm. Are you sure about this? Is it "safe" or recommended to do it at the repository level? Although I've never used this pattern, I was rather expecting this Begin/End to embrace each interop call and keep it for the smallest possible duration. Similarly to a lock pattern. Google seems to agree with this. If you agree with the former statement, in order to reduce the code noise, how about something like the following. It's not a very elegant approach, but I haven't been able to make lambdas play nicely with ---
LibGit2Sharp/Core/NativeMethods.cs | 19 +++++++++++++++++++
LibGit2Sharp/Repository.cs | 7 ++++++-
2 files changed, 25 insertions(+), 1 deletions(-)
diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs
index 274f2d7..812a9b7 100644
--- a/LibGit2Sharp/Core/NativeMethods.cs
+++ b/LibGit2Sharp/Core/NativeMethods.cs
@@ -4,6 +4,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Threading;
using LibGit2Sharp.Core.Handles;
namespace LibGit2Sharp.Core
@@ -788,5 +789,23 @@ public static bool RepositoryStateChecker(RepositorySafeHandle repositoryPtr, Fu
[DllImport(libgit2)]
public static extern void git_treebuilder_free(IntPtr bld);
+
+ public static IDisposable ThreadAffinity()
+ {
+ return new DisposableThreadAffinityWrapper();
+ }
+
+ internal class DisposableThreadAffinityWrapper : IDisposable
+ {
+ public DisposableThreadAffinityWrapper()
+ {
+ Thread.BeginThreadAffinity();
+ }
+
+ public void Dispose()
+ {
+ Thread.EndThreadAffinity();
+ }
+ }
}
}
diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs
index 395f5be..4b69aa7 100644
--- a/LibGit2Sharp/Repository.cs
+++ b/LibGit2Sharp/Repository.cs
@@ -45,7 +45,12 @@ public Repository(string path, RepositoryOptions options = null)
{
Ensure.ArgumentNotNullOrEmptyString(path, "path");
- int result = NativeMethods.git_repository_open(out handle, path);
+ int result;
+ using (NativeMethods.ThreadAffinity())
+ {
+ result = NativeMethods.git_repository_open(out handle, path);
+ }
+
if (result == (int)GitErrorCode.NotFound)
{
--
1.7.8.msysgit.0 This would definitely be a sweet spot for some AOP... Let's hope it's a temporary fix (@carlosmn?). While we're here, do you think that #172 was also related to some threading issue ("Switching to IntPtr fixes the issue. No idea why it works sometimes...")? How about trying to put back the marshaller directive on the callbacks and check if the thread affinity was the root cause? As most of the issues are being raised from a Posh-Git context, would it be possible to "script" some scenario which would put the interop layer under some stress? I'd be interesting to make sure that we're actually fixing something ;-) |
No, it was just a starting point. I guess it's not clear to me the scope in which thread affinity is important, so I cast a wide net. :) I can try specifically in the scope of the status call (along with rolling back #172) and see if that helps. I like the |
If I could repro reliably, yes, but I haven't been able to yet. |
@nulltoken yeah, this is temporary until we move the control structures to be global rather than thread-local. While it's not necessarily needed for every call in the repo, we can't simply wrap the native calls in it because all calls that read from a packfile need to happen on the same thread, and we may switch in between them. The idea in the library was this one anyway, anything to do with a repo happens in the same thread. It turns out that's not realistic for most languages/systems, so we'll have to move away from it. |
So it sounds like repository-wide thread affinity is the way to go? I wonder how that will impact performance... The update I just pushed to this PR only uses affinity while iterating over status, which is the only place posh-git uses libgit2. I'll try it out locally for a while (and you can too: https://github.com/dahlbyk/posh-git/tree/libgit2) and see the failures persist. |
Yes, every native method call and their related
In progress ;-) |
@dahlbyk From what I understand we'll still have to stick with Currently, we're applying the following pattern int result;
using (NativeMethods.ThreadAffinity())
{
result = NativeMethods.git_repository_open(out handle, path);
Ensure.Success(result); // The error message is fetched here is result < 0
} I was thinking if we could change it to something like this: ErrorStruct result; // Contains an int and a string fields
result = Proxy.git_repository_open(out handle, path);
Ensure.Success(result); // Only throws exception
[...]
internal class Proxy
{
public ErrorStruct git_repository_open(out RepositoryHandle handle, string path)
{
int result;
using (NativeMethods.ThreadAffinity())
{
result = NativeMethods.git_repository_open(out handle, path);
return BuildErrorStruct(result); // The error message is fetched here is result < 0
}
}
} Cons:
Pros:
|
By repository-wide, I meant between |
I think this is the correct way of handling this. We should only ensure that there's no thread switch between a function call, its potential callbacks and the retrieval of the error message. |
One option would be to skip a separate proxy class and just add "improved" methods to diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs
index 887ed10..cc1bece 100644
--- a/LibGit2Sharp/Core/NativeMethods.cs
+++ b/LibGit2Sharp/Core/NativeMethods.cs
@@ -695,7 +695,7 @@ public static void git_status_foreach(RepositorySafeHandle repo, status_callback
}
[DllImport(libgit2)]
- public static extern int git_tag_create(
+ private static extern int git_tag_create(
out GitOid oid,
RepositorySafeHandle repo,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name,
@@ -705,6 +705,20 @@ public static void git_status_foreach(RepositorySafeHandle repo, status_callback
[MarshalAs(UnmanagedType.Bool)]
bool force);
+ public static GitOid git_tag_create(
+ RepositorySafeHandle repo,
+ [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name,
+ GitObjectSafeHandle target,
+ SignatureSafeHandle signature,
+ [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message,
+ [MarshalAs(UnmanagedType.Bool)]
+ bool force)
+ {
+ GitOid oid;
+ Ensure.Success(git_tag_create(out oid, repo, name, target, signature, message, force));
+ return oid;
+ }
+
[DllImport(libgit2)]
public static extern int git_tag_create_lightweight(
out GitOid oid,
diff --git a/LibGit2Sharp/TagCollection.cs b/LibGit2Sharp/TagCollection.cs
index 89d83fb..a6f0d94 100644
--- a/LibGit2Sharp/TagCollection.cs
+++ b/LibGit2Sharp/TagCollection.cs
@@ -89,16 +89,12 @@ public virtual Tag Add(string name, string target, Signature tagger, string mess
string prettifiedMessage = ObjectDatabase.PrettifyMessage(message);
- int res;
using (var objectPtr = new ObjectSafeWrapper(objectToTag.Id, repo))
using (SignatureSafeHandle taggerHandle = tagger.BuildHandle())
{
- GitOid oid;
- res = NativeMethods.git_tag_create(out oid, repo.Handle, name, objectPtr.ObjectPtr, taggerHandle, prettifiedMessage, allowOverwrite);
+ NativeMethods.git_tag_create(repo.Handle, name, objectPtr.ObjectPtr, taggerHandle, prettifiedMessage, allowOverwrite);
}
- Ensure.Success(res);
-
return this[name];
} |
I think I prefer to have a second class to host the proxyfying.
Why not. I'm not completely sure if embedding the call to |
@carlosmn I think I need your advice to check if I understand the current issue correctly. From what I understand, the libgit2/libgit2#884 makes it OK to call the repo from different threads. Before proceeding to fix this, do you think there is something else that I'm missing? |
It looks like you got it. I would qualify the first sentence by saying that you can call it from an arbitrary thread ("different threads" could be interpreted as many threads at the same time, which you shouldn't do). |
See libgit2/libgit2#624 and libgit2/libgit2#624 (comment)
One caveat:
Thread.BeginThreadAffinity()
is decorated withSecurityCriticalAttribute
: