MB: Durchführung PHP gesteuerte Funktionen ohne http-Request

moin,

Meines wissens nach wird PHP-Skripte erst durch einen http-Request Aufruf aktiv. Ich frag mich wie PHP dann Operationen durchführt die mit einem Datum getriggert werden. Es müsste doch im Runtime des Servers ein kontrollierte Endlosschleife laufen um Aufgaben zu erledigen die http-Request unabhängig laufen.

Ein konkretes Beispiel zu Erläuterung meiner Frage…

Kunde Alice soll am Datum x eine Automail erhalten".

…ein Problem das ohne http-Request zu lösen ist. Nur wie?

lgmb

  1. Hello,

    Meines wissens nach wird PHP-Skripte erst durch einen http-Request Aufruf aktiv. Ich frag mich wie PHP dann Operationen durchführt die mit einem Datum getriggert werden. Es müsste doch im Runtime des Servers ein kontrollierte Endlosschleife laufen um Aufgaben zu erledigen die http-Request unabhängig laufen.

    Ein konkretes Beispiel zu Erläuterung meiner Frage…

    Kunde Alice soll am Datum x eine Automail erhalten".
    

    …ein Problem das ohne http-Request zu lösen ist. Nur wie?

    Wie man PHP-Scripte auch eigenständig in den Hintergrund stellen kann, habe ich hier schon oft gepostet. Bitte einfach mal im Archiv suchen nach "Author:tom" oder "Author:TS" und "PHP" und "Dauerlauf" als Suchbegriffe.

    Ich suche das morgen Abend auch gerne heraus, aber vorher komme ich nicht dazu :-)

    Liebe Grüße
    Tom S.

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
  2. Bei ganz schlimmen Billighostern muss man darauf zurückgreifen, dass zufällig ein PHP-Skript aufgerufen wird, welches mal nachschaut, ob ein Job offen ist und den erledigen.

    Bei besseren Hostern kann man mittels crontab einen Job anlegen. Zuständig sind dann Dienste wie cron oder anacron. Entsprechend werden die Jobs dann von php-cli ausgeführt (AVE: Inzwischen hat das oft eine eigene php.ini, also andere Einstellungen)

    Die crontab wird in einer ssh-Sitzung editiert, also konfiguriert:

    [sudo] crontab -e [-u alice]
    

    (Die Angabe des Benutzers nach sudo ist optional!)

    Darin würde dann

    50 23 * * 0 /usr/bin/php /foo/bar/cron.php
    

    dafür sorgen, dass jeden Sonntag um 23:50 /foo/bar/cron.php gestartet wird. Alle Ausgaben bekommt (wenn nicht anders eingstellt) der locale user, dem die crontab gehört über das lokale Mailsystem.

    50 23 * * 0 /usr/bin/php /foo/bar/cron.php 1> /dev/null 2>> /foo/bar/cron.errors 
    

    würde alle Ausgaben unterdrücken und die Fehler nach /foo/bar/cron.errors loggen. Es würde dann KEIN Mail verschickt.

  3. Tach!

    Meines wissens nach wird PHP-Skripte erst durch einen http-Request Aufruf aktiv.

    Man kann PHP-Scripte aufrufen. Soviel ist richtig. Oder besser gesagt, man kann PHP aufrufen und ein Script zur Ausführung übergeben. Dass der Webserver das aufgrund eines Requests macht, ist eine Möglichkeit.

    Ich frag mich wie PHP dann Operationen durchführt die mit einem Datum getriggert werden.

    Da muss ein Mechanismus laufen, und beim Erreichen des Datums PHP und das Skript aufruft. Unter Unixoiden üblich ist es, cron dafür zu verwenden.

    Es müsste doch im Runtime des Servers ein kontrollierte Endlosschleife laufen um Aufgaben zu erledigen die http-Request unabhängig laufen.

    Nein, muss nicht. Könnte man dem Server vielleicht mit einem Modul beibringen, mir ist aber kein solcher Dauerläufer bekannt. Wozu auch, cron existiert.

    Andererseits steht cron nicht jedem Webhoster-Kunden zur Verfügung, da kann man sich Alternativen ausdenken, um Routine-Arbeiten zu erledigen. Beispielsweise kann bei jedem Request eine Aufgabe miterledigt werden. Aber das passiert dann per Zufall, je nachdem, wie Requests reinkommen.

    Ein konkretes Beispiel zu Erläuterung meiner Frage…

    Kunde Alice soll am Datum x eine Automail erhalten".
    

    …ein Problem das ohne http-Request zu lösen ist. Nur wie?

    Mit cron kann man in regelmäßigen Abständen Programme starten. Das Skript kann dann selbst prüfen, ob zum Aufrufzeitpunkt zu erledigende Dinge vorliegen. Auch at kann man nehmen, wenn man einmalige Dinge in Auftrag zu geben hat. Das ist aber etwas umständlicher in Auftrag zu geben als einmalig den Cron einzurichten.

    dedlfix.

  4. Hallo MB,

    Meines wissens nach wird PHP-Skripte erst durch einen http-Request Aufruf aktiv

    Das ist eine Möglichkeit, ja.

    Möglichkeit 2 ist der Aufruf durch einen von Systemtimer gesteuerten Job. Unter Unix CRON, unter Windows SCHTASKS (a.k.a. Aufgabenplanung).

    Möglichkeit 3 ist ein eigenständiger Prozess, der einfach in einer Endlosschleife läuft und irgendwie seine Arbeit einplant. Wenn er nichts zu tun hat, legt er sich schlafen. Das geht mit der sleep-Funktion, oder bei einem Socket-Listener über socket_select(). PHP ist aber meines Wissens nicht als Langläufer gedacht, es mag da zu Speicherproblemen kommen wenn der Prozess zu lange läuft. Und PHP ist auch nicht unbedingt absturzsicher.

    Für die Möglichkeiten 2 und 3 brauchst Du Rechte am Server, die Du bei einem einfachen Webhoster normalerweise nicht bekommst.

    Es gibt noch eine andere Möglichkeit: Ich habe vor vielen Jahren ein MMOG gespielt, das war in PHP geschrieben und da mussten im Halbstundentakt die Welt-Updates laufen. Der Autor hat das GANZ clever gelöst. Weil er wusste, dass es im Spiel ständig PHP Requeste hagelte, hat er einfach die User-Requeste als Timer verwendet, in der Script-Initialisierung abgefragt, ob das Welt-Update fällig ist, dann eine Sperre in die DB gesetzt, das Welt-Update durchgeführt, die Sperre freigegeben und dann ganz normal den Request weiter ausgeführt. So ein Update dauerte locker 20-30 Sekunden, und so lange saß man vor seinem Browser und konnte nichts tun. Was BITTER war, denn man konnte Angriffe gegen gegnerische Städte fahren und wenn man derjenige war, in dessen Request das Update ausgeführt wurde, saß man mit seinen Katapulten 20s wie die Ente auf dem Teich vor den feindlichen Mauern und konnte nicht den Kopf einziehen, wenn da eine Verteidigungsarmee stand. Irgendwann haben sie es dann auf CRON umgestellt, damit wurde der Server im Halbstundentakt einfach für alle grottenlahm. Hach ja, Erwin "Videoblitz" F. und sein Zorn der Götter, selbst Google kennt das nicht mehr 😂

    Rolf

    --
    sumpsi - posui - clusi
    1. Hello,

      Möglichkeit 3 ist ein eigenständiger Prozess, der einfach in einer Endlosschleife läuft und irgendwie seine Arbeit einplant. Wenn er nichts zu tun hat, legt er sich schlafen. Das geht mit der sleep-Funktion, oder bei einem Socket-Listener über socket_select(). PHP ist aber meines Wissens nicht als Langläufer gedacht, es mag da zu Speicherproblemen kommen wenn der Prozess zu lange läuft. Und PHP ist auch nicht unbedingt absturzsicher.

      Da habe ich bisher mit gut gebauten PHP-Prozessen keine Probeme gehabt. Ich habe mehrere laufen, die breits über sechs Monate alt sind. Systemlast ist kaum messbar.

      Allerdings bin ich auch ein wenig paranoid und lasse daher einen Cron-Job von Zeit zu Zeit nachschauen, ob die PHP-Dauerläufer noch leben. Außerdem produzieren die auch Logs zur Kontrolle.

      Man muss nur ein paar wenige Regeln einhalten, dann klappt das auch mit PHP ;-)

      Liebe Grüße
      Tom S.

      --
      Es gibt nichts Gutes, außer man tut es!
      Das Leben selbst ist der Sinn.
      1. Hallo Tom,

        gibt's da best practices, die man bei PHP Dauerläufern einhalten muss, um den Heap nicht volllaufen zu lassen?

        Rolf

        --
        sumpsi - posui - clusi
        1. Einiges kann man sich doch denken:

          PHP.ini:

          output_buffering=Off

          • Beim Start Ausgaben nach /dev/null umleiten, Fehler und Notizen in ein Logfile umleiten, welches von Logroate behandelt wird.

          • Kann man das output_buffering nicht setzen, dann regelmäßig ob_clean() anwenden.

          Im aufgerufenen Skript:

          <?php
          #…
          #
          ob_clean(); # optional
          # ?> "PHP"-Tag NICHT schließen
          
          • Keine Arrays, die immer größer werden, verwenden.
          • Bei Bedenken: Ein mit nohub oder batch gestartetes, ewig laufendes Shell-Skript startet das PHP-Skript (welches selbst NICHT ewig läuft) immer wieder neu und sorgt so neben den Ausgabeumleitungen auch für den Neustart von PHP und also Vermeidung des "heap-overflow". Will man den Job killen muss dann natürlich das Shell-Skript gekillt werden. Optimal schreibt es hierzu die PID in eine Datei mit bekannten Name und startet nicht, wenn diese Datei existiert. Der Job-Killer muss also auch die Datei löschen.
          • ist das PHP-Skript ein "ewiger Selbstläufer", dann sollte man seine Variablen innerhalb des while(true) { … } mit unset() aufräumen lassen. Das gilt auch für Objektvariablen.
        2. gibt's da best practices, die man bei PHP Dauerläufern einhalten muss, um den Heap nicht volllaufen zu lassen?

          Der Heap ist nur eine mögliche Ursache für einen Programmabsturz, es gibt noch andere Möglichkeiten:

          Beginnend mit dem offensichtlichen: Das Programm darf nicht regulär terminieren, man braucht irgendwo eine Endlosschleife oder eine Endlosrekursion. Das mit der Endlosrekursion ist nicht ganz einfach, weil eine naive Endlosrekursion zu einem Stackoverflow führen würde. Tail-rekursive Funktionen haben diesen Nachteil theoretisch nicht, weil jeder rekursive Aufruf den zuletzt verwendeten Stackframe wieder verwenden kann. Manche Laufzeitsysteme können das automatisch optimieren, PHP macht das, soweit ich weiß, aber nicht. Es gibt Workarounds, wie Trampolining, die diese Limitierung umgehen, aber das ist nicht ganz einfach. Im Falle von PHP würde ich eher zur guten alten Endlosschleife raten.

          Dann gibt es diverse Möglichkeiten, wie das Programm abnormal terminieren könnte. Ein break, die oder exit innerhalb der Endlosschleife hätte das zur Folge. Das lässt sich aber relativ einfach vermeiden.

          Nicht abgefangene Exceptions würden ebenfalls das Programmm abnormal terminieren. Das ist der wohl kritischste Punkt. Jede Anweisung, die eine Exception zur Folge haben kann, muss in einem try/catch-Block stattfinden und der catch-Block muss so getypt sein, dass er auch alle möglichen Fehler abfängt. Insbesondere muss man hier an Typfehler denken und dann gibt es leider auch ein paar Laufzeit-Fehler, die man in PHP überhaupt nicht abfangen kann. declare(strict_types=1), Linter und andere Code-Analyse-Tools können hier helfen.

          Wie im ersten Punkt schon erwähnt muss man sich vor Stapelüberlaufen schützen, das ist kompliziert in den Griff zu kriegen. Ein Stackoverflow tritt nicht nur bei Endlosrekursionen auf, sondern auch bei Rekursionen, die eigentlich gegen einen Basisfall konvergieren und sogar in nicht rekursiven Funktionsaufrufen, wenn die Verschatelungstiefe zu groß wird.

          Und, um auf deine ursprüngliche Frage einzugehen: Der Heap darf nicht unbeschränkt wachsen, sondern muss durch eine obere Schranke begrenzt sein. Die Garbage-Collection hilft dabei, unset kann die GC unterstützen, ist aber kein Allheilmittel. unset gibt keinen Speicher selbständig frei, sondern teilt der GC nur mit, dass sie der Variablen nicht folgen muss. Das ist nur selten nötig: Nur bei globalen Variablen oder Variablen, die durch Closures gecaptured worden sind. Lokale Variablen, werden beim Funktionsrücksprung automatisch gelöscht und die GC weiß, dass sie ihnen nicht folgen muss. Hilfe kann außerdem eine WeakRef bringen, das ist ist eine Referenz, der die GC automatisch nicht folgt. Das heißt, das der referenzierte Speicher aufgeräumt wird, wenn es nur noch schwache Referenzen darauf gibt. Schlimmer für die GC als Variablen sind Objekt-Eigenschaften. Es gibt in PHP eine Datenstruktur WeakMap, die ebenfalls nur schwache Referenzen auf die gespeicherten Werte enhält. Beide Datenstrukturen sind leider nicht Teil des Standard-Sprachumfangs von PHP, sondern erfordern eine Erweiterung. Außerdem ist eine WeakRef kein Drop-In-Replacement für eine Variable, Gleiches gilt für WeakRef und Objekte und Arrays, man muss sich vorher Gedanken machen, ob das wirklich gewollt ist. Im Zweifel zählt Korrektheit meistens mehr als Speicherbedarf.

          Ein schlimmeres Problem ist, dass auch wenn so eine obere Speicherschranke für die Heap-Größe exisitiert, das Programm weiterhin in Folge zu wenig freien Speichers abstürzen kann. Zum Beispiel wenn das System gerade anderweitig so viel Speicher benutzt, dass der PHP-Prozess schlicht keinen weiteren Speicher allozieren kann. Einige Laufzeit-Systeme können eingeschränkt dagegen schützen, indem sie beim Starten der virtuellen Maschine oder des Interpreters große Blöck Speicher alloziieren, die garantiert während der gesamten Laufzeit zur Verfügung stehen. Ganz darauf verlassen kann man sich aber auch nicht.

          Am besten man wappnet sich immer für den Fall, dass doch mal was schief läuft: Unter Linux kann man einen Prozess-Supervisor einsetzen, der abgesürzte Prozesse automatisch neustartet. Der Supervisor sollte auch nach einem Systemneustart automatisch wieder hochfahren, das geht bswp. mit einem @reboot-Cronjob. Und letztlich sollte man sich auch mal Gedanken über Hardware-Ausfälle, Stromausfälle oder Netzwerk-Ausfälle gemacht haben.

          Wenn ich noch einen Rat geben darf: Es gibt Sprachen, die sind mehr auf Ausfallsicherheit ausgelegt als PHP. Wenn es nicht nur um risikolose Haushaltsarbeit auf einem Webserver geht, sondern um kritische Anwendungen, bei denen ein Programmabsturz schwere Folgen haben könnte, dann bedient man sich besser bei diesen Werkzeugen.

          1. Tach!

            Nicht abgefangene Exceptions würden ebenfalls das Programmm abnormal terminieren. Das ist der wohl kritischste Punkt. Jede Anweisung, die eine Exception zur Folge haben kann, muss in einem try/catch-Block stattfinden und der catch-Block muss so getypt sein, dass er auch alle möglichen Fehler abfängt. Insbesondere muss man hier an Typfehler denken und dann gibt es leider auch ein paar Laufzeit-Fehler, die man in PHP überhaupt nicht abfangen kann. declare(strict_types=1), Linter und andere Code-Analyse-Tools können hier helfen.

            PHP verwendet Exceptions nur an ganz wenigen Stellen. Die meisten Fehler werden mit dem herkömmlichen Error Reporting erzeugt und lassen sich auch nicht try-catchen. Stattdessen gibts die gute alte user-defined error handler function, die im Falle eines Fehlers aufgerufen wird. Ein Rücksprung oder Weitermachen wie nach einem catch ist da aber nicht vorgesehen. Da müsste man mit einem fiesen goto vor die Endlosschleife springen. Das geht aber nicht, man kann nicht aus Funktionen herausspringen. So bleibt nur die Main-Funktion in einer Funktion zu kapseln, die man dann aus dem Error-Handler aufrufen kann. Womit sich aber eine Rekursion oder ein ähnlicher Zustand ergibt, besonders wenn der Fehler immer wieder ausgelöst wird.

            Die erwähnten nicht abfangbaren Laufzeitfehler sind derart schwerwiegend, dass ein Abfangen nicht sinnvoll ist. Zum einen sind das Syntaxfehler, die man aber vermeiden kann. Zum anderen sind das solche Kaliber wie Out of Memory. Da kann man nichts mehr machen oder reparieren. Wenn der Speicher voll ist, ist da auch kein Platz mehr für den ErrorHandler-Aufruf.

            dedlfix.

            1. Danke für die Ergänzung.

              PHP verwendet Exceptions nur an ganz wenigen Stellen. Die meisten Fehler werden mit dem herkömmlichen Error Reporting erzeugt und lassen sich auch nicht try-catchen.

              Das stimmt so nur für PHP 5, in PHP 7 wurde die Situation verbessert. Das Handbuch schreibt dazu u.a. Folgendes:

              PHP 7 changes how most errors are reported by PHP. Instead of reporting errors through the traditional error reporting mechanism used by PHP 5, most errors are now reported by throwing Error exceptions.

              Nichtsdestotrotz ist das traditionelle Error-Reporting wohl noch in Ausnahmefällen aktiv und man muss sich auch damit beschäftigen. Leider steht nirgendwo zusammenfassend welche Ausnahmen das sind.

              1. Tach!

                PHP verwendet Exceptions nur an ganz wenigen Stellen. Die meisten Fehler werden mit dem herkömmlichen Error Reporting erzeugt und lassen sich auch nicht try-catchen.

                Das stimmt so nur für PHP 5, in PHP 7 wurde die Situation verbessert.

                Du meinst, ich sollte mir langsam mal das Changelog für die Versionen 7.x anschauen? Na gut.

                dedlfix.