Philipp Hasenfratz: fork => END-Blöcke in separatem Prozess ausführen

Halihallo Forumer

Ich habe eine Webapplikation, wo viele automatische Aufgaben erledigt werden müssen (z. B. Mails senden, empfangen [das schluckt am meisten Zeit]). Diese Aufgaben werden in den jeweiligen Modulen in den END {...} - Blöcken ausgeführt. Nun habe ich festgestellt, dass diese auf dem Webspace ziemlich langsam sind... Jedesmal auf den POP zugreifen, oder mit dem SMTP-Server kommunizieren oder irgend eine Antwort analysieren und in die Datenbank zu schreiben... Ich konnte zwar die Performance durch die interne Konfiguration schon erheblich steigern, aber ich denke, dass es hierfür noch eine weitere Verbesserung gibt:

use POSIX;
$SIG{CHLD} = 'IGNORE';

wird beim beenden gestartet:

my $pid = fork();
   die 'could not fork' unless (defined $pid);
   if ($pid == 0) {
      # child
      close STDOUT; close STDERR;
      open( STDOUT, '>./t.txt' );
      exit(0);   # calls END Blocks on Schedulers (Automatisms)
   } else {
      # parent, $pid is child-pid
      POSIX::_exit(0);  # no END Block is ever called -> exits immediately
   }

also: der Sinn dieses Codes besteht darin, beim 'die' oder 'exit' des Hauptprogrammes den Prozess über POSIX abzuwürgen (sodass der Client den Output kriegt und das Programm auf dem Server nicht weiter ausgeführt wird). Aber die Autmatisationsprozesse dennoch über den Childprozess weitergeführt wird (fork), der dann die END-Blöcke ausführt.

Mit autoflush von STDOUT und close(STDOUT) hatte ich leider keine Erfolge erzielt (also, dass der Client die HTML-Seite kriegt und weiterarbeiten kann, noch bevor das Programm wirklich beendet ist).

Ich bitte um Meinungen zu dem obengenannten (über fork) Lösungsvorschlag. Was sind Risiken, was ist zu beachten, wird das Hauptprogramm dann wirklich gleich abgewürgt. Funktioniert das ohne auf den Childprozess zu warten und keine Zombies zu generieren? - Bitte verweist mich nicht auf cron oder eine völlig andere Lösung, da ich in 5 Tagen in die Ferien will und noch anderes zu tun habe (OK, ihr dürft gerne andere Vorschläge machen, die auch wenig Zeit in Anspruch nehmen, alle anderen werde ich jedoch nicht mehr vor den Ferien hinkriegen).

Viele Grüsse und Danke im Voraus

Philipp
   <-- der ziemlichen Stress hat und auf eure Hilfe hofft :-(((

  1. Halihallo Forumer

    vielleicht sollte ich meine Fragen nochmals etwas "zusammenfassen" (mir scheint selber, dass vorangegangenes Posting etwas "kompliziert" ist):

    Meine GUI Programme liefern HTML Dokumente an den Client zurück (soviel war wohl klar). Nach dem ganzen HTML-Zeug werden jedoch noch einige andere Jobs abgearbeitet, die eigentlich gar nichts mit dem Client zu tun haben. Es geht nun darum, dass diese ohne grössere Programmierarbeiten in einen separaten Prozess verlegt werden (=>fork). Dies hat den Zweck, dass das eigentlich vom Client aufgerufene Programm _gleich_ beendet werden kann und somit der Webserver das generierte HTML Dokument an den Client sendet. Leider habe ich den Verdacht (bestätigt), dass der Webserver dennoch auf das Ende des Programmes wartet, auch wenn ich STDOUT, STDERR geschlossen habe und autoflushed habe, deshalb versuche ich es mit POSIX::_exit abzuwürgen (Apache "überlisten") und die etwas langsamen END-Blöcke in einem separaten Prozess abzuarbeiten.

    Fragen:
     - Gibt es eine andere Lösung, die Ausgabe (+ Übertragung an den Client!) explizit zu erzwingen, auch wenn das Programm noch läuft?
     - Gibt es Sachen, die ich bei der Lösung über fork und POSIX::_exit beachten muss (Zombies), ich kenne mich mit dem nicht so wahnsinnig gut aus.
     - Fällt euch eine andere, schnell umzusetzende Stategie ein?

    Background:
    Der Performancefresser ist ein Synchronisations-Modul auf E-Mail Basis, um gewisse Daten auf ein völlig unabhängiges System zu transferieren (die ganze genannte Webapp liesse sich als GUI-Interface zu einem Background-System bezeichnen). Nötig hierzu sind frequentierte Zugriffe auf POP und SMTP Server (bei SMTP sogar vorerst Zugriff auf POP-Server zur Authentication) und Analyse/Weiterverarbeitung der Daten des E-Mails. Diese Zugriffe habe ich schon mit einer "Frequenz" (Aktion nur jede Minute) verkleinert und noch verbessert durch eine Queue. Jedes senden generiert eine neue Queue. Das lesen des POP's muss folglich auch nur stattfinden, wenn eine Queue gesendet wurde; der Analyser springt erst an, wenn eine QueueID empfangen wurde etc. Fakt bleibt jedoch, dass immer ein Kunde länger auf die HTML Seite warten muss, da wiedermal im POP nachgeschaut werden muss, bzw. eine Mail versendet wird. Dies gilt es zu verhindern.

    Umgebung/Voraussetzungen:
     - Linux
     - Apache
     - Keine Möglichkeit auf Konfiguration des Webservers (dann ist das nicht die Wahl deines Providers, ich weiss)
     - Perl
     - aus internen Gründen keine Möglichkeit über sendmail
     - kein cron
     - Interface und Backgroundprogramm nicht änderbar
     - nur noch 5 Tage Zeit ;) oder besser :-(((((
         - durch Design und sonstige Überarbeitungen des
         - Sourcecodes erschwert.

    Viele Grüsse

    Philipp

    PS: Sehe ich hier den Wald vor lauter Bäumen nicht mehr? - Wäre froh, wenn mich einer auf die Erde zurückholt... Schliesslich will ich nach Brasilien! juhuii ;)

  2. Halihallo Forumer

    use POSIX;
    $SIG{CHLD} = 'IGNORE';

    wird beim beenden gestartet:

    my $pid = fork();
       die 'could not fork' unless (defined $pid);
       if ($pid == 0) {
          # child
          close STDOUT; close STDERR;
          open( STDOUT, '>./t.txt' );
          exit(0);   # calls END Blocks on Schedulers (Automatisms)
       } else {
          # parent, $pid is child-pid
          POSIX::_exit(0);  # no END Block is ever called -> exits immediately
       }

    Ach, wie ich cygwin liebe... (sorry, hab mir grad die neuste Version beschafft und da hat einfach alles auf anhieb funktioniert, was bei der alten irgendwie nicht so war... Ich bin begeistert!). Na, ich hab das dann grad mal versucht und es schient zu funktionieren... Jetzt hab ich es auch in die Webapp implementiert und siehe da... Es funktioniert _fast_, wär auch zu schön gewesen um wahr zu sein. Aber ich denke, dass ich da schon noch was rumbasteln kann und es zum Laufen bringe.

    Eine kleine Frage... Nach einigen Debugging-Sessions hab ich herausgefunden, dass mein "Automizer" (Scheduler-Programm) im END Block durch einen 'die' gestorben ist (was mich erst glauben liess, dass der Beispielscode irgendwo nicht so ganz klappt, dass irgendwo irgendwelche Sachen beim Multithreading/-processing schief laufen, was sonst funktioniert). Nun, wenn ein die im END - Block stattfindet wird die interne END-Queue im ParseTree des Perlinterpreters abgebrochen und das Programm ended abruppt. Ich hab dann einen eval um den "unsicheren" Code gelegt und den Fehler abzufangen versucht. Das schien auch einige male zu funktionieren, bald jedoch nicht mehr. Was meint ihr? - Ist dieses einmal-ja, einmal-nein durch mich verursacht? - Wie ist das mit dem 'die' im END Block? - Kann man den mit eval nun abfangen oder nicht? - In der Doku hab ich leider nix mehr gefunden (evtl. sind meine Augen schon etwas müd geworden).

    Viele Grüsse

    Philipp
       <-- der das erste mal X auf cygwin gesehen hat... schön, schön; ja sogar den xemacs läuft... Jetzt muss ich mir nur noch den Apachen kompilieren... Geil, geil ;-) [naja, eigentlich sollte ich ja anderes tun :-(]
       <-- der froh ist, das ganze Design-Zeug auch erledigt zu haben...
       <-- der seine Ferien immer näher kommen sieht...
       <-- der zur Abwechslung wiedermal ein Lichtlein am Ende des Tunnels sieht.
       <-- der euch einen schönen Morgen wünscht und jetzt langsam auch schlafen geht...
       <-- der anscheinend etwas Müd ist ;-)

  3. Hoi,

    use POSIX;
    $SIG{CHLD} = 'IGNORE';

    wird beim beenden gestartet:

    my $pid = fork();
       die 'could not fork' unless (defined $pid);
       if ($pid == 0) {
          # child
          close STDOUT; close STDERR;
          open( STDOUT, '>./t.txt' );
          exit(0);   # calls END Blocks on Schedulers (Automatisms)
       } else {
          # parent, $pid is child-pid
          POSIX::_exit(0);  # no END Block is ever called -> exits immediately
       }

    Mach das mal richtig und sauber:

    use POSIX qw/setsid/;

    my $pid = fork;
    unless($pid) {
      die 'could not fork!' unless defined $pid;
      # process is no longer a child
      die 'could not remove session' unless setsid;
    }
    else {
      # main process ends
      exit 0;
    }

    Code, der lange dauert

    Dein Getrickse mit POSIX::_exit() ist da voellig ueberfluessig und verwirrt
    nur. Der 'Trick' ist hier recht einfach: der Child wird aus der Prozess-Gruppe
    herausgeloest und bekommt eine eigene Session.

    Gruesse,
     CK

    1. Halihallo Christain

      use POSIX;
      $SIG{CHLD} = 'IGNORE';

      wird beim beenden gestartet:

      my $pid = fork();
         die 'could not fork' unless (defined $pid);
         if ($pid == 0) {
            # child
            close STDOUT; close STDERR;
            open( STDOUT, '>./t.txt' );
            exit(0);   # calls END Blocks on Schedulers (Automatisms)
         } else {
            # parent, $pid is child-pid
            POSIX::_exit(0);  # no END Block is ever called -> exits immediately
         }

      Mach das mal richtig und sauber:

      OK. Versuche ich mal ;)

      use POSIX qw/setsid/;

      warum brauche ich den Session Identifier? - In der Doku hab ich nix gefunden, warum ich den explizit angeben muss. Kannst (oder wer anders) du es als fork/linux Experte erleutern?

      my $pid = fork;
      unless($pid) {
        die 'could not fork!' unless defined $pid;
        # process is no longer a child
        die 'could not remove session' unless setsid;
      }
      else {
        # main process ends
        exit 0;
      }

      Das kommt ja etwa auf das selbe raus, was ich getan hab, oder?

      Dein Getrickse mit POSIX::_exit() ist da voellig ueberfluessig und verwirrt
      nur. Der 'Trick' ist hier recht einfach: der Child wird aus der Prozess-Gruppe
      herausgeloest und bekommt eine eigene Session.

      Bist du dir sicher? - Das Problem mit exit(0) ist, dass der Prozess dann eben weiterläuft und der Perlinterpreter die END-Blöcke _auch_ ausführt (wie beim 'die'/'exit', dort werden auch DESTROY jedes Objektes aufgerufen). Es geht jedoch darum, dass der "Parent-Prozess", sprich das vom Kunden aufgerufene GUI Programm jene END-Blöcke _nicht_ aufruft und dies in einen separaten Prozess ausgelagert wird (btw. was ist eine session? - Klingt irgendwie nach Thread?). Dies erreicht man durch die C-Funktion POSIX::_exit(), dort wird selbst der Perlinterpreter abgewürgt.

      Viele Grüsse und Danke für deine Antwort.

      Philipp

      1. Hallo Philipp,

        use POSIX qw/setsid/;

        warum brauche ich den Session Identifier?

        Damit du den Child-Prozess aus der Prozess-Hierarchie herausloesen kannst.

        • In der Doku hab ich nix gefunden, warum ich den explizit angeben muss.

        Musst du nicht. Du kannst auch POSIX::setsid() schreiben -- nervt mich nur.
        Ich importiere lieber explizit.

        Kannst (oder wer anders) du es als fork/linux Experte erleutern?

        Was hat das damit zu tun?

        my $pid = fork;
        unless($pid) {
          die 'could not fork!' unless defined $pid;
          # process is no longer a child
          die 'could not remove session' unless setsid;
        }
        else {
          # main process ends
          exit 0;
        }

        Das kommt ja etwa auf das selbe raus, was ich getan hab, oder?

        Nein, ich trickse nicht so rum mit den END-Block-Geschichten :)

        Bist du dir sicher? - Das Problem mit exit(0) ist, dass der Prozess dann
        eben weiterläuft und der Perlinterpreter die END-Blöcke _auch_ ausführt

        Richtig, ja. Deshalb sollst du das ja auch nicht in die END- und
        DESTROY-Bloecke packen. Wenn du das nicht *immer* bei *jedem* Script-Ende
        ausfuehren willst, ist das eben das falsche Mittel.

        Gruesse,
         CK

    2. Halihallo Christian

      Ich hab mir deinen Vorschlag zu Herzen genommen: ;)

      use POSIX qw/setsid _exit/;

      my $pid = fork;
      unless($pid) {
        die 'could not fork!' unless defined $pid;
        # process is no longer a child
        open STDOUT, '/dev/null';
        die 'could not remove session' unless setsid;
      }
      else {
        # main process ends
        POSIX::_exit(0);
      }

      END {
         print 'test';
         open( F, '>>test.txt' );
         print F 'test'."\n";
         close F;
      }

      leider musste ich das POSIX::_exit stehen lassen, da, wie bereits gesagt, sonst die END-Blöcke und DESTROY's (gut, die wären ja OK) gleich in beiden Prozessen abgearbeitet. Das funktioniert so auch, wie erwartet (getestet auf cygwin).

      Dein Getrickse mit POSIX::_exit() ist da voellig ueberfluessig und verwirrt
      nur. Der 'Trick' ist hier recht einfach: der Child wird aus der Prozess-Gruppe
      herausgeloest und bekommt eine eigene Session.

      Also, wenn ich dich hier recht verstehe, dann erreichst du mit setsid eine komplette Trennung zwischen dem Parent und dem Child (das ist wohl mit Session gemeint), also kein Risiko auf Zombies, kein Risiko, dass der Parentprozess (GUI-Programm) durch den Child "aufgehalten" wird. Ist diese Aussage richtig?

      Viele Grüsse und Danke nochmals

      Philipp

      1. Hallo,

        Ich hab mir deinen Vorschlag zu Herzen genommen: ;)

        Nischt so ganz :)

        else {
          # main process ends
          POSIX::_exit(0);
        }

        Immer noch. Tss.
        Mensch, wenn du die END- und DESTROY-Bloecke nicht *jedesmal* ausfuehren
        willst, dann benutze sie nicht. Dann sind sie das falsche Mittel. Auf biegen
        und Brechen irgendwelche Features zu benutzen kann nicht Sinn und Zweck der
        Sache sein.

        Also, wenn ich dich hier recht verstehe, dann erreichst du mit setsid eine
        komplette Trennung zwischen dem Parent und dem Child (das ist wohl mit
        Session gemeint),

        Richtig.
        Unter UNIX sind Prozesse hierarchisch angeordnet. Wenn du ein Tool wie pstree
        hast, kannst du dir das wunderbar anschauen. Unter Linux koennte das z. B.
        so aussehen:

        INIT
         |
         |- login
         |    - bash
         |       - pstree
         |
         |- httpd
             - httpd[30]
             - httpd
                - perl
                   - perl

        wenn du jetzt im letzten Perl-Prozess (ein Child des ersteren Perl-Prozesses)
        ein setsid() aufrufst, sieht die Prozess-Hierarchie eben ganz anders aus:

        INIT
         |
         |- login
         |    - bash
         |       - pstree
         |
         |- httpd
         |   - httpd[30]
         |   - httpd
         |      - perl
         |- perl

        Das Perl-Programm hat eine eigene Session bekommen.

        also kein Risiko auf Zombies, kein Risiko, dass der Parentprozess
        (GUI-Programm) durch den Child "aufgehalten" wird. Ist diese Aussage
        richtig?

        Ja.

        Gruesse,
         CK

        1. Halihallo Christian

          else {
            # main process ends
            POSIX::_exit(0);
          }

          Immer noch. Tss.

          ich bin untröstlich ;-)

          Mensch, wenn du die END- und DESTROY-Bloecke nicht *jedesmal* ausfuehren
          willst, dann benutze sie nicht. Dann sind sie das falsche Mittel. Auf biegen
          und Brechen irgendwelche Features zu benutzen kann nicht Sinn und Zweck der
          Sache sein.

          Nun denn. Ich muss mich vielleicht etwas rechtfertigen:
          Ich verstehe deine Kritik zu schätzen und werde mich auch danach richten. Der Grund, warum ich überhaupt mit dem END Zeug angefangen habe, war der, dass einige Aufgaben einfach am Ende ausgeführt werden sollen. Das diese sehr viel Zeit in Anspruch nehmen würde und so die Interaktion zwischen Benutzer und Webapp beeinträchtigt, hätte ich nicht erwartet. Folglich habe ich nach einer einfachen Lösung gesucht, wie ich das auf die schnelle hinbiegen kann (ohne die Ferien auf's Spiel zu setzen) und bin auf POSIX::_exit gestossen. Mir ist das auch zuwider (ich kritisiere das selbe wie du) und werde das nachher auch ausbessern. Aber vorerst läuft das und ich hab nicht die Zeit dies auf eine akzeptable Lösung umzuprogrammieren.

          Also, wenn ich dich hier recht verstehe, dann erreichst du mit setsid eine
          komplette Trennung zwischen dem Parent und dem Child (das ist wohl mit
          Session gemeint),

          Unter UNIX sind Prozesse hierarchisch angeordnet. Wenn du ein Tool wie pstree
          hast, kannst du dir das wunderbar anschauen. Unter Linux koennte das z. B.
          so aussehen:

          [...]

          Aha. Das hat man eben davon, wenn man sich bisher nur an Win gewagt hat... Da gibt's sowas wie ein Taskmanager, wo alle Prozesse in einer linearen Liste aufgezeigt wird... M$ mässig eben... ;-)

          zum anderen Posting:

          • In der Doku hab ich nix gefunden, warum ich den explizit angeben muss.

          Musst du nicht. Du kannst auch POSIX::setsid() schreiben -- nervt mich nur.

          Ich importiere lieber explizit.

          Ja, das ist klar. Sorry, hab da was durcheinandergebracht.

          Bist du dir sicher? - Das Problem mit exit(0) ist, dass der Prozess dann
          eben weiterläuft und der Perlinterpreter die END-Blöcke _auch_ ausführt

          Richtig, ja. Deshalb sollst du das ja auch nicht in die END- und

          DESTROY-Bloecke packen. Wenn du das nicht *immer* bei *jedem* Script-Ende ausfuehren willst, ist das eben das falsche Mittel.

          s. oben. War von Anfang an eben so geplant, jedoch die Verzögerung des Outputs hat mir einen Strich durch die Rechnung gemacht. Werde das nach den Ferien umprogrammieren. Ein Modul, was die Automatisierungen ausführt (fork und eigene Session) und der Parent läuft ganz normal weiter. Gut, der END-Block wird bleiben (das macht ja auch Sinn), aber es wird kein _exit(0) mehr geben.

          ach ja:

          Kannst (oder wer anders) du es als fork/linux Experte erleutern?

          Was hat das damit zu tun?

          das es setsid in Windows nicht gibt (bzw. das ganze fork-setsid Gespann), bzw. der Unterschied nicht erkennbar ist, vielleicht? :-)

          Viele Grüsse und nochmals danke für deine Hilfe

          Philipp

          1. Taegelchen,

            Aha. Das hat man eben davon, wenn man sich bisher nur an Win gewagt hat...
            Da gibt's sowas wie ein Taskmanager, wo alle Prozesse in einer linearen
            Liste aufgezeigt wird... M$ mässig eben... ;-)

            Ich weiss nicht, wie Windows das handelt. Aber auch da muesste es eigentlich
            eine Hierarchie geben, denn auch da kann man angeblich Signale an eine ganze
            Gruppe von Prozessen schicken.

            Kannst (oder wer anders) du es als fork/linux Experte erleutern?

            Was hat das damit zu tun?

            das es setsid in Windows nicht gibt (bzw. das ganze fork-setsid Gespann), bzw.
            der Unterschied nicht erkennbar ist, vielleicht? :-)

            Ach so, du wolltest die Funktionsweise erklaert haben. Sag das doch :)

            Viele Grüsse und nochmals danke für deine Hilfe

            Es gibt nix zu danken.

            Gruesse,
             CK

            1. Taegelchen,

              Re: ;)

              Aha. Das hat man eben davon, wenn man sich bisher nur an Win gewagt hat...
              Da gibt's sowas wie ein Taskmanager, wo alle Prozesse in einer linearen
              Liste aufgezeigt wird... M$ mässig eben... ;-)

              Ich weiss nicht, wie Windows das handelt. Aber auch da muesste es eigentlich
              eine Hierarchie geben, denn auch da kann man angeblich Signale an eine ganze
              Gruppe von Prozessen schicken.

              Könnte mir gut vorstellen, das Billy das alles in die Registry schreibt :-)
              Nein, Spass bei Seite:
              Es muss implementiert sein, jeder Parentprozess muss seine Kinder kennen und umgekehrt wohl auch. Ansonsten wäre Win32::Process::Wait wohl nicht zu realisieren. Fakt bleibt jedoch, dass man sieht, dass das eigentlich kein gewolltes Feature von M$ ist, sondern implementiert wurde, weil es sein muss. Bei Unix scheint dies auch durchaus gewollt zu sein, eine Struktur in die Prozesshierarchie zu kriegen, wo M$ eben mehr auf Listen steht... Gut, das ist ja nicht allzuschwer. Es wird ja wohl in M$ eine Prozessliste im kernel geben, wo die einzelnen Prozesse (eben linear) aufgerührt sind; die Hierarchische Struktur entnimmt man dann den ParentPID Einträgen der einzelnen "Prozess-Objekte". Wobei ich mir gut vorstellen kann, dass dies in Unix gleich ist; so dass pstree einfach durch die Liste "browsed" und durch die ParentPID Einträge die hierarchische Struktur aufbaut. Aber davon versteh ich nicht viel (ach, ich bin ehrlich: gar nix ;-))...

              Kannst (oder wer anders) du es als fork/linux Experte erleutern?

              Was hat das damit zu tun?

              das es setsid in Windows nicht gibt (bzw. das ganze fork-setsid Gespann), bzw.
              der Unterschied nicht erkennbar ist, vielleicht? :-)

              Ach so, du wolltest die Funktionsweise erklaert haben. Sag das doch :)

              Ich bitte um Verzeihung ;-)

              Viele Grüsse

              Philipp