From 43abd0aebb12bb5c2f2d99327de75bf93d4be7d0 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 15 Sep 2026 10:04:45 +0200 Subject: [PATCH] Keep the libuv event loop alive during builds Julia 1.12 can lose the wakeup of the thread that services the libuv event loop: when a worker thread arms IO (Pkg downloads artifacts from a Threads.@spawn task) while thread 0 is parked, nobody runs uv_run again and the download stalls forever with data waiting in the socket. On Yggdrasil this shows up as jobs stuck at "Installing N artifacts" until the 5-hour timeout. Arm a periodic Timer for the lifetime of build_tarballs when running with more than one thread. An active handle keeps the loop alive, so thread 0 blocks inside uv_run rather than parking and registers new watchers on its next iteration. Remove once the runtime bug is fixed. Works around JuliaLang/julia#63173. --- src/AutoBuild.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/AutoBuild.jl b/src/AutoBuild.jl index 75b75326..280a1ff0 100644 --- a/src/AutoBuild.jl +++ b/src/AutoBuild.jl @@ -137,6 +137,21 @@ const BUILD_HELP = ( """ ) +# Julia 1.12 can lose the wakeup of the thread that services the libuv event +# loop when a worker thread arms IO (e.g. Pkg downloading artifacts in a +# `Threads.@spawn` task) while that thread is parked. The download then stalls +# forever with data waiting in the socket. Keeping a libuv handle active means +# the event loop never becomes idle, so thread 0 blocks inside `uv_run` instead +# of parking and picks up new watchers on its next iteration. +# See https://github.com/JuliaLang/julia/issues/63173. +const EVENT_LOOP_KEEPALIVE = Ref{Union{Nothing,Timer}}(nothing) +function keep_event_loop_alive() + if EVENT_LOOP_KEEPALIVE[] === nothing && Threads.nthreads() > 1 + EVENT_LOOP_KEEPALIVE[] = Timer(_ -> nothing, 1; interval=1) + end + return nothing +end + """ build_tarballs(ARGS, src_name, src_version, sources, script, platforms, products, dependencies; kwargs...) @@ -210,6 +225,8 @@ function build_tarballs(ARGS, src_name, src_version, sources, script, return nothing end + keep_event_loop_alive() + if compression_format != "gzip" && minimum_compat(julia_compat) < v"1.6" error("Compression formats different from gzip are supported only from Julia v1.6, increase the Julia compat if you want to use a non-default format") end