When using channels, errors do not get raised, and computation stalls

This is intended behavior, a known issue, or an unknown bug?

julia> function test()     
           buff = Channel{Int}(3)
           for _ in 1:3
              put!(buff, 0)
           end
           @sync for _ in 1:30
               Threads.@spawn begin
                   take!(buff)
                   s = 0.0
                   for j in 1:10^6 
                       s += log(j)*exp(j)*s 
                   end
                   error()
                   put!(buff, 1)
               end
           end
       end
test (generic function with 2 methods)

julia> test() # stalls, but does not raise any error.
^CERROR: InterruptException:

(edited post, localizing better the problem)

1 Like

I think this is an unfortunate behavior, which is more or less intended due to the way @sync works:

@sync

Wait until all lexically-enclosed uses of @async, @spawn, @spawnat and @distributed are complete. All exceptions thrown by enclosed async operations are collected and thrown as a CompositeException.

In your case, the first 3 tasks error out and are the only ones to complete because all other tasks wait for input. This means @sync can’t return and show you the errors.

With errormonitor, you can at least see the errors thrown by the failing tasks in real time:

julia> function test()     
                  buff = Channel{Int}(3)
                  for _ in 1:3
                     put!(buff, 0)
                  end
                  @sync for _ in 1:30
                      t = Threads.@spawn begin
                          take!(buff)
                          s = 0.0
                          for j in 1:10^6 
                              s += log(j)*exp(j)*s 
                          end
                          error()
                          put!(buff, 1)
                      end
                      errormonitor(t)
                  end
              end
test (generic function with 1 method)
julia> test()
Unhandled Task ERROR: 
Stacktrace:
 [1] error()
   @ Base ./error.jl:44
 [2] macro expansion
   @ ./REPL[11]:13 [inlined]
 [3] (::var"#13#14"{Channel{Int64}})()
   @ Main ./threadingconstructs.jl:373
Unhandled Task ERROR: 
Stacktrace:
 [1] error()
   @ Base ./error.jl:44
 [2] macro expansion
   @ ./REPL[11]:13 [inlined]
 [3] (::var"#13#14"{Channel{Int64}})()
   @ Main ./threadingconstructs.jl:373
Unhandled Task ERROR: 
Stacktrace:
 [1] error()
   @ Base ./error.jl:44
 [2] macro expansion
   @ ./REPL[11]:13 [inlined]
 [3] (::var"#13#14"{Channel{Int64}})()
   @ Main ./threadingconstructs.jl:373
^CERROR: InterruptException:
2 Likes

Good to know, thanks, hard to debug something like that, though, it would be better if errormirror() was the default behavior, and possibly allowing its suppression by some @sync option.

There is also Experimental.@sync, which errors directly. This is pretty convenient when developing at least.

1 Like