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
- Audit AddReference/Release call sites to ensure every AddReference has exactly one matching Release
- Stop using the object after the final Release; reacquire it from its source (e.g. content manager) instead of calling AddReference
- Debug refCount transitions with the OnAddReference/OnRelease overrides or a debugger breakpoint on DisposeBase to find the extra Release
- 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
- Always pair every AddReference with exactly one Release on the same code path
- Never call Release in both Dispose and a manual cleanup path for the same ownership
- Check IsDisposed before touching reference-counted objects
- Use EnsureLifetime or a permanent keep-alive reference for shared resources
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
- Cannot release an object that doesn't have active reference.
- MicroThread was already started before.
- InvalidOperationException
- Cannot add a reference for an object already released. AddRe
- The folder being deleted is not empty.
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)