stride3d/stride · error · InvalidOperationException

Cannot add a reference for an object already released. AddRe

Error message

Cannot add a reference for an object already released. AddReference/Release pair must match.

What it means

Stride.Core reference-counted objects (DisposeBase implementing IReferencable) maintain an internal refCount. AddReference increments it atomically and throws InvalidOperationException when the counter is at or below 1 after incrementing, meaning the object has already been fully released/destroyed and cannot be resurrected. The AddReference/Release pairs must be balanced; adding a reference to a released object indicates a use-after-release bug.

Solutions

  1. Audit AddReference/Release call sites to ensure every AddReference has exactly one matching Release
  2. Stop using the object after the final Release; reacquire it from its source (e.g. content manager) instead of calling AddReference
  3. Debug refCount transitions with the OnAddReference/OnRelease overrides or a debugger breakpoint on DisposeBase to find the extra Release
  4. Use EnsureLifetime or a keep-alive reference if the object must outlive certain subsystems

Example fix

// before
effect.Release();
// ... later, elsewhere ...
effect.AddReference(); // InvalidOperationException: object already released
// after
if (!effect.IsDisposed && effect.RefCount > 0)
{
    effect.AddReference();
}
else
{
    effect = Content.Load<Effect>("my-effect"); // reacquire instead of resurrecting
}
Defensive patterns

Strategy: validation

Validate before calling

if (obj is IReferencable r && !((DisposeBase)obj).IsDisposed)
{
    r.AddReference();
}
else
{
    // reacquire the object from its owner/content manager
}

Type guard

static bool CanAddReference(DisposeBase obj) =>
    obj is IReferencable && !obj.IsDisposed && obj.RefCount > 0;

Try / catch

try
{
    ((IReferencable)obj).AddReference();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("already released"))
{
    // object destroyed: reacquire from source instead of using this instance
}

Prevention

When it happens

Trigger: Calling IReferencable.AddReference() on an object whose refCount has already dropped to 0 (or gone negative) — i.e. after the final Release() destroyed it. Also happens when an unbalanced Release brought the counter to 0 and other components still hold what they believe are live references.

Common situations: Graphics resource lifetime bugs (textures, buffers, effects) where a resource was released but still referenced by a render pipeline; double-release caused by both manual Dispose/Release and GC-driven disposal; caching a released module and trying to reload it via AddReference.

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 stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/cfea31d2a458ea4c. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core/DisposeBase.cs:69

    ///     This method is automatically called whenever a call to <see cref="Dispose"/> (or to <see cref="IReferencable.Release"/>)
    ///     has decreased the internal reference count to zero, meaning no other objects (hopefully) hold a reference to this one
    ///     and its resources can be safely released.
    ///   </para>
    /// </remarks>
    protected virtual void Destroy() { }


    /// <inheritdoc/>
    int IReferencable.ReferenceCount => refCount;

    /// <inheritdoc/>
    int IReferencable.AddReference()
    {
        OnAddReference();

        var newCounter = Interlocked.Increment(ref refCount);
        if (newCounter <= 1)
            throw new InvalidOperationException(FrameworkResources.AddReferenceError);

        return newCounter;
    }

    /// <inheritdoc/>
    int IReferencable.Release()
    {
        OnReleaseReference();

        var newCounter = Interlocked.Decrement(ref refCount);
        if (newCounter == 0)
        {
            Destroy();
            IsDisposed = true;
        }
        else if (newCounter < 0)
        {
            throw new InvalidOperationException(FrameworkResources.ReleaseReferenceError);

View on GitHub (pinned to 96fad776d2)