Skip to content

World age problem with global_logger and tasks launched before logger definition #33865

Open
@tkf

Description

@tkf

It seems global_logger does not work well with tasks in the following scenario:

  1. A task using the logger is launched.
  2. A new logger is defined (e.g., via import).
  3. The new logger is set as the global logger.
  4. The task tries to log something.

As an example, running the following code in a fresh julia process (i.e., the one that has not imported TerminalLoggers yet) throws a method error.

using Logging

ch = Channel() do ch
    for _ in 1:2
        try
            @info take!(ch)
        catch err
            bt = catch_backtrace()
            with_logger(ConsoleLogger()) do
                @error "`@info` threw" exception = (err, bt)
            end
        end
    end
end

put!(ch, "hello to ConsoleLogger")

using TerminalLoggers
# yield()

global_logger(TerminalLogger())
put!(ch, "hello to TerminalLogger")

Output:

[ Info: hello to ConsoleLogger
┌ Error: `@info` threw
│   exception =
│    MethodError: no method matching shouldlog(::TerminalLogger, ::Base.CoreLogging.LogLevel, ::Module, ::String, ::Symbol)
│    The applicable method may be too new: running in world age 26999, while current world is 27001.
│    Closest candidates are:
│      shouldlog(::TerminalLogger, ::Any, ::Any, ::Any, ::Any) at /home/takafumi/.julia/dev/TerminalLoggers/src/TerminalLogger.jl:40 (method too new to be called from this world context.)
│      shouldlog(::Base.CoreLogging.NullLogger, ::Any...) at logging.jl:84
│      shouldlog(::Base.CoreLogging.SimpleLogger, ::Any, ::Any, ::Any, ::Any) at logging.jl:529
│      ...
│    Stacktrace:
│     [1] macro expansion at ./logging.jl:316 [inlined]
│     [2] (::var"#3#5")(::Channel{Any}) at /home/takafumi/demo.jl:6
│     [3] (::Base.var"#630#631"{var"#3#5",Channel{Any}})() at ./channels.jl:129
└ @ Main ~/junk/2019/11/15-174444.jl:10
ERROR: LoadError: InvalidStateException("Channel is closed.", :closed)
Stacktrace:
 [1] try_yieldto(::typeof(Base.ensure_rescheduled), ::Base.RefValue{Task}) at ./task.jl:611
 [2] wait() at ./task.jl:668
 [3] wait(::Base.GenericCondition{ReentrantLock}) at ./condition.jl:106
 [4] put_unbuffered(::Channel{Any}, ::String) at ./channels.jl:350
 [5] put!(::Channel{Any}, ::String) at ./channels.jl:325
 [6] top-level scope at /home/takafumi/demo.jl:22
 [7] include(::Module, ::String) at ./Base.jl:377
 [8] exec_options(::Base.JLOptions) at ./client.jl:296
 [9] _start() at ./client.jl:492

Here I'm using https://github.com/c42f/TerminalLoggers.jl (as that's the easiest to set up) but it happens with other packages like https://github.com/oxinabox/LoggingExtras.jl.

Note that uncommenting # yield() fixes the problem. So, as a short term solution, is calling yield() inside global_logger(logger) reasonable? Or maybe implicitly call yield() after using/import?

As a long term solution, maybe start discouraging the use of global_logger and add an API to set up a logger for the RPL, as @c42f suggested in https://discourse.julialang.org/t/different-logging-output-between-juno-execute-and-julia-repl/30933/5? IIUC, with this solution, the task-local logger is inherited to the "background" task at the time it is started and pre-launched tasks will not try to log to the logger that is defined after it is launched. But I'm not 100% sure this is the desired behavior. Maybe it is desirable to be able to swap the logger after it is started?

Metadata

Metadata

Assignees

No one assigned

    Labels

    loggingThe logging framework

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions