// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. using System.Threading; namespace System.Reactive.Disposables { /// /// Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed. /// public sealed class RefCountDisposable : ICancelable { private readonly bool _throwWhenDisposed; private IDisposable _disposable; /// /// Holds the number of active child disposables and the /// indicator bit (31) if the main _disposable has been marked /// for disposition. /// private int _count; /// /// Initializes a new instance of the class with the specified disposable. /// /// Underlying disposable. /// is null. public RefCountDisposable(IDisposable disposable) : this(disposable, false) { } /// /// Initializes a new instance of the class with the specified disposable. /// /// Underlying disposable. /// Indicates whether subsequent calls to should throw when this instance is disposed. /// is null. public RefCountDisposable(IDisposable disposable, bool throwWhenDisposed) { if (disposable == null) throw new ArgumentNullException(nameof(disposable)); _disposable = disposable; _count = 0; _throwWhenDisposed = throwWhenDisposed; } /// /// Gets a value that indicates whether the object is disposed. /// public bool IsDisposed => Volatile.Read(ref _count) == int.MinValue; /// /// Returns a dependent disposable that when disposed decreases the refcount on the underlying disposable. /// /// A dependent disposable contributing to the reference count that manages the underlying disposable's lifetime. /// This instance has been disposed and is configured to throw in this case by . [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Backward compat + non-trivial work for a property getter.")] public IDisposable GetDisposable() { // the current state var cnt = Volatile.Read(ref _count); for (; ; ) { // If bit 31 is set and the active count is zero, don't create an inner if (cnt == int.MinValue) { if (_throwWhenDisposed) throw new ObjectDisposedException("RefCountDisposable"); return Disposable.Empty; } // Should not overflow the bits 0..30 if ((cnt & 0x7FFFFFFF) == int.MaxValue) { throw new OverflowException($"RefCountDisposable can't handle more than {int.MaxValue} disposables"); } // Increment the active count by one, works because the increment // won't affect bit 31 var u = Interlocked.CompareExchange(ref _count, cnt + 1, cnt); if (u == cnt) { return new InnerDisposable(this); } cnt = u; } } /// /// Disposes the underlying disposable only when all dependent disposables have been disposed. /// public void Dispose() { var cnt = Volatile.Read(ref _count); for (; ; ) { // already marked as disposed via bit 31? if ((cnt & 0x80000000) != 0) { // yes, nothing to do break; } // how many active disposables are there? var active = cnt & 0x7FFFFFFF; // keep the active count but set the dispose marker of bit 31 var u = int.MinValue | active; var b = Interlocked.CompareExchange(ref _count, u, cnt); if (b == cnt) { // if there were 0 active disposables, there can't be any more after // the CAS so we can dispose the underlying disposable if (active == 0) { _disposable?.Dispose(); _disposable = null; } break; } cnt = b; } } private void Release() { var cnt = Volatile.Read(ref _count); for (; ; ) { // extract the main disposed state (bit 31) var main = (int)(cnt & 0x80000000); // get the active count var active = cnt & 0x7FFFFFFF; // keep the main disposed state but decrement the counter // in theory, active should be always > 0 at this point, // guaranteed by the InnerDisposable.Dispose's Exchange operation. System.Diagnostics.Debug.Assert(active > 0); var u = main | (active - 1); var b = Interlocked.CompareExchange(ref _count, u, cnt); if (b == cnt) { // if after the CAS there was zero active disposables and // the main has been also marked for disposing, // it is safe to dispose the underlying disposable if (u == int.MinValue) { _disposable?.Dispose(); _disposable = null; } break; } cnt = b; } } private sealed class InnerDisposable : IDisposable { private RefCountDisposable _parent; public InnerDisposable(RefCountDisposable parent) { _parent = parent; } public void Dispose() { Interlocked.Exchange(ref _parent, null)?.Release(); } } } }