Exception Control in Iteration over Channels, Latest Version

Dear all,

I got intrigued when I realized that exceptions can be silenced when iterating over a channel. However, the source code on GitHub indicates that exceptions should actually be rethrown. Then I realized that the file Channels.jl on GitHub is actually different from its counterpart on my computer, though I should have the latest version of Julia.

Now to details. Here is my example along with version details:

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.11.6 (2025-07-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> ch = Channel() do c
           for x in 3:-1:-2
               put!(c, sqrt(x))
           end
       end
Channel{Any}(0) (1 item available)

julia> for y in ch
           println(y)
       end
1.7320508075688772
1.4142135623730951
1.0
0.0

As one can see, no error is reported: when sqrt(-1) is to be computed, the iteration ends and the error is silenced.

However, the definition of the method of iterate() for channels in the file channels.jl on GitHub reads:

function iterate(c::Channel, state=nothing)
    if isopen(c) || isready(c)
        try
            return (take!(c), nothing)
        catch e
            if isa(e, InvalidStateException) && e.state === :closed
                return nothing
            else
                rethrow()
            end
        end
    else
        # If the channel was closed with an exception, it needs to be thrown
        if (@atomic :acquire c.state) === :closed
            e = c.excp
            if isa(e, InvalidStateException) && e.state === :closed
                nothing
            else
                throw(e)
            end
        end
        return nothing
    end
end

This code does capture the error: when I defined a new function with that method, an error was reported. Then I realized that the file channels.jl on my computer is different. The underlying method reads:

function iterate(c::Channel, state=nothing)
    if isopen(c) || isready(c)
        try
            return (take!(c), nothing)
        catch e
            if isa(e, InvalidStateException) && e.state === :closed
                return nothing
            else
                rethrow()
            end
        end
    else
        return nothing
    end
end

This code silences possible exceptions, as displayed above.

However, I am wondering why the files are different. I have run juliaup update. The date of the file channels.jl on my computer is July 9, 2025, which matches the Julia version date. Is the file on GitHub (pointed by the official Julia documentation) actually a future version and is to appear on my computer after some time? Or is it, less likely, a dated version, so that exceptions are now no longer captured in this situation? This is important for me to know: will be in future safe simply to use Channel() do ... end or should one create a task and a channel separately, bind the channel to the task, schedule the task and finally call fetch() to capture possible exceptions?

Many thanks for the answer in advance!

1 Like

The throwing iterate is in the master branch on github. The 1.11.6 source does not have it. It came with commit de1dba2c, Jan 13, 2025 in Check channel state on iterate by jakobnissen · Pull Request #52981 · JuliaLang/julia · GitHub

2 Likes

The channel creates a task. One can put an error monitor on the task in the scope where the channel is defined. The docstring of channel explains how to get the task reference.

Hmm, so you are saying that the link to GitHub on the official Julia language page does not point to the branch associated to the current release. Indeed a bit surprising to me, though I am aware that I am not very familiar with the culture here. Nevertheless, there is of course a branch associated to the current release, so that you may wish to consider adding a link to that branch to the official Julia language page.

If I have noticed correctly, the version of iterate for channels in Julia 1.12 coincides with the version in the master branch. You have probably figured out that this is my desired version. Any idea when 1.12 will replace the current release?

Thanks a lot and all the best!

Yes indeed, I have noticed how to get the task reference, but my impression is that references are not very usual in Julia. Am I right? Therefore, if the task is necessary, I prefer the longer version with creating it separately and binding the channel to it. In my opinion, the code is more readable this way. Nevertheless, thank you for your answer!

1.12 is already in rc1, so I suppose some weeks until release. I don’t think there’s a fixed schedule, There are still some unfixed issues, but they might choose to postpone some of them. GitHub · Where software is built.

1 Like

Switching branches is just basic Git/Github usage.

Refs are used now and then for various purposes, there’s nothing special about them. In this case it’s just a

t = Ref{Task}()
c = Channel(; taskref=t)
... use t[] ...

You’re right that using them for returning values is uncommon in base julia. Typically when things like that are needed in a function, it’s more common that the caller creates the object, which is then mutated by the function.

1 Like

Well … yes, branches are the basic idea of Git, but are people always absolutely aware to check the branch? :thinking: Indeed, I (still) use Git just for browsing, as I am an older and just occasional programmer. Nevertheless, I will try to remember this lesson. Maybe I will agree with you when I get really used to Git. :slightly_smiling_face:

1 Like