phpbar.de logo

Mailinglisten-Archive

[php] Schlafende Prozesse / flock() / ignore_user_abort() / file_get_contents()

[php] Schlafende Prozesse / flock() / ignore_user_abort() / file_get_contents()

Mario Haßler M.Hassler at gmx.de
Fr Mär 19 20:32:07 CET 2010


Vielen Dank für die Antworten! Fangen wir "hinten" an:


Am 16.03.2010 13:09 schrieb Thomas Koudela:

 >> Wenn sich viele Benutzer gleichzeitig auf meiner Seite tummeln, gehen
 >> manche Funktionen in die Knie, z. B. die Suchfunktion. Das liegt meiner
 >> Vermutung nach jedoch nicht an der Suche selbst,
 >
 > Wenn es nicht an diesen Funktionen selbst liegt, warum tritt es Deiner
 > Meinung dann nur bei _manchen_ Funktionen auf?

Die Suche durchsucht einerseits statische Seiten (hier sind keine
Probleme zu erwarten) und andererseits diverse Index-Dateien, die
gegebenenfalls neu aufgebaut werden müssen (hier schon). Das meinte
ich.

 > [...] spätestens da, wäre die Entscheidung für eine Datenbanklösung
 > bei mir gefallen.

Es sind verschiedene dynamische Datenbestände, allesamt historisch
gewachsen. Sicher wären Datenbanken eine feine Sache, aber ich schätze
die Portierbarkeit und bequeme Datensicherung von Textdateien. (Habe
für Testzwecke und als Backup alles auch auf meinem PC; Abgleich er-
folgt ganz simpel mit FTP.)


Am 16.03.2010 13:39 schrieb Christian Grobmeier:

 > [...] Ich glaube, du solltest mal einen meiner Blogartikel lesen:
 > http://www.grobmeier.de/2009/08/21/performance-ofnonblocking-write-to-files-via-php.html

Naja, den dortigen Vorschlag

     $fp = fopen($file, 'a+');
     flock($fp, LOCK_UN);
     while($count < $loop) {
        if (flock($fp, LOCK_EX)) {
           fwrite($fp, $text);
        }
        flock($fp, LOCK_UN);
     }
     fclose($fp);

halte ich erstens für umständlich (wenn die Schreibzugriffe hinterein-
ander erfolgen, warum nicht einmal sperren, dann alles schreiben, dann
entsperren?) und zweitens für fehlerträchtig (was ist, wenn ein zweiter
Prozess eine Zeile anhängt, aber der Dateizeiger des ersten Prozesses
noch eine Zeile darüber steht und selbst eine Zeile schreiben will?).

Ohne das wiederholte Entsperren und Sperren ist das ja meiner ursprüng-
lichen Anfrage ähnlich (bis auf ftruncate($fp, 0);), und mir ging es
ja weniger um die Geschwindigkeitsdetails als vielmehr um das Sperren
an sich.


Am 16.03.2010 21:52 schrieb Dieter Schmidt:

 > damit du nicht herumstochern musst, kann ich dir nur eines empfehlen,
 > gehe der Sache wirklich auf den Grund. Ich würde an deiner Stelle eine
 > Profiler installieren. [...]

Nett gemeint, aber ich glaube, das kann ich als Kunde eines Webhosting-
Anbieters nicht. Außerdem hat mich schon die Produktbeschreibung er-
schlagen -- bis ich das kapiere, habe ich durch ein paar gut durch-
dachte Änderungen vielleicht schon mehr gewonnen...


Am 17.03.2010 20:48 schrieb Yannik Hampe:

> [...] der schlafende Prozess hält noch Systemressourcen, wie zum
> Beispiel Filelocks. Das kann andere Prozesse wiederrum blockieren.

Ja, das ist meine Vermutung (leider nicht mehr als eine Vermutung).

> [...] Wie viele Prozesse sammeln sich denn da an? 5? 10? 100? 1000?
> Irgendwann werden auch schlafende Prozesse für das System zu Last,
> wenn es nur genug sind. [...]

Größenordnung bis zu 50 würde ich sagen.

> Dateisperren werden auch dann freigegen, wenn man fclose() aufruft.
> fclose() wird am Ende jedes Scripts von php für alle noch nicht
> freigegeben Dateien automatisch aufgerufen.

Gilt das auch für Skripte, die per Timeout beendet werden?

> [...] Bedenke hier, das ftruncate() die Datei auf 0 Byte kürzt. Der
> Dateipointer, der sich am Ende der Datei befindet bleibt von
> ftruncate jedoch unberührt. Da du jedoch mit fputs wohl an den
> Anfang der Datei schreiben möchtest, solltest du einmal rewind($fp);
> ausführen.

Ok, verstanden. Guter Tipp!

> [...] Und ja, für Dateien zum lesen musst du auch sperren, aber dann
> brauchst du keine exklusive Sperren, dann tut flock($fp, LOCK_SH).

Ja, das hatte ich auch so verstanden und gemeint. Danke für die Be-
stätigung.

> [...] Bei file_put_content kannst du einen Parameter zum Sperren
> mitgeben: file_put_contents("/some/path", "some data", LOCK_EX);
> Leider habe ich keine Ahnung, wie file_get_contents intern
> funktioniert und die Leute die sich in Foren dazu auslassen
> spekulieren alle nur und haben keine Ahnung ;-). [...]

Ok, dann werde ich auch die durch fopen() usw. ersetzen.

> Woher weißt du denn, dass der Server hängt?

Eine Suchanfrage (bei der vermutlich eine der Index-Dateien neu auf-
gebaut werden muss, s. o.) führt zum "halben" Laden der Seite (eben
bis zu der Ausgabe vor Neuaufbau der Index-Datei) und macht danach
nicht weiter (keine weitere HTML-Ausgabe, Browser zeigt weiter "Lade
Seite..." an). Vermutung: Prozess wartet "ewig" auf Freigabe zum
Schreiben der Index-Datei.

> Was ist das überhaupt für ein Server? Linux?

Webhosting auf Linux-Server (x86_64).

> Hast du ssh-Zugang drauf?

Öh... Was ist das?

> Meinst du er hängt, weil in deinem Browser nichts ankommt?

So in etwa, s. o.

> Oder weil du auf einer Konsole warst und gesehen hast, dass der
> Prozessor auf 100% Auslastung steht?

Nein, den Server bringe ich nicht in die Knie (zum Glück -- mit End-
losschleifen habe ich das schon hinbekommen...).

> [...] Untersuchen könntest du das in dem du alle deine flock-Aufrufe
> durch so etwas erstzt:
> flock($fp, LOCK_EX | LOCK_NB, $wouldblock);
> if ($wouldblock) throw new Exception('Could not acquire lock for file ...');

Naja, es soll ja durchaus zulässig sein, dass ein Prozess warten muss,
bis er an die Reihe kommt.

> Eine weitere Möglichkeit zur Fehlersuche sind debugger. [...] in dem
> du die entsprechende option in der php.ini [...]  Dann spuckt dir
> xdebug bei jeder Anfrage eine Datei aus (in das ebenfalls in der
> php.ini eingestellte Verzeichnis. Normalerweise /tmp) [...]

Ich weiß nicht, ob ich a) die nötigen Zugriffsrechte dafür habe und
b) verstehe, was mir der Debugger mitteilt.

> [...] Wenn der Fehler nur bei jedem hundertsten Aufruf zum Fehler
> führt, dann ist das allerdings doch ein sehr frustrierende
> Angelegenheit. Genau wie die Methode mit dem profiler [...]

Und so in etwa dürfte es sein, vielleicht ist die Zahl sogar noch
größer.


Alles in allem gab es ja das eine oder andere ermutigende Wort, die
flock()-Funktion so einzusetzen, wie ich das vorhatte (nun ergänzt um
die rewind()-Funktion). Daran werde ich mich nun begeben.

Außerdem hatte ich noch eine andere Idee: Da ich (aus purer Faulheit,
eine Funktion zu entwickeln, die nach Änderung im Datenbestand die
bestehende Index-Datei an der richtigen Stelle geraderückt) bei Daten-
änderung die Index-Datei verwerfe und bei der nächsten Suchanfrage neu
aufbaue, kann es bisher zu folgender Situation kommen: Anfrage A löst
Neuaufbau aus und sperrt währenddessen die Index-Datei, Anfrage B
stellt ebenfalls fest, dass die Index-Datei neu aufgebaut werden muss
und wartet auf Freigabe der Sperre -- um dann die Index-Datei eben-
falls neu aufzubauen. Währenddessen kommt Anfrage C usw. Ich kann mir
vorstellen, dass dieses wenig durchdachte Prinzip zu einem Dateisperre-
Stau führen kann und habe es nun dahingehend abgewandelt, dass nach
Erhalt der Sperre die Bedingung für Neuaufbau nochmals überprüft wird.
Falls der Index gerade neu erzeugt wurde und keine Änderung in der
Zwischenzeit den Index als überholt deklassiert, biege ich vor
ftruncate() wieder ab, baue die Index-Datei nicht wieder auf und gebe
die Sperre gleich wieder frei.

Mal schauen, ob es mit diesen Änderungen besser wird.

Eine Frage bleibt für mich aber trotzdem offen: Wieso hat es bei dem
oben beschriebenen "Hängenbleiben" nicht geholfen, die Seite neu zu
laden, wenn es geholfen hat, erst das Session-Cookie im Browser zu
löschen und dann die Seite neu zu laden? Teilt mir der Server keinen
neuen Prozess zu, wenn er über die Session-ID feststellt, dass ich
schon einen Prozess (oder mehrere) laufen habe? Ich kann mir nicht
vorstellen, dass den Server die Session-IDs interessieren, aber woran
kann es sonst liegen?

Nochmals vielen Dank an alle, die mir geantwortet haben!

Mario Haßler

php::bar PHP Wiki   -   Listenarchive