Skip to content

Async-enumerable void path surfaces only the first exception; remaining failures are unrecoverable #362

Description

@thomhurst

Found in the pre-4.0 audit (follow-up to #360/#361).

AsyncEnumerableWorkerPool.ThrowIfFailed throws only the first queued exception; the rest of the ConcurrentQueue<Exception> is discarded entirely — not aggregated, not reachable via any task's Exception property.

Contrast with the enumerable paths, which preserve every failure: processor WhenAll tasks carry all faults, and ParallelExtensions completes its TCS with the full exception set.

Failure scenario: asyncItems.ForEachAsync(f).ProcessInParallel(maxConcurrency: 4).ExecuteAsync() where 5 of 10 items throw different exceptions → caller observes exactly 1; the other 4 are gone. Same fluent call on an IEnumerable<T> source surfaces all 5.

Related: faulted per-item completion sources abandoned after the first failure propagates in ProcessResultsAsync used to raise UnobservedTaskException at GC — the consumer-side observation added in #360 covers the bounded result path, but the void path's discarded queue remains.

Proposal: throw AggregateException when more than one failure is queued (matches Task.WhenAll expectations), or at minimum document the first-wins contract. Decide before 4.0 if the fix is considered breaking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions