PHP Eventhandler

scrippi

Grünschnabel
Hallo PHP-Community,

mein Browsergameprojekt verfügt über einen Systemprozess (Eventhandler), welche aus der shell als daemon gestartet wird. Dort erledigt dieser verschiedene Ereignisse parallel, durch Threadingklassen, welche die pcntl_fork() und Co Funktionen verwendet.

Mit der Version 5.3.0 von PHP war das alles halbwegs stabil. Bei mittlerer Belastung lief er Monate lang ohne abzubrechen. Seit Serverumzug und Update auf 5.3.3 hält der Prozess mehrere Stunden, und hängt sich wortlos auf.

Meine Frage ist, was kann generell der Auslöser für dieses verhalten sein? Was kann ich tun dafür, dass der Prozess stabil läuft. Wie gesagt, es sind keine Fehler, die Ausgaben hören einfach auf und der Prozess ist eingefroren bis ich per STRG+C abbreche und neu starte.


MfG
scrippi
 
Was genau tut dieser Prozess denn, sobald er geforkt hat? Welches Betriebssystem? Wie ist PHP konfiguriert (configure-Parameter)? Evtl. mal ein bisschen Code? In welchem Status befindet sich der Prozess, wenn er hängt (siehe top(1))

Prinzipiell würde ich es mal mit Debugging versuchen. strace(1) kann hier auch weiter helfen.

Alles in allem gibst du nicht wirklich Informationen, die weiter helfen, das Problem zu analysieren.

Wenn das Problem mit 5.3.3 auftritt und mit 5.3.0 nicht, sollte man die Unterschiede zwischen den Versionen - insbesondere configure-Flags - untersuchen.

Schau ins Changelog von Version 5.3.0 bis 5.3.3, ob da irgendwas spezielles reingekommen oder rausgeflogen ist, was das Verhalten erklären könnte.

Wie geschrieben, ohne weitere Infos, was das Child im Code macht, sehe ich da keine Chance, was sinnvolles mitteilen zu können.
 
Hallo,

ich finds normal, dass wenn man an mehreren Stellen fragt wenn die Absicht ist ein Problem zu lösen...vor allem da ich einer von denen bin, die meistens nach Lösung eine Gesamtlösung veröffentlichen.

Nun gut:

Der Eventhandler soll Ereignisse zu einem bestimmten Zeitpunkt ausführen. Aus verschiedensten Datenquellen in der Datenbank wurde ein View zusammengebaut, was via UNION alles in eine Stelle zusammenführt. Das Script holt sich dann stapelweise "events" aus diesem View, bearbeitet diese und sobald sie abgearbeitet sind, verschwinden diese Einträge aus dem View, so dass die nächste Zeile rankommt.

Allerdings ist das ganze nicht durchgehend gestapelt, sondern gruppiert. Das heisst: das stapelweise Arbeiten bezieht sich auf bestimmte Koordinaten. Werden Ereignisse zu mehreren Koordinaten zur selben Zeit freigegeben, so sollen die unabhängig voneinander parallel bearbeitet werden. Daher eine Lösung mit Threads.

Für die Threads selbst setze ich die Klasse von folgender Quelle ein: http://blog.motane.lu/2009/01/02/multithreading-in-php/

Im ersten Schritt beim Start des Eventhandlers wird der eigentliche Eventlistender als Child gestartet, damit der root-Prozess eine einzige Aufgabe hat: "prüfe ob child noch lebt, wenn nein, starte ihn neu" und macht dadurch nichts was ihn selbst zum Absturz brignt.

Der Child des Root-Prozesses ist der Eventlistender. Dieser Fragt das Eventview ab und erstellt pro Koordinate/Event einen neuen Thread und startet diesen. Alle laufenden Eventthreads werden vom Eventlistender in ein Array gespeichert, wo zum einen Kollisionen vorgebeugt werden, und andererseits regelmäßig und sehr kurzen Abständen geprüft wird, ob einer der Threads tot (Fatal Error oder einfach fertig) ist, und gibt den Thread per unset() auf Arrayindex frei.

Die Eventthreads selbst erledigen diverse Aufgaben in Verbindung mit SQL Abfragen und Anweisungen, und da gibt es einige.


Mehr kann ich nicht angeben, da das Projekt ClosedSource ist.

Folgende Probleme treten permanent auf:

1. Durch das forken geht zu einer unbestimmten Wahrscheinlichkeit die DB Verbindung kaputt. Selbst das neustarten der Verbindung durch mysql_ping, mysql_close + mysql_connect usw schaffen es nicht immer.
Folgende verschiedene DB Fehler gibt es dabei:
Mysql has gone away
No Database selected
Could not select database xxx
Lost connection during query.....

2. Gut ist es, wenn die DB Verbindung komplett stirbt, schlecht wenn mittendrin. (Hier kommen wir zu einer viel größeren Problemspanne...)


Neu ist nun der Einfriereffekt. Nach einer bestimmten Arbeitszeit (oder auch Last) stoppen die Logausgaben und der Prozess steht still. Keine Hinweise auf irgendwelche Fehler.

Zu sagen was da schief läuft ist demnach nicht einfach, da ich absolut nicht einschätzen kann, wann es einfriert. Derzeit läuft er wieder 4 Tage, ist davor aber in 4 - 10 Stundentakt hängen geblieben...

Hoffe, ich konnte die Umstände meines Problems etwas näher bringen und hoffe, dass es hier noch mehr Leute gibt, die so böse Sachen in PHP anstellen :)


MfG
scrippi
 
Das sind keine bösen Sachen. Nur wäre die Sprache meiner Wahl eine andere gewesen: C ;-)

Egal. Interessant finde ich das die SQL-Verbindung einfach weg bricht. Was kann die Ursache dafür sein? Schon mal mit strace auf den Prozess gehängt, wenn er hängt?

Bash:
$ > strace -ff -o /pfad/zu/leeren/order/programp -p `pidof programm`

wobei programm dann halt dein Programm ist oder aber du lässt `pidof programm` weg ung gibst direkt die PID ein. Dann könntest du zu mindest schon mal rausfinden, wo dein Programm hängt (Socket, Futex, $whatever).

Übrigens solltest du dir angewöhnen, klar zu unterscheiden, was ein Thread ist und was ein Fork. Das sind nämlich zwei verschiedene Sachen.

Bei einem Fork wird der komplette Adress-Raum des Parent-Prozesses kopiert und quasi ein neuer Prozess erstellt (wenn du in Bash einen Command aufrufst, wird auch geforkt). Bei einem Thread wird das nicht gemacht, "Parent" und "Child" (es gibt kein Eltern-Thread oder Kind-Thread, alle sind gleichgestellt) teilen sich einen Adress-Raum. Bei pcntl_fork() wird tatsächlich geforkt, daher ist es kein Multithreading sondern Multiprocessing. So, genug kluggescheissert.

Wenn ich die Beschreibung so lese, könnte man annehmen, das du keine Transaktionen in der DB verwendest, richtig? Wenn ja, wie managest du konkurrierende Zugriffe? Was ich meine: Besteht die Möglichkeit, das du auf eine Synchronisation aktiv wartest, und diese nie eintrifft? Arbeitest du evtl. mit flock()- oder sem_acquire()-"Mutex"?

Vielleicht sollte man erstmal ausschließen, was es nicht sein kann.
 
Interessant, den Unterschied zwischen Fork und Thread kannte ich nicht, dachte das eine wird mit dem anderen realisiert, da ja auch die Threadklasse die ich verwende mit fork arbeitet.

Das Kollisionsproblem wird auf ebene der DB gelöst. Ein Ereigniss passiert immer im Bezug zu einer Koordinate wie etwa "1:1:1". Damit dort keine Konkurenz entsteht, wird per MIN() eine Menge von Ereignissen pro Koordinate nach Priorität sortiert und eine wiedergegeben, welche andere Ereignisse blockiert, bis diese abgearbeitet wird.

Wer jetzt sagt "na das wirds sein": andere Prozesse laufen ja auch nebenher und wenn es an sowas hängenbleibt, sehe ich in der Ausgabe des Scripts das da etwas klemmt. Bei meinem Fall hängt sich eben alles auf, sodass insgesamt nichts mehr läuft.

Durch das Kopieren des Adressraums bricht auch die Verbindung ab wahrscheinlich. Jedenfalls ist ein fork + mysql Problem bei PHP bekannt. Bisher verhelfen wir uns damit, dass wir autocommit deaktivieren und erst nach erfolgreichem Abarbeiten aller Anweisung manuell comitten.

Das mit den Transaktionen in der DB habe ich leider nicht verstanden.

das mit strace müsst ich mir noch aneignen :)
 
Auf der Seite, wo die Thread-Klasse beschrieben wird, wird ausdrücklich darauf hingewiesen, das Threads emuliert werden ;-)

Wenn du schreibst, das autocommit abgeschaltet und manuell commited wird, hast du die Antwort auf Transaktionen bereits gegeben. Eine Transaktion hat einen Kontext, der abgeschlossen wird, wenn man commited. Erst dann wird der Bonsai-Tree (oder wie auch immer die Engine das implementiert) auf die Platte genagelt. Bis dahin ist ein Rollback jederzeit möglich. Tipp für Transaktionen: http://dev.mysql.com/doc/refman/5.1/de/ansi-diff-transactions.html

An der Stelle sei noch angemerkt, das MyISAM keine Transaktionen unterstützt (häufig ist das die Default-Storage-Engine, also aufpassen ;-))

Du könntest mal mit mysql_pconnect() oder der korrespondierten Eigenschaft des PDO-Adapters (PDO::ATTR_PERSISTENT auf PDO::setAttribute()) rum spielen, vielleicht kannst du damit das Connection-Problem lösen.

Ein richtiger Mutex auf Datenbank-Ebene wäre eigentlich nur realisierbar, wenn man die Tabellen lockt, die involviert sind.
 
Zurück