Server überlastet nach PHP Fatal Error

wahnsinn

Teilzeitcholeriker
ID: 13929
L
20 April 2006
1.168
153
Hallo zusammen,

auf einem Server, der meines Wissens nach nur von einem Kunden genutzt wird, hatte ich ein kleines Problem.

Ich habe ein Script angestoßen, dass nach einer Weile einen "PHP Fatal error: Maximum execution time of 30 seconds exceeded" geworfen hat. Nach 40 Minuten hat sich der Server-Betreiber bei mir gemeldet und gefragt, was da los sei. Sein Server sei zu 100% ausgelastet, die Festplatten voll beschäftigt.

Nach meiner Logik müsste der Apache doch nach dem PHP-Fatal-Error seine Arbeit wieder einstellen und alle Resourcen frei geben.

Kann es sein, dass der Server durch den (bzw. trotz des) Timeouts in die Knie gezwungen wurde?

Das Script mit dem Problem ist ein Synchronisation-Script, das nicht öffentlich erreichbar ist. Dass also mehrere Instanzen davon liefen, ist auszuschließen. Und auch in der Error-Log ist nur der eine Fatal-Error zu sehen.
Das Script kopiert Bilder auf der Festplatte umher. Als das Problem auftrat war er vermutlich gerade bei einem JPG mit fast 10 MB.

Kann mir jemand sagen, ob ich ein sch*** Script geschrieben habe, oder ob der Hoster seinen Server schlecht konfiguriert hat?
 
Zuletzt bearbeitet:
Eigentlich sollte das Script stoppen.
Kannst du näheres beschreiben, was das Script gemacht hat und was das Script böses machen sollte?
"Die Festplatten sind auf 100%" ist nämlich eine sehr kompetente Aussage eines ServerAdmins :LOL:

Fängst du möglicherweise Fehler mit einem eigenen Error-Handler ab? Dann kann es nämlich weiterlaufen.
Startet das Script neue HTTP-Aufrufe? Hat es eine DB-Verbindung die irgendwelche komplexen Dinge macht?
 
Ja, ich hab einen eigenen Error-Handler. Heißt das, ich müsste da irgendwo ein exit; einbauen?

PHP:
set_error_handler(array(&$this, "error_handler"), E_ALL ^ E_NOTICE);

PHP:
function error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
	// ...
}

Innerhalb meiner error_handler()-Funktion wird dann eine Datei geöffnet, ein String mit die Parameter $errstr und $errline in die Datei geschrieben und die Datei wieder geschlossen.


// Edit:
Wobei ich sagen muss, dass das Script auf meiner lokalen Testumgebung nach einen Fatal-Memory-Error wunderbar gestoppt hat.


// Edit 2:
Zum Ablauf des Scripts:
Es wird eine XML-Datei geparst, dazu dann Datenbank-Einträge geschrieben und anschließend werden Dateien über eine HTTP-Verbindung geholt.

Dazu wird geprüft, ob eine Datei schon existiert. Wenn nicht, wird sie von einem anderen Server über HTTP geholt. Wenn ja, wird sie von lokal kopiert. Zu jeder Datei gibt es dann noch eine einfache INSERT-Anweisung für die Datenbank. Aber nix komplexes, sondern einfach nur eine Tabelle, wo sich das Script die Original-Datei-Namen in Verbindung mit den lokal gespeicherten Dateinamen merkt.
 
Zuletzt bearbeitet:
Ja, ich hab einen eigenen Error-Handler. Heißt das, ich müsste da irgendwo ein exit; einbauen?
bei Fehlern die zum Ende des Scriptes führen sicherlich eine gute Idee ;)

Innerhalb meiner error_handler()-Funktion wird dann eine Datei geöffnet, geschrieben und wieder geschlossen.
sehr interessant, ja so langsam ergibt es Sinn ;)
Ich stelle mal Vermutungen auf:
Dein Script erreicht das memory limit (FATAL ERROR) und du möchtest eine Datei öffnen, was wiederum Speicher benötigen würde, du hast aber bereits das memory_limit erreicht, weshalb wieder ein Fehler geworfen, somit springt dein error handler wieder an ....

Edit: Bezogen auf deinen Edit: Hmm, vllt ist es auf Linux anders als auf Windows oder verschiedene PHP-Versionen, wer weiß, kannst es ja mal testen ;) (Bei 3-4 Aufrufen des error-handlers muss das Script stoppen)
 
sehr interessant, ja so langsam ergibt es Sinn ;)
Ich stelle mal Vermutungen auf:
Dein Script erreicht das memory limit (FATAL ERROR) und du möchtest eine Datei öffnen, was wiederum Speicher benötigen würde, du hast aber bereits das memory_limit erreicht, weshalb wieder ein Fehler geworfen, somit springt dein error handler wieder an ....
Ich hatte aber auf dem Live-Server kein Memory-Limit-Error, sondern einen Timeout-Error. Ändert das was an deiner Vermutung?

Edit: Bezogen auf deinen Edit: Hmm, vllt ist es auf Linux anders als auf Windows oder verschiedene PHP-Versionen, wer weiß, kannst es ja mal testen ;) (Bei 3-4 Aufrufen des error-handlers muss das Script stoppen)
Mac OS X, nicht Windows.
 
Ich hatte aber auf dem Live-Server kein Memory-Limit-Error, sondern einen Timeout-Error. Ändert das was an deiner Vermutung?
Ja. Dann kann sich der Error-Handler nicht mehr rekursiv in die Unendlichkeit aufrufen.

Unabhängig vom konkreten Sachverhalt: Wenn PHP sich nicht rechtzeitig beendet, is das maximal ein Bug in PHP. Für den kann aber der Kunde nichts, selbst, wenn er Script einsetzt, dass ne Endlosschleife mit Festplattenattacke macht. Das is Job des Serveradministrators dafür zu sorgen, dass PHP aktuelle bugfreie Version installiert ist.
 
Das ist doch mal eine Aussage.
Ich mein, wie soll es sonst sein?
Der Otto-Normal-Verbraucher-Kunde hat Webspace, wo er PHP-Scripts reinladen kann und ausführen kann. Meistens hat man noch nicht mal Zugriff zur php.ini, geschweige dem hat das Recht, Prozesse zu verwalten.

Als Beispiel, wie es bei uns an der Uni läuft: Du kannst dort Prozesse starten, wie du willst. Aber egal, was du tust, ob sich der Browser aufhängt, du ein zu rechenintensives Programm geschrieben und gestartet hast, etc. etc.
Der Process Checker schießt gnadenlos alles von dir ab, was den Betrieb stört. Du kriegst dann nur ne Mail, wo drinsteht, welcher Prozess warum abgeschossen wurde.

So sollte es auch auf einem Webserver laufen (wo mehr als nur 2 oder 3 Studenten gleichzeitig arbeiten). Drum gibts ja extra die Berufe Netzwerkadministrator, Systemadministrator und das ganze Zeugs. Man sollte sich auskennen, wenn man so ne Kiste am laufen hat. Der Kunde tuts sicher nicht - und selbst wenn, er hat gar keinen Zugriff.
 
Dankeschön für die Einschätzung. Jetzt bin ich noch über etwas gestolpert:

Laut phpinfo() läuft auf dem Server PHP 5.3.2. Auch dort aufgefallen ist mir die Option exit_on_timeout, diese ist scheinbar neu seit PHP 5.3 (https://php.net/manual/en/migration53.ini.php) Diese ist auf Off gesetzt, was wohl Standard-Auslieferungszustand ist.

Allerdings hab ich keinerlei Anhaltspunkte gefunden, was diese Einstellung bewirkt. Kann es sein, dass diese verhindert dass das Script bei einem Timout-Error abbricht? Der Name lässt mich das ja annehmen. Aber einen Sinn dahinter kann ich nicht finden.
 
The second thing this does is to make it possible to terminate the current
child process (only for Apache1 at the moment) on a timeout. There are
a number of extensions that are unhappy about being longjmp'ed out of
and when this happens on a timeout they are left in an inconsistent state.
By turning on exit_on_timeout you can now force the process to terminate
on a timeout which will clean up any hanging locks and/or memory left
hanging after the longjmp.

https://www.mail-archive.com/[email protected]/msg33498.html

die Einstellung scheint nix mit deinem Problem zu tun zu haben, scheint eher für PHP-Erweiterungen zu sein, die mit nem timeout nicht zurecht kommen.
 
Hi,

ich hatte mal ein ganz ähnliches Problem, welches jedoch nichts mit einem Timeout oder dem Error-Handler zu tun hatte...

wahnsinn schrieb:
Das Script mit dem Problem ist ein Synchronisation-Script, das öffentlich erreichbar ist. Dass also mehrere Instanzen davon liefen, ist auszuschließen.
Weil Du schreibst, dass das Script öffentlich erreichbar ist, kannst Du zu 100% ausschließen, dass wirklich nicht mehrere Instanzen liefen (in etwa durch einen Bot der an Content will)? Das würde zumindest erklären können, warum der Server immer noch unter Strom steht, wenn das Script bei Dir schon beendet wurde. Dies war jedenfalls bei mir die Ursache. Nachdem ich den Bots den Zugriff darauf verwehrt hatte, lief wieder alles wie gewohnt.

Möglicherweise hab ich's nicht richtig verstanden, aber:

Warum für solche Aktionen im Errorhandler? Dieser ist doch dafür gedacht Fehler zu Handlen, um diese meinetwegen benutzerfreundlich auszugeben. So würde Deine Aufgabe bei JEDEM auftretenden Fehler durchgeführt werden...egal ob nun E_PARSE, E_NOTICE, etc... ist das so gewollt?

Wenn ja, die Kopieraktion nur bei bspw. E_USER_NOTICE (den Du selbst setzen kannst) anlaufen lassen, und bei allen anderen ERRNOs per exit(); abbrechen, bzw. die Fehlermeldung regulär ausgeben lassen?
 
Weil Du schreibst, dass das Script öffentlich erreichbar ist, kannst Du zu 100% ausschließen, dass wirklich nicht mehrere Instanzen liefen (in etwa durch einen Bot der an Content will)?
Aaaaaaaa!!!! Nein, ich meinte natürlich NICHT öffentlich erreichbar. Hab's oben auch schon korrigiert.

Warum für solche Aktionen im Errorhandler?
Weil ich für die ganze Synchronisation eine einzige Log-Datei haben will. Bei der dann auch drin steht, wenn der XML-Parser Probleme hat, oder irgendwo ein foreach() ohne Array aufgerufen wird.
 
Ich stelle mal Vermutungen auf:
Dein Script erreicht das memory limit (FATAL ERROR) und du möchtest eine Datei öffnen, was wiederum Speicher benötigen würde, du hast aber bereits das memory_limit erreicht, weshalb wieder ein Fehler geworfen, somit springt dein error handler wieder an ....

das dürfte eigentlich nicht passieren:

https://de.php.net/manual/de/function.set-error-handler.php schrieb:
Die folgenden Fehlertypen können nicht von einer benutzerdefinierten Funktion behandelt werden: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING und die meisten E_STRICT, die in der Datei ausgelöst werden, in der set_error_handler() aufgerufen wird.
 
Aaaaaaaa!!!! Nein, ich meinte natürlich NICHT öffentlich erreichbar. Hab's oben auch schon korrigiert.
Ah, okay :)

Weil ich für die ganze Synchronisation eine einzige Log-Datei haben will. Bei der dann auch drin steht, wenn der XML-Parser Probleme hat, oder irgendwo ein foreach() ohne Array aufgerufen wird.
Konnte man die Fehlerausgaben denn nicht auch mittels error_log(); in eine Datei umleiten? Hätte den gleichen Effekt, nur dass PHP diesen Part (Datei öffnen, schreiben, schließen) für Dich übernimmt.

thown_out schrieb:
das dürfte eigentlich nicht passieren:
Richtig. Hab's gerade auch selbst versucht, no way.

Wenn ich den Handler mittels trigger_error() ansprechen will geht nur E_USER_*, auch wenn ich innerhalb des Handlers erneut einen trigger_error($errmsg, $errno) aufrufe, passiert nix (PHP5.3.2).
Doch...PHP macht intern irgendwann ein E_WARNING daraus, und bricht ab, weil trigger_error('foo', E_WARNING); nicht erlaubt ist.