From 7a46e9cd7d65141c9dfd0f59659988d60354942d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 28 Feb 2025 21:57:32 -0500 Subject: [PATCH 01/10] add a lock to SimpleLogger and ConsoleLogger and use for maxlog and stream writes --- base/logging/ConsoleLogger.jl | 18 +++++++++++------- base/logging/logging.jl | 16 ++++++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/base/logging/ConsoleLogger.jl b/base/logging/ConsoleLogger.jl index 818b2272b773c..7eae28e7c7f3f 100644 --- a/base/logging/ConsoleLogger.jl +++ b/base/logging/ConsoleLogger.jl @@ -29,23 +29,24 @@ struct ConsoleLogger <: AbstractLogger show_limited::Bool right_justify::Int message_limits::Dict{Any,Int} + lock::ReentrantLock end function ConsoleLogger(stream::IO, min_level=Info; meta_formatter=default_metafmt, show_limited=true, right_justify=0) ConsoleLogger(stream, min_level, meta_formatter, - show_limited, right_justify, Dict{Any,Int}()) + show_limited, right_justify, Dict{Any,Int}(), ReentrantLock()) end function ConsoleLogger(min_level=Info; meta_formatter=default_metafmt, show_limited=true, right_justify=0) ConsoleLogger(closed_stream, min_level, meta_formatter, - show_limited, right_justify, Dict{Any,Int}()) + show_limited, right_justify, Dict{Any,Int}(), ReentrantLock()) end shouldlog(logger::ConsoleLogger, level, _module, group, id) = - get(logger.message_limits, id, 1) > 0 + @lock logger.lock get(logger.message_limits, id, 1) > 0 min_enabled_level(logger::ConsoleLogger) = logger.min_level @@ -109,9 +110,11 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module hasmaxlog = haskey(kwargs, :maxlog) ? 1 : 0 maxlog = get(kwargs, :maxlog, nothing) if maxlog isa Core.BuiltinInts - remaining = get!(logger.message_limits, id, Int(maxlog)::Int) - logger.message_limits[id] = remaining - 1 - remaining > 0 || return + @lock logger.lock begin + remaining = get!(logger.message_limits, id, Int(maxlog)::Int) + logger.message_limits[id] = remaining - 1 + remaining > 0 || return + end end # Generate a text representation of the message and all key value pairs, @@ -184,6 +187,7 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module println(iob) end - write(stream, take!(buf)) + b = take!(buf) + @lock logger.lock write(stream, b) nothing end diff --git a/base/logging/logging.jl b/base/logging/logging.jl index a9e41777f29b8..5413b8e056793 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -668,12 +668,13 @@ struct SimpleLogger <: AbstractLogger stream::IO min_level::LogLevel message_limits::Dict{Any,Int} + lock::ReentrantLock end -SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, level, Dict{Any,Int}()) +SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, level, Dict{Any,Int}(), ReentrantLock()) SimpleLogger(level=Info) = SimpleLogger(closed_stream, level) shouldlog(logger::SimpleLogger, level, _module, group, id) = - get(logger.message_limits, id, 1) > 0 + @lock logger.lock get(logger.message_limits, id, 1) > 0 min_enabled_level(logger::SimpleLogger) = logger.min_level @@ -684,9 +685,11 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, @nospecialize maxlog = get(kwargs, :maxlog, nothing) if maxlog isa Core.BuiltinInts - remaining = get!(logger.message_limits, id, Int(maxlog)::Int) - logger.message_limits[id] = remaining - 1 - remaining > 0 || return + @lock logger.lock begin + remaining = get!(logger.message_limits, id, Int(maxlog)::Int) + logger.message_limits[id] = remaining - 1 + remaining > 0 || return + end end buf = IOBuffer() stream::IO = logger.stream @@ -706,7 +709,8 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, println(iob, "│ ", key, " = ", val) end println(iob, "└ @ ", _module, " ", filepath, ":", line) - write(stream, take!(buf)) + b = take!(buf) + @lock logger.lock write(stream, b) nothing end From d70393bc89f50487e98cc0c629f1d06abb920e8d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 28 Feb 2025 22:47:24 -0500 Subject: [PATCH 02/10] make `_min_enabled_level` Threads.Atomic --- base/logging/logging.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/logging/logging.jl b/base/logging/logging.jl index 5413b8e056793..d41d24f231e80 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -132,6 +132,7 @@ isless(a::LogLevel, b::LogLevel) = isless(a.level, b.level) +(level::LogLevel, inc::Integer) = LogLevel(level.level+inc) -(level::LogLevel, inc::Integer) = LogLevel(level.level-inc) convert(::Type{LogLevel}, level::Integer) = LogLevel(level) +convert(::Type{Int32}, level::LogLevel) = level.level """ BelowMinLevel @@ -171,7 +172,7 @@ Alias for [`LogLevel(1_000_001)`](@ref LogLevel). const AboveMaxLevel = LogLevel( 1000001) # Global log limiting mechanism for super fast but inflexible global log limiting. -const _min_enabled_level = Ref{LogLevel}(Debug) +const _min_enabled_level = Threads.Atomic{Int32}(Debug) function show(io::IO, level::LogLevel) if level == BelowMinLevel print(io, "BelowMinLevel") @@ -394,7 +395,7 @@ function logmsg_code(_module, file, line, level, message, exs...) level = $level # simplify std_level code emitted, if we know it is one of our global constants std_level = $(level isa Symbol ? :level : :(level isa $LogLevel ? level : convert($LogLevel, level)::$LogLevel)) - if std_level >= $(_min_enabled_level)[] + if std_level.level >= $(_min_enabled_level)[] group = $(log_data._group) _module = $(log_data._module) logger = $(current_logger_for_env)(std_level, group, _module) From fdcb9a8c571666dccd38ff59007dd3e70ce15111 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 17 Feb 2025 16:43:41 -0500 Subject: [PATCH 03/10] test multithreaded logging (cherry picked from commit 349709aa40783b9c9fda232c0fc6c363af88c168) --- stdlib/Logging/test/runtests.jl | 43 +++++++++++++++++++++++++++++ stdlib/Logging/test/threads_exec.jl | 13 +++++++++ 2 files changed, 56 insertions(+) create mode 100644 stdlib/Logging/test/threads_exec.jl diff --git a/stdlib/Logging/test/runtests.jl b/stdlib/Logging/test/runtests.jl index 2fedbde557078..3e92b7d9e2697 100644 --- a/stdlib/Logging/test/runtests.jl +++ b/stdlib/Logging/test/runtests.jl @@ -306,4 +306,47 @@ end @test isempty(undoc) end +@testset "Logging when multithreaded" begin + n = 10000 + cmd = `$(Base.julia_cmd()) -t4 --color=no $(joinpath(@__DIR__, "threads_exec.jl")) $n` + fname = tempname() + @testset "Thread safety" begin + f = open(fname, "w") + @test success(run(pipeline(cmd, stderr=f))) + close(f) + end + + @testset "No tearing in log printing" begin + # Check for print tearing by verifying that each log entry starts and ends correctly + f = open(fname, "r") + entry_start = r"┌ (Info|Warning|Error): iteration" + entry_end = r"└ " + + open_entries = 0 + total_entries = 0 + for line in eachline(fname) + starts = count(entry_start, line) + starts > 1 && error("Interleaved logs: Multiple log entries started on one line") + if starts == 1 + startswith(line, entry_start) || error("Interleaved logs: Log entry started in the middle of a line") + open_entries += 1 + total_entries += 1 + end + + ends = count(entry_end, line) + starts == 1 && ends == 1 && error("Interleaved logs: Log entry started and and another ended on one line") + ends > 1 && error("Interleaved logs: Multiple log entries ended on one line") + if ends == 1 + startswith(line, entry_end) || error("Interleaved logs: Log entry ended in the middle of a line") + open_entries -= 1 + end + # Ensure no mismatched log entries + open_entries >= 0 || error("Interleaved logs") + end + + @test open_entries == 0 # Ensure all entries closed properly + @test total_entries == n * 3 # Ensure all logs were printed (3 because @debug is hidden) + end +end + end diff --git a/stdlib/Logging/test/threads_exec.jl b/stdlib/Logging/test/threads_exec.jl new file mode 100644 index 0000000000000..497a22b1c7b22 --- /dev/null +++ b/stdlib/Logging/test/threads_exec.jl @@ -0,0 +1,13 @@ +using Logging + +function test_threads_exec(n) + Threads.@threads for i in 1:n + @debug "iteration" maxlog=1 _id=Symbol("$(i)_debug") i Threads.threadid() + @info "iteration" maxlog=1 _id=Symbol("$(i)_info") i Threads.threadid() + @warn "iteration" maxlog=1 _id=Symbol("$(i)_warn") i Threads.threadid() + @error "iteration" maxlog=1 _id=Symbol("$(i)_error") i Threads.threadid() + end +end + +n = parse(Int, ARGS[1]) +test_threads_exec(n) From bcbd677ad87f19a508f05fb99be5bc598b555c35 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 13 Mar 2025 22:56:08 -0400 Subject: [PATCH 04/10] split locks into stream_lock and message_limits_lock --- base/logging/ConsoleLogger.jl | 16 ++++++++++------ base/logging/logging.jl | 14 +++++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/base/logging/ConsoleLogger.jl b/base/logging/ConsoleLogger.jl index 7eae28e7c7f3f..1f5814497c1d1 100644 --- a/base/logging/ConsoleLogger.jl +++ b/base/logging/ConsoleLogger.jl @@ -9,6 +9,9 @@ interactive work with the Julia REPL. Log levels less than `min_level` are filtered out. +This Logger is thread-safe, with locks for both orchestration of message +limits i.e. `maxlog`, and writes to the stream. + Message formatting can be controlled by setting keyword arguments: * `meta_formatter` is a function which takes the log event metadata @@ -24,29 +27,30 @@ Message formatting can be controlled by setting keyword arguments: """ struct ConsoleLogger <: AbstractLogger stream::IO + stream_lock::ReentrantLock min_level::LogLevel meta_formatter show_limited::Bool right_justify::Int message_limits::Dict{Any,Int} - lock::ReentrantLock + message_limits_lock::ReentrantLock end function ConsoleLogger(stream::IO, min_level=Info; meta_formatter=default_metafmt, show_limited=true, right_justify=0) - ConsoleLogger(stream, min_level, meta_formatter, + ConsoleLogger(stream, ReentrantLock(), min_level, meta_formatter, show_limited, right_justify, Dict{Any,Int}(), ReentrantLock()) end function ConsoleLogger(min_level=Info; meta_formatter=default_metafmt, show_limited=true, right_justify=0) - ConsoleLogger(closed_stream, min_level, meta_formatter, + ConsoleLogger(closed_stream, ReentrantLock(), min_level, meta_formatter, show_limited, right_justify, Dict{Any,Int}(), ReentrantLock()) end shouldlog(logger::ConsoleLogger, level, _module, group, id) = - @lock logger.lock get(logger.message_limits, id, 1) > 0 + @lock logger.message_limits_lock get(logger.message_limits, id, 1) > 0 min_enabled_level(logger::ConsoleLogger) = logger.min_level @@ -110,7 +114,7 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module hasmaxlog = haskey(kwargs, :maxlog) ? 1 : 0 maxlog = get(kwargs, :maxlog, nothing) if maxlog isa Core.BuiltinInts - @lock logger.lock begin + @lock logger.message_limits_lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) logger.message_limits[id] = remaining - 1 remaining > 0 || return @@ -188,6 +192,6 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module end b = take!(buf) - @lock logger.lock write(stream, b) + @lock logger.stream_lock write(stream, b) nothing end diff --git a/base/logging/logging.jl b/base/logging/logging.jl index d41d24f231e80..eb99b5fb4ba64 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -664,18 +664,22 @@ close(closed_stream) Simplistic logger for logging all messages with level greater than or equal to `min_level` to `stream`. If stream is closed then messages with log level greater or equal to `Warn` will be logged to `stderr` and below to `stdout`. + +This Logger is thread-safe, with a lock taken around orchestration of message +limits i.e. `maxlog`, and writes to the stream. """ struct SimpleLogger <: AbstractLogger stream::IO + stream_lock::ReentrantLock min_level::LogLevel message_limits::Dict{Any,Int} - lock::ReentrantLock + message_limits_lock::ReentrantLock end -SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, level, Dict{Any,Int}(), ReentrantLock()) +SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, ReentrantLock(), level, Dict{Any,Int}(), ReentrantLock()) SimpleLogger(level=Info) = SimpleLogger(closed_stream, level) shouldlog(logger::SimpleLogger, level, _module, group, id) = - @lock logger.lock get(logger.message_limits, id, 1) > 0 + @lock logger.message_limits_lock get(logger.message_limits, id, 1) > 0 min_enabled_level(logger::SimpleLogger) = logger.min_level @@ -686,7 +690,7 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, @nospecialize maxlog = get(kwargs, :maxlog, nothing) if maxlog isa Core.BuiltinInts - @lock logger.lock begin + @lock logger.message_limits_lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) logger.message_limits[id] = remaining - 1 remaining > 0 || return @@ -711,7 +715,7 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, end println(iob, "└ @ ", _module, " ", filepath, ":", line) b = take!(buf) - @lock logger.lock write(stream, b) + @lock logger.stream_lock write(stream, b) nothing end From 8f4f96be865ff6af33ffc0f81d4092fc52ff71e1 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 13 Mar 2025 22:57:34 -0400 Subject: [PATCH 05/10] add comment --- base/logging/logging.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/logging/logging.jl b/base/logging/logging.jl index eb99b5fb4ba64..abb0cb195608c 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -172,6 +172,7 @@ Alias for [`LogLevel(1_000_001)`](@ref LogLevel). const AboveMaxLevel = LogLevel( 1000001) # Global log limiting mechanism for super fast but inflexible global log limiting. +# Atomic ensures that the value is always consistent across threads. const _min_enabled_level = Threads.Atomic{Int32}(Debug) function show(io::IO, level::LogLevel) From 5b7e3c39adc538781435db2666525eb13dcf2c59 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 3 Apr 2025 13:11:57 -0400 Subject: [PATCH 06/10] fix message limits undercount bug --- base/logging/ConsoleLogger.jl | 2 +- base/logging/logging.jl | 2 +- stdlib/Test/src/logging.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base/logging/ConsoleLogger.jl b/base/logging/ConsoleLogger.jl index 1f5814497c1d1..75838e44a54be 100644 --- a/base/logging/ConsoleLogger.jl +++ b/base/logging/ConsoleLogger.jl @@ -116,8 +116,8 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module if maxlog isa Core.BuiltinInts @lock logger.message_limits_lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) + remaining == 0 && return logger.message_limits[id] = remaining - 1 - remaining > 0 || return end end diff --git a/base/logging/logging.jl b/base/logging/logging.jl index abb0cb195608c..efe36ec7cfef6 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -693,8 +693,8 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, if maxlog isa Core.BuiltinInts @lock logger.message_limits_lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) + remaining == 0 && return logger.message_limits[id] = remaining - 1 - remaining > 0 || return end end buf = IOBuffer() diff --git a/stdlib/Test/src/logging.jl b/stdlib/Test/src/logging.jl index b224d79e47cd9..a3a94a642f250 100644 --- a/stdlib/Test/src/logging.jl +++ b/stdlib/Test/src/logging.jl @@ -107,8 +107,8 @@ function Logging.handle_message(logger::TestLogger, level, msg, _module, if maxlog isa Core.BuiltinInts @lock logger.lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) + remaining == 0 && return logger.message_limits[id] = remaining - 1 - remaining > 0 || return end end end From 5587c4e522701dca40653eccad53344145271928 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 3 Apr 2025 13:15:57 -0400 Subject: [PATCH 07/10] return to a single lock --- base/logging/ConsoleLogger.jl | 13 ++++++------- base/logging/logging.jl | 11 +++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/base/logging/ConsoleLogger.jl b/base/logging/ConsoleLogger.jl index 75838e44a54be..04cea6cd66b4d 100644 --- a/base/logging/ConsoleLogger.jl +++ b/base/logging/ConsoleLogger.jl @@ -27,30 +27,29 @@ Message formatting can be controlled by setting keyword arguments: """ struct ConsoleLogger <: AbstractLogger stream::IO - stream_lock::ReentrantLock + lock::ReentrantLock min_level::LogLevel meta_formatter show_limited::Bool right_justify::Int message_limits::Dict{Any,Int} - message_limits_lock::ReentrantLock end function ConsoleLogger(stream::IO, min_level=Info; meta_formatter=default_metafmt, show_limited=true, right_justify=0) ConsoleLogger(stream, ReentrantLock(), min_level, meta_formatter, - show_limited, right_justify, Dict{Any,Int}(), ReentrantLock()) + show_limited, right_justify, Dict{Any,Int}()) end function ConsoleLogger(min_level=Info; meta_formatter=default_metafmt, show_limited=true, right_justify=0) ConsoleLogger(closed_stream, ReentrantLock(), min_level, meta_formatter, - show_limited, right_justify, Dict{Any,Int}(), ReentrantLock()) + show_limited, right_justify, Dict{Any,Int}()) end shouldlog(logger::ConsoleLogger, level, _module, group, id) = - @lock logger.message_limits_lock get(logger.message_limits, id, 1) > 0 + @lock logger.lock get(logger.message_limits, id, 1) > 0 min_enabled_level(logger::ConsoleLogger) = logger.min_level @@ -114,7 +113,7 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module hasmaxlog = haskey(kwargs, :maxlog) ? 1 : 0 maxlog = get(kwargs, :maxlog, nothing) if maxlog isa Core.BuiltinInts - @lock logger.message_limits_lock begin + @lock logger.lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) remaining == 0 && return logger.message_limits[id] = remaining - 1 @@ -192,6 +191,6 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module end b = take!(buf) - @lock logger.stream_lock write(stream, b) + @lock logger.lock write(stream, b) nothing end diff --git a/base/logging/logging.jl b/base/logging/logging.jl index efe36ec7cfef6..bfcf771eb2ea5 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -671,16 +671,15 @@ limits i.e. `maxlog`, and writes to the stream. """ struct SimpleLogger <: AbstractLogger stream::IO - stream_lock::ReentrantLock + lock::ReentrantLock min_level::LogLevel message_limits::Dict{Any,Int} - message_limits_lock::ReentrantLock end -SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, ReentrantLock(), level, Dict{Any,Int}(), ReentrantLock()) +SimpleLogger(stream::IO, level=Info) = SimpleLogger(stream, ReentrantLock(), level, Dict{Any,Int}()) SimpleLogger(level=Info) = SimpleLogger(closed_stream, level) shouldlog(logger::SimpleLogger, level, _module, group, id) = - @lock logger.message_limits_lock get(logger.message_limits, id, 1) > 0 + @lock logger.lock get(logger.message_limits, id, 1) > 0 min_enabled_level(logger::SimpleLogger) = logger.min_level @@ -691,7 +690,7 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, @nospecialize maxlog = get(kwargs, :maxlog, nothing) if maxlog isa Core.BuiltinInts - @lock logger.message_limits_lock begin + @lock logger.lock begin remaining = get!(logger.message_limits, id, Int(maxlog)::Int) remaining == 0 && return logger.message_limits[id] = remaining - 1 @@ -716,7 +715,7 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, end println(iob, "└ @ ", _module, " ", filepath, ":", line) b = take!(buf) - @lock logger.stream_lock write(stream, b) + @lock logger.lock write(stream, b) nothing end From 618679be6b77e4690a2d844773e4d2bc304187e1 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 3 Apr 2025 13:42:31 -0400 Subject: [PATCH 08/10] add note about `disable_logging` Fixes https://github.com/JuliaLang/julia/issues/34037 --- base/logging/logging.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/logging/logging.jl b/base/logging/logging.jl index bfcf771eb2ea5..fdd2affa39080 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -543,7 +543,8 @@ with_logstate(f::Function, logstate) = @with(CURRENT_LOGSTATE => logstate, f()) Disable all log messages at log levels equal to or less than `level`. This is a *global* setting, intended to make debug logging extremely cheap when -disabled. +disabled. Note that this cannot be used to enable logging that is currently disabled +by other mechanisms. # Examples ```julia From 678e057d4357763ce9c0e2b460abf2e8a719066f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 3 Apr 2025 13:42:56 -0400 Subject: [PATCH 09/10] check logger.stream isopen immediately before write --- base/logging/logging.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/base/logging/logging.jl b/base/logging/logging.jl index fdd2affa39080..b43b7d9faa594 100644 --- a/base/logging/logging.jl +++ b/base/logging/logging.jl @@ -699,9 +699,6 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, end buf = IOBuffer() stream::IO = logger.stream - if !(isopen(stream)::Bool) - stream = stderr - end iob = IOContext(buf, stream) levelstr = level == Warn ? "Warning" : string(level) msglines = eachsplit(chomp(convert(String, string(message))::String), '\n') @@ -716,7 +713,12 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, end println(iob, "└ @ ", _module, " ", filepath, ":", line) b = take!(buf) - @lock logger.lock write(stream, b) + @lock logger.lock begin + if !(isopen(stream)::Bool) + stream = stderr + end + write(stream, b) + end nothing end From 019258a9efaaaff5552f54709e4faf422f28baf9 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 3 Apr 2025 13:48:40 -0400 Subject: [PATCH 10/10] add note about not logging within logger.lock --- base/logging/ConsoleLogger.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/logging/ConsoleLogger.jl b/base/logging/ConsoleLogger.jl index 04cea6cd66b4d..8766d0ae56331 100644 --- a/base/logging/ConsoleLogger.jl +++ b/base/logging/ConsoleLogger.jl @@ -27,7 +27,7 @@ Message formatting can be controlled by setting keyword arguments: """ struct ConsoleLogger <: AbstractLogger stream::IO - lock::ReentrantLock + lock::ReentrantLock # do not log within this lock min_level::LogLevel meta_formatter show_limited::Bool