dotnet/reactive · error · ObjectDisposedException

RefCountDisposable

Error message

RefCountDisposable

What it means

GetDisposable throws ObjectDisposedException named 'RefCountDisposable' when the RefCountDisposable was constructed with throwWhenDisposed:true, is already disposed, and the active reference count is zero. Because bit 31 of the packed count marks disposal, the code detects 'disposed and no children' and refuses to hand out inner disposables.

Solutions

  1. Check the IsDisposed property before calling GetDisposable and take an alternative path.
  2. Construct with throwWhenDisposed:false if post-dispose GetDisposable calls should return Disposable.Empty instead of throwing.
  3. Fix lifecycle ordering so GetDisposable is only called while the parent is alive (e.g. within a using block or while holding a lease).

Example fix

// before
var d = refCount.GetDisposable(); // may throw after dispose
// after
if (!refCount.IsDisposed)
{
    var d = refCount.GetDisposable();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (refCount.IsDisposed) { /* take alternate path */ } else { var d = refCount.GetDisposable(); }

Type guard

static bool CanGetDisposable(RefCountDisposable rc) => !rc.IsDisposed;

Try / catch

try { var d = refCount.GetDisposable(); }
catch (ObjectDisposedException) { /* treat as Disposable.Empty or re-acquire a new RefCountDisposable */ }

Prevention

When it happens

Trigger: Calling GetDisposable() on a RefCountDisposable created with new RefCountDisposable(disposable, throwWhenDisposed:true) after Dispose() has been called and all outstanding inner disposables have been released.

Common situations: Code that keeps a long-lived RefCountDisposable, disposes it on shutdown, and then accidentally calls GetDisposable again from a race or a stale code path; also tests intentionally verifying the throw-on-disposed behavior.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/8106c1bda52c68a1. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Disposables/RefCountDisposable.cs:69

        /// <summary>
        /// Returns a dependent disposable that when disposed decreases the refcount on the underlying disposable.
        /// </summary>
        /// <returns>A dependent disposable contributing to the reference count that manages the underlying disposable's lifetime.</returns>
        /// <exception cref="ObjectDisposedException">This instance has been disposed and is configured to throw in this case by <see cref="RefCountDisposable(IDisposable, bool)"/>.</exception>
        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);
                }

View on GitHub (pinned to 94b5d5ab91)