From 2244c057ad320a00b7fd8a4a26e3dc89cd5a4a27 Mon Sep 17 00:00:00 2001 From: Christophe Combelles Date: Sat, 16 May 2026 19:00:05 +0200 Subject: [PATCH 1/2] fix(docker): set musl pthread default stack via PT_GNU_STACK (real fix for #60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #60 diagnosed the SIGSEGV correctly — musl's 128 KB default pthread stack overflowed on the first BLAS3 call from MUMPS factorization — but patched the wrong place: the sed inserted pthread_attr_setstacksize into a block guarded by `#ifdef NEED_STACKATTR`, which blas_server.c itself `#undef`s unconditionally on Linux (line 103). The injected code compiled out. `strings volca | grep pthread_attr_setstacksize` on the merged image returns zero matches. User-confirmed: `OPENBLAS_NUM_THREADS=1` avoids the crash (no worker threads → no 128 KB stacks). Real fix: bake the desired default into the ELF's PT_GNU_STACK header via `-Wl,-z,stack-size=8388608`. musl reads p_memsz at process start and uses it as __default_stacksize, so every pthread created with `NULL` attr — OpenBLAS workers, GHC RTS capabilities, anything else — starts with 8 MB. No source patching of OpenBLAS needed. --- docker/Dockerfile | 9 --------- gen-cabal-config.sh | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d999f0..f7d6215 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -88,15 +88,6 @@ RUN . /tmp/versions.env \ && curl -fsSL "https://github.com/OpenMathLib/OpenBLAS/releases/download/v${OPENBLAS_VERSION}/OpenBLAS-${OPENBLAS_VERSION}.tar.gz" \ | tar -xz -C /tmp \ && cd "/tmp/OpenBLAS-${OPENBLAS_VERSION}" \ - # musl's pthread default stack is 128 KB (vs glibc's 8 MB read from - # RLIMIT_STACK). OpenBLAS worker threads inherit it and overflow on - # DYNAMIC_ARCH Fortran kernels with large auto-arrays → SIGSEGV at the - # first BLAS3 call from MUMPS. Force an 8 MB stack on each worker. - # Guard: fail the build if the upstream anchor disappears so a silent - # OpenBLAS refactor can't reintroduce the crash. - && grep -q 'pthread_attr_init(&attr);' driver/others/blas_server.c \ - && sed -i 's|pthread_attr_init(&attr);|pthread_attr_init(\&attr); pthread_attr_setstacksize(\&attr, 8 << 20);|' driver/others/blas_server.c \ - && grep -q 'pthread_attr_setstacksize(&attr, 8 << 20);' driver/others/blas_server.c \ && make -j"$(nproc)" \ NO_SHARED=1 \ USE_THREAD=1 USE_OPENMP=0 \ diff --git a/gen-cabal-config.sh b/gen-cabal-config.sh index 94092b0..72d47d5 100755 --- a/gen-cabal-config.sh +++ b/gen-cabal-config.sh @@ -59,7 +59,20 @@ EOF # Effective on the C/Fortran archives that were compiled with # -ffunction-sections / -fdata-sections (OpenBLAS in our pipeline); # harmless on the others. - MUSL_LINK_FLAGS="-optl-L$MUMPS_LIB_DIR -optl-L$OPENBLAS_LIB_DIR -optl-Wl,--gc-sections -optl-Wl,--start-group -optl-ldmumps_seq -optl-lmumps_common_seq -optl-lpord_seq -optl-lmpiseq_seq -optl-lopenblas -optl-lgfortran $QUADMATH_FLAG -optl-Wl,--end-group -optl-lpthread -optl-lm" + # + # -z stack-size=8388608: bake an 8 MB PT_GNU_STACK into the ELF. + # musl reads this header at startup and uses it as the default + # pthread stack size (its hardcoded fallback is 128 KB, vs glibc's + # 8 MB picked up from RLIMIT_STACK). OpenBLAS DYNAMIC_ARCH Fortran + # kernels have large auto-arrays that overflow 128 KB on the first + # BLAS3 call inside MUMPS factorization (SIGSEGV / exit 139). + # Setting it at link time covers every pthread the binary creates + # — RTS capabilities and OpenBLAS workers alike — without patching + # OpenBLAS source. (An earlier attempt to sed the stack size into + # OpenBLAS's blas_server.c was a no-op: the relevant block sits + # under #ifdef NEED_STACKATTR, which blas_server.c #undef's + # unconditionally on Linux.) + MUSL_LINK_FLAGS="-optl-L$MUMPS_LIB_DIR -optl-L$OPENBLAS_LIB_DIR -optl-Wl,--gc-sections -optl-Wl,-z,stack-size=8388608 -optl-Wl,--start-group -optl-ldmumps_seq -optl-lmumps_common_seq -optl-lpord_seq -optl-lmpiseq_seq -optl-lopenblas -optl-lgfortran $QUADMATH_FLAG -optl-Wl,--end-group -optl-lpthread -optl-lm" cat >> "$OUTPUT" << EOF optimization: 2 split-sections: True From 7b149c13440e37d861802be42d65bfc30e96d2cb Mon Sep 17 00:00:00 2001 From: Christophe Combelles Date: Sat, 16 May 2026 19:08:36 +0200 Subject: [PATCH 2/2] fix(docker): assert PT_GNU_STACK MemSiz=8 MiB at build time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #60 shipped broken because nothing in the build pipeline verified the injected pthread_attr_setstacksize call survived compilation; the silent regression was only caught by a production SIGSEGV. The fix in this branch (-Wl,-z,stack-size=8388608) replaces that with a different invariant — "PT_GNU_STACK->p_memsz == 0x800000 in the shipped ELF" — which is again only checked manually. Add a readelf-based assertion right after UPX so a future linker-flag refactor (or a UPX-side header rewrite) fails the image build with a diagnostic naming the actual value found, instead of recurring as a runtime crash on a customer VM. The check runs on /build/output/volca (post-strip, post-UPX) because that's the binary that actually runs. binutils — which provides readelf — is already in the build-stage apk add. --- docker/Dockerfile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index f7d6215..1227cb9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -165,6 +165,18 @@ RUN mkdir -p /build/output \ && echo "Size before UPX: $(du -h /build/output/volca | cut -f1)" \ && upx /build/output/volca \ && echo "Size after UPX: $(du -h /build/output/volca | cut -f1)" \ + # Guard the musl pthread-stack fix from PR #60/#61. The link-time + # -Wl,-z,stack-size=8388608 (see gen-cabal-config.sh, LINK_MODE=musl) + # bakes an 8 MiB PT_GNU_STACK header into the ELF; musl reads it at + # startup as __default_stacksize. If a future linker-flag refactor + # drops it — or UPX strips it — every pthread falls back to musl's + # hardcoded 128 KB and OpenBLAS DYNAMIC_ARCH Fortran workers SIGSEGV + # on the first BLAS3 call inside MUMPS factorization (exit 139). + # Fail the build loudly here rather than rediscover it from a + # production crash log a third time. + && { STACK_MEMSZ=$(readelf -l /build/output/volca | grep -A1 GNU_STACK | tail -n1 | awk '{print $2}'); \ + echo "$STACK_MEMSZ" | grep -qE '^0x0*800000$' \ + || { echo "ERROR: PT_GNU_STACK MemSiz=$STACK_MEMSZ on shipped binary, expected 8 MiB (0x800000) — musl default pthread stack would fall back to 128 KB; OpenBLAS workers will SIGSEGV in MUMPS factorization. See PR #60/#61."; exit 1; }; } \ && file /build/output/volca # Stage 2: small runtime image. The volca binary is fully static (no libc