stride3d/stride · error · InvalidOperationException

Cannot add a reference for an object already released…

Error message

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

What it means

ReferenceBase.AddReference atomically increments the reference counter; a correct AddReference/Release sequence never lets the counter reach 0 while references are still being added. Interlocked.Increment returning <= 1 means the counter was already 0 or negative — the object was released — so InvalidOperationException (FrameworkResources.AddReferenceError) is thrown to flag a mismatched reference-count lifecycle.

Solutions

  1. Fix ownership so every AddReference has exactly one matching Release (avoid Release without ownership).
  2. Re-acquire/reload the resource instead of adding a reference to a released instance.
  3. Audit disposal order so objects are not released while still referenced elsewhere.
  4. Check ReferenceCount > 0 (or a Disposed flag) before calling AddReference in defensive code.

Example fix

// before
texture.Release();
...
texture.AddReference(); // throws: already released
// after: keep it alive while you still need it
if (texture.ReferenceCount > 0) texture.AddReference();
else texture = LoadTexture(name); // re-acquire
Defensive patterns

Strategy: try-catch

Validate before calling

if (obj.ReferenceCount <= 0 || obj.IsDisposed)
    throw new InvalidOperationException("Object already released; cannot AddReference");

Type guard

static bool IsAlive(ReferenceBase r) => r.ReferenceCount > 0;

Try / catch

try { obj.AddReference(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already released"))
{
    // re-acquire the resource instead of resurrecting the released instance
}

Prevention

When it happens

Trigger: Calling AddReference() on an IReferencable object after its reference count already dropped to 0 (Release brought it to zero and the object was considered released).

Common situations: Double-Release on one code path then AddReference from another holder; caching a released GraphicsResource/texture and reusing it next frame; resurrection after a using/Dispose block; passing a disposed object across threads that already finalized its last reference.

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/4a7513f1b79c1dbc. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core/ReferenceBase.cs:19

// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
namespace Stride.Core;

/// <summary>
/// Base class for a <see cref="IReferencable"/> class.
/// </summary>
public abstract class ReferenceBase : IReferencable
{
    private int counter = 1;

    /// <inheritdoc/>
    public int ReferenceCount { get { return counter; } }

    /// <inheritdoc/>
    public virtual int AddReference()
    {
        var newCounter = Interlocked.Increment(ref counter);
        if (newCounter <= 1) throw new InvalidOperationException(FrameworkResources.AddReferenceError);
        return newCounter;
    }

    /// <inheritdoc/>
    public virtual int Release()
    {
        var newCounter = Interlocked.Decrement(ref counter);
        if (newCounter == 0)
        {
            try
            {
                Destroy();
            }
            finally
            {
                // Reverse back the counter if there are any exceptions in the destroy method
                Interlocked.Exchange(ref counter, newCounter + 1);
            }

View on GitHub (pinned to 96fad776d2)