{"record":{"id":"0711220ee5e154e5","repo":"dotnet/reactive","slug":"strings-core-observer-terminated","errorCode":null,"errorMessage":"Strings_Core.OBSERVER_TERMINATED","messagePattern":"Strings_Core\\.OBSERVER_TERMINATED","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive/Internal/CheckedObserver.cs","lineNumber":72,"sourceCode":"\n            try\n            {\n                _observer.OnCompleted();\n            }\n            finally\n            {\n                Interlocked.Exchange(ref _state, Done);\n            }\n        }\n\n        private void CheckAccess()\n        {\n            switch (Interlocked.CompareExchange(ref _state, Busy, Idle))\n            {\n                case Busy:\n                    throw new InvalidOperationException(Strings_Core.REENTRANCY_DETECTED);\n                case Done:\n                    throw new InvalidOperationException(Strings_Core.OBSERVER_TERMINATED);\n            }\n        }\n    }\n}\n","sourceCodeStart":54,"sourceCodeEnd":77,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive/Internal/CheckedObserver.cs#L54-L77","documentation":"CheckedObserver marks itself Done once a terminal notification (OnError/OnCompleted) has been delivered. Any further OnNext/OnError/OnCompleted after termination hits the Done case in CheckAccess and throws InvalidOperationException(OBSERVER_TERMINATED), enforcing the IObservable grammar that an observer receives at most one terminal event.","triggerScenarios":"Invoking OnNext/OnError/OnCompleted on a CheckedObserver after OnError or OnCompleted was already called, e.g. holding the observer and calling OnNext in a loop after completion, or a Subject delivering events after termination.","commonSituations":"Publishing to a cached subject/observer after Dispose; custom operators that emit after OnCompleted; long-lived handlers stored in fields that keep firing after the sequence ended.","solutions":["Stop sending notifications after OnError/OnCompleted (return from the producer loop).","Route late events through a Subject that is recreated per sequence instead of reusing a terminated observer.","Check a completion flag in your producing code before calling OnNext."],"exampleFix":"// before\nif (_cache) _observer.OnNext(value);\n// after\nif (_cache && !_completed) _observer.OnNext(value);","handlingStrategy":"validation","validationCode":"private bool _terminated;\nvoid SafeOnNext(IObserver<T> o, T v) { if (!_terminated) o.OnNext(v); }","typeGuard":"bool IsAlive(IObserver<T> o) => !_completed && !_errored; // track terminal state in your producer","tryCatchPattern":"try { observer.OnNext(value); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"terminated\")) { /* stop producing; observer is done */ }","preventionTips":["Set a completed/errored flag in OnCompleted/OnError wrappers and check it before further notifications.","Do not retain observers in long-lived producers after the sequence terminates.","Recreate subjects/observers per subscription instead of reusing them."],"tags":["observer","lifecycle","invalid-operation","reactive-grammar"],"backgroundTag":"invalid-state-transition","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}