2011-09-06 14 views
31

hipotetyczne pytanie do was wszystkich do gryzienia ...Dlaczego funkcja nieskończenie rekursywna w PHP powoduje błąd segfault?

Niedawno odpowiedział na jedno pytanie SO gdzie skrypt PHP został segfaulting, i przypomniało mi coś Zawsze zastanawiałem się, więc zobaczymy, czy ktoś może rzucić jakiekolwiek światło na nim.

rozważyć następujące:

<?php 

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

    segfault(); 

?> 

Oczywiście, funkcja (bezużyteczne) pętli w nieskończoność. W końcu zabraknie pamięci, ponieważ każde wywołanie funkcji zostanie wykonane przed zakończeniem poprzedniej. Coś w rodzaju bomby widłowej bez rozwidlenia.

Ale ... w końcu, na platformach POSIX, skrypt umrze z SIGSEGV (to również umiera w Windowsie, ale z większą wdziękiem - o ile moje ekstremalnie ograniczone umiejętności debugowania na niskim poziomie mogą powiedzieć). Liczba pętli różni się w zależności od konfiguracji systemu (pamięć przydzielona do PHP, 32bit/64bit itd.) I systemu operacyjnego, ale moim prawdziwym pytaniem jest: dlaczego tak się dzieje z segfaultem?

  • Czy w ten sposób PHP radzi sobie z błędami "braku pamięci"? Z pewnością musi być bardziej zgrabny sposób radzenia sobie z tym?
  • Czy to błąd w silniku Zend?
  • Czy jest jakiś sposób, że można to kontrolować lub obsługiwać z wdziękiem za pomocą skryptu PHP?
  • Czy istnieje ustawienie, które ogólnie kontroluje maksymalną liczbę wywołań rekursywnych, które można wprowadzić w funkcji?
+0

Współczesne wersje php (5 iirc) mają ograniczoną głębokość rekurencji, aby temu zapobiec. rodzaj rzeczy. Jeśli jest to segfault, to jest to z pewnością błąd, który powinien być zgłoszony ... – ircmaxell

+7

[Według PHP] (https://bugs.php.net/bug.php?id=43187), jest to zamierzone zachowanie. – NullUserException

+0

Jeśli szukasz języka, który ma limit rekursji, spróbuj [Python] (http://docs.python.org/library/sys.html#sys.setrecursionlimit) – NullUserException

Odpowiedz

24

Jeśli używasz xdebug, istnieje maksymalna głębokość funkcja gniazdowania, który jest kontrolowany przez ini setting:

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

produkuje następujący błąd:

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

To IMHO jest o wiele lepszym rozwiązaniem niż a segfault, ponieważ zabija tylko bieżący skrypt, a nie cały proces.

Istnieje this thread, który był na liście wewnętrznych kilka lat temu (2006). Jego komentarze są:

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.

Teraz, # 1 jest całkiem dosłownie niemożliwe do rozwiązania ze względu na halting problem. # 2 jest trywialne, jeśli utrzymujesz licznik głębokości stosu (ponieważ sprawdzasz tylko zwiększony poziom stosu przy stosie push).

Wreszcie, nr 3 jest o wiele trudniejszym problemem do rozwiązania. Biorąc pod uwagę, że niektóre systemy operacyjne będą alokowały przestrzeń stosu w sposób nieciągły, nie będzie możliwe wdrożenie ze 100% dokładnością, ponieważ nie można w przenośni uzyskać rozmiaru stosu lub użycia (dla konkretnej platformy może być to możliwe lub nawet łatwe, ale nie w ogóle).

Zamiast PHP powinien wziąć wskazówkę z XDebug i innych języków (Python, etc) i dokonać konfigurowalny poziom zagnieżdżenia (Python jest set to 1000 domyślnie) ....

Albo to, albo alokacji pamięci pułapki błędy na stosie, aby sprawdzić, czy nie wystąpił błąd segfault, i przekonwertuj go na RecursionLimitException, aby móc odzyskać ...

+0

Złap SIGSEGV i wyrzuć wyjątek? – Demi

+0

Dlaczego nie znalazłem tego posta wcześniej, gdy szukałem przyczyny usterki segmentacji. Spędziłem godziny na debugowaniu tego problemu na serwerze testowym. –

4

Mogłem się mylić, ponieważ moje testy były dość krótkie. Wygląda na to, że Php wykrywa błąd tylko wtedy, gdy zabraknie mu pamięci (i prawdopodobnie próbuje uzyskać dostęp do nieprawidłowego adresu). Jeśli limit pamięci jest ustawiony i niski, otrzymasz wcześniej błąd braku pamięci. W przeciwnym razie kod skasuje usterki i jest obsługiwany przez system operacyjny.

Nie można stwierdzić, czy jest to błąd, czy nie, ale prawdopodobnie skrypt nie może uzyskać takiej kontroli.

Zobacz skrypt poniżej. Zachowanie jest praktycznie identyczne, niezależnie od opcji. Bez limitu pamięci znacznie spowalnia też komputer, zanim zostanie zabity.

<?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

Niezły eksperyment, ilustruje problem i daje dobre rezultaty. Po tym, jak poznałem Googling dziś rano, znalazłem [to] (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 = pl & ct = clnk & gl = uk) (Google buforuje, ponieważ witryna jest wyłączona), co sugeruje, że możesz pułapki i obsługiwać segfaults - chociaż a) Wątpię, by działało w z sytuacją braku pamięci, z którą mamy do czynienia i b) Nie mam komputera z zainstalowanym rozszerzeniem PCNTL do przetestowania go. – DaveRandom

2

wiedzą nic o realizacji PHP, ale to nie jest rzadkością w czasie wykonywania języka opuszczenia stron nieprzydzielone w „górę” stosu tak, że segfault nastąpi jeśli przepełnienia stosu. Zwykle jest to obsługiwane w środowisku uruchomieniowym i albo stos jest wydłużany, albo zgłaszany jest bardziej elegancki błąd, ale mogą istnieć implementacje (i sytuacje w innych), w których segfault może po prostu wznieść się (lub uciekać).

+0

W pewnym sensie rozumiem powód, który kryje się za tym, ale utrudnia on debugowanie skryptu PHP - nie wiem, czy błąd ten spowodowany był przez mój skrypt lub silnik Zend. Byłoby miło uzyskać znaczący komunikat o błędzie, ale akceptuję, że nie można praktycznie nic z tym zrobić. – DaveRandom

+0

Zgadzam się, że generalnie nie zależy mi na dopuszczeniu wyjątków tego rodzaju. Rozumiem jednak okoliczności, które mogą wymusić taki wybór - przepełnienie stosu jest jedną z najtrudniejszych rzeczy, które trzeba wykonać w środowisku uruchomieniowym języka. –

Powiązane problemy