Skip to content

Fix IOBuffer data race in stdio_loop / take!#120

Merged
giordano merged 1 commit intoJuliaTesting:mainfrom
DaniGlez:main
Mar 26, 2026
Merged

Fix IOBuffer data race in stdio_loop / take!#120
giordano merged 1 commit intoJuliaTesting:mainfrom
DaniGlez:main

Conversation

@DaniGlez
Copy link
Copy Markdown
Contributor

I got some ParallelTestRunner 2.5.0 failures that seem to be related to concurrent accesses to an IOBuffer; I will post a full stack trace in comments. This PR is AI-assisted, but it seems reasonable to the best of my knowledge and I have manually tested and verified that it does indeed fix the issue.

Detailed description:
Two background tasks (stdout + stderr) wrote to the same IOBuffer concurrently, and the main test loop called take! without any synchronisation. Julia's IOBuffer is not thread-safe, so this caused torn reads of io.size vs io.data under higher I/O load, manifesting as:

DimensionMismatch: Attempted to wrap a MemoryRef of length N with
an Array of size dims=(M,)

The proposed fix involves adding a ReentrantLock to PTRWorker; both stdio_loop write tasks and the take! call in runtests hold the lock.

Two background tasks (stdout + stderr) wrote to the same IOBuffer
concurrently, and the main test loop called take! without any
synchronisation. Julia's IOBuffer is not thread-safe, so this caused
torn reads of io.size vs io.data under higher I/O load, manifesting as:

  DimensionMismatch: Attempted to wrap a MemoryRef of length N with
  an Array of size dims=(M,)

Fix: add a ReentrantLock to PTRWorker; both stdio_loop write tasks
and the take! call in runtests hold the lock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@DaniGlez
Copy link
Copy Markdown
Contributor Author

Example stack trace:

Caught an error, stopping...
ERROR: LoadError: TaskFailedException
Stacktrace:
 [1] wait(t::Task)
   @ Base ./task.jl:370
 [2] runtests(mod::Module, args::ParallelTestRunner.ParsedArgs; testsuite::Dict{String, Expr}, init_code::Expr, init_worker_code::Expr, test_worker::Returns{Nothing}, stdout::IOContext{Base.PipeEndpoint}, stderr::IOContext{Base.PipeEndpoint}, max_worker_rss::Int64)
   @ ParallelTestRunner [redacted]/packages/ParallelTestRunner/EzCGZ/src/ParallelTestRunner.jl:1138
 [3] #runtests#58
   @ [redacted]/packages/ParallelTestRunner/EzCGZ/src/ParallelTestRunner.jl:1282 [inlined]
 [4] top-level scope
   @ [redacted]/test/runtests.jl:71
 [5] include(fname::String)
   @ Main ./sysimg.jl:38
 [6] top-level scope
   @ none:6

    nested task error: DimensionMismatch: Attempted to wrap a MemoryRef of length 30 with an Array of size dims=(507,) which is invalid because prod(dims) = 507 > 30 so that the array would have more elements than the underlying memory can store.
    Stacktrace:
     [1] invalid_wrap_err(len::Int64, dims::Tuple{Int64}, proddims::Int64)
       @ Base ./array.jl:3102
     [2] _wrap
       @ ./array.jl:3094 [inlined]
     [3] wrap
       @ ./array.jl:3120 [inlined]
     [4] take!(io::IOBuffer)
       @ Base ./iobuffer.jl:469
     [5] (::ParallelTestRunner.var"#33#49"{Dict{String, Expr}, Expr, Expr, Returns{Nothing}, Int64, ParallelTestRunner.ParsedArgs, Channel{Tuple}, ParallelTestRunner.TestIOContext, ParallelTestRunner.var"#stop_work#41"{Vector{Task}}, ReentrantLock, Dict{String, Float64}, Vector{Any}, Vector{String}})()
       @ ParallelTestRunner [redacted]/packages/ParallelTestRunner/EzCGZ/src/ParallelTestRunner.jl:1064
in expression starting at [redacted]/test/runtests.jl:14
ERROR: Package ... errored during testing
Stacktrace:
 [1] pkgerror(msg::String)
   @ Pkg.Types /usr/local/julia/share/julia/stdlib/v1.11/Pkg/src/Types.jl:68
 [2] test(ctx::Pkg.Types.Context, pkgs::Vector{Pkg.Types.PackageSpec}; coverage::Bool, julia_args::Cmd, test_args::Cmd, test_fn::Nothing, force_latest_compatible_version::Bool, allow_earlier_backwards_compatible_versions::Bool, allow_reresolve::Bool)
   @ Pkg.Operations /usr/local/julia/share/julia/stdlib/v1.11/Pkg/src/Operations.jl:2128
 [3] test
   @ /usr/local/julia/share/julia/stdlib/v1.11/Pkg/src/Operations.jl:2011 [inlined]
 [4] test(ctx::Pkg.Types.Context, pkgs::Vector{Pkg.Types.PackageSpec}; coverage::Bool, test_fn::Nothing, julia_args::Vector{String}, test_args::Cmd, force_latest_compatible_version::Bool, allow_earlier_backwards_compatible_versions::Bool, allow_reresolve::Bool, kwargs::@Kwargs{io::IOContext{IO}})
   @ Pkg.API /usr/local/julia/share/julia/stdlib/v1.11/Pkg/src/API.jl:481
 [5] test(pkgs::Vector{Pkg.Types.PackageSpec}; io::IOContext{IO}, kwargs::@Kwargs{coverage::Bool, julia_args::Vector{String}, force_latest_compatible_version::Bool, allow_reresolve::Bool})
   @ Pkg.API /usr/local/julia/share/julia/stdlib/v1.11/Pkg/src/API.jl:159
 [6] test(; name::Nothing, uuid::Nothing, version::Nothing, url::Nothing, rev::Nothing, path::Nothing, mode::Pkg.Types.PackageMode, subdir::Nothing, kwargs::@Kwargs{coverage::Bool, julia_args::Vector{String}, force_latest_compatible_version::Bool, allow_reresolve::Bool})
   @ Pkg.API /usr/local/julia/share/julia/stdlib/v1.11/Pkg/src/API.jl:174
 [7] top-level scope
   @ none:7

@giordano giordano merged commit 9e48ebf into JuliaTesting:main Mar 26, 2026
22 checks passed
@giordano
Copy link
Copy Markdown
Collaborator

This is now live in v2.5.1

@DaniGlez
Copy link
Copy Markdown
Contributor Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants