Fragen? Antworten! Siehe auch: Alternativlos
Es handelt sich um einen Bug in der Logik von libowfat. Ich habe einen Fix eingecheckt. Mal gucken, ob das hilft.
libowfat stellt ja ein API zur Verfügung, um sich Events auf Sockets geben zu lassen. Das läuft im Wesentlichen so:
io_fd(3); // ich bin an deskriptor 3 interessiert io_wantread(3); // und zwar will ich lesen io_wait(); // warten, bis ein angemeldetes Event eintritt while ((fd = io_canread())) { // alle read events durchlaufen ... } while ((fd = io_canwrite())) { // alle write events durchlaufen ... }
Leider ergibt sich aus dieser einfachen API hinter den Kulissen eine bemerkenswerte Komplexität. io_wait ruft ein Low-Level-API des Betriebssystems auf, das (hoffentlich!) mehr als ein Event auf einmal melden kann.
io_wait hat pro mit io_fd angemeldetem Deskriptor ein bisschen State, und in dem State gibt es next-Membervariablen, einmal für den nächsten Deskriptor mit read-Event und einmal für den nächsten Deskriptor mit write-Event. Diese Liste traversieren io_canread und io_canwrite dann.
Nehmen wir mal an, jemand trägt auf einen Deskriptor sowohl Lese- als auch Schreib-Interesse ein. Dann kommen auf denselben Deskriptor gleichzeitig Lese- und Schreib-Events rein. Bei der Behandlung des ersten davon passiert ein Fehler.
Der Code schließt den Deskriptor.
Aber der State zu dem Deskriptor ist noch in der verketteten Liste für den anderen Event-Typ eingetragen!
Klingt jetzt nicht so schlimm. Der andere Event-Handler macht dann halt read oder write und kriegt einen Fehler, weil der Deskriptor geschlossen ist.
Leider kann aber der read-Event auch auf den Listen-Socket sein, und der Handler dafür ruft accept() auf, und accept() gibt einen neuen Deskriptor zurück. Das könnte derselbe sein, auf den in der Datenstruktur noch ein Write-Event eingetragen ist!
Um dieses Szenario zu verhindern, hat libowfat ein io_close(), das man statt close() aufruft. Wenn der sieht, dass da noch ein Event offen ist, dann markiert er den Deskriptor als geschlossen aber schließt ihn nicht wirklich.
Jetzt will man diese halboffenen Deskriptoren aber auch mal irgendwann loswerden. Dafür gibt es zwei Stellen, die sich anbieten.
Erstens: In io_canread und io_canwrite. Die müssen ja sowieso checken, ob der Deskriptor geschlossen wurde, und das Event aus der Liste dann verwerfen. Die könnten dann auch den Deskriptor ganz schließen.
Zweitens: In io_wait. Das ruft man ja erst wieder auf, nachdem man alle ausstehenden Events abgearbeitet hat. Der könnte dann alle als geschlossen markierten Deskriptoren final schließen.
Ich hatte Code in beiden drin, und der Bug ist glaube ich, dass die sich gestört haben. Mal laufen lassen und gucken, ob es das war.