2011-09-06 16 views
31

Una domanda ipotetica per tutti voi da masticare ...Perché una funzione infinitamente ricorsiva in PHP causa un segfault?

recente ho risposto un'altra domanda sul SO in cui uno script PHP è stato segfault, e mi ha ricordato di qualcosa che ho sempre chiesto, e vediamo se qualcuno può gettare qualsiasi luce su di esso.

consideri il seguente:

<?php 

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

    segfault(); 

?> 

Ovviamente, questa funzione (inutile) loop infinito. E alla fine, esaurirà la memoria perché ogni chiamata alla funzione viene eseguita prima della precedente. Una specie di bomba a forcella senza il biforcarsi.

Ma ... alla fine, su piattaforme POSIX, lo script morirà con SIGSEGV (anch'esso muore su Windows, ma con più grazia - per quanto le mie capacità di debug a basso livello estremamente limitate possano dirlo). Il numero di loop varia a seconda della configurazione del sistema (memoria allocata a PHP, 32 bit/64 bit, ecc. Ecc.) E del sistema operativo, ma la mia vera domanda è: perché succede con un segfault?

  • Si tratta semplicemente di come PHP gestisce gli errori di "memoria esaurita"? Sicuramente ci deve essere un modo più elegante di gestire questo?
  • Si tratta di un bug nel motore Zend?
  • C'è un modo in cui questo può essere controllato o gestito in modo più agevole da uno script PHP?
  • Esiste qualche impostazione che generalmente controlla il numero massimo di chiamate ricorsive che è possibile effettuare in una funzione?
+0

Le versioni moderne di php (5 iirc) hanno un limite di profondità sulla ricorsione per prevenire questo tipo di cosa Se è segfaulting, è sicuramente un bug che dovrebbe essere segnalato ... – ircmaxell

+7

[Secondo PHP] (https://bugs.php.net/bug.php?id=43187), questo è il comportamento previsto. – NullUserException

+0

Se stai cercando un linguaggio con un limite di ricorsione, prova [Python] (http://docs.python.org/library/sys.html#sys.setrecursionlimit) – NullUserException

risposta

24

Se si utilizza XDebug, c'è una profondità massima funzione di annidamento che è controllata da un ini setting:

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

produce il seguente errore:

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

Questo IMHO è un'alternativa molto meglio di un segfault, poiché uccide solo lo script corrente, non l'intero processo.

C'è this thread che era nella lista interna qualche anno fa (2006). I suoi commenti sono:

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.

Ora, # 1 è letteralmente impossibile da risolvere a causa della halting problem. # 2 è banale se si mantiene un contatore di profondità dello stack (dal momento che si sta solo controllando il livello di stack incrementato nello stack push).

Infine, # 3 è un problema molto più difficile da risolvere. Considerando che alcuni sistemi operativi allocheranno lo spazio dello stack in modo non contiguo, non sarà possibile implementarlo con una precisione del 100%, dal momento che è impossibile ottenere la dimensione dello stack o l'utilizzo (per una piattaforma specifica potrebbe essere possibile o anche facile, ma non in generale).

Invece, PHP dovrebbe prendere il suggerimento da XDebug e altre lingue (Python, ecc) e fare un livello di annidamento configurabile (Python è set to 1000 per impostazione predefinita) ....

O che, o l'allocazione di memoria trappola errori nello stack per verificare il segfault prima che accada e convertirlo in un RecursionLimitException in modo che tu possa essere in grado di recuperare ....

+0

Cattura SIGSEGV e genera l'eccezione? – Demi

+0

Perché non ho trovato questo post prima, quando stavo cercando la causa dell'errore di segmentazione. Ho passato ore a eseguire il debug di questo problema su un server di gestione temporanea. –

4

Potrei essere totalmente in errore su questo poiché il mio test era abbastanza breve. Sembra che Php segnerà solo un errore se esaurisce la memoria (e presumibilmente cerca di accedere a un indirizzo non valido). Se il limite di memoria è impostato e sufficientemente basso, si verificherà in anticipo un errore di memoria insufficiente. In caso contrario, il codice sega errori ed è gestito dal sistema operativo.

Impossibile dire se si tratta di un bug o meno, ma probabilmente lo script non dovrebbe essere lasciato fuori controllo in questo modo.

Vedere lo script di seguito. Il comportamento è praticamente identico indipendentemente dalle opzioni. Senza un limite di memoria, rallenta anche il mio computer gravemente prima che venga ucciso.

<?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 bel po 'di sperimentazione lì, illustra bene il problema ed i risultati. Dopo qualche ulteriore ricerca su Google questa mattina, ho trovato [questo] (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 = it & ct = clnk & gl = uk) (Google memorizzato nella cache perché il sito è inattivo) che suggerisce che è possibile intercettare e gestire segfaults - anche se a) Dubito che funzionerebbe in la situazione di esaurimento della memoria con cui ci stiamo occupando eb) Non ho una macchina con l'estensione PCNTL installata per testarla. – DaveRandom

2

so niente di applicazione PHP, ma non è raro in un language runtime di lasciare pagine non allocate al "top" della pila in modo che un segfault si verificherà se le overflow dello stack. Di solito questo viene gestito all'interno del runtime e lo stack viene esteso o viene segnalato un errore più elegante, ma potrebbero esserci implementazioni (e situazioni in altri) in cui il segfault è semplicemente permesso di salire (o fuggire).

+0

Capisco il ragionamento alla base di questo, ma rende lo script PHP più difficile da eseguire il debug - non posso sapere se il segfault sia stato causato dal mio scripting o dal motore Zend. Sarebbe bello ricevere un messaggio di errore significativo, ma accetto che non si possa praticamente fare nulla al riguardo. – DaveRandom

+0

Sono d'accordo sul fatto che in genere non mi interessa lasciare eccezioni di quella superficie di questo tipo. Ma capisco anche le circostanze che possono imporre una tale scelta - l'overflow dello stack è una delle cose più difficili da gestire in un runtime di linguaggio. –

Problemi correlati