Skip to content

Commit

Permalink
Add pending state to ObjectReference._disposedFlags
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Jan 26, 2024
1 parent 1f31b17 commit 7b2669c
Showing 1 changed file with 21 additions and 4 deletions.
25 changes: 21 additions & 4 deletions src/WinRT.Runtime/ObjectReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@

namespace WinRT
{

#if EMBED
internal
#else
public
#endif
abstract class IObjectReference : IDisposable
{
// Flags for the '_disposedFlags' field, see notes in the Dispose() method below
private const int NOT_DISPOSED = 0;
private const int DISPOSE_PENDING = 1;
private const int DISPOSE_COMPLETED = 2;

private readonly IntPtr _thisPtr;
private IntPtr _referenceTrackerPtr;
private int _isDisposed;
private int _disposedFlags;

public IntPtr ThisPtr
{
Expand Down Expand Up @@ -259,7 +263,7 @@ public IntPtr GetRef()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
if (Volatile.Read(ref _isDisposed) != 0)
if (Volatile.Read(ref _disposedFlags) == DISPOSE_COMPLETED)
{
ThrowObjectDisposedException();
}
Expand Down Expand Up @@ -292,7 +296,18 @@ public void Dispose()
// lock object guarding all the logic within the Dispose() method. The only difference is that simply using
// a flag this way avoids one object allocation per ObjectReference instance, and also allows making the size
// of the whole object smaller by sizeof(object), when taking into account padding.
if (Interlocked.Exchange(ref _isDisposed, 1) == 0)
//
// Additionally, note that the '_disposedFlags' field has 3 different states:
// - NOT_DISPOSED: the initial state, when the object is alive
// - DISPOSE_PENDING: indicates that a thread is currently executing the Dispose() method and got past the
// first check, and is in the process of releasing the native resources. This state is checked by the
// ThrowIfDisposed() method above, and still treated as if the object can be used normally. This is
// necessary, because the dispose logic still has to access the 'ThisPtr' property and others in order
// to perform the various Release() calls on the native objects being used. If the state was immediately
// set to disposed, that method would just start throwing immediately, and this logic would not work.
// - DISPOSE_COMPLETED: set when all the Dispose() logic has been completed and the object should not be
// used at all anymore. When this is set, the ThrowIfDisposed() method will start throwing exceptions.
if (Interlocked.CompareExchange(ref _disposedFlags, DISPOSE_PENDING, NOT_DISPOSED) == NOT_DISPOSED)
{
#if DEBUG
if (BreakOnDispose && System.Diagnostics.Debugger.IsAttached)
Expand All @@ -307,6 +322,8 @@ public void Dispose()
}

DisposeTrackerSource();

Volatile.Write(ref _disposedFlags, DISPOSE_COMPLETED);
}
}

Expand Down

0 comments on commit 7b2669c

Please sign in to comment.