2011-09-06 15 views
31

Una pregunta hipotética para que todos puedan masticar que ...¿Por qué una función infinitamente recursiva en PHP causa una segfault?

recientemente respondí otra pregunta sobre el SO donde fue violación de segmento de un script PHP, y me acordé de algo que siempre he preguntado, así que vamos a ver si alguien puede arrojar cualquier luz sobre eso

considerar lo siguiente:

<?php 

    function segfault ($i = 1) { 
    echo "$i\n"; 
    segfault($i + 1); 
    } 

    segfault(); 

?> 

Obviamente, esta función (inútil) bucles infinitamente. Y, finalmente, se quedará sin memoria porque cada llamada a la función se ejecuta antes de que la anterior haya finalizado. Algo así como una bomba de horquilla sin la bifurcación.

Pero ... finalmente, en las plataformas POSIX, la secuencia de comandos morirá con SIGSEGV (también se muere en Windows, pero con más gracia, en la medida en que mis habilidades de depuración de bajo nivel extremadamente limitadas pueden decir). El número de bucles varía según la configuración del sistema (memoria asignada a PHP, 32 bits/64 bits, etc., etc.) y el sistema operativo, pero mi verdadera pregunta es: ¿por qué ocurre con una segfault?

  • ¿Es así simplemente como maneja PHP los errores de "falta de memoria"? Seguramente debe haber una manera más elegante de manejar esto?
  • ¿Es esto un error en el motor Zend?
  • ¿Hay alguna manera de que esto se pueda controlar o manejar con más elegancia desde un script PHP?
  • ¿Hay alguna configuración que generalmente controle la cantidad máxima de llamadas recursivas que se pueden realizar en una función?
+0

Las versiones modernas de php (5 iirc) tienen un límite de profundidad en la recursión para evitar esto Tipo de cosa. Si es segfaulting, definitivamente es un error que se debe informar ... – ircmaxell

+7

[Según PHP] (https://bugs.php.net/bug.php?id=43187), este es el comportamiento previsto. – NullUserException

+0

Si está buscando un idioma que tenga un límite de recursión, intente con [Python] (http://docs.python.org/library/sys.html#sys.setrecursionlimit) – NullUserException

Respuesta

24

Si utiliza XDebug, hay una profundidad máxima función de anidación que está controlado por un ini setting:

$foo = function() use (&$foo) { 
    $foo(); 
}; 
$foo(); 

produce el siguiente error:

Fatal error: Maximum function nesting level of '100' reached, aborting!

este mi humilde opinión es una alternativa mucho mejor que una segfault, ya que solo mata el script actual, no todo el proceso.

Hay this thread que estaba en la lista de elementos internos hace unos años (2006). Sus comentarios son:

So far nobody had proposed a solution for endless loop problem that would satisfy these conditions:

  1. No false positives (i.e. good code always works)
  2. No slowdown for execution
  3. Works with any stack size

Thus, this problem remains unsloved.

Ahora, # 1 es literalmente imposible de resolver debido a la halting problem. # 2 es trivial si mantienes un contador de la profundidad de la pila (ya que solo estás verificando el nivel de pila incrementado en la pila).

Finalmente, # 3 es un problema mucho más difícil de resolver. Teniendo en cuenta que algunos sistemas operativos asignarán el espacio de la pila de forma no contigua, no será posible implementarlo con una precisión del 100%, ya que es imposible obtener el tamaño o el uso de la pila (para una plataforma específica, es posible o incluso fácil, pero no en general).

En cambio, PHP debe tomar la pista de XDebug y otros lenguajes (Python, etc.) y crea un nivel de anidamiento configurable (Python es set to 1000 por defecto) ....

O eso, o la asignación de memoria trampa errores en la pila para comprobar la segfault antes de que ocurra y convertir eso en RecursionLimitException para que pueda recuperar ...

+0

¿Coge el SIGSEGV y lanza la excepción? – Demi

+0

¿Por qué no encontré esta publicación antes, cuando estaba buscando la causa de la falla de segmentación? Pasé horas depurando este problema en un servidor intermedio. –

4

Podría estar totalmente equivocado sobre esto ya que mi prueba fue bastante breve. Parece que Php solo se segregará la falla si se queda sin memoria (y presumiblemente intenta acceder a una dirección no válida). Si el límite de memoria está configurado y es lo suficientemente bajo, obtendrá un error de falta de memoria de antemano. De lo contrario, el código seg falla y es manejado por el sistema operativo.

No puedo decir si esto es un error o no, pero probablemente no se permita que la secuencia de comandos se salga de control de esta manera.

Consulte la secuencia de comandos a continuación. El comportamiento es prácticamente idéntico independientemente de las opciones. Sin un límite de memoria, también ralentiza mi computadora severamente antes de que se mate.

<?php 
$opts = getopt('ilrv'); 
$type = null; 
//iterative 
if (isset($opts['i'])) { 
    $type = 'i'; 
} 
//recursive 
else if (isset($opts['r'])) { 
    $type = 'r'; 
} 
if (isset($opts['i']) && isset($opts['r'])) { 
} 

if (isset($opts['l'])) { 
    ini_set('memory_limit', '64M'); 
} 

define('VERBOSE', isset($opts['v'])); 

function print_memory_usage() { 
    if (VERBOSE) { 
     echo memory_get_usage() . "\n"; 
    } 
} 

switch ($type) { 
    case 'r': 
     function segf() { 
     print_memory_usage(); 
     segf(); 
     } 
     segf(); 
    break; 
    case 'i': 
     $a = array(); 
     for ($x = 0; $x >= 0; $x++) { 
     print_memory_usage(); 
     $a[] = $x; 
     } 
    break; 
    default: 
     die("Usage: " . __FILE__ . " <-i-or--r> [-l]\n"); 
    break; 
} 
?> 
+0

Un buen poco de experimentación allí, ilustra el problema y los resultados muy bien. Después de buscar en Google esta mañana, encontré [esto] (http://webcache.googleusercontent.com/search?q=cache:xGfXmRpzat4J:nicktelford.net/2010/06/18/handling-segmentation-faults-in-userland -php/+ handling + segfaults + in + userland + php & cd = 1 & hl = en & ct = clnk & gl = uk) (Google almacena en caché porque el sitio está caído) lo que sugiere que puedes atrapar y manejar segfaults, aunque a) dudo que funcione en la situación de falta de memoria con la que nos enfrentamos yb) no tengo una máquina con la extensión PCNTL instalada para probarla. – DaveRandom

2

no sabe nada sobre la ejecución de PHP, pero no es raro que en un tiempo de ejecución de lenguaje para salir de páginas no asignados en la "cima" de la pila de modo que una violación de segmento se producirá si los desbordamientos de pila. Usualmente esto se maneja dentro del tiempo de ejecución y la pila se extiende o se informa un error más elegante, pero podría haber implementaciones (y situaciones en otras) donde simplemente se permite que aumente (o escape) la segfault.

+0

Entiendo el razonamiento detrás de esto, pero hace que el script PHP sea más difícil de depurar: no puedo saber si el segfault fue causado por mi script o el motor Zend. Sería bueno recibir un mensaje de error significativo, pero acepto que no hay nada que prácticamente se pueda hacer al respecto. – DaveRandom

+0

Acepto que generalmente no me importa dejar que salgan excepciones de ese tipo. Pero también entiendo las circunstancias que pueden forzar tal elección: el desbordamiento de la pila es una de las cosas más difíciles que hay que manejar en un tiempo de ejecución de idioma. –

Cuestiones relacionadas