Christian Kruse: "Too many open files" ?

Beitrag lesen

Moin!

Naja, so leider finde ich das gar nicht. Es erfordert halt einfach eine Konsequent saubere Programmierung -- nichts anderes als Threadsafeness (geiles Wort, leider bestimmt falsch ;-)

Ich kenne auch nichts anderes als "threadsafe" (Man könnte es auch übersetzen, aber "einfädelsicher"? ;-)

Naja, aber threadsafeness? Ich bin fast sicher, es muss threadsafety heissen ;-)

Aber (für's Archiv) was genau heißt den Threadsafe in Perl? Was gibt es da zu beachten?

Naja. Das ist bei mod_perl etwas komplizierter. Generell kann man IMHO sagen:

  • globale Variablen immer initialisieren! Man kann nie wissen, was   da nicht schon drin steht.

  • Niemals Konstrukte wie

use strict;

my $counter = 0;

addone();     addone();

sub addone {       $counter++;     }

Benutzen. In 'sub addone' ist bei mod_perl nicht die lexikalisierte   Variable '$counter' gemeint, sondern die globale Variable   '$counter'. Das haengt damit zusammen, wie mod_perl ein Script   sieht. Denn bevor der Code ausgefuehrt wird, macht mod_perl daraus

package Apache::ROOT::verzeichnis::zum::script;    use Apache qw(exit);    sub handler {       use strict;

my $counter = 0;

addone();       addone();

sub addone {         $counter++;       }    }

Aus jedem Script wird ein Modul mit nur einer Sub (handler).   Daraus folgt, dass man an Subroutinen grundsaetzlich alles   uebergeben sollte, als Funktionsargumente. Ausserdem muss man   natuerlich grundsaetzlich alle Variablen initialisieren!

  • Dann gilt: Scripte, die auf mod_perl laufen sollen, grundsaetzlich   sehr gut durchtesten. Es kann sehr gut sein, dass ein   Fehlerverhalten erst nach einer gewissen Zeit auftritt. Das liegt   daran, dass der Apache forked: in jedem Apache-Prozess wird das   Script neu geladen. Und weil der Apache den Child, der den Request   bearbeiten soll, nach dem Round-Robin-Verfahren aussucht, kann es   durchaus sein, dass ein Fehlverhalten erst nach 40 oder 50   Testlaeufen zum Vorschein kommt. Ich wuerde empfehlen, bei dem   eigenen Testserver die Anzahl der Childs auf nur sehr wenige   einzuschraenken. Die Alternative ist natuerlich, dem Apachen   komplett zu verbieten zu forken. Das erreicht man, indem man ihn   mit 'httpd -x' aufruft.

  • Man sollte niemals $^W abschalten. mod_perl schreibt alle Warnings   ins Errorlog, ein unersetzbares Werkzeug.

  • Man sollte aufpassen mit exit()-Aufrufen. Das kann ins Auge gehen   und dazu fuehren, dass der Apache-Prozess beendet wird. Lieber   Apache::exit() benutzen.

  • Ich kann es nie oft genug wiederholen: vorsicht mit globalen   Variablen! Globale Variablen erhalten ihren Wert fuer die gesamte   Lebensdauer des Apache-Kind-Prozesses! Eine typische Falle waere   beispielsweise

use strict;     use vars qw($user);

use CGI;     my $cgi = new CGI;

$user = 1 if $cgi->param('username') eq 'admin' and $cgi->param('password') eq 'strenggeheim';

Wenn hier die Authentifizierung fehl schlaegt, ist $user trotzdem   noch 1! Ein moeglicher Loesungsansatz waere:

$user = $cgi->param('username') eq 'admin' && $cgi->param('password') eq 'strenggeheim' ? 1 : 0;

  • Vorsicht mit precompiled regular expressions! Wenn man z. B.

$pattern = '^blub$'; # $pattern wird normal dynamisch gefuellt     foreach(@list) {       print if /$pattern/o;     }

benutzt, dann ist das zwar uU ein ziemlicher Performance-Gewinn,   aber das heisst auch, dass die Pattern sich nicht aendert, bis der   Apache-Child beendet wird. Das kann natuerlich zu ziemlich   fiddeligen Bugs fuehren... Eine moegliche Loesung ist das folgende   Code-Stueck:

$pattern = '^blub$';     eval q{       foreach(@list) {         print if /$pattern/o;       }     };

Allerdings muss man schon die ganze Schleife in das eval q{}   schreiben -- sonst kann man das 'o' bei /o genau so gut   weglassen ;-) Denn dann wuerde die RegEx trotzdem jedesmal neu   kompiliert. Wem eval() nicht zusagt, der kann sich zunutze machen,   dass Perl Regexe cached. Die folgende Loesung wuerde genau so   laufen:

$pat = '^blub$';     'blub' =~ /$pat/; #1     foreach(@list) {       print if //;     }

Bei dieser Loesung ist wichtig, dass der mit '#1' titulierte RegEx   in jedem Fall matcht! Sonst wird die RegEx nicht gecached. Das   kann man btw. auf mehrere Arten erreichen: wenn man sicherstellen   kann, dass $pat keine Meta-Zeichen enthaelt (wie z. B. '.+' oder   '*' oder so), dann wird das hier immer wahr sein:

$pat =~ /\Q$path\E/;

Ansonsten muss man tricksen:

"\380" =~ /$pat|^\380/;

  • Wichtig ist auch, daran zu denken, dass Scripte nicht im Namespace   'main' laufen, sondern in einem eigenen Namespace. Das haengt, wie   weiter oben schon erwaehnt, damit zusammen, dass mod_perl aus   Scripten ein Modul macht.

  • Man kann @INC zur Laufzeit nicht mehr aendern! mod_perl ist ein   laufender Apache-Prozess, da kann man @INC nicht mehr veraendern.   Um @INC zu beeinflussen, muss man das vorher machen. Z. B. ueber   die Konfigurations-Direktive

PerlSetEnv PERL5LIB /home/httpd/perl

  • Module werde nicht neu geladen. Das heisst, wenn das Script das   erste mal laeuft, wird das Modul frisch geladen. Beim zweiten mal   nicht mehr. Um das zu erreichen, muss man...

- den Server neu starten.   - Apache::StatINC benutzen. Gilt nur fuer den Developement-Prozess!     Danach sollte man das lassen, um Performance zu sparen.     Apache::StatINC benutzt man so:

PerlModule Apache::StatINC        PerlInitHandler Apache::StatINC

- Apache::Reload benutzen. AFAIK ist das eine Weiterentwicklung     von Apache::StatINC und wird so benutzt:

PerlInitHandler Apache::Reload

Jetzt gibt es zwei Moeglichkeiten. Die erste ist das implizite     registrieren von Modulen. Das erfordert eine weitere     Konfigurations-Direktive:

PerlSetVar ReloadAll Off

und in jedem Modul, dass neu geladen werden soll, ein

use Apache::Reload;

Die zweite Moeglichkeit waere ein explizites registrieren von     Modulen, die neu geladen werden sollen. Das geht nur ueber     Konfigurations-Direktiven:

PerlInitHandler Apache::Reload       PerlSetVar ReloadModules "My::Foo My::Bar Foo::Bar::Test::*"

Richtig, hier sind auch Wildcards erlaubt :-)

- Eine Touch-Datei definieren. Man kann mit mod_perl eine Datei     definieren, auf die ein stat() gemacht wird. Wenn die Access-Time     sich geaendert hat, dann werden die Module neu geladen. Das geht     auch mit einer Konfigurations-Direktive:

PerlSetVar ReloadTouchFile /tmp/reload_modules

  • Man sollte mit den Modulen aufpassen. Das beste Beispiel ist hier   CGI.pm: oft benutzt man (ich selber auch) etwas wie

use CGI qw/param/;

Das ist bei mod_perl ziemlich boese. CGI.pm erstellt intern ein   eigenes Objekt, um die CGI-Paramter zu speichern. Das wuerde bei   der Benutzung von param() jetzt natuerlich boese auf uns zurueck   fallen: wir haetten die alten Parameter. Deshalb, bei   mod_perl-Scripten immer ein neues Objekt erstellen. Oder auch   Konfig-Module: ich hab schon relativ haeufig etwas wie

package MyConfig;

use strict;     use vars qw(%conf @EXPORT_OK @INC);

@INC = qw(Exporter);     @EXPORT_OK = qw(%conf);

%conf = (       Config => {         containing => 'many',         entries => 'sic!'       }     );

Das geht hier natuerlich so nicht, aus bekannten Gruenden. Loesung   waere dafuer vielleicht sowas wie

PerlHandler "sub { do 'MyConfig.pm'; }"

  • mod_perl-Scripte koennen keine END und DATA Sektionen   enthalten, weil das Script in ein Modul umgewandelt wird.

  • die() verhaelt sich nicht wie gewohnt. mod_perl setzt einen   Signal-Handler auf SIG__DIE__, also bitte kein use CGI::Carp   qw/fatalsToBrowser/ benutzen!

  • print() und printf() verhalten sich nicht wie gewohnt. Man muss   CGI::print benutzen, also

my $rq = new CGI;     $rq->print 'text';

  • END{} und BEGIN{} Bloecke verhalten sich nicht normal.

  • Die SheBang-Zeile hat keinen Einfluss mehr. Wenn man command line   switches setzen will, dann muss man das entweder mit den   aequivalenten Perl-Variablen (z. B. $^W fuer '-w') oder mit einer   Konfigurations-Option (z. B. PerlWarn On).

Puh. Mehr faellt mir jetzt nicht ein, aber ich bin sicher, da gibt es noch mehr zu beachten.

Zumindest die, die mir so unter die Finger gekommen sind.

Meiner leider nicht.

Wie sieht es denn da aus?

Da wird Perl ganz normal als CGI-Prozess benutzt :-)

Das kann bei den Default(?)grenzen von 1024 aber schnell mal passieren.

Naja, also eigentlich ja nicht. Und IMHO liegen die Grenzen auch hoeher, aber das ist natuerlich in hoechstem Masse systemabhaengig.

Nein, gottseidank nicht, sie sind einstellungsabhängig.

Na, das ist in diesem Fall doch dasselbe :-)

Ach, ich weiss auch nicht. Der ISP war anfangs recht gut, ich hatte sehr viele Freiheiten. Aber jetzt... ich hab nichtmal mehr ein eigenes Errorlog. Und dann

Noch nicht einmal einen Errorlog? Na, das ist aber ziemlich hart!

Ja. Sagte ich ja. Und vom Support-Team kommen nur dumme Kommentare wie 'Leider koennen wir Ihnen kein Errorlog zur Verfuegung stellen'. Naja, wird Zeit, dass ich (wieder mal) umziehe :-)

bezahle ich auch noch einen ziemlich teuren Preis dafuer. Der ISP ist all- -- den Rest musst du dir selber suchen ;-))

"all-exclusive" ? >;->

hehe fast ;-))

Wenn's nur nicht immer so ein Aufwand wäre, nicht? KK stellen, neuen Hoster suchen, da alles finden und wahrscheinlich auch noch dies und das portieren usw.

Jo... Das anpassen der Scripte ist das muehsamste. Ueberall Pfadangaben aendern.

Gruesse,  CK