Race condition when `close`ing the Pipe() used for redirect_*
There is a race condition in the implementation of the capture_* and suppress_* macros, described in this julia issue: https://github.com/JuliaLang/julia/issues/47759#issuecomment-1334194540, racing against another Task in another Thread doing logging.
It comes from the fact that we do not lock around the critical section in between "redirect" and "close": https://github.com/JuliaIO/Suppressor.jl/blob/cbfc46f1450b03d6b69dad4c35de739290ff0aff/src/Suppressor.jl#L65-L72
If another task starts a log message and grabs a local reference to stderr before the redirect, it will grab a local reference to the Pipe. And then if it finishes logging after the Pipe has been closed, the log message will throw an error.
You can see this easily with this program:
Threads.nthreads() # 4
using Suppressor
t = Threads.@spawn begin
for _ in 1:100
@info "hello, friends"
sleep(0.001)
end
end
for _ in 1:1000
@suppress_err sleep(0.001)
end
display(t)
Which for me gives:
Task (failed) @0x000000011353af50
IOError: stream is closed or unusable
We think the easiest fix would be to not manually close the Pipe, but rather to let GC close it, once all references have disappeared. This ensures that if someone has accidentally grabbed a reference to stderr in another thread, that they will not break because the Pipe is closed.
I'm not sure whether GC will already close a pipe when it's GC'd, or if we would need to add a finalizer for it? But either way, I think that's probably the best solution.
CC: @vilterp, @dewilson.
@quinnj can we reopen this, since we had to revert #49?