[FIX] install s390x: the compiler was killed for lack of memory
matplotlib dies on "c++: fatal error: Killed signal terminated program cc1plus". "Killed" is a SIGKILL: the kernel's OOM killer, and nothing in the message names memory. A single matplotlib source file asks cc1plus for up to 2.5 GiB. Two causes compound. The VM is small -- the catalogue starts at 1 or 2 GiB -- and every build backend launches nproc compilations in parallel, each with its own cc1plus. Six cores exhaust 8 GiB. Hence two answers. A swap file tops memory up to 8 GiB, never taking more than half the free disk nor touching fstab -- a declared and missing swap degrades boot. And parallelism is bounded by actual memory, 2 GiB per task. ninja has no parallelism environment variable, but meson-python reads "NINJA", the executable PATH -- verified in mesonpy 0.20. We point it at a wrapper that adds the -j. That is what saves matplotlib; MAKEFLAGS and CMAKE_BUILD_PARALLEL_LEVEL cover the rest. --- FR --- matplotlib s'arrête sur « c++: fatal error: Killed signal terminated program cc1plus ». « Killed » est un SIGKILL : c'est le tueur du noyau, et rien dans le message ne nomme la mémoire. Un seul fichier de matplotlib demande jusqu'à 2,5 Gio à cc1plus. Deux causes se cumulent. La VM est petite — le catalogue démarre à 1 ou 2 Gio — et chaque moteur de build lance nproc compilations en parallèle, chacune avec son cc1plus. Six cœurs épuisent 8 Gio. D'où deux réponses. Un fichier d'échange complète la mémoire jusqu'à 8 Gio, sans jamais prendre plus de la moitié du disque libre ni toucher à fstab — un swap déclaré et disparu dégrade le démarrage. Et le parallélisme est borné d'après la mémoire réelle, 2 Gio par tâche. ninja n'a aucune variable de parallélisme, mais meson-python lit « NINJA », le CHEMIN de l'exécutable — vérifié dans mesonpy 0.20. On y met une enveloppe qui ajoute le -j. C'est ce qui sauve matplotlib ; MAKEFLAGS et CMAKE_BUILD_PARALLEL_LEVEL couvrent les autres. Assisted-by: Claude Opus 5
This commit is contained in:
parent
d6221ee48d
commit
62f3e62e83
5 changed files with 153 additions and 0 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
. ./env_var.sh
|
||||
. ./script/install/lib_qpdf.sh
|
||||
. ./script/install/lib_lowmem.sh
|
||||
|
||||
EL_USER=${USER}
|
||||
#EL_INSTALL_WKHTMLTOPDF="True"
|
||||
|
|
@ -72,6 +73,11 @@ fi
|
|||
#--------------------------------------------------
|
||||
if [ "$(uname -m)" = "s390x" ]; then
|
||||
echo "Arch s390x detected"
|
||||
# La mémoire est la ressource qui manque en premier ici : cc1plus demande
|
||||
# jusqu'à 2,5 Gio pour un seul fichier de matplotlib, et le tueur du noyau
|
||||
# abrège sans jamais nommer la mémoire (« Killed signal terminated program
|
||||
# cc1plus »). On complète par du swap avant d'en arriver là.
|
||||
el_swap_ensure
|
||||
# Sur une VM fraîche, l'index apt peut être vide : « apt install » échouerait
|
||||
# alors sur TOUT le lot, et l'absence d'un seul paquet ne se voit que bien
|
||||
# plus loin, sous la forme d'une commande introuvable.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
. ./env_var.sh
|
||||
. ./script/install/lib_qpdf.sh
|
||||
. ./script/install/lib_lowmem.sh
|
||||
|
||||
EL_USER=${USER}
|
||||
|
||||
|
|
@ -100,6 +101,12 @@ sudo dnf group install -y ${DNF_SKIP} development c-development \
|
|||
# « The headers or library files could not be found for jpeg » pour pillow.
|
||||
if [ "$(uname -m)" = "s390x" ]; then
|
||||
echo -e "\n---- Dependances de compilation s390x ----"
|
||||
# La mémoire est la ressource qui manque en premier ici : cc1plus
|
||||
# demande jusqu'à 2,5 Gio pour un seul fichier de matplotlib, et le
|
||||
# tueur du noyau abrège sans jamais nommer la mémoire (« Killed signal
|
||||
# terminated program cc1plus »). On complète par du swap avant d'en
|
||||
# arriver là.
|
||||
el_swap_ensure
|
||||
# Best-effort : un nom qui change d'une version à l'autre ne doit pas
|
||||
# emporter le lot. Ce qui est vraiment indispensable est déjà installé
|
||||
# au-dessus (compilateurs) ou plus bas (pyenv, PostgreSQL).
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ POETRY_ODOO_PATH=${VENV_ODOO_PATH}/bin/poetry
|
|||
# Choix pip/uv : un seul endroit décide, comme pour mise/pyenv.
|
||||
# shellcheck source=script/install/lib_pip_provider.sh
|
||||
. ./script/install/lib_pip_provider.sh
|
||||
# Bornage des compilations : voir el_build_limit_jobs.
|
||||
. ./script/install/lib_lowmem.sh
|
||||
export WITH_POETRY_INSTALLATION=1
|
||||
|
||||
# Verbosité de l'installation Poetry. « -q » a été retiré du défaut : dans Cleo,
|
||||
|
|
@ -153,6 +155,14 @@ if [[ "${EL_PHASE}" != "setup" ]]; then
|
|||
echo "Poetry introuvable a ${POETRY_ODOO_PATH}, arret."
|
||||
exit 1
|
||||
fi
|
||||
# Là où rien n'a de roue, « poetry install » COMPILE des centaines de
|
||||
# paquets, et le moteur de build de chacun lance nproc tâches en
|
||||
# parallèle. cc1plus demande jusqu'à 2,5 Gio pour un seul fichier de
|
||||
# matplotlib : six cœurs épuisent 8 Gio, et le noyau tue le compilateur
|
||||
# sans jamais nommer la mémoire. On borne d'après la mémoire disponible.
|
||||
if [[ "$(uname -m)" == "s390x" ]]; then
|
||||
el_build_limit_jobs
|
||||
fi
|
||||
"${POETRY_ODOO_PATH}" --version
|
||||
# To fix keyring problem when installation is blocked, use
|
||||
export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
. ./env_var.sh
|
||||
. ./script/install/lib_qpdf.sh
|
||||
. ./script/install/lib_lowmem.sh
|
||||
|
||||
EL_USER=${USER}
|
||||
|
||||
|
|
@ -224,6 +225,12 @@ echo -e "\n--- Installing suse dependency --"
|
|||
# compiler ». Tumbleweed livre 1.94, bien au-dessus du 1.78 qu'exige le
|
||||
# Cargo.lock v4 de cryptography. On évite ainsi ~200 Mo inutiles sur amd64.
|
||||
if [ "$(uname -m)" = "s390x" ]; then
|
||||
# La mémoire est la ressource qui manque en premier ici : cc1plus
|
||||
# demande jusqu'à 2,5 Gio pour un seul fichier de matplotlib, et le
|
||||
# tueur du noyau abrège sans jamais nommer la mémoire (« Killed signal
|
||||
# terminated program cc1plus »). On complète par du swap avant d'en
|
||||
# arriver là.
|
||||
el_swap_ensure
|
||||
zyp_soft rust cargo
|
||||
if ! command -v cargo > /dev/null 2>&1; then
|
||||
# FATAL, et non plus un simple avertissement : sur s390x, bcrypt et
|
||||
|
|
|
|||
123
script/install/lib_lowmem.sh
Normal file
123
script/install/lib_lowmem.sh
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env bash
|
||||
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
#
|
||||
# Bibliothèque SOURÇABLE : compiler sans se faire tuer par manque de mémoire.
|
||||
#
|
||||
# Le symptôme est toujours le même, et il ne nomme jamais la mémoire :
|
||||
# c++: fatal error: Killed signal terminated program cc1plus
|
||||
# « Killed » est un SIGKILL, donc le tueur de mémoire du noyau. Il est arrivé
|
||||
# sur matplotlib, dont _backend_agg_wrapper.cpp instancie assez de gabarits
|
||||
# C++ pour demander 1,5 à 2,5 Gio à cc1plus — À LUI SEUL.
|
||||
#
|
||||
# Deux causes se cumulent, et il faut les traiter toutes les deux :
|
||||
# 1. la VM est petite (le catalogue démarre à 1 ou 2 Gio) ;
|
||||
# 2. le moteur de build lance nproc compilations EN PARALLÈLE, chacune avec
|
||||
# son propre cc1plus. Six cœurs suffisent à épuiser 8 Gio.
|
||||
#
|
||||
# Réservé aux architectures sans roue PyPI par l'appelant : ailleurs pip pose
|
||||
# des binaires et rien de tout cela ne s'exécute.
|
||||
|
||||
# RAM + swap visés, en Gio. « 0 » désactive entièrement la création de swap,
|
||||
# pour qui préfère dimensionner sa machine lui-même.
|
||||
EL_SWAP_TARGET_GIB="${EL_SWAP_TARGET_GIB:-8}"
|
||||
EL_SWAP_FILE="${EL_SWAP_FILE:-/swapfile.erplibre}"
|
||||
# Mio de mémoire à réserver par compilation simultanée. 2048 vient du pic
|
||||
# mesuré de cc1plus sur matplotlib ; en dessous, le tueur revient.
|
||||
EL_MEM_PER_JOB_MB="${EL_MEM_PER_JOB_MB:-2048}"
|
||||
|
||||
el_mem_mb() {
|
||||
awk '/^MemTotal:/ {print int($2 / 1024)}' /proc/meminfo 2> /dev/null || echo 0
|
||||
}
|
||||
|
||||
el_swap_mb() {
|
||||
awk '/^SwapTotal:/ {print int($2 / 1024)}' /proc/meminfo 2> /dev/null || echo 0
|
||||
}
|
||||
|
||||
# Complète la mémoire par un fichier d'échange, si elle manque.
|
||||
#
|
||||
# Volontairement NON persisté dans /etc/fstab : un swap déclaré et disparu
|
||||
# dégrade le démarrage, et cette machine n'en a besoin que le temps de
|
||||
# l'installation. La fonction est idempotente, elle repassera au besoin.
|
||||
el_swap_ensure() {
|
||||
local ram swap missing free_mb size
|
||||
[ "${EL_SWAP_TARGET_GIB}" -gt 0 ] 2> /dev/null || return 0
|
||||
ram="$(el_mem_mb)"
|
||||
swap="$(el_swap_mb)"
|
||||
missing=$((EL_SWAP_TARGET_GIB * 1024 - ram - swap))
|
||||
if [ "${missing}" -le 0 ]; then
|
||||
echo " memoire : ${ram} Mio + ${swap} Mio de swap, suffisant."
|
||||
return 0
|
||||
fi
|
||||
if [ -e "${EL_SWAP_FILE}" ]; then
|
||||
# Déjà là mais pas actif : un « swapon » suffit, on ne le réécrit pas.
|
||||
sudo swapon "${EL_SWAP_FILE}" 2> /dev/null && {
|
||||
echo " swap : ${EL_SWAP_FILE} reactive."
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
# Ne jamais remplir le disque pour gagner de la mémoire : au plus la moitié
|
||||
# de ce qui reste libre. Un disque plein casse l'installation autrement.
|
||||
free_mb="$(df -Pm / | awk 'NR==2 {print $4}')"
|
||||
size="${missing}"
|
||||
[ "${size}" -gt $((free_mb / 2)) ] && size=$((free_mb / 2))
|
||||
if [ "${size}" -lt 512 ]; then
|
||||
echo " swap : pas assez d'espace disque libre (${free_mb} Mio), abandon." >&2
|
||||
return 0
|
||||
fi
|
||||
echo " memoire : ${ram} Mio seulement — ajout de ${size} Mio de swap."
|
||||
# fallocate est instantané ; dd est le repli des systèmes de fichiers qui
|
||||
# ne le supportent pas (et un swap sur trous ne fonctionnerait pas).
|
||||
if ! sudo fallocate -l "${size}M" "${EL_SWAP_FILE}" 2> /dev/null; then
|
||||
sudo dd if=/dev/zero of="${EL_SWAP_FILE}" bs=1M count="${size}" \
|
||||
status=none 2> /dev/null || {
|
||||
echo " swap : creation impossible, on continue sans." >&2
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
sudo chmod 600 "${EL_SWAP_FILE}"
|
||||
if sudo mkswap "${EL_SWAP_FILE}" > /dev/null 2>&1 \
|
||||
&& sudo swapon "${EL_SWAP_FILE}"; then
|
||||
echo " swap : ${size} Mio actifs (${EL_SWAP_FILE})."
|
||||
else
|
||||
echo " swap : activation impossible, on continue sans." >&2
|
||||
sudo rm -f "${EL_SWAP_FILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Nombre de compilations simultanées que la mémoire tolère.
|
||||
el_build_jobs() {
|
||||
local total cpus jobs
|
||||
total=$(($(el_mem_mb) + $(el_swap_mb)))
|
||||
cpus="$(nproc 2> /dev/null || echo 1)"
|
||||
jobs=$((total / EL_MEM_PER_JOB_MB))
|
||||
[ "${jobs}" -lt 1 ] && jobs=1
|
||||
[ "${jobs}" -gt "${cpus}" ] && jobs="${cpus}"
|
||||
echo "${jobs}"
|
||||
}
|
||||
|
||||
# Exporte de quoi brider les moteurs de build. À appeler AVANT « poetry
|
||||
# install », dans le shell qui le lance.
|
||||
#
|
||||
# make MAKEFLAGS, lu par tout Makefile.
|
||||
# cmake CMAKE_BUILD_PARALLEL_LEVEL, lu par « cmake --build » (manifold3d).
|
||||
# ninja aucune variable de parallélisme n'existe — mais meson-python lit
|
||||
# « NINJA », le CHEMIN de l'exécutable (vérifié dans mesonpy 0.20 :
|
||||
# « env_ninja = os.environ.get('NINJA') »). On y met une enveloppe
|
||||
# qui ajoute le -j. C'est ce qui sauve matplotlib.
|
||||
# MAX_JOBS convention de PyTorch et de quelques autres.
|
||||
el_build_limit_jobs() {
|
||||
local jobs real wrapper
|
||||
jobs="$(el_build_jobs)"
|
||||
export MAKEFLAGS="-j${jobs}"
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL="${jobs}"
|
||||
export MAX_JOBS="${jobs}"
|
||||
real="$(command -v ninja || command -v ninja-build || true)"
|
||||
if [ -n "${real}" ]; then
|
||||
wrapper="${TMPDIR:-/tmp}/el-ninja-j${jobs}"
|
||||
printf '#!/bin/sh\nexec %s -j%s "$@"\n' "${real}" "${jobs}" > "${wrapper}"
|
||||
chmod +x "${wrapper}"
|
||||
export NINJA="${wrapper}"
|
||||
fi
|
||||
echo " compilation bridee a ${jobs} tache(s) simultanee(s)."
|
||||
}
|
||||
Loading…
Reference in a new issue