From ea77dd1ded9fc48af6797d6d2757f1db292054c8 Mon Sep 17 00:00:00 2001 From: Soeren Schoenbrod Date: Mon, 19 Jan 2026 13:21:16 +0100 Subject: [PATCH] fix: propagate bound task exceptions in iterate Match Julia's Channel behavior: when iterating over a closed channel, check for stored exceptions from bound tasks and throw them instead of silently returning nothing. Previously, if a bound task failed with an exception, iterate() would catch the InvalidStateException from take!() and return nothing, swallowing the original exception. Now we check ch.excp and propagate the stored exception if present. This ensures errors in producer tasks properly surface to consumers iterating over the channel. Co-Authored-By: Claude Opus 4.5 --- src/PipeChannels.jl | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/PipeChannels.jl b/src/PipeChannels.jl index ecfc7ea..d1112d9 100644 --- a/src/PipeChannels.jl +++ b/src/PipeChannels.jl @@ -336,14 +336,32 @@ Note: The `@inline` annotation is critical for avoiding heap allocation of the returned `(value, nothing)` tuple when `T` is not an isbits type. """ @inline function Base.iterate(ch::PipeChannel{T}, state=nothing) where {T} - try - value = take!(ch) - return (value, nothing) - catch e - if e isa InvalidStateException - return nothing + # Fast path: if channel is open or has data, try to take + if isopen(ch) || isready(ch) + try + value = take!(ch) + return (value, nothing) + catch e + if e isa InvalidStateException + # Check if there's a stored exception we should throw instead + # This handles the case where take! threw InvalidStateException but + # there's a more specific exception from a bound task + stored = ch.excp + if stored !== nothing && !isa(stored, InvalidStateException) + throw(stored) + end + return nothing + end + rethrow() + end + else + # Channel is closed and empty - check for stored exception from bind + # This matches Julia's Channel behavior: propagate bound task exceptions + e = ch.excp + if e !== nothing && !isa(e, InvalidStateException) + throw(e) end - rethrow() + return nothing end end