How to create a macro that suppresses errors and w

2019-04-15 03:37发布

I'd like to redefine or overwrite some functions in Base, without the users noticing about this. I found this trick sometime ago:

original_stderr = STDERR
redirect_stderr()
# code
redirect_stderr(original_stderr)

This became a repetitive pattern for me, so I did a macro:

macro suppress_err(block)
    quote
        orig_err = STDERR
        redirect_stderr()
        val = $block
        redirect_stderr(orig_err)
        val
    end
end

But it has never behaved as expected (even without the macro):

In Windows:

julia> @suppress_err warn()

julia> @suppress_err error()
ERROR:
 in error at error.jl:22

Windows Subsystem for Linux:

julia> @suppress_err warn()
[1]    17 abort (core dumped)  julia
julia  7.69s user 2.36s system 106% cpu 9.441 total

In Linux:

julia> @suppress_err +(x, y) = x - y                                                                                                                                                                                                      
ERROR: UndefVarError: #11#x not defined                                                                                                                                                                                                   
 in eval(::Module, ::Any) at ./boot.jl:234                                                                                                                                                                                                
 in macro expansion at ./REPL.jl:92 [inlined]                                                                                                                                                                                             
 in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46  

Obviously I'm doing something terribly wrong, what's the correct way I should tackle this? Should I raise an issue at the Julia repository?

I have also tried using close on both err_rd, err_wr = redirect_stderr(), before redirecting again to the original STDERR without success.

Update:

I have made a Suppressor package with what I've done so you can easily test it:

Pkg.clone("https://github.com/Ismael-VC/Suppressor.jl")

All the macros have the same basic form:

macro suppress_err(block)
    quote
        let
            if ccall(:jl_generating_output, Cint, ()) == 0
                ORIGINAL_STDERR = STDERR
                err_rd, err_wr = redirect_stderr()

                value = $(esc(block))

                REDIRECTED_STDERR = STDERR
                redirect_stderr(ORIGINAL_STDERR)

                # This doesn't help:
                # close(err_rd)
                # close(err_wr)

                return value
            end
        end
    end
end
  • I added the safety check from JuliaParser here

1条回答
Viruses.
2楼-- · 2019-04-15 04:11

You need to keep a reference to the return value of the original redirect_stderr. It returns both the read and the write end, but while the write end is stored in STDERR, the read end is not, so the GC may decide to get rid of it. If that happens, it gets closed at the system level, so when julia tries to write to it, it gets that error.

Note: example code below updated as per current Suppressor.jl version.

macro suppress_err(block)
    quote
        if ccall(:jl_generating_output, Cint, ()) == 0
            ORIGINAL_STDERR = STDERR
            err_rd, err_wr = redirect_stderr()
            err_reader = @async readstring(err_rd)

            value = $(esc(block))

            redirect_stderr(ORIGINAL_STDERR)
            close(err_wr)

            return value
        end
    end
end
查看更多
登录 后发表回答