T-Rex: PHP während der Ausführung stoppen

Moin,

wie ich andern Orts schon mal beschrieben habe, arbeite ich aktuell an Skripten die eine längere Ausführung haben können.

Es ist mir jetzt schon öfters passiert, dass ich eine Berechnung über den Browser angestoßen habe die länger braucht. Das Skript rattert los und zeigt nach einer Minute das Ergebnis. Eigentlich kein Problem, wenn ich nach wenigen Sekunden nicht eine Korrektur vornehmen wollen würde.

Wie breche ich das Skript wieder ab? Einfach Browser schließen ist nicht die Lösung - der lokale Webserver arbeitet trotzdem weiter. Öffne ich die Seite neu muss ich trotzdem warten.

Gruß Sanduhr nachzeichnender T-Rex

  1. Hallo,

    wie ich andern Orts schon mal beschrieben habe, arbeite ich aktuell an Skripten die eine längere Ausführung haben können.

    kein Kommentar. Aber du weißt, was ich dazu eigentlich sagen möchte. 😉

    Es ist mir jetzt schon öfters passiert, dass ich eine Berechnung über den Browser angestoßen habe die länger braucht. Das Skript rattert los und zeigt nach einer Minute das Ergebnis. Eigentlich kein Problem, wenn ich nach wenigen Sekunden nicht eine Korrektur vornehmen wollen würde.

    Sowas wie "Halt, ich wollte nicht die ganze Datenbank durchsuchen, sondern nur die jüngsten 100 Datensätze"?

    Wie breche ich das Skript wieder ab? Einfach Browser schließen ist nicht die Lösung - der lokale Webserver arbeitet trotzdem weiter.

    Der Webserver läuft sowieso weiter. Du meintest vermutlich den PHP-Prozess, den der Webserver als Kind startet. Ja, der läuft zunächst mal weiter. Aber dadurch, dass der Client die Verbindung beendet hat, fehlt dem PHP-Prozess plötzlich der Ausgabekanal (stdout). Sobald er das nächste Mal versucht, etwas auf das nicht mehr gültige stdout-Handle zu schreiben, wird er vom Betriebssystem beendet.[1]

    Solange er aber keine Ausgabe macht, läuft der eigentlich tote Prozess im Gruselkabinett des Betriebssystems weiter.

    Öffne ich die Seite neu muss ich trotzdem warten.

    Ja. Aber nicht auf den vom vorigen Request noch laufenden Prozess. Normalerweise forkt der Webserver für jeden Request einen neuen Kindprozess. Wenn du also warten musst, bis der scheintote Prozess endlich fertig ist, hast du selbst einen Flaschenhald gebaut. Zum Beispiel exklusiven Zugriff auf eine bestimmte Systemressource.

    Allerdings darf sich das PHP-Script auch gern etwas kooperativ verhalten und in der lange laufenden Schleife zyklisch nachfragen, ob die Verbindung noch steht, und falls nicht, das Script kontrolliert beenden.

    Immer eine Handbreit Wasser unterm Kiel
     Martin

    --
    Wenn ich den See seh, brauch ich kein Meer mehr.

    1. Es sei denn, er hat sich vorher unsterblich gemacht. ↩︎

    1. Hallo Martin,

      danke, nun hab ich wieder was gelernt! Das ist deutlich eleganter als meine Idee mit der Semaphordatei.

      Der Flaschenhald könnte die Dession dein, die wird von PHP exkludiv fedtgehalten.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hallo Rolf,

        danke, nun hab ich wieder was gelernt! Das ist deutlich eleganter als meine Idee mit der Semaphordatei.

        danke für die Blumen.

        Der Flaschenhald könnte die Dession dein, die wird von PHP exkludiv fedtgehalten.

        Huch, du hadt dein D vergedden!

        Immer eine Handbreit Wasser unterm Kiel
         Martin

        --
        Wenn ich den See seh, brauch ich kein Meer mehr.
  2. Hallo T-Rex,

    wie ich andern Orts schon mal beschrieben habe, solltest Du solche Scripte nicht auf einem Webserver ausführen.

    Wenn dort das Script läuft, dann läuft es und ist nur vom Webserver noch kontrollierbar.

    Edit: Der Rest dieses Beitrags ist ein Beispiel für mein solides Halbwissen, was PHP angeht. Martins Idee mit der Connection ist besser.

    Eine Behelfslösung kann in einer Steuerdatei bestehen. Solange die Datei da ist, darf das Script weiterlaufen. Ist sie weg, beendet sich das Script. Dazu musst Du im Worker einen Checkpoint programmieren, der die Existenz der Datei in regelmäßigen Abständen prüft (nicht zu oft, sonst leidet die Performance und nicht zu selten, sonst funktioniert es nicht gut). Ist die Datei weg, beendet sich das Script.

    Dein Worker-Script startest Du über eine Hilfsseite, die Du ebenfalls in PHP schreibst. Diese erzeugt einen eindeutigen Namen für die Temp-Datei und gibt dann eine Mini-HTML Seite aus, auf der zwei Links sind.

    <a target="_blank" href="run_worker.php?semaphore=li3rdge6re12o2ce.dat">Worker starten</a>
    <a href="?abort=li3rdge6re12o2ce.dat">Worker abbrechen</a>
    

    Der erste Link startet den Worker und nennt ihm den Namen für die Semaphordatei, die er benutzen soll. Der Worker kann diese Datei selbst erzeugen und prüft dann am Checkpoint ihre Existenz. Kommt er zum normalen Ende, löscht er sie selbst wieder. Über eine mit register_shutdown_function registrierte Aufräumfunktion kannst Du sicherstellen, dass die Datei am Ende immer gelöscht wird.

    Der zweite Link enthält keinen Ressourcennamen, d.h. wenn Du da ein "controller.php" hast, würde er controller.php?abort=.... aufrufen.

    Dein controller.php schaut, ob es den abort-Parameter bekommt. Wenn ja, löscht es die genannte Temp-Datei und bewirkt damit den Worker-Abbruch.

    Statt URL-Parametern kannst Du auch einen Cookie setzen. Aber es darf nur ein temporärer Cookie sein, sonst musst Du eine DSGVO-konforme Speichererlaubnis einholen 🤣

    Statt einer handgemachten Steuerdatei kannst Du das auch mit den Session-Features von PHP zu steuern versuchen, aber ich glaube, das ist (a) mühsamer, (b) langsamer und (c) kniffliger, weil PHP die Session-Datei exklusiv im Zugriff hält. Eine weitere Alternative wäre eine SQL Tabelle, in der eine Steuerzeile steht. Aber auch das ist umständlich und aufwändig. Eine Datei ist mit file_exists schnell abgefragt und ermöglicht eine flinke Checkpoint-Verarbeitung.

    Das ist nicht elegant. Das ist nicht sicher. Das ist ein Workaround. Dein controller.php sollte auch eine Aufräumfunktion besitzen, die alle Temp-Dateien löscht - falls doch mal was hängen bleibt.

    Statt den Worker in einem neuen Fenster zu starten, kannst Du ihn auch mit JavaScript und fetch über das gleiche Fenster starten, sein Ergebnis abfangen und bei Erfolg in ein div einfügen. Keine Ahnung, ob das für Dich passt.

    Rolf


    sumpsi - posui - obstruxi

  3. Hello T-Rex,

    das könnte ein längeres Thema werden. :-)

    Mit Linux als OS funktioniert das Folgende:

    PHP hat ein paar Schalter, die den User-Abort steuern.

    Und schau Dir mal die alten Threads zum Thema Dauerlauf und Background an.

    Suche im Archiv nach den Themen. Dann kommst Du ans Ziel.

    Wenn Du nur lesend auf Daten zugreifst (und rechnest) ist das Ganze auch unkritisch. Wenn Du aber auch Daten schreibst, darf dein Script den Abbruch aber erst an einer definierten Stelle durchführen. Sonst hast Du nachher inkonsistente Daten.

    Außerdem benötigt dein PHP-User exec()-Rechte.

    Dann kannst Du mit der PID und kill den Prozess stoppen.
    Wenn der Prozess in einer Schleife läuft, kann es aber besser sein, ein Semaphor zu setzen (kleine Hilfsdatei), die der Prozess bei jedem Durchlauf der Schleife prüft. Ist sie nicht mehr da (oder steht was anderes drin) reagiert er darauf, wie von Dir programmiert.

    Weiteren Einfluss hat auch, ob du die Ausgabebuffer ausgeschaltet hast beim Scriptstart.

    Viel Erfolg!

    Glück Auf
    Tom vom Berg

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

      man kann die PID auch abfragen, und z. B. in ein File wegschreiben, ohne den Prozess in den Hintergrund zu stellen: getmypid(). Was im File drinsteht, ist im Prinzip egal, also für Infos nutzbar. Identifizieren kann man es über den Dateinamen. Hat man mehrere solcher Prozesse (in einer Multi-User-Umgebung durchaus normal) nutzt man für die PID-Files am besten ein eigenes Verzeichnis. Linux macht das ja sowieso so ähnlich mit den /proc/run-Files.

      Dann kann man den Prozess auch mit kill und der PID beseitigen. Oder man bringt dem Prozess das Abfragen des Semaphors (z. B. PID-File) in der Schleife bei, so wie schon beschrieben. Dann benötigt man noch nicht einmal die PID, und der Prozess kann sich selber geordnet beenden. Sonst braucht es die von Rolf beschriebene Exit-Routine.

      Aber gebe Tom Recht, dass man vom Browser angestoßene lang laufende PHP-Prozesse ohne Zwischenausgaben besser in den Hintergrund stellt. Dann ist die Browserverbindung (http) zum Server nämlich gleich wieder frei.

      Früher haben die Browser nur zwei Verbindungen zum Server gleichzeitig zugelassen. Später waren es dann mal typisch acht. Wieviele das heute sind, weiß ich leider nicht. Zu Zeiten von AJAX braucht man vermutlich mehr.

      LG
      Ralf

      1. Hallo Ralf,

        ich halte die Idee eines Kill auf den PHP Prozess für nicht so gelungen. Denn

        (1) Muss man das ja remote ausführen, d.h. über einen weiteren Webrequest und ein PHP Script. Dieses PHP muss ein KILL-Recht besitzen - was etwas ist, was ich einem remote angestoßenen Prozess niemals einräumen möchte.

        (2) Wenn das zu stoppende Script über ein FastCGI PHP ausgeführt wird, muss man den entsprechenden FastCGI Prozess stoppen. Der muss dann zum Ausführen weiterer Prozesse erstmal wieder neu gestartet werden - bei FastCGI halte ich das für eine suboptimale Idee. Da soll ja das PHP-CGI.EXE durchlaufen, fleißig Opcodes und sonstiges cachen und nur vom Webserver die Ausführungsrequeste erhalten.

        Allerdings hat FastCGI und output buffering auch einen Einfluss auf das Verhalten von PHP beim Verlust der Connection, dazu habe ich diverse Berichte gelesen. Man muss genau wissen, in welcher Umgebung man läuft, um es richtig zu machen.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hello,

          wenn man das Ganze nicht "nur mal so eben schnell" auf dem Home-Webserver benötigt (hinter der Firewall), müsste man sich vermutlich auch noch mit Signalen beschäftigen. Wie das mit PHP auf Windows gehen würde, weiß ich leider (noch) nicht. Da gibt es ja auch Messages an die Prozessteile.

          Dazu gibt es auch anderswo Fragesteller und ansatzweise Antworten.

          Darum schrieb ich auch, dass es ein längeres Thema werden könnte.

          Wer da nun wieder Minuspunkte verteilt, ohne diese zu begründen, sollte sich schämen. Sowas ist keine gute Zusammenarbeit!

          Glück Auf
          Tom vom Berg

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

      das könnte ein längeres Thema werden. :-)

      aber zumindest aus meiner Sicht fruchtlos, oder wenn nützlich, dann nur aus akademischer Sicht.

      PHP hat ein paar Schalter, die den User-Abort steuern.

      Genau, das hatte ich ja auch schon angesprochen - okay, nicht die Einstellung in der php.ini, sondern die Funktion gleichen Zwecks, mit der das Script das selbst regeln kann.

      Wenn Du nur lesend auf Daten zugreifst (und rechnest) ist das Ganze auch unkritisch. Wenn Du aber auch Daten schreibst, darf dein Script den Abbruch aber erst an einer definierten Stelle durchführen. Sonst hast Du nachher inkonsistente Daten.

      Richtig. Das heißt, man darf solche Turnübungen nicht mit jedem x-beliebigen Script machen, sondern muss es ein bisschen "domestizieren".

      Wenn man das sowieso tun muss, dann kann man aber auch gleich auf eine zyklische Abfrage von connection_aborted() setzen und auf das Rumhampeln mit PIDs, Signals und kill-Aufrufen verzichten.

      Weiteren Einfluss hat auch, ob du die Ausgabebuffer ausgeschaltet hast beim Scriptstart.

      Das hat einen Einfluss darauf, ob der PHP-Prozess mit dem Script vom OS gewaltsam beendet wird, nachdem die Verbindung getrennt wurde. Aber auch nur dann, wenn das Script so freundlich ist, etwas auf stdout zu schreiben. Tut es das nicht, ist auch die Pufferung egal.

      Immer eine Handbreit Wasser unterm Kiel
       Martin

      --
      Wenn ich den See seh, brauch ich kein Meer mehr.
      1. Hello Martin,

        Wenn man das sowieso tun muss, dann kann man aber auch gleich auf eine zyklische Abfrage von connection_aborted() setzen und auf das Rumhampeln mit PIDs, Signals und kill-Aufrufen verzichten.

        Das gilt nur für den Fall, dass man die Connection die gesamte Scriptlaufzeit halten will und kann. Das halte ich bei Dauerläufern, die mehr als 30 Sekunden (Standard) laufen dürfen sollen für unpraktisch. Außerdem hält nicht jeder Browser die Connection beim Tab-Wechsel. Ich denke da nur an meine Erfahrungen mit dem Tablet.

        Und die sind leider nicht nur akademischer Natur :-(

        Es ist mMn daher immer besser, Dauerläufer sofort in den Hintergrund zu stellen und mittels einer Statusseite den Zustand und das Ergebnis abfragbar zu machen. Das kann die bei Bedarf auch für mehrere solcher Prozesse auch mit AJAX tun. Das entspricht auch eher dem Schema "Client-Sever mit HTTP/s".

        Weiteren Einfluss hat auch, ob du die Ausgabebuffer ausgeschaltet hast beim Scriptstart.

        Das hat einen Einfluss darauf, ob der PHP-Prozess mit dem Script vom OS gewaltsam beendet wird, nachdem die Verbindung getrennt wurde. Aber auch nur dann, wenn das Script so freundlich ist, etwas auf stdout zu schreiben. Tut es das nicht, ist auch die Pufferung egal.

        Es ging darum, warum der Prozess noch nicht wieder freigegeben wurde, obwohl der Browser zugeklappt wurde. Das kann einerseits mit dem Schalter für User-Abort und andererseits damit zusammenhängen, dass der Puffer - je nach eingestellter Größe - noch nicht wieder voll genug ist.

        Außerdem buffered z. B. der Apache auch noch selber 4kB (32-Bit-System) oder 8kB (64-Bit-System). Das kann man aber auch ausschalten (und konfigurieren?).
        Der Buffer ist also auch noch zweistufig. Und bitte die eventuellen Proxies nicht vergessen :-)

        Glück Auf
        Tom vom Berg

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

          Außerdem hält nicht jeder Browser die Connection beim Tab-Wechsel.

          Ist "Connection" und "HTTP" nicht eigentlich ein Widerspruch?

          Wenn ich das richtig verstanden habe, ist die einzige Möglichkeit, wie der Server einen verschollenen HTTP Client entdecken kann, das Ausbleiben von TCP ACKs - und das heißt: Es gibt eine MENGE Möglichkeiten, wie der Server zur "der Kunde ist weg" Erkenntnis kommen kann. Oder sich dabei irren kann.

          Sicherlich ist dieses Vorgehen in einer kontrollierten und wohldefinierten Umgebung machbar und vermutlich auch am einfachsten. Aber sobald man mit Proxies oder Caches zu tun bekommt, wird es schwieriger. Und total am Ar...m ist man, wenn der Webserver ein Cluster ist, denn dann ist eine Semaphordatei im tmp-Ordner auch nicht mehr die Lösung und ein Process Kill gelingt auch nicht, wenn Dauerläufer und Assassine auf verschiedenen Servern landen.

          Aber wie ich schon eingangs schrub: Dauerläufer im Webserver sind ein Irrweg.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Hi Rolf,

            Außerdem hält nicht jeder Browser die Connection beim Tab-Wechsel.

            Ist "Connection" und "HTTP" nicht eigentlich ein Widerspruch?

            nicht wirklich - die Connection existiert ja auf TCP-Ebene und besteht vom Senden des Requests bis zum vollständigen Empfang der Response.

            Und dass diese Connection beim Tab-Wechsel verlorengeht, kann ich mir nicht vorstellen. Das würde ja heißen: Link anklicken, Seite fängt an zu laden, ich wechsle zum anderen Tab, komme nach 20 Sekunden zurück und sehe: Connection reset by peer.

            Wobei ich schon immer wissen wollte, wer denn dieser Peer ist. 😉

            Ich denke aber, Tom hat tatsächlich eine andere Vorstellung von Connection, nämlich etwas Persistentes. Das hätte dann aber ebensowenig mit Tab- oder sogar Task-Wechseln zu tun.

            Aber wie ich schon eingangs schrub: Dauerläufer im Webserver sind ein Irrweg.

            ACK.

            Immer eine Handbreit Wasser unterm Kiel
             Martin

            --
            Wenn ich den See seh, brauch ich kein Meer mehr.
            1. Hello,

              Außerdem hält nicht jeder Browser die Connection beim Tab-Wechsel.

              Ist "Connection" und "HTTP" nicht eigentlich ein Widerspruch?

              nicht wirklich - die Connection existiert ja auf TCP-Ebene und besteht vom Senden des Requests bis zum vollständigen Empfang der Response.

              Und das diese Connection beim Tab-Wechsel verlorengeht, kann ich mir nicht vorstellen. Das würde ja heißen: Link anklicken, Seite fängt an zu laden, ich wechsle zum anderen Tab, komme nach 20 Sekunden zurück und sehe: Connection reset by peer.

              Nee, dann wird einfach neu geladen. Der URi ist ja noch gespeichert.

              Wobei ich schon immer wissen wollte, wer denn dieser Peer ist. 😉

              image Peer
              ?

              Ich denke aber, Tom hat tatsächlich eine andere Vorstellung von Connection, nämlich etwas Persistentes. Das hätte dann aber ebensowenig mit Tab- oder sogar Task-Wechseln zu tun.

              Nee. Dann hätte ich ja nicht von "Client-Server mit HTTP/s" geschrieben!

              Kann aber sein, dass es auch mit Cache oder AJAX zu tun hat. Das auf dem Tablet genauer zu untersuchen, ist schon eine besondere Herausforderung.

              Ich habe jedenfalls mehrere solcher "Dauerläufer" auf dem Webserver, um irgendwelche Dinge zu loggen und zu visualisieren. Die haben fast alle Zicken gemacht, wenn man sie auf dem Tablet oder Smartfon aufgerufen hat. Die Alternative war dann: ab in den Hintergrund und vom Client aus immer nur mit einem eigenen Request (per AJAX) die Ergebnisdateien (und die Status) abfragen.

              Glück Auf
              Tom vom Berg

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