8000 Call git_threads_shutdown() after last handle get disposed by jbialobr · Pull Request #358 · libgit2/libgit2sharp · GitHub
[go: up one dir, main page]

Skip to content

Call git_threads_shutdown() after last handle get disposed #358

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

Closed
wants to merge 20 commits into from

Conversation

jbialobr
Copy link
Contributor
@jbialobr jbialobr commented Mar 1, 2013

Implementation for #249 (comment)

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

If you have separate AddHandle and RemoveHandle methods, then you can use Interlocked.Increment and Interlocked.Decrement without a loop.

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

Also, you should only call AddHandle(-1) (or RemoveHandle) from an implementation of SafeHandle.ReleaseHandle().

The runtime ensures this is only called once, where Dispose can (and is allowed to) be called any number of times.

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

Your call to CheckForShutdown needs to check the value from the Interlocked method before the call, or git_threads_shutdown could be called more than once in multithreaded scenarios.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

Thanks, it is my first time with Interlocked. Omitting bugy imple 8000 mentation, do you think it is an acceptable solution?

@jamill
Copy link
Member
jamill commented Mar 1, 2013

Do we care about the NotOwnedSafeHandle derived classes? There are a bunch of types that do not actually free resources (but we still want the type safety and consistency of deriving from SafeHandleBase).

@nulltoken
Copy link
Member

Omitting bugy implementation, do you think it is an acceptable solution?

"The last one turns off the light" -> This sure looks like an interesting proposal.

/cc @xpaulbettsx @phkelley

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

@jamill No, the finalization process for those handles never makes any calls into libgit2.

@jamill
Copy link
Member
jamill commented Mar 1, 2013

@sharwell correct - but with the current implementation here, they are still included in the handle counting (unless I am mistaken).

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

The runtime will not call ReleaseHandle on these handles because they are constructed by calling the SafeHandle constructor that marks them as unowned handles. The handle count would never be decremented.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

unless I am mistaken

NotOwnedSafeHandle inherits from SafeHandle

@jamill
Copy link
Member
jamill commented Mar 1, 2013

NotOwnedSafeHandle inherits from SafeHandle

Thanks - I missed that originally, I was mistaken - my apolgies.

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

Since ReleaseHandle won't be called if the handle is invalid, we need to make sure that NativeMethods.AddHandle isn't called from the following constructors:

  • NullIndexSafeHandle
  • NullGitObjectSafeHandle

The concern at that point is what the P/Invoke layer does when a method returns the handle 0 - it will still construct the SafeHandle but it will be invalid so ReleaseHandle will never be called.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

I would introduce new abstract function IsReleasable().

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

It already exists as !(IsClosed||IsInvalid)

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

Yes but it can not be used to determine whether AddHandle or not. Maybe IsReleasable is not a good name. I meant that I would introduce a new function that will tell if AddHandle/RemoveHandle should be called.

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

You can delay the call to AddHandle by placing it in SafeHandleBase.IsInvalid. When IsInvalid is returning false for the first time for an instance, call NativeMethods.AddHandle.

This is based on the following documentation: http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.releasehandle.aspx

The ReleaseHandle method is guaranteed to be called only once and only if the handle is valid as defined by the IsInvalid property.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

Can you guarantee that invalid handle never becomes valid?

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

As I am not familiar with libgit handles: Can a valid handle becomes invalid?

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

You can delay the call to AddHandle by placing it in SafeHandleBase.IsInvalid. When IsInvalid is returning false for the first time for an instance, call NativeMethods.AddHandle.

I would prefer to call RemoveHandle from Dispose, ensuring it is called only once.

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

As I am not familiar with libgit handles: Can a valid handle becomes invalid?

Yes, someone could call SafeHandle.SetHandleAsInvalid which would prevent the resource from ever being freed. The impact here would be failing to free resources (and finally failing to call git_threads_shutdown), but it wouldn't crash.

I would prefer to call RemoveHandle from Dispose, ensuring it is called only once.

SafeHandle.ReleaseHandle is guaranteed to be called only once. SafeHandle.Dispose can be freely called any number of times and the absolute expectation is this should never ever be a problem.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 1, 2013

SafeHandle.ReleaseHandle is guaranteed to be called only once. SafeHandle.Dispose can be freely called any number of times and the absolute expectation is this should never ever be a problem.

Yes, but I can check if this is the first call to Dispose and only in that case call RemoveHandle

@sharwell
Copy link
Contributor
sharwell commented Mar 1, 2013

I sent you a pull request to cover the AddHandle case.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 2, 2013

As we can not assume that handle does not become valid after ~LibraryLifetimeObject was called I consider IsInvalid approach as unsafe. I pushed other proposal which unregisters handle from both finalizer and dispose.

@sharwell
Copy link
Contributor
sharwell commented Mar 2, 2013

My modified approach (the pull request yesterday) handles the case you refer to.

  • Call AddHandle from the constructor
  • Call RemoveHandle from IsInvalid if we determine that ReleaseHandle won't be called
  • Call RemoveHandle from ReleaseHandle for valid handles

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 2, 2013

What will happen when I call IsInvalid before it becomes valid?

@sharwell
Copy link
Contributor
sharwell commented Mar 2, 2013

That would require calling IsInvalid from the constructor.

Edit: Or a derived type manipulating the value of protected field SafeHandle.handle in a non-standard manner (i.e. not recommended). Even then the fallback path for registration in IsInvalid would likely catch it when the calling code checked to make sure the handle was valid.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 2, 2013
new LibraryLifetimeObject() //hendlesCount = 1
handle = new XXHanlde(); //hendlesCount = 2
hanlde.IsInvalid; //returns false //hendlesCount = 1
//handle becomes valid
//close app
~LibraryLifetimeObject(); // handle count becomes 0 and git_threads_shutdown() is called
handle.Dispose is called.

@sharwell
Copy link
Contributor
sharwell commented Mar 2, 2013

//handle becomes valid

How is this going to happen?

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 2, 2013

Newly created SafeHandleBase is invalid. Then it becomes valid when its handle value becomes non zero.

@sharwell
Copy link
Contributor
sharwell commented Mar 2, 2013

handle is a protected field that's assigned inside the P/Invoke layer before the handle is returned to the calling code. The only way to reassign it is through reflection or by deriving a new class from SafeHandleBase for the sole purpose of manipulating the shutdown process.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 2, 2013

But IsInvalid is public and can be called at any time. If you add an assert that invalid handle never becomes valid after IsInvalid was called then I accept your solution.

@sharwell
Copy link
Contributor
sharwell commented Mar 2, 2013

I'm about to send you a pull request with a "special" assertion that's safe for execution at this point in the application shutdown process, plus better documentation and a couple small tweaks.

@sharwell
Copy link
Contributor
sharwell commented Mar 2, 2013

It automatically updated the previous pull request (now there are a total of 4 commits in it)

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 3, 2013

@sharwell I think your solution is 99.9% safe. It fails only when one uses handle in unusual way. My solution uses the same technique as LibraryLifetimeObject and it is resistant to cases that your solution is not. As you know this project and handles better, let me know if you see any weak points in my solution.

@sharwell
Copy link
Contributor
sharwell commented Mar 3, 2013

What exactly do you define as "failure"? Currently I don't see any cases where yours handles a situation that mine does not.

  • Your code still calls Debug.Assert from RemoveHandle, which shouldn't be called from the finalizer.
  • My code detects and reports [most] situations where intentional misuse may result in the shutdown order breaking, and the report is made through an MDA which is reliable even during the CLR shutdown process.

@sharwell
Copy link
Contributor
sharwell commented Mar 3, 2013

Your use of a finalizer is a big problem as well. See C# - What does “destructors are not inherited” actually mean?.

In your implementation, if a SafeHandleBase is not explicitly disposed then UnregisterHandle will be called before ReleaseHandle (which was the original problem).

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 4, 2013

Thanks, I removed finalizer - it is unneccessary (and badly used as you noticed) since ReleaseHandle is called from SafeHandle.Dispose. If SafeHandle.Dispose will not be called then ReleaseHandle also will not be called, thus I think it is enough to unregister hanlde from SafeHandleBase.Dispose.

What exactly do you define as "failure"? Currently I don't see any cases where yours handles a situation that mine does not.

Since IsInvalid is a part of public API, there should be no limitation to use it. It is very hard to define what
intentional misuse means at this level.

@sharwell
Copy link
Contributor
sharwell commented Mar 4, 2013

There is no requirement that the CLR implement critical finalization of SafeHandle by calling Dispose. Now you are relying on an implementation detail to perform cleanup.

I believe the limitations on the use of IsInvalid in my implementation are completely reasonable. A user would have to derive a new type from SafeHandleBase and evaluate IsInvalid inside the constructor of that object, knowing full well that the handle is never valid at that point because the P/Invoke layer assigns the value after the object is constructed. External code can use IsInvalid anywhere/whenever.

@jbialobr
Copy link
Contributor Author
jbialobr commented Mar 4, 2013

I believe the limitations on the use of IsInvalid in my implementation are completely reasonable. A user would have to derive a new type from SafeHandleBase and evaluate IsInvalid inside the constructor of that object, knowing full well that the handle is never valid at that point because the P/Invoke layer assigns the value after the object is constructed. External code can use IsInvalid anywhere/whenever.

I see that regular case is to obtain a handle from native method. I thought that handle can be set by external code but this would be very unusual to expose handle to public scope. Thanks, I learnt a lot.

@sharwell
Copy link
Contributor
sharwell commented Mar 4, 2013

At this point I think we have a pretty solid solution worked out. Thanks for working through this with me 👍

@nulltoken
Copy link
Member

Guys, this is an awesome collaboration!

Could you please consider squashing those commits altogether?

{
int count = Interlocked.Decrement(ref handlesCount);
if (count == 0)
git_threads_shutdown();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Could you please surround this statement with braces?

@jbialobr
Copy link
Contributor Author

@sharwell As the most of the code comes from you, could you please squash it to one commit and create a new pull request?

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.

4 participants
0