louthy/language-ext · info · OperationCanceledException

OperationCanceledException

Error message

OperationCanceledException

What it means

Iterable.Enumerable.AsEnumerableIO wraps a plain IEnumerable<A> in an IO.lift whose iterator checks env.Token.IsCancellationRequested before yielding every element, throwing OperationCanceledException when cancelled. This makes any derived IO pipeline (CountIO, Map, Filter, Reverse, AsAsyncEnumerableIO all call it) cancellation-aware. It is cooperative cancellation, not an unexpected fault.

Solutions

  1. Catch OperationCanceledException where the IO is run and handle shutdown explicitly
  2. Pass a different or non-cancelled token: io.Run(EnvIO.New(newCts.Token))
  3. Use CancellationToken.IsCancellationRequested in your own loop with FoldWhileIO to stop before the library throws
  4. If cancellation was accidental, audit the token source lifetime (e.g. using-blocks that dispose/cancel too early)

Example fix

// before
var count = iterable.CountIO().Run(EnvIO.New(token));
// after
int count;
try { count = iterable.CountIO().Run(EnvIO.New(token)); }
catch (OperationCanceledException) { count = -1; /* cancelled */ }
Defensive patterns

Strategy: try-catch

Validate before calling

if (env.Token.IsCancellationRequested) { /* abort before running the pipeline */ }

Try / catch

try { var r = iterable.CountIO().Run(EnvIO.New(token)); }
catch (OperationCanceledException) { /* handle cancellation */ }

Prevention

When it happens

Trigger: Enumerating the IO<IEnumerable<A>> from Iterable.Wrap/creation's AsEnumerableIO (directly or via CountIO, AsAsyncEnumerableIO, Reverse, Map, Filter) after the EnvIO token is cancelled; the throw occurs at line 26 before the first yield of each element.

Common situations: Cancelling a Map/Filter/Count pipeline on HTTP request abort; a timeout token expiring while a large enumerable is being processed; shared CancellationTokenSource cancelled by another subsystem mid-iteration.

Related errors


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

Appendix: source

Thrown at LanguageExt.Core/Immutable Collections/Iterable/DSL/Iterable.Enumerable.cs:26

sealed class IterableEnumerable<A>(IO<IEnumerable<A>> runEnumerable) : Iterable<A>
{
    internal override bool IsAsync =>
        false;
    
    public override IO<int> CountIO() =>
        AsEnumerableIO().Map(xs => xs.Count());

    public override IO<IEnumerable<A>> AsEnumerableIO()
    {
        // This makes a regular IEnumerable cancellable.
        return IO.lift(env => go(env, runEnumerable));
        static IEnumerable<A> go(EnvIO env, IO<IEnumerable<A>> run)
        {
            var xs = run.Run(env);
            foreach (var x in xs)
            {
                if (env.Token.IsCancellationRequested) throw new OperationCanceledException();
                yield return x;
            }
        }
    }

    public override IO<IAsyncEnumerable<A>> AsAsyncEnumerableIO() =>
        AsEnumerableIO().Map(xs => xs.ToAsyncEnumerable());

    public override Iterable<A> Reverse() =>
        new IterableEnumerable<A>(AsEnumerableIO().Map(xs => xs.Reverse()));

    public override Iterable<B> Map<B>(Func<A, B> f) =>
        new IterableEnumerable<B>(AsEnumerableIO().Map(xs => xs.Select(f)));

    public override Iterable<A> Filter(Func<A, bool> f) =>
        new IterableEnumerable<A>(AsEnumerableIO().Map(xs => xs.Where(f)));

    public override IO<S> FoldWhileIO<S>(Func<A, Func<S, S>> f, Func<(S State, A Value), bool> predicate, S initialState) => 

View on GitHub (pinned to 2f0e362824)