Skip to content

Unbounded async-enumerable result streaming: early break blocks until all in-flight tasks finish, then swallows every failure #363

Description

@thomhurst

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

ResultAsyncEnumerableParallelProcessor<TInput, TOutput>.ExecuteAsync, unbounded path: if the consumer breaks out of await foreach early, iterator disposal runs the finally, which awaits ALL in-flight tasks — nothing cancels the processor's linked CTS on abandonment — and the bare catch { } then discards every failure.

Failure scenario: consume 1 result of a 10,000-item unbounded run and break → the await foreach exit blocks until every started task naturally completes (minutes, if the delegate is slow), and any exceptions thrown by those tasks silently vanish.

Its void twin has the opposite bug: AsyncEnumerableParallelProcessor.ExecuteAsync's finally has no catch, so when source enumeration throws and an in-flight task later faults, the finally's Task.WhenAll throw REPLACES the original source exception — the user sees the wrong root cause.

Proposal: cancel the internal CTS when the result iterator is disposed before completion (abandonment = cancellation), bound the drain wait, and unify the two paths' exception policy (preserve the in-flight/original exception, don't swallow or mask).

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