Muß ein API doppeltes Schließen eines HANDLEs oder Verwendung eines ungültigen HANDLEs aushalten?
Das war gestern die Frage in Mission Control und SFRs Standpunkt war: "Ja. auf jeden Fall!". Mein Standpunkt war: "Well, it depends." SFR schreibt nämlich gerade superfiesen Unittesting-Code und ist dabei einigen Läßlichkeiten in einem API von DEI auf die Spur gekommen. SFR hat nämlich ein HANDLE des APIs zweimal geschlossen und hat damit gerechnet, daß die Funktion zum Schließen beim zweiten Mal einen ordentlichen Fehlerrückgabecode liefert. Tatsächlich ist jedoch der Prozeß gecrasht. Jedenfalls war's so ähnlich. Bei den nachfolgenden Überlegungen gehe ich immer davon aus, daß das API in Form einer DLL implementiert ist und nicht als individuelle Source Files.
Wenn man solch einen Fehler *immer* graceful abhandeln will, muß man zu einigen Maßnahmen greifen, die vielleicht nicht unbedingt immer so schön sind. Was man braucht, ist dann immer ein Set (wenn man in STL denkt), das die aktuell für den Prozeß gültigen HANDLEs beinhaltet und jedes API, das ein HANDLE entgegennimmt, muß das HANDLE am Anfang prüfen und garantieren, daß im weiteren Verlauf der Codeausführung im API das HANDLE auch immer gültig bleibt, denn es könnte ja sein, daß unmittelbar nach der Gültigkeitsprüfung der aktuelle Thread preempted wird und ein nebenläufiger Thread das HANDLE schließt. Wenn dann der Thread im API wieder eine Zeitscheibe bekommt und weiter mit dem HANDLE operiert, kann es ja dann genauso scheppern. Die andere Variante wäre, daß man sich innerhalb der Prüfung, die natürlich von einer critsec geschützt sein muß, die threads gegenseitig ausschließt, alle relevanten Daten für den weiteren Verlauf des API-Codes kopiert und dann nur noch mit Kopien arbeitet.
All das erfordert aufwendige Synchronisationsmechanismen und nach meinem unmaßgeblichen Dafürhalten auch eine per-Prozeß-Initialisierungsroutine, die das prozeßweite HANDLE-Set und seine Synchronisationsobjekte aufsetzt, gepaart natürlich mit einer prozeßweiten Deinitialisierungsroutine. Denn wir benutzen ja keine globalen Objekte für sowas, die wir von der runtime Erzeugen lassen, oder? Doch? Und einen weiteren Effekt hat man dadurch: Alle APIs müssen zumindest eine zeitlang durch das prozeßweite HANDLE-Set durchtunneln, wodurch sich ein "Lock Convoy" ("Boxcar Problem") ergeben kann. Die Performance wird also zumindest nicht gesteigert durch sowas.
Es gibt aber auch einen psychologischen Aspekt des Problems: Eigentlich ist doppeltes Schließen eines HANDLEs oder ein Operieren mit ungültigen HANDLEs ein Programmierfehler, daran gibt es nichts zu deuteln. Ein Bruder-Leichtfuß-Programmierer, der ein API benutzt, tendiert schon auch mal dazu, Fehlercodes von APIs zu ignorieren und einfach nicht auszuwerten. Er wird also eventuell nie im Leben das doppelte Schließen des HANDLEs bemerken und vielleicht noch andere "silly things" (O-Ton SBRYANT) tun wenn er ungültige HANDLEs verwendet. Crasht hingegen sein Programm an dieser Stelle, kann er nicht umhin, sich mit dem Programmierfehler zu befassen. Ein super-graceful API kann also durchaus Fehler des API-Anwenders vertuschen.
Eine Variante des Themas wäre, daß das API nur die Verwendung von HANDLEs gestattet, die auch in dem Thread erzeugt wurden, wo sie verwendet werden, d.h. ich darf ein HANDLE nicht in einen anderen Thread weiterreichen. Damit hätte man unter dem Preis dieser Limitation das Boxcar-Problem beseitigt. Das setzt dann aber ein per-thread-HANDLE-Set, einen per-Thread-Initialisierer/Deinitialisierer und einen TLS-Slot mehr pro-Prozess voraus und TLS-Slots sind knappe prozeßweite Ressourcen. There ain't no such thing as a free meal.
Drum bin ich der Meinung, daß man die Frage im Subject dieses Blogposts für jedes API im Einzelfall prüfen und festlegen muß, es aber eine pauschale Antwort nicht geben kann.
(Edited: Fixed Typos)
Trackback address for this post
2 comments

Nach etwas grübeln über diesen Blog bin ich zum Schluss gekommen, dsa die per Thread-Handle-Varianten mit TLS Erfolg verspricht. Damit kann ich sichern das ein Handle wirklich nur in einem Thread gültig ist (und das wollte ich auch) und haben einen Lock-Convoi von 0. Jetzt kommt nur die Raketen-Wissenschaft an der Sache. Ich benötige ein Set von APIs welches ein Handle von Thread A vorbereitet für die Übergabe an Thread B und gewolltest Kopieren zwischen Threads zu ermöglichen.
Ich werde das mal am Wochenende Probieren und über die aufgekommenen Schwierigkeiten berichten.

Comments are closed for this post.