¿Qué podría estar causando que make se cuelgue al compilar en varios núcleos?

¿Qué podría estar causando que make se cuelgue al compilar en varios núcleos?

Ayer estaba intentando compilar elRAÍZpaquete desde la fuente. Como lo estaba compilando en una máquina monstruosa de 6 núcleos, decidí seguir adelante y compilarlo usando múltiples núcleos usando make -j 6. La compilación fue fluida y muy rápida al principio, pero en algún momento makese bloqueó usando el 100% de la CPU en un solo núcleo.

Busqué en Google y encontréestepublicar en los foros de mensajes de ROOT. Desde que construí esta computadora yo mismo, me preocupaba no haber aplicado correctamente el disipador de calor y que la CPU se estuviera sobrecalentando o algo así. Desafortunadamente, aquí en el trabajo no tengo un frigorífico donde poder meterlo. ;-)

Instalé el lm-sensorspaquete y lo ejecuté make -j 6nuevamente, esta vez monitoreando la temperatura de la CPU. Aunque subió (cerca de 60 C), nunca pasó de la temperatura alta o crítica.

Intenté ejecutarlo make -j 4pero nuevamente makeme colgué en algún momento durante la compilación, esta vez en un lugar diferente.

Al final, compilé simplemente ejecutándolo makey funcionó bien. Mi pregunta es: ¿Por qué estaba colgado? Debido al hecho de que se detuvo en dos lugares diferentes, supongo que se debió a algún tipo de condición de carrera, pero creo que makedebería ser lo suficientemente inteligente como para poner todo en el orden correcto, ya que ofrece la -jopción.

Respuesta1

No tengo una respuesta a este problema preciso, pero puedo intentar darle una pista de lo que puede estar sucediendo: Faltan dependencias en Makefiles.

Ejemplo:

target: a.bytecode b.bytecode
    link a.bytecode b.bytecode -o target

a.bytecode: a.source
    compile a.source -o a.bytecode

b.bytecode: b.source
    compile b.source a.bytecode -o a.bytecode

Si llama, make targettodo se compilará correctamente. Primero se realiza la compilación a.source(arbitraria, pero determinista). Luego b.sourcese realiza la compilación .

Pero si make -j2 targetambos compilecomandos se ejecutarán en paralelo. Y realmente notarás que las dependencias de tu Makefile están rotas. La segunda compilación supone a.bytecodeque ya está compilada, pero no aparece en las dependencias. Por lo tanto, es probable que se produzca un error. La línea de dependencia correcta b.bytecodedebería ser:

b.bytecode: b.source a.bytecode

Volviendo a su problema, si no tiene suerte, es posible que un comando se cuelgue en un bucle del 100% de la CPU debido a una dependencia faltante. Probablemente eso es lo que está sucediendo aquí, la dependencia faltante no pudo revelarse mediante una compilación secuencial, pero sí ha sido revelada por su compilación paralela.

Respuesta2

Me doy cuenta de que esta es una pregunta muy antigua, pero todavía aparece en la parte superior de los resultados de búsqueda, así que aquí está mi solución:

GNU make tiene un mecanismo de servidor de trabajos para garantizar que make y sus hijos recursivos no consuman más del número especificado de núcleos: http://make.mad-scientist.net/papers/jobserver-implementation/

Se basa en una tubería compartida por todos los procesos. Cada proceso que quiera bifurcar hijos adicionales primero debe consumir tokens de la tubería y luego abandonarlos cuando haya terminado. Si un proceso hijo no devuelve los tokens que consumió, el nivel superior se bloquea para siempre esperando que se devuelvan.

https://bugzilla.redhat.com/show_bug.cgi?id=654822

Encontré este error al compilar binutils con GNU make en mi caja Solaris, donde "sed" no es GNU sed. Jugar con PATH para hacer que sed==gsed tenga prioridad sobre el sed del sistema solucionó el problema. Sin embargo, no sé por qué sed consumía fichas de la tubería.

Respuesta3

makeparece crear un punto muerto. Al usar ps -ef, estos procesos parecen ser los culpables:

raíz 695 615 1 22:18? 00:00:00 hacer PRECONSTRUCCIÓN -j32
raíz 2127 695 20 22:18? 00:00:04 make -f Makefile.prenobuild

Si verifica lo que está haciendo cada uno, el proceso hijo escribe en el descriptor de archivo 4 y el proceso padre está esperando a que todos los procesos hijos salgan:

raíz@ltzj2-6hl3t-b98zz:/# strace -p 2127
strace: Proceso 2127 adjunto
escribir(4, "+", 1
raíz@ltzj2-6hl3t-b98zz:/# strace -p 695
strace: Proceso 695 adjunto
{{espera4(-1, }}

El descriptor de archivo 4 resulta ser una tubería:

root@ltzj2-6hl3t-b98zz:/# ls -la /proc/2127/fd/4
l-wx------ 1 raíz raíz 64 3 de septiembre 22:22 /proc/2127/fd/4 -> 'pipe:[1393418985]'

y esa tubería es sólo entre los procesos padre e hijo:

raíz@ltzj2-6hl3t-b98zz:/# lsof | grupo 1393418985
hacer 695 raíz 3r FIFO 0,12 0t0 1393418985 tubería
hacer 695 raíz 4w FIFO 0,12 0t0 1393418985 tubería
hacer 2127 raíz 3r FIFO 0,12 0t0 1393418985 tubería
hacer 2127 raíz 4w FIFO 0,12 0t0 1393418985 tubería

Entonces, parecería que 2127 está atascado al intentar agregar salida a la tubería de nuevo a 695, pero 695 está pendiente wait4(), por lo que nunca vaciará esa tubería.

Si vacío la tubería del shell usando cat, entonces la compilación se reanuda y se completa como se esperaba...

raíz@ltzj2-6hl3t-b98zz:/# gato /proc/695/fd/3
+++++++++++++++++++++++++++++++++

La compilación se desbloquea y continúa ejecutándose...


Mi comprensión original estaba equivocada, pero después de más investigación finalmente terminé con este defecto del Kernel de Linux:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=46c4c9d1beb7f5b4cec4dd90e7728720583ee348

Una explicación exacta de cómo se hacen estos colgadores está aquí:https://lore.kernel.org/lkml/1628086770.5rn8p04n6j.none@localhost/.

Puede solucionar este problema en espera de un parche del kernel con la siguiente solución aplicada al código fuente de gnu make:

--- a/src/posixos.c 2020-01-02 23:11:27.000000000 -0800
+++ b/src/posixos.c 2021-09-18 09:12:02.786563319 -0700
@@ -179,8 +179,52 @@
 jobserver_release (int is_fatal)
 {
   int r;
- EINTRLOOP (r, escribir (job_fds[1], &token, 1));
- si (r! = 1)
+ int n;
+ carácter b[32];
+
+ /* Utilice escritura sin bloqueo para evitar interbloqueos debido a múltiples creaciones secundarias
+ *liberando trabajos al mismo tiempo. */
+ set_blocking (trabajo_fds[1], 0);
+ memset(b,token,tamañode(b));
+ norte = 1;
+ mientras (n > 0)
+ {
+ r = escribir (job_fds[1], b, n);
+ /* Llamada al sistema interrumpida, inténtalo de nuevo */
+ si ( r == -1 )
+ {
+ si (errno == EINTR)
+ continuar;
+
+ /* Llegamos aquí porque este proceso y otro intentaron escribir en la tubería en
+ * exactamente al mismo tiempo, y la tubería solo contiene 1 página. perdimos, el otro
+ * proceso ganado (escribió en la tubería). Sólo podemos restablecer esta condición primero
+ * lectura de la tubería. Por supuesto, eso significa que entonces tendremos que devolver un extra
+ * ficha. */
+ if ( errno == EWOULDBLOCK || errno == OTRA VEZ )
+ {
+ si (jobserver_acquire(0))
+ {
+n++;
+ /* Probablemente casi imposible... */
+ si (n > 32)
+ descanso;
+ continuar;
+ }
+ }
+ }
+ if ( r == 0 ) /* Escribí 0 bytes, pero no es un error, inténtalo de nuevo */
+ continuar;
+ si ( r > 0 )
+ {
+ norte -= r;
+ continuar;
+ }
+ descanso; /* Todos los demás errores, pausa. */
+ }
+ set_blocking (trabajo_fds[1], 1);
+
+ si (n != 0)
     {
       si (es_fatal)
         pfatal_with_name (_("escribir servidor de trabajos"));

Respuesta4

Es posible que su sistema esté bien, pero podría ser una condición de carrera que ocurre makecuando se ejecutan compilaciones en paralelo.

Si algo anda mal con su sistema, se bloqueará o fallará en otros escenarios, no solo al realizar compilaciones paralelas.

información relacionada