Die Ausführungsdauer eines Programms oder einer Routine mit alarm() begrenzen

Veröffentlicht von Thomas Fahle am (Permalink)

Hinweis: alarm() steht leider nicht auf allen Betriebssystemen zur Verfügung.

  1. Fall 1: Einfache Notbremse:
    Sie begrenzen die Laufzeit des gesamten Programms mit alarm(x). Das Programm wird nach spätestens x Sekunden kompromisslos beendet.
  2. Fall 2: Sie müssen Aufräumarbeiten im Fehlerfall ausführen:
    1. Setzen Sie einen eigenen SIGALRM-Handler mit einem Unterprogramm. Dieses stirbt mit die("Ihre ganz spezielle Fehlermeldung"), falls ein entsprechendes Signal eintrifft.
    2. Setzen Sie den Timer mit alarm().
    3. Führen Sie die Operation, das Unterprogramm usw. einschliesslich Timer in einem eval-Block {}; aus.
    4. Schreiben Sie geeignete Routinen für den Fehlerfall.

Beispiel Fall 1: Einfache Notbremse

#!/usr/bin/perl
alarm(5);  # Höchstens 5 Sekunden

sleep(6);  # "Langzeitoperation"

liefert "Alarm clock" auf der Console, d.h. das gesamte Programm wird beendet.

Beispiel Fall 2: Aufräumarbeiten im Fehlerfall

#!/usr/bin/perl
use warnings;
use strict;

	# Handler für SIGALRM und Unterprogramm für den Fall der Zeitüberschreitung
	# Ich setze $SIG{ALRM} stets in den eval()-Block.
	# Varianten mit globalen Signalhandlern und Signalhandlern
	# in Unterprogrammen habe ich auch probiert.
	# Es funktioniert auf belasteten Maschinen nicht 
	# zufriedenstellend und manchmal gar nicht.

	# eval-block	 
eval {
	local $SIG{ALRM} = sub { die "Zeitlimit überschritten $!"};

                        # alarm(x) muss in den eval()-Block		
	alarm(10);	# Maximale Zeit: 9 bis 10 Sekunden
	
	&mach_was();	# Kritische Operation
	
	alarm(0);	# alarm zurücksetzen

}; ## end of eval

	# Irgendetwas schiefgelaufen?
if ($@) {
		# Zeitlimit ueberschritten?
		# Stets patternmatching verwenden,
		# nie eq, da $@ in etwa so aussieht:
		# "Zeitlimit überschritten! died at xxx.pl line xxx"
	if ($@ =~ /Zeitlimit/) {
        		# Fehlerbehandlung
		&Behandle_Fehler_aus_Zeitueberschreitung();

	} else {
		# Irgendetwas anderes in eval ist schiefgelaufen
			# alarm zurücksetzen, der läuft noch
		alarm(0);
		
			# Fehlerbehandlung
		&Behandle_sonstigen_Fehler();		
	}

        # Alles hat geklappt
} else {

        &Mach_weiter();
}

exit;

#############################################################
sub Behandle_Fehler_aus_Zeitueberschreitung {
		# Fehlermeldung ausgeben
	print STDERR "Gotcha! Du warst zu langsam, Baby.\n";
	# your code ...
        exit;
}
#############################################################
sub Behandle_sonstigen_Fehler {
        	# Fehlermeldung ausgeben
	print STDERR "$@\n";
        # your code ... 
        exit;
}
#############################################################

Erläuterungen:

Die Funktion alarm(x) sendet ein SIGALRM-Signal nach x Sekunden. Jeder Aufruf deaktiviert den vorhergehenden Timer, mit alarm(0) kann der Timer deaktiviert werden. Rückgabewert ist die verbleibende Zeit des vorhergehenden Timers.

Verwenden Sie genügend grosse Zeitspannen.
Auf manchen Systemen wird das Alarm-Signal jeweils zu Beginn einer Sekunde ausgeführt. Ein alarm(1) könnte also sofort ausgeführt werden, bevor Ihr Unterprogramm überhaupt etwas macht. Ein alarm(3) würde dann in den nächsten 2 bis 3 Sekunden ausgeführt, ein alarm(10) in den nächsten 9 bis 10 Sekunden.

Messen Sie vorher unter Praxisbedingungen die Laufzeit und legen Sie dann eine geeignete Zeitspanne fest.

Für Zeitmessungen steht Ihnen in Perl (times)[0] oder das Standardmodul Benchmark zur Verfügung. Wenn Sie ein UNIX-System verwenden, können Sie auch das time <Programm> verwenden.

Wenn Sie eine genauere Auflösung benötigen, müssen Sie auf Betriebssystemfunktionen oder auf Time::HiRes zurückgreifen.

Beachten Sie, dass eval {} evt. nur einmal kompiliert wird.

Siehe auch:

Weitere Posts