louthy/language-ext · warning · OperationCanceledException

OperationCanceledException

Error message

OperationCanceledException

What it means

ObservableExt's buffering/consume loop rethrows cancellation: when a queued item is a Fail whose error is Errors.Cancelled, the iterating coroutine throws OperationCanceledException. This signals that the underlying async stream/token was cancelled rather than a normal error.

Solutions

  1. Catch OperationCanceledException around enumeration and treat it as normal cancellation.
  2. Check the CancellationToken.IsCancellationRequested before/while consuming.
  3. Pass the token through so cancellation is cooperative rather than exception-based.

Example fix

// before
foreach (var x in observableExt) Process(x);
// after
try { foreach (var x in observableExt) Process(x); }
catch (OperationCanceledException) { /* expected on shutdown */ }
Defensive patterns

Strategy: try-catch

Validate before calling

if (token.IsCancellationRequested) return; // check before enumerating

Try / catch

try { foreach (var x in obs) { } } catch (OperationCanceledException) { /* cancelled: expected */ }

Prevention

When it happens

Trigger: Iterating the observable extension while the source completes with Errors.Cancelled (cancellation token fired upstream), e.g. via a CancellationToken passed to the producing async operation.

Common situations: User-initiated cancellation of long-running observable pipelines, host shutdown, or timeout tokens firing while an observable consumer is mid-enumeration.

Related errors


AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15). Data as JSON: /api/errors/03f1ecfe57e687d4. Report an issue: GitHub.

Appendix: source

Thrown at LanguageExt.Core/Extensions/ObservableExt.cs:71

        }

        public static async IAsyncEnumerable<A> Run(
            IObservable<A> observable, 
            [EnumeratorCancellation] CancellationToken token)
        {
            using var wait  = new AutoResetEvent(false);
            var       queue = new ConcurrentQueue<Fin<A>>();
            observable.Subscribe(new Observe<A>(wait, queue));

            while (true)
            {
                await wait.WaitOneAsync(token).ConfigureAwait(false);
                while (queue.TryDequeue(out var item))
                {
                    if (item.IsFail)
                    {
                        if (item.FailValue == Errors.None) yield break;
                        if (item.FailValue == Errors.Cancelled) throw new OperationCanceledException();
                        item.FailValue.Throw();
                        yield break;
                    }
                    else
                    {
                        yield return item.SuccValue;
                    }
                }
            }                
        }

        public void OnCompleted()
        {
            queue.Enqueue(Errors.None);
            wait.Set();
        }

        public void OnError(Exception error)

View on GitHub (pinned to 2f0e362824)