Skip to content
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

Add Dispose/IDisposable to GCHandle #54792

Open
Sergio0694 opened this issue Jun 27, 2021 · 14 comments
Open

Add Dispose/IDisposable to GCHandle #54792

Sergio0694 opened this issue Jun 27, 2021 · 14 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Runtime.InteropServices
Milestone

Comments

@Sergio0694
Copy link
Contributor

Follow up from @jkotas's comment: #19459 (comment). The same issue also contains an extensive conversation about this proposal, with some practical use case scenarios as well (in particular, see this comment by @jkoritzinsky: #19459 (comment)).

Proposed API

namespace System.Runtime.InteropServices
{
      public struct GCHandle
+        : IDisposable
      {
+        public void Dispose();
      }
}

Usage Examples

// Given...
[DllImport("nativelib")]
public static unsafe extern void DoCallback(IntPtr state, delegate*<IntPtr, void> callback);

[UnmanagedCallersOnly]
public static void Callback(IntPtr state)
{
    GCHandle handle = GCHandle.FromIntPtr(state);
    object state = handle.Target;
}

// ...this PR would enable this
using (GCHandle handle = GCHandle.Alloc(state))
{
    DoCallback(handle, &Callback);
}

Risks

Copying/boxing a GChandle value and calling Dispose on each copy will still possibly result in a crash. This is the same behavior as other approved APIs already though (eg. DependentHandle), and it's a limitation with C# not having proper move semantics.

@Sergio0694 Sergio0694 added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jun 27, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jun 27, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@AaronRobinsonMSFT AaronRobinsonMSFT added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation untriaged New issue has not been triaged by the area owner labels Jun 28, 2021
@AaronRobinsonMSFT
Copy link
Member

Based on the conversation in #19459 I think there is enough to move onto an API review.

@stephentoub
Copy link
Member

I assume the behavior / implementation is just:

public void Dispose() => Free();

@AaronRobinsonMSFT
Copy link
Member

AaronRobinsonMSFT commented Jun 29, 2021

@stephentoub I think it would check if the handle was allocated first.

public void Dispose()
{
    if (IsAllocated)
        Free();
}

@stephentoub
Copy link
Member

Yup. Thanks.

@jkotas
Copy link
Member

jkotas commented Jun 29, 2021

public void Dispose()
{
   if (IsAllocated)
       Free();
}

This implementation is both not thread safe (you cannot safely call it from multiple threads and assuming first one wins) and slower than necessary (pays extra for the interlocked compare change inside Free).

I think we would either want thread safe implementation; or non-thread safe fast implementation (w/o interlocked compare exchange).

@stephentoub
Copy link
Member

I think we would either want thread safe implementation; or non-thread safe fast implementation (w/o interlocked compare exchange).

Yes, sorry, I was just trying to ask about the semantics of what Dispose did, rather than actually implement it in a comment.

From a thread-safety perspective, I think it'd be really confusing/error-prone if Free and Dispose had different thread-safety guarantees, so my preference would be to have Dispose use CompareExchange, e.g.

IntPtr handle = Interlocked.Exchange(ref _handle, IntPtr.Zero);
if (handle != IntPtr.Zero)
    InternalFree(GetHandleValue(handle));

@MichalStrehovsky
Copy link
Member

Is IDisposable on a struct really a good idea?

As a quick poll:

var m = MyStruct.Alloc();
using (m)
{
}
Console.WriteLine(m);

struct MyStruct : IDisposable
{
    private bool _isUsed;

    public static MyStruct Alloc() => new MyStruct { _isUsed = true };

    public void Dispose()
    {
        _isUsed = false;
    }

    public override string ToString()
    {
        return $"IsUsed: {_isUsed}";
    }
}

People whose intuition says this is going to print IsUsed: true, can you give this comment a "Laugh" reaction? If you think this will print IsUsed: false, can you give this comment a "Confused" reaction?

@stephentoub
Copy link
Member

Is IDisposable on a struct really a good idea?

We do it in other advanced places that have a similar potential for misuse, e.g.
https://docs.microsoft.com/en-us/dotnet/api/system.buffers.memoryhandle.dispose?view=net-5.0

@jkotas
Copy link
Member

jkotas commented Jun 29, 2021

From a thread-safety perspective, I think it'd be really confusing/error-prone if Free and Dispose had different thread-safety guarantees,

Note that the thread-safety guarantee in Free is nearly useless. I have not ever seen a code that would take advantage of it - such code would have to catch the InvalidOperationException that gets thrown when the handle has been freed already.

@stephentoub
Copy link
Member

Note that the thread-safety guarantee in Free is nearly useless

If we really believe it's useless, then we should remove it from both.

My point is that I think Free and Dispose should make the same guarantees.

@jkoritzinsky jkoritzinsky added this to the 7.0.0 milestone Jul 7, 2021
@terrajobst
Copy link
Member

  • Looks good as proposed
namespace System.Runtime.InteropServices
{
    public struct GCHandle : IDisposable
    {
        public void Dispose();
    }
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jul 13, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jul 13, 2021
@AaronRobinsonMSFT
Copy link
Member

Given some of the fallout from #55596, I think we need to consider the impact of this change. Moving back to API needs work.

@AaronRobinsonMSFT AaronRobinsonMSFT added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-approved API was approved in API review, it can be implemented labels Nov 1, 2021
@AaronRobinsonMSFT AaronRobinsonMSFT modified the milestones: 7.0.0, Future Nov 1, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Nov 1, 2021
@Neme12
Copy link

Neme12 commented Mar 27, 2024

I don't think the issue with xunit should block this API. This could happen when any type whatsoever starts implementing IDisposable. And it's only in tests - users can change their code. This issue wouldn't come from a library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Runtime.InteropServices
Projects
Status: No status
Development

Successfully merging a pull request may close this issue.

8 participants