dotnet/reactive · error · InvalidOperationException
Strings_Core.REENTRANCY_DETECTED
Error message
Strings_Core.REENTRANCY_DETECTED
What it means
CheckedObserver is a diagnostic wrapper that detects reentrant notifications. CheckAccess uses an Interlocked.CompareExchange state machine (Idle/Busy/Done); if a callback (OnNext/OnError/OnCompleted) is invoked while the same observer is already inside a notification (state Busy), it throws InvalidOperationException(REENTRANCY_DETECTED).
Solutions
- Restructure the notification graph so the observer never re-enters itself (break the cycle, defer with ObserveOn or a Scheduler).
- Ensure notifications are serialized to one thread at a time (e.g. ObserveOn a single-threaded context) if concurrency is the cause.
- Remove or replace the CheckedObserver wrapper if you only needed it for debugging.
Example fix
// before
source.Subscribe(x => subject.OnNext(x)); // subject feeds back into same observer
// after
source.ObserveOn(TaskPoolScheduler.Default).Subscribe(x => { if (x != last) subject.OnNext(x); }); Defensive patterns
Strategy: try-catch
Validate before calling
// track in-flight state yourself before re-notifying private int _inFlight; bool CanNotify() => Interlocked.CompareExchange(ref _inFlight, 1, 0) == 0;
Type guard
bool IsReentrantCall() => _insideNotification; // set true at callback entry, false at exit
Try / catch
try { observer.OnNext(value); } catch (InvalidOperationException ex) when (ex.Message.Contains("Reentrancy")) { /* defer via scheduler or break the cycle */ } Prevention
- Never call the same observer from inside its own OnNext callback.
- Serialize notifications with ObserveOn on a single-threaded scheduler.
- Defer feedback loops through Subject/Scheduler instead of direct calls.
When it happens
Trigger: Calling OnNext/OnError/OnCompleted on the observer from within one of its own callbacks, or concurrently from another thread while a notification is in flight. Typically occurs when using Observable.Create with a CheckedObserver in CreateCheckedObserver-based tests or instrumented pipelines.
Common situations: Subjects/subscribers whose OnNext re-triggers the same observer (circular reactive graphs); shared observers called from multiple threads; debug/instrumentation builds where CheckedObserver wraps production observers.
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
- Disposable already assigned.
- AdvanceTo cannot be called when the scheduler is already…
- Strings_Core.OBSERVER_TERMINATED
- Value cannot be null. (Parameter 'observer')
- Value cannot be null. (Parameter 'onNextAsync')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/42c4c7fc0ae1a567.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Internal/CheckedObserver.cs:70
{
CheckAccess();
try
{
_observer.OnCompleted();
}
finally
{
Interlocked.Exchange(ref _state, Done);
}
}
private void CheckAccess()
{
switch (Interlocked.CompareExchange(ref _state, Busy, Idle))
{
case Busy:
throw new InvalidOperationException(Strings_Core.REENTRANCY_DETECTED);
case Done:
throw new InvalidOperationException(Strings_Core.OBSERVER_TERMINATED);
}
}
}
}
View on GitHub (pinned to 94b5d5ab91)