RAII + Multithread
-
Nutze doch zum Testen eine Klasse, die dir den Destruktoraufruf via cerr oder so protokolliert, dann siehst du es.
-
Zwischen Allokation und und dem schieben in den unique_ptr darf keine Exception fliegen. Das mag offensichtlich klingen, ist aber oft nicht ganz einfach zu garantieren. Du kannst auch einfach direkt einen unique_ptr nehmen, dann release() nutzen um ihn zu übergeben. (move sollte da leider zu umständlich sein, ändert sich wohl mit C++14.) So ist die "Gefahrenzone" relativ dünn.
Edit: Besonders darauf achten, dass wenn beim Erstellen des Threads eine Exception fliegen kann, du die manuell fangen und den Speicher freigeben musst!
-
Ich habe mir eure Ratschläge mal zu Herzen genommen und nachgeschaut.
> Ev. kannst du ein kleines Bsp. Programm machen, welches den Fehler demonstriert?
Der Code gehört leider zu 'nem recht komplexen Multithread-Konglomerat in einem Server. Sehr schwer das zu minimalisieren...
> Nutze doch zum Testen eine Klasse, die dir den Destruktoraufruf via cerr oder so protokolliert, dann siehst du es.
Dafür sind das zu viele Objekte.
> Zwischen Allokation und und dem schieben in den unique_ptr darf keine Exception fliegen.
Deshalb habe ich in weiser Vorausicht ein catch(...) vor allen anderen Catches eingefügt (der weiter wirft), um dort den Speicher zu löschen. Damit sollte ich die kritische Phase doch 100%tig umschifft haben, oder?
> Edit: Besonders darauf achten, dass wenn beim Erstellen des Threads eine Exception fliegen kann, du die manuell fangen und den Speicher freigeben musst!
Das kann mir zum Glück nicht passieren, da das alles schon Threads aus laufenden Threadpools sind.
Ich werde mal konkreter:
Ich habe einen etwas größeren Server gebaut, den ich nun mit der LOIC (Low Orbit Ion Canon, das einfachste Stress-Tool, was mir einfiel) ausprobiere. Dabei habe ich festgestellt, dass mein RAM sich bis zur Grenze auffüllt und das gesamte System lahm legt (ist zwar etwas Windows-spezifisch, aber sollte nicht vorher mal ein operator new eine Exception werfen?)
Nun habe ich VLD bemüht, der 242 Leaks findet (beim Leerlauf). Dummerweise findet der auch genauso viele Leaks, wenn ich die LOIC auf meinen Server loslasse, also wird der Speicher scheinbar korrekt freigegeben. Die Leaks, die der VLD ausspuckt, haben solche (oder so ähnliche) Callstacks:Call Stack: f:\dd\vctools\crt_bld\self_64_amd64\crt\src\stdenvp.c (127): DatabaseProxyServer.exe!_setenvp + 0x27 bytes f:\dd\vctools\crt_bld\self_64_amd64\crt\src\crt0.c (224): DatabaseProxyServer.exe!__tmainCRTStartup + 0x5 bytes f:\dd\vctools\crt_bld\self_64_amd64\crt\src\crt0.c (164): DatabaseProxyServer.exe!mainCRTStartup 0x00000000772B652D (File and line number not available): kernel32.dll!BaseThreadInitThunk + 0xD bytes 0x00000000774EC521 (File and line number not available): ntdll.dll!RtlUserThreadStart + 0x21 bytes
Da kommt nicht einmal ein Modul von mir vor. Sind das Falschmeldungen oder Memory-Leaks in den Implementierungen?
Wenn ich absichtlich Memory-Leaks einbaue, findet der VLD auch welche, je mehr, desto länger ich den Server laufen lasse (wie erwartet). Dennoch steigt der Speicherverbrauch stetig ohne die Leaks. So ein mysteriöser Speicherverbauchs-Anstieg ist mir noch nie vorgekommen. Ich kann auch ausschließen, dass irgendwelche STL-Container aufgefüllt werden, die werden korrekt aufgeräumt und sind immer recht leer.
-
Low Orbit Ion Cannon (LOIC, engl. „Ionenkanone in niedriger Umlaufbahn“) ist eine Lasttest-Anwendung für Netzwerke. Sie erzeugt eine hohe Belastung beim Zielrechner, dessen Verhalten dann bis hin zum Versagen beobachtet werden kann.
Die Anwendung legt es also drauf an den Server platt zu kriegen, bau doch ein Verbindungslimit ein sonst ist es ja klar das es irgendwann platzt.
-
> Die Anwendung legt es also drauf an den Server platt zu kriegen,
Allerdings, alleine der Besitz ist im UK strafbar. Deshalb halte ich einen Test mit diesem (recht aktuellen) Tool für realistisch.
> bau doch ein Verbindungslimit ein sonst ist es ja klar das es irgendwann platzt.
So klar ist mir das nicht. Das ganze läuft ja so ab:
Client verbindet sich und sendet Daten (so viele wie möglich)
Server fängt an Daten zu verarbeiten und wird die Verbindung trennen, sobald er feststellt, dass die Daten falsch sind. An der Stelle (so meine ich) kann der Server doch 100% der Resourcen, die er beim Verarbeiten und Halten der Socket-Strukturen aufwändet, wieder freigeben.
VLD bestätigt mir, dass keine (zusätzlichen, zeitabhängigen) Leaks auftreten. Alle Resourcen für eine serverseitge Client-Darstellung (i.e. sowas wie eine Liste der verbundenen Sockets) sind hinreichend leer und wachsen auch nicht. Dennoch kann ich einen linearen Anstieg des RAM-Verbrauchs verbuchen.
-
Fehler gefunden:
recht komplexen Multithread-Konglomerat
-
Wenn VLD es nicht erkennt dann werden es wohl Sachen sein die nicht mit malloc und new angelegt worden sind.
-
Sockets, VirtualAlloc, GDI etc.
-
> Fehler gefunden:
Konkret geht sind es I/O-Completion Ports und die halte ich zu komplex für ein kompilierbares Minimalbeispiel mit Threadpool. Aber ohne geht es leider nicht.
[Anmerkung: Das wird jetzt doch sehr Windows-lastig, kann das bitte ein Mod vielleicht nach WinAPI verschieben?]
> Sockets
Bingo! Der RAM-Verbrauch ist massiv auf den Footprint von WSASocket()-Aufrufen zurückzuführen. Aber ich hätte wetten können, dass wirklich alle Resourcen per closesocket() freigegeben werden. Dem ist scheinbar nicht so (oder doch?). Kann man da was machen?
-
Du verwendest nicht zufällig CreateThread() direkt?
http://support.microsoft.com/kb/104641/en-us
-
Nein, std::thread.
Ich habe gerade tatsächlich noch ein Socket-Leak gefunden und gestopft. (Der Code ist nicht von mir, ich muss in refactorn). Jetzt ist der RAM-Anstieg sehr viel kleiner (wenn auch nicht 0).