Overlapped IO mit Completion Ports
-
Guten Tag!
Also ich habe hier eine zugegebenermaßen improvisierte Situation, in der es zu zufälligen Abstürzen kommt, wenn man das Programm beendet, was natürlich nicht so schön ist. Das hat irgendwie damit zu tun, dass bei den Completion-Port-Threads noch OVERLAPPED-Pakete reinhuschen, wenn meine Objekte zumindest schon teilweise Zerstört sind.
Daher wollte ich jetzt einmal ein paar klärende Fragen stellen, sodass ich hinterher zu einer besseren Behandlung der Sache komme:Wenn ich CloseHandle auf ein Filehandle aufrufe, blockiert das solange, bis alle ausstehenden Overlapped-Pakete ausgeliefert wurden?
Ich kann mir das anders gar nicht vorstellen, weil das OS ja gar nicht weiß, was mit OVERLAPPED-Objekten geschehen soll, wenn niemand sie mehr abnimmt.Eine andere Sache: Ich habe mir eine OverlappedFile-Klasse geschrieben, die zum Lesen und Schreiben OVERLAPPED-Member hat, sodass ich nicht ständig neu allozieren muss. Nun hält diese Klasse aber auch das Handle auf die Datei. Sagen wir, das Objekt wird zerstört und ruft in Folge CloseHandle auf, welches wie oben beschrieben wartet, bis die letzte Overlapped-Operation durchgeführt wurde. Wenn das resultierende Overlapped-Ergebnis nun aber in einem anderen Thread behandelt wird, dann kann es ja durchaus vorkommen, dass das OVERLAPPED-Objekt ausgeliefert wird, aber das sie enthaltende Objekt (Eben vom Typ OverlappedFile) schon zerstört ist, oder nicht? Also wenn man das richtig machen will, darf man die Lebenszeiten der OVERLAPPED-Pakete gar nicht so stark an sonstige C++-Objektlebenszeiten binden oder muss irgendwie Wege finden, zu warten, bis auf die das letzte IO-Ergebnis behandelt wurde (und nicht nur die Dateioperation selber fertig ist). Gibt es hier "sichere" Wege, die ganzen Sachen beim Schließen und Zerstören zu synchronisieren?
Ich weiß, das klingt irgendwie alles wirr, aber es ist ja auch schon spät
-
Mal als kleiner Hinweis:
http://www.osronline.com/showthread.cfm?link=213975Siehe auch:
Canceling Pending I/O Operations
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363789Synchronous and Asynchronous I/O
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365683Ist eigentlich alles Dokumentiert, oder?
As previously stated, when working with an asynchronous handle, applications should use care when making determinations about when to free resources associated with a specified I/O operation on that handle. If the handle is deallocated prematurely, ReadFile or WriteFile may incorrectly report that the I/O operation is complete. Further, the WriteFile function will sometimes return TRUE with a GetLastError value of ERROR_SUCCESS, even though it is using an asynchronous handle (which can also return FALSE with ERROR_IO_PENDING). Programmers accustomed to synchronous I/O design will usually release data buffer resources at this point because TRUE and ERROR_SUCCESS signify the operation is complete. However, if I/O completion ports are being used with this asynchronous handle, a completion packet will also be sent even though the I/O operation completed immediately. In other words, if the application frees resources after WriteFile returns TRUE with ERROR_SUCCESS in addition to in the I/O completion port routine, it will have a double-free error condition. In this example, the recommendation would be to allow the completion port routine to be solely responsible for all freeing operations for such resources.
-
Bitte glaube mir, dass ich alle diese Dokumente bereits gelesen hatte, bevor ich schrieb.
Ich kann nicht einfach CancelIo benutzen, weil ich nicht weiß, in welchen Thread ich das letzte mal das Lesen beauftragt habe und CancelIoEx erst ab Vista unterstützt ist.
Auch habe ich keine Probleme mit dem double-delete, weil es eigentlich gar kein delete gibt, vor allem nicht an zwei Enden.Folgendes Szenario ist jetzt dasjenige, welches wohl das problematische ist bereitet:
Es ist noch eine IO-Op unterwegs, meine Klasse benutzt CloseHandle, CloseHandle blockiert bis die IO-Operation abgeschlossen wurde. IO-Operation wurde abgeschlossen, OVERLAPPED ist auf dem Weg zu einem IO-Completion-Thread und der Destruktor vom Datei-Objekt läuft nun. OVERLAPPED kommt danach beim IO-Completion-Thread an und ich habe den Salat, weil der OVERLAPPED-Zeiger nun ungültig ist. In dem OVERLAPPED hätte zudem noch Information bezüglich des Datei-Objekts gestanden, weshalb auch ein einfaches erzeugen des OVERLAPPED auf dem Heap und dem Löschen im IO-Completion-Thread nicht die 100%ige Lösung ist.So, wenn das OVERLAPPED nun auf dem Heap erzeuge, dann könnte ich es natürlich im IO-Completion-Port löschen. Das Datei-Objekt könnte im OVERLAPPED-Objekt signalisieren, dass es nicht mehr da ist und sich sofort löschen. Der IO-Completion-Port würde dann bei Ankunft einfach das OVERLAPPED löschen, statt es zu behandeln. Aber folgendes Problem: Wenn nun keine IO-Operation mehr abschließt, dann bekommt der IO-Completion-Thread auch keine Benachrichtigung mehr und ich habe ein Leak. Bei einigen File-Handle-Typen (COM-Port...) gibt es die Möglichkeit ein Time-Out für Operationen zu haben, sodass der IO-Completion-Thread auf jeden Fall noch benachrichtigt wird, aber das gibt es halt nich immer.
Dieses Problem habe ich speziell, weil es in dem Fall um eine Kommunikation geht, die endlos Daten bekommt und es nur vom Zufall abhängt, ob beim Zerstören noch ein Datenpaket übertragen wird oder aber nicht. Ich kann mir auch in diesem speziellen Fall behelfen, indem ich den Kommunikationspartner stumm schalte, aber diese Möglichkeit habe ich nun auch nicht immer.
-
Wie es schon überall steht: Wenn es sich nicht um Dateien handelt, dann hängt es vom Treiber ab, ob beim beenden gewartet wird oder nicht.
Aber es spielt auch keien Rolle. Du musst auf jeden Fall den von Dir vorgeschlagenen Weg machen und das Overlapped immer im Handler selber freigeben und dort schauen ob Dein eigentliches Objekt noch lebt...
Es kann dann zu Memmory leaks kommen, aber eben nur wenn der Treiber es nicht korrekt implementiert, oder?
-
Also mit nem Memory-Leak pro Abbruch könnte ich wahrscheinlich an sich leben, auch wenn es unschön ist. Aber Resource-Leaks kann ich nicht akzeptieren. Das heißt, ich kann dann auch weder das Event-Handle im OVERLAPPED benutzen, noch irgendwas anderes "besessenes" hinzufügen. Damit fällt Synchronisation zwischen IO-Completion-Thread und dem Hauptthread in Bezug auf Dateioperationen irgendwie komplett flach. Also keine Critical-Section, um den File-Pointer im OVERLAPPED zurückzusetzen (will meinen, der File-Pointer könnte auf 0 zurückgesetzt werden, wenn der Thread schon annimmt, dass die Datei noch offen ist. -.-
-
Ich verstehe das Problem nicht.
Du musst doch einfach nur sicherstellen dass der Completion-Handler fehlerfrei laufen kann, auch wenn das Objekt zuvor schon gelöscht wurde.
Dazu kann es nötig sein bestimmte Resourcen in einem Objekt unterzubringen das erst gelöscht wird nachdem der letzte Completion-Handler gelaufen ist (-> ref-counting für ausstehende Completion-Callbacks).
Oder du machst gleich Pimpl und lässt das gamze Impl Objekt weiterleben bis der letzte Handler gelaufen ist.
Gibt viele Möglichkeiten, und keine davon ist komplett trivial, aber so schwer dass man es als halbwegs fähiger Programmierer nicht hinbekommt ist es auch wieder nicht.
-
Naja, dann bin ich halt kein halbwegs fähiger Programmierer
Ich sage ja nicht, dass es nicht unlösbar ist, aber ich habe einige Komplikationen durch Anforderungen, die ich stelle, die mir das ganze aufwendiger gestalten, als ich es gerne gehabt hätte.
- Dieses spezielle Overlapped wird immer wiederverwendet, anstatt für jede Operation ein neues zu erzeugen (Weil immer nur eine Lese-Operation gleichzeitig stattfindet
- Ich möchte den Completion-Handler auf dem Overlapped Lock-Free gestalten, weil dieser Lock überhaupt nur beim Abbau benötigt wird (Um den Objektzeiger zurückzusetzen, weil der Handler vielleicht gerade ein Datenpaket abhandelt, was durchaus länger dauern kann, aber wiederum auch nocht so lange, dass ich denke, man müsste parallel schon die nächste Lese-Operation starten bevor man anfängt, die letzten Daten abzuarbeiten).
- Im Completion-Handler kann und wird direkt eine neue Datei-Operation durchgeführt (Endlos-Stream von Daten).
Und als Beobachtung, die ich noch nicht 100%ig verifizieren konnte, aber meine deren Auswirkungen schon gespürt zu haben:
4) Es werden manchmal die Pakete gar nicht mehr ausgeliefert
5) Ich bin das erste Mal so richtig auf Threads unterwegs und reibe mich noch mit Details, bis ich die passenden Gedankengänge intus habe.Ich versuche mich gerade an einer alternativen Implementierung, mal sehen ob das funktioniert.
-
So!
Also ich habe jetzt im OVERLAPPED die flags "flying" und "stoprequest" sowie ein Event, das ausgelöst wird, wenn der IO-Completion-Handler solch ein OVERLAPPED in die Hände bekommt und "stoprequest" true ist.
Wenn man die Datei nun schließt, wird, falls "flying" true ist, "stoprequest" auf true gesetzt und auf das event gewartet.
"flying" wird überhaupt nur dann auf false gesetzt, wenn der IO-Completion-Handler ein Paket abgearbeitet hat und keine neue Lese-Operation gestartet wurde. Ich verlasse mich damit darauf, dass jede begonnene Datei-Operation auf jeden Fall zu einem Completion-Port-Paket führt.Ich konnte mir keine Situation vorstellen, in der das nicht klappt und mein Testprogramm läuft jetzt eine halbe Stunde durch, erzeugt neue Verbindungen und schließt sie wieder. (Edit: Wobei, die Konstellation könnte so ungünstig sein, dass "stoprequest" gesetzt wird, wenn der Completion-Thread gerade flying auf false setzt -.-)
Aber jetzt tue ich ja genau nicht das, was von euch und der MSDN empfohlen wurde: Der IO-Completion-Thread ist dafür zuständig, das OVERLAPPED auszulöschen (Im Prinzip könnte er das auch, aber so wie ich die Sache handhabe macht es ja gar keinen Unterschied mehr, weil beim Destruktor vom Datei-Objekt auf jeden Fall kein IO-Paket mehr unterwegs ist).