stride3d/stride · error · InvalidOperationException
Cannot release an object that doesn't have active…
Error message
Cannot release an object that doesn't have active reference. AddReference/Release pair must match.
What it means
Release() decrements the reference counter atomically; if the resulting counter is negative, the caller has released more times than it acquired. The library throws InvalidOperationException to flag the unbalanced AddReference/Release pair before the corrupted counter causes worse failures. Every Release must correspond to exactly one prior AddReference.
Solutions
- Find the extra Release call and remove it, or guard it so it only runs when that code path actually owns a reference
- Track reference ownership explicitly: only call Release on objects your component AddReference'd itself
- Check whether Dispose/DisposeMember is also releasing, causing a double decrement, and restructure ownership
- Add logging in OnRelease overrides to trace the count per call site during development
Example fix
// before
finally
{
texture.Release(); // may run after texture was already released on the error path above
}
// after
finally
{
if (!released)
{
texture.Release();
released = true;
}
} Defensive patterns
Strategy: validation
Validate before calling
if (obj is IReferencable r && r.RefCount > 0)
{
r.Release();
} Type guard
static bool OwnsReference(DisposeBase obj) =>
obj is IReferencable && obj.RefCount > 0 && !obj.IsDisposed; Try / catch
try
{
((IReferencable)obj).Release();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("doesn't have active reference"))
{
// unbalanced Release: log and skip; do not retry the release
} Prevention
- Only release references your component explicitly acquired via AddReference
- Set an ownership flag/disposed flag so Release cannot run twice on the same path
- Beware that Dispose may itself release member references — do not double-release
- Trace refCount transitions in debug builds via OnAddReference/OnRelease overrides
When it happens
Trigger: Calling IReferencable.Release() more times than AddReference() was called on the same object — e.g. an extra Release in an error/cleanup path, double-disposal, or calling Release on a never-referenced object.
Common situations: Cleanup code that releases unconditionally in a finally block after an earlier release already ran; wrapping code that both disposes and releases; sharing objects between systems where one side also releases.
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 add a reference for an object already released…
- MicroThread was already started before.
- InvalidOperationException
- Cannot add a reference for an object already released…
- The folder being deleted is not empty.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/d9f4f831915d420d.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core/DisposeBase.cs:87
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);
}
return newCounter;
}
/// <summary>
/// Called when a new reference of this object has been counted (via a call to <see cref="IReferencable.AddReference"/>).
/// </summary>
protected virtual void OnAddReference() { }
/// <summary>
/// Called when a call to <see cref="IReferencable.Release"/> has decremented the reference count of this object.
/// </summary>
protected virtual void OnReleaseReference() { }
}
View on GitHub (pinned to 96fad776d2)