Obwohl das Programm jetzt eigentlich recht brauchbar aussieht, weist es noch einen echten Schwachpunkt auf. Dieser wird aber erst klar, wenn man im Detail versteht, wie die sichere Signal-Behandlung in Perl funktioniert. Dabei wird sich zeigen, dass sie für unseren Fall eigentlich nicht tauglich ist.
Welches Problem versuchen die sicheren Signale in Perl eigentlich zu lösen? Wenn ein Signal eintrifft, wird der Signal-Handler aufgerufen. Das kann zu einem beliebigen Zeitpunkt passieren. Wenn nun das Programm gerade dabei ist, bestimmte globale Variablen zu verändern, und der Signal-Handler mit denselben Variablen hantieren will, ist das Chaos vorprogrammiert.
Das Paradebeispiel hierfür ist
»malloc
«
. Das eigentliche Programm versucht gerade, über diese Funktion an mehr Speicher zu gelangen. Je nach Implementation verwaltet sie dazu mehrere globale Listen oder ähnliche Strukturen. Wenn nun aber der Signal-Handler auch Speicher braucht, ist genau diese Situation eingetreten. Sprachen wie Perl arbeiten sehr viel mit der Speicherverwaltung. Der Platz für Variablen in Perl wird von der Speicherverwaltung angefordert und auch wieder freigegeben. Daher ist es kaum möglich, einen Signal-Handler in Perl zu schreiben, der nicht mit globalen Strukturen arbeitet.
Für die sichere Signal-Behandlung verwaltet Perl nun intern eine Anzahl von Flags. Installiert ein Skript für ein Signal einen Handler, ruft der Interpreter nicht etwa die Perl-Funktion auf, sobald das Signal eintrifft. Der eigentliche Signal-Handler ist nämlich eine interne Funktion, die nur eines der Flags setzt. An geeigneten Stellen prüft der Perl-Interpreter dann mit Hilfe des Makros
»PERL_ASYNC_CHECK
«
, ob eines der Flags gesetzt ist und ruft gegebenenfalls den Signal-Handler in Perl auf.
Abbildung 5
stellt dies grafisch dar. Da der eigentliche Signal-Handler jetzt nur noch seine eigenen Datenstrukturen verändert, treten keine unerwarteten Programmabstürze mehr auf. In diesem Sinne ist die Signal-Behandlung sicher.
Leider kann dabei die zeitnahe Zustellung der Signale auf der Strecke bleiben. Das Problem resultiert aus einer Race Condition. Der
»can_read
«
-Aufruf in Zeile 26 ruft irgendwann den Perl-Befehl
»select
«
auf. Innerhalb dieses Befehls passiert auf C-Ebene ungefähr Folgendes:
PERL_ASYNC_CHECK; /* einige weitere C-Kommandos */ select(...); <-- Syscall
Es wird geprüft, ob Signale vorliegen. Dann folgt eine Reihe von Kommandos, um die Parameter des Perl-Befehls
»select
«
in solche zu übersetzen, die der Kernel-Aufruf
»select
«
versteht. Schließlich wird der Kernel aufgerufen und der Prozess blockiert.
Was passiert aber, wenn das Signal zwischen
»PERL_ASYNC_CHECK
«
und dem Kernel-Aufruf eintrifft? In dem Fall setzt der C-Level-Signal-Handler wie gewohnt sein Bit. Er kann jedoch nicht mehr verhindern, dass der Prozess im System-Aufruf blockiert. Das Signal wird also erst an den Perl-Handler zugestellt, wenn der System-Aufruf zurückkehrt. Für das CGI-Programm sieht es dann so aus, als gehe das SIGTERM verloren. Der Apache sendet kurz darauf das SIGKILL, und das CGI-Programm bricht ab. Der Datenbank-Prozess läuft jedoch weiter.