{"record":{"id":"d8314cdd54a38591","repo":"stride3d/stride","slug":"cannot-release-an-object-that-doesn-t-have-active-reference-d8314c","errorCode":null,"errorMessage":"Cannot release an object that doesn't have active reference. AddReference/Release pair must match.","messagePattern":"Cannot release an object that doesn't have active reference\\. AddReference/Release pair must match\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core/ReferenceBase.cs","lineNumber":41,"sourceCode":"    /// <inheritdoc/>\n    public virtual int Release()\n    {\n        var newCounter = Interlocked.Decrement(ref counter);\n        if (newCounter == 0)\n        {\n            try\n            {\n                Destroy();\n            }\n            finally\n            {\n                // Reverse back the counter if there are any exceptions in the destroy method\n                Interlocked.Exchange(ref counter, newCounter + 1);\n            }\n        }\n        else if (newCounter < 0)\n        {\n            throw new InvalidOperationException(FrameworkResources.ReleaseReferenceError);\n        }\n        return newCounter;\n    }\n\n    /// <summary>\n    /// Releases unmanaged and - optionally - managed resources\n    /// </summary>\n    protected abstract void Destroy();\n}\n","sourceCodeStart":23,"sourceCodeEnd":51,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core/ReferenceBase.cs#L23-L51","documentation":"Stride's ReferenceBase implements manual reference counting; Release() decrements the counter returned by AddReference()/IncrementReference. Throwing here means the internal counter went below zero, i.e. Release was called more times than AddReference (or on an object with no active references), which would corrupt lifetime management and cause premature destruction or leaks.","triggerScenarios":"Calling Release() (or Dispose path that releases) on an object without a prior matching AddReference(); calling Release() twice for one reference; releasing an object owned by another subsystem; calling Release() after the counter was restored by the exception-rollback path (Interlocked.Exchange back to newCounter+1) when a destroy method threw.","commonSituations":"Double-dispose in user code (e.g. object disposed by both a using block and a container); sharing an entity/component between two systems that each call Release; a destroy callback throwing inside Release leaves the counter partially rolled back and a later Release then underflows; porting code that assumed GC semantics.","solutions":["Ensure every Release() call has exactly one matching AddReference()/IncrementReference() call on the same object instance","Audit ownership: only the component that called AddReference should Release; wrap Add/Release in RAII-style helpers or using blocks","Guard against double-dispose with a bool flag or by checking the object's disposed state before calling Release","If a destroy method throws during Release, fix the exception source; the rollback path leaves the counter incremented and the object alive","Use a memory profiler or debug counter logging to find the unbalanced Add/Release pair"],"exampleFix":"// before\nentity.Release();\nentity.Release(); // counter underflows -> InvalidOperationException\n// after\nentity.AddReference();\ntry { Use(entity); } finally { entity.Release(); } // exactly one release per reference","handlingStrategy":"try-catch","validationCode":"// track references yourself alongside the API\nprivate readonly HashSet<object> heldRefs = new();\nbool CanRelease(object o) => heldRefs.Contains(o);","typeGuard":"bool HasActiveReference(object o) => o is ReferenceBase rb && heldRefs.Contains(rb);","tryCatchPattern":"try\n{\n    referenceBase.Release();\n}\ncatch (InvalidOperationException ex)\n{\n    logger.Warn(ex, \"Unbalanced Release: no active reference for {Type}\", referenceBase.GetType());\n}","preventionTips":["Pair every AddReference with exactly one Release in the same scope","Use try/finally so exceptions never skip the Release (or the Add) side","Track ownership in a container instead of ad-hoc Release calls","Never Release objects you did not AddReference yourself","Log Add/Release calls in debug builds to spot imbalances early"],"tags":["reference-counting","lifetime","double-dispose","stride"],"backgroundTag":"reference-count-mismatch","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}