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()

Yannik Hampe yannik at cipher-code.de
Mi Mär 17 20:48:41 CET 2010



Thomas Koudela wrote:
> Hi.
> 
> 
> Ein schlafender Prozess ist ein Programm, welches auf seinen Einsatz
> wartet. Damit es in dieser Zeit keine Rechenleistung verschleudert
> schläft es im Arbeitsspeicher. Ein schlafender Prozess dürfte IMHO keine
> Zugriffsrechte blockieren - es sei denn die Programmierer haben Mist
> gebaut.

Es sei denn der schlafende Prozess hält noch Systemressourcen, wie zum
Beispiel Filelocks. Das kann andere Prozesse wiederrum blockieren. Wie
Christian schon erklärt hat, könnte es sich um das normale pooling von
Prozessen deines Webservers handeln. Muss aber nicht. 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.
Schließlich muss das System auch alle im Auge behalten und Prozesse wenn
nötig weiter laufen lassen.
> 
>> Ein Problem könnte darin liegen, dass die Suche manche Bereiche der
>> Seite "indiziert", d. h. eine Art Zusammenfassung erstellt und diese
>> speichert. (Wenn sich bis zur nächsten Suche nichts ändert, kann die
>> Zusammenfassung wiederverwendet werden und spart dann Zeit.) Bei den
>> Dateioperationen benutze ich die flock()-Funktion in ihrer Reinform,
>> auch wenn mir bewusst ist, dass ein fopen(..., 'w') nicht wirklich dazu
>> passt. Um das Thema "Abbruch durch den Benutzer" habe ich mich bisher
>> noch nicht gekümmert. Kann es also sein, dass ein Abbruch nach flock()
>> die folgenden Prozesse ausbremst, die sich mit flock() auf die gleiche
>> Datei beziehen? Kann ich das mit ignore_user_abort() in geordnete
>> Bahnen lenken?
> 
> Natürlich ist ein Script-Abbruch bei gesperrten Datei ziemlich fatal,
> wenn keine Vorkehrungen zur Entsperrung im Fehlerfall getroffen wurden.
> Sinnvoll wäre, wenn PHP beim Schließen der Datei oder spätestens nach
> Ende des Scriptes alle noch gesperrten Dateien automatisch entsperrt.
> Ich weiß allerdings nicht, ob die PHP-Entwickler so etwas implementiert
> haben. Ich würde eine Datenbanklösung vorziehen, zumal Du dann mehrere
> Indizierungen speichern könntest und absolut sicher wärst, dass die
> Implementierung auf jedem System gleich gehandhabt wird. Wenn Du die
> Datenbank intelligent nutzt, müsstest Du in Deinem Fall auch auf den
> Lock verzichten können.

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.
> 
>> Dann zu flock(): http://www.php.net/manual/de/function.flock.php
>> bietet in den Benutzerkommentaren viele Anregungen, wie man aus dem
>> etwas ungeschickten Ansatz, den Zugriff erst nach Öffnen zu sperren,
>> etwas Brauchbares macht. Ist
>>
>>    $fp = fopen(..., 'a'); // <- Dateiinhalt erst bewahren
>>    flock($fp, LOCK_EX);
>>    ftruncate($fp, 0);     // <- jetzt erst löschen
>>    fputs($fp, ...);

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.

>>    flock($fp, LOCK_UN);
>>    fclose($fp);
>>
>> der "richtige" Ansatz als Ersatz für fopen(..., 'w') + flock()? Sehe
>> ich das richtig, dass auch fopen(..., 'r') mit flock() kombiniert
>> werden muss, damit das Konzept stimmig ist? 

Sieht für mich gut aus :-).
Und ja, für Dateien zum lesen musst du auch sperren, aber dann brauchst
du keine exklusive Sperren, dann tut flock($fp, LOCK_SH).


>> Und was ist mit Funktionen wie file_get_contents() / file_put_contents()?
> 
> Bei file_get_contents() und file_put_contents() ist die dargestellte
> Kombination mit flock() überflüssig, da in einem Rutsch gelesen bzw.
> geschrieben wird. Eigentlich bezweifle ich, dass das dargestellte
> überhaupt Sinn macht, denn sobald eine Datei zum Schreiben geöffnet
> wird, müsste automatisch auf Betriebssystemebene, die Datei für andere
> Prozesse gesperrt sein, bzw. warten bis nicht mehr aus der Datei gelesen
> wird. Sonst könnte es zu Konflikten zwischen unabhängigen Programmen
> kommen! Konflikte kann ich mir nur bei Multi-Thread-Servern vorstellen,
> welche den Zugriff auf eine externe Ressource intern vorverwalten. 

Öhm... Nein.
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 ;-). Das wäre allerdings mal interessant, wenn es
einer weiß... Raus damit!
> 
>> Für Hilfestellung jeder Art bin ich dankbar; auch Hinweise / Vermutun-
>> gen, dass das Problem vielleicht ganz woanders liegt. Ach ja: Wenn der
>> Server ganz zu hängen scheint, hilft es manchmal, das Session-Cookie
>> im Browser zu löschen und die Seite neu zu laden, dann geht's. (Weiß
>> zwar nicht, ob das irgendetwas mit dem Problem zu tun hat, aber ich
>> erwähne es lieber mal.)

Woher weißt du denn, dass der Server hängt? Was ist das überhaupt für
ein Server? Linux? Hast du ssh-Zugang drauf? Meinst du er hängt, weil in
deinem Browser nichts ankommt? Oder weil du auf einer Konsole warst und
gesehen hast, dass der Prozessor auf 100% Auslastung steht? Wenn du es
nur anhand der Ausgabe deines Browsers gesehen hast, dann ist es in der
Tat eine recht gute Chance, dass ein Skript versucht eine Dateisperre zu
bekommen aber am Versuch leider hängen bleibt. Grund könnte ein Deadlock
sein. 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 ...');
(Allerdings nutzt dir diese Möglichkeit der Fehlersuche nur etwas, wenn
du die Methode auch wirklich verstehst, da sie auch "Fehlalarme" liefern
kann. Was du letztlich wissen willst ist wann ein Prozess wirklich
langfirstig nicht in der Lage ist eine Datei zu sperren, aber diese
Methode wird dir schon einen Fehler anzeigen, wenn es nur in einem
Moment nicht geklappt hat. Du musst dann in der Lage sein dass zu trennen.)

Eine weitere Möglichkeit zur Fehlersuche sind debugger. Ich verwende
beispielsweise xdebug (auf Linuxsystemen über den Paketmanager mit
`<Paketmanager> install php5-xdebug` meist schmerzfrei in wenigen
Sekunden installiert). Xdebug ist auch mit einem profiler ausgestattet
(danke Dieter, dass du mich daran erinnerst das zu erwähnen :-)), den du
aktivierst, in dem du die entsprechende option in der php.ini (gerne
ausgelagert in einer anderen Datei, die neben der php.ini auch noch
geparst wird) aktivierst (xdebug.profiler_enabled oder so ganz ähnlich.
Ist recht sprechend). Dann spuckt dir xdebug bei jeder Anfrage eine
Datei aus (in das ebenfalls in der php.ini eingestellte Verzeichnis.
Normalerweise /tmp), die praktischerweise mit cachegrind kompatibel ist,
so dass du die Datei mit irgendwelchen tollen Programmen öffnen kannst,
die dir tolle Graphiken dazu zeichenen, was dich natürlich nicht
interessiert, schließlich willst du nur wissen, was der letzte Befehl
war, den das Skript ausgeführt hat, damit du weißt, wo es hängt ^^. Wie
auch immer. Es gibt zum Beispiel das Programm KCachegrind, damit kannst
du die generierten Dateien öffnen und Bauklötze staunen, was der alles
tolles rausfinden kann (Laufzeiten jedes einzelnen Funktionsaufrufs,
alles schön grafisch dargestellt,...) (außerdem kannst du staunen, wie
erheblich langsamer der Profiler deine Skripte macht und wie unintuitiv
die Bedienung von KCachegrind ist, aber das ist ein anderes Thema).

Du kannst xdebug allerdings auch einfach benutzen um deinen Code gezielt
zeilenweise auszuführen. Mit ein kleines bißchen Frustrationstoleranz
kann man das alles in eclipse zum laufen bringen, so dass du super
komfortabel dich da durch klicken kannst. Bis in irgend einer Zeile es
nicht mehr weiter geht und du dann weißt, wo du weiter forschen musst.
Jaa... 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, denn dann musst
du dir auch jede Menge Dateien ansehen, bis du eine Gefunden hast in der
das Skript erfolgreich hängen geblieben ist.
> 
> Unter den Beiträgen zur Funktion flock() gibt es jemanden, der darauf
> hinweist, dass er bei 20 Anfragen in der Sekunde auf einen exklusiven
> Lock der Apache-Server 100% der CPU-Leistung auffraß ohne abzubrechen.
> Er gibt zwar einen Workaround an, aber spätestens da, wäre die
> Entscheidung für eine Datenbanklösung bei mir gefallen.

flock ist ein Systemaufruf und Systemaufrufe sind verdammt teuer. Jepp.
Das ganze haut so extrem rein, dass sich einige große Seitenbetreiber
überlegt haben bei ihrem Bytecodecache den test deaktivieren, der prüft
ob sich die Orginaldatei geändert hat und daher neu kompiliert werden
muss. Alleine dieser übersprungene eine Test kann die Serverlast auf ein
Bruchteil kürzen. Nur weil er nicht anfragt, wann eine Datei das letzte
mal geändert wurde. Erstaunlich, nicht wahr? :-).

Es sei übrigens an dieser Stelle nochmal auf den Kommentar, der bei
flock in der Bediene steht (und mit einem großen roten Kasten und einem
fetten Warnhinweis versehen ist) hingewiesen:

"On some operating systems flock() is implemented at the process level.
When using a multithreaded server API like ISAPI you may not be able to
rely on flock() to protect files against other PHP scripts running in
parallel threads of the same server instance!"

Ich versuche schon seit längerem herauszufinden, ob ich ein some OS
habe. Das könnte ja dann doch recht interessant sein zu wissen auf
welchen Server das ALLES DENN BACH RUNTERGEHT, wenn der Server auf
threads setzt und nicht auf fork, was ja eigentlich auch viel schneller
ist...
Also wenn es einer auf der Liste hier weiß, wäre ein kleiner Hinweis
dazu sicherlich sehr nett :-).

> beste Grüße,
> Thomas
> 

Yannik

php::bar PHP Wiki   -   Listenarchive