Fail: Timeout NEVER = ?
« | 08 Apr 2023 | »Ich mag Funktionen mit Timeouts, denn mit denen kann man “kalkulieren”, während andere ewig blockieren können.
Folglich baue ich auch selbst gerne solche ein.
Doch … was ist, wenn man unendlich auf ein Ereignis warten will?
Die kurze Antwort: Man kann zwei Funktionen für ein Feature implementieren. Eine mit Timeout und eine ohne. Aber das ist lästig.
An der Stelle tauchen dann (in vielen Frameworks) Konstanten auf, die die
Unendlichkeit repräsentieren.
Titel wie NOTIMEOUT
, INFINITE
, NEVER
und viele mehr sollen ausdrücken,
dass man nicht eine bestimmte Anzahl von Sekunden oder Millisekunden warten
soll, sondern so lange “blockieren” darf, bis die gewünschte Operation fertig
ist.
Solche Timeout-Konstanten können aber Wertebereiche repräsentieren, die sich mit anderen Zeitangaben überlappen können.
Ein Klassiker unter Windows ist INFINITE
welches als DWORD
Typ den
Wert 0xffffffff
trägt.
Die meisten Warteroutinen akzeptieren einen DWORD
als Millisekundenwert,
somit man maximal maximal 4 294 967 295
Millisekunden (50 Tage) auf ein
Ereignis warten kann.
Der INFINITE
Wert lässt das System allerdings ewig warten.
Hier kann man dann den Fehler machen und das unendliche Timeout durch eine Subtraktion per Überlauf auf diesen Wert bringen.
Ein anderer Klassiker ist die falsche Portierung dieses Wertes (z.B. auf Linux).
Es ist übrigens nicht lustig solche Fehler in anderen Programmen zu suchen, wenn die einzige Info ist:
Das Programm bleibt hängen.
-1 != NEVER
Ich bin dabei in meine eigene Falle getappt, als ich die DOS Keyboard
Warteroutinen erstellt habe. Ein leichtfertiges
await_and_read_kbd(ptr_char, -1)
ließ genau (unsigned int)-1
Millisekunden Zeit verstreichen.
Und auf einem 16-Bit System sind das 65 Sekunden.
Mein Problem lag in der Erwartungshaltung:
Warte unendlich lange auf Ereignis, brich bei Fehlern ab.
“Unendlich” bedeutet ohne Timeout, und der Timeout-Fehler nach
65 Sekunden brach dann unerwartet die Funktion ab.
Wenn man also schon ein NEVER
als Zeiteinheit zulässt, dann muss man
diesen Spezialfall auch ausprogrammieren.
wait, wait_for, wait_until
C++ löst dieses Problem aus meiner Sicht sehr schön, indem es nicht auf eine API mit magischen Konstanten setzt, sondern in vielen Bereichen 3 Warte-Varianten anbietet.
wait()
wartet unendlichwait_for()
wartet eine fixe Zeitspanne abwait_until()
wartet bis ein übergebener Zeitpunkt erreicht ist.
Eine solche Lösung schwebt mir daher in meinem C++-Layer auch vor.
Fazit
Man spielt nicht mit der Zeit, schon gar nicht mit der Unendlichkeit.
Tatsächlich sind mir Endlosschleifen wegen falscher Zeit und Warteroutinen schon öfter untergekommen. Die Fixes sind dann in wenigen Zeilen durchgeführt, aber das Auffinden solcher Schwachstellen ist mühsam.
Es macht also Sinn, sich vorher über eine “gute API” den Kopf zu zerbrechen und dieses Schema einheitlich projektweit umzusetzen, als auf Bugs und Inkonsistenzen zu warten.
Leider spielen da auch viele Hilfsbibliotheken nicht gut mit, die Warteroutinen auch alle etwas anders ausgestalten.