louthy/language-ext · warning · OperationCanceledException
OperationCanceledException
Error message
OperationCanceledException
What it means
AsEnumerable on IteratorAsync checks the caller-supplied CancellationToken at each step and throws OperationCanceledException when cancellation was requested. Cooperatively cancels the enumeration loop.
Solutions
- Pass a non-cancelled token when you need full enumeration; only pass request/timeout tokens when aborting early is intended.
- Catch OperationCanceledException around the enumeration and treat it as normal cancellation (return 499/Cancelled, not 500).
- Use `token.ThrowIfCancellationRequested()`-aware patterns and cts.Token registration for cleanup before rethrowing.
Example fix
// before
await foreach (var x in it.AsEnumerable(ct)) Process(x);
// after
try { await foreach (var x in it.AsEnumerable(ct)) Process(x); }
catch (OperationCanceledException) when (ct.IsCancellationRequested) { /* expected cancel */ } Defensive patterns
Strategy: try-catch
Validate before calling
if (ct.IsCancellationRequested) return; // check before starting enumeration
await foreach (var x in it.AsEnumerable(ct)) { ... } Type guard
static bool IsUsableToken(CancellationToken ct) => ct.CanBeCanceled && !ct.IsCancellationRequested;
Try / catch
try { await foreach (var x in it.AsEnumerable(ct)) Process(x); }
catch (OperationCanceledException) when (ct.IsCancellationRequested) { /* cancellation is expected; clean up */ } Prevention
- Catch OperationCanceledException at the request boundary and map it to a cancelled/499 response, not an error.
- Use `catch ... when (token.IsCancellationRequested)` so genuine bugs aren't swallowed.
- Only pass request-scoped or timeout tokens when early abort is acceptable.
- Register cleanup with token.Register before long enumerations.
When it happens
Trigger: Enumerating an IteratorAsync via AsEnumerable(token) (directly or through GetAsyncEnumerator) while/after the CancellationToken is cancelled — e.g. request abort in ASP.NET Core or a timeout token firing mid-iteration.
Common situations: HTTP request cancelled by the client while streaming results; Task timeout token expiring during a long async enumeration; sharing a linked CTS and one branch cancelling.
Related errors
- Nil iterator has no head
- AggregateException from collected inner errors
- Ord attribute should have a struct type that derives from…
- Hashable attribute should have a struct type that derives…
- Don't use Equals - use either RecordType
AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15).
Data as JSON: /api/errors/69dd7bcf90607d16.
Report an issue: GitHub.
Appendix: source
Thrown at LanguageExt.Core/Immutable Collections/IteratorAsync/IteratorAsync.cs:100
/// this are to break the linked-list chain so that there isn't a big linked-list of objects in memory that
/// can't be garbage collected.
/// </summary>
/// <remarks>
/// Any other iterator references that came before this one will terminate at this point. Splitting the
/// previous and subsequent iterators here.
/// </remarks>
/// <returns>New iterator that starts from the current iterator position.</returns>
public abstract IteratorAsync<A> Split();
/// <summary>
/// Create an `IEnumerable` from an `Iterator`
/// </summary>
[Pure]
public async IAsyncEnumerable<A> AsEnumerable([EnumeratorCancellation] CancellationToken token)
{
for (var ma = Clone(); !await ma.IsEmpty; ma = await ma.Tail)
{
if (token.IsCancellationRequested) throw new OperationCanceledException();
yield return await ma.Head;
}
}
/// <summary>
/// Functor map
/// </summary>
[Pure]
public IteratorAsync<B> Select<B>(Func<A, B> f) =>
Map(f);
/// <summary>
/// Functor map
/// </summary>
[Pure]
public IteratorAsync<B> Map<B>(Func<A, B> f)
{
return Go(this, f).GetIteratorAsync();View on GitHub (pinned to 2f0e362824)