Verständnisproblem bei Verwendung von libfcgi und pthreads



  • Hallo zusammen,

    das hier soll keine "ich habe ein Problem mit einer Library" Anfrage werden, viel mehr geht es um das fehlende Verständnis, wie das hier geschehende überhaupt sein kann.

    Rahmenbedinungen sind:
    - Root Server mit Debian 5.0 und 6.0 64bit
    - eigens geschriebene Streaming Software, welche mittels pthreads Daten via libfcgi an einen nginx webserver sendet
    - als gcc kommt dank Debian 5.0 auch noch gcc 4.3.2 zu Einsatz, aber auch mit neueren Versionen geschieht das gleich Folgende.

    Die erwähnte Software funktioniert über Monate problemlos (keine Memory Leaks, Dead Locks oder andere nervige Fehler), bricht aber mit einem Signal 11 ab, sobald ca. 700 "laufende" Threads überschritten werden (aktuell lassen wir 1000 konstant laufen, um keine Fehler an dieser Stelle zu erzeugen).

    An den ulimits kann und darf es nicht liegen, selbst als Root "ohne Regeln" tritt es auf. Einen Stack Overflow möchte ich quasi ausschliessen, selbst dramatische Anhängungen pro Thread (oder Verkleinerungen) bringen keinen Unterschied.

    Zum Problem:

    Wenn die besagte aktive Thread Menge überschritten wird (es sind dann wirklich 700 File Descriptoren via TCP zum Webserver offen), crasht die Software mit einem:

    Program terminated with signal 11, Segmentation fault.
    #0  OS_IpcClose (ipcFd=<error reading variable: Cannot access memory at address 0xfffffffffffffffc>, ipcFd@entry=<error reading variable: Cannot access memory at address 0x8>, 
        shutdown=<error reading variable: Cannot access memory at address 0xfffffffffffffff8>, shutdown@entry=<error reading variable: Cannot access memory at address 0x8>) at os_unix.c:1264
    1264	}
    

    An dieser Stelle tippte ich zu erst auf einen libfcgi Fehler, mit einer Rohversion der Software (reduziert auf das nötigste) gehen allerdings auch 1000 Verbindungen und mehr.

    Da wir schon Wochen am Debuggen sind, wurde irgendwann die Library selber modifiziert und mit Debug Outputs versehen.

    Sie sind jedes Mal in der Form:

    printf("%ld %x %d\n", thread_id, &request.ipcFd, request.ipcFd);
    

    Die Ausgabe erfolgt startend in der eigenen Software (bevor das Request beendet wird), über alle Unterfunktionen von libfcgi, bis zum finalen "OS_Close()", welches nach dem Backtrace (der übrigens nur dieses eine Frame hat, Valgrind konnte aber den Rest anzeigen) aber gar nicht mehr aufgerufen wird.

    Für den normalen Ablauf sieht dies dann ca. so aus (die Adresse enspricht der Speicherstelle des INT Wertes, welcher danach angezeigt wird):

    836 41c243b0 1199
    41c243b0 1199
    41c23eec 1199 <-- Ausgabe direkt in OS_IpcClose()
    41c23a1c 1199
    

    Die 836 ist hier die Thread-ID, welche in den eigenen Klasse noch vorhanden ist, in den libfcgi Funktionen ist sie natürlich unbekannt.

    Bis zum letzten Cycle erfolgt allerdings die Debug Ausgabe ohne merklichen Fehler.

    Der letzte Lauf soeben ergab:

    475 40edf3b0 1409
    40edf3b0 1409
    40edeeec 1409
    40edea1c 1409
    

    Hat eventuell ein User dieses Forums eine Idee, wie die Debug Ausgabe bis zur letzten Sekunde fehlerfrei sein kann, der Backtrace aber derart andere Daten liefert?
    Nach diesem sieht es eher nach überschriebenen Daten aus anderen Threads aus (was aber mit viel Sucherei ebenfalls quasi auszuschliessen ist).

    LG,
    Ronny



  • Was willst du denn mit dem printf("%ld %x %d\n", thread_id, &request.ipcFd, request.ipcFd); ausgeben?
    %x erwartet ein unsigned int (32-Bit)
    &request.ipcFd ist eine Adresse (64-Bit)

    Nimm mal den richtigen Formatspecifier für Zeiger: %p


  • Mod

    0x8, 0xfffffffffffffffc, das riecht doch sehr danach, als ob da irgendwo ein Nullpointer vorkommt. Ich spekuliere mal, dass in dem Programm irgendwo mittels malloc (oder realloc, calloc o.ä., aber jedenfalls nicht mit einem (normalen) new) dynamisch Speicher besorgt wird. Der Aufruf schlägt fehl (zu wenig Speicher oder zu fragmentiert), das Programm enthält jedoch keinerlei Sicherheitsabfragen für diesen Fall und dereferenziert munter den Nullzeiger.

    P.S.: Ist das überhaupt C++ oder nicht vielleicht doch C?



  • @DirkB:

    das stimmt natürlich. Aber die Ausgabe funktionierte dennoch für unsere Zwecke so zutreffend, dass das "egal" war. Hätte ich korrigieren sollen, sorry.

    @SeppJ:

    in die Richtungen gingen auch schon eine ganze Reihe Suchen, allerdings sind quasi keine Speicher Anforderungen mehr vorhanden, welche von der eigenen Software stammen. Vieles wurde absichtlich erstmal auf Stack basierte Nutzung umgestellt, da man über Multithreading und Heap immer mal wieder negatives liesst. Diese Umstellungen haben aber bisher keinen Unterschied gebracht.
    Valgrind findet auch keine Probleme, zeigt allerdings eine ganze Reihe "possibly lost" Meldungen, welche mit std::string Nutzungen zusammen hängen, dies allerdings nur, wenn die Software via Signal 11 abbricht. Bei einem normalen Programm Ende sind die Meldungen leer.

    Es handelt sich wirklich um C++. Pthreads, libfcgi und hiredis sind dabei die einzigen beiden C Libraries (es gäbe Wrapper Klassen, aber in denen sah ich keinen Nutzen).

    Mich wurmt weiterhin, wie die Debug Ausgaben durchlaufen können, der Debugger allerdings schon weit vorher stoppt.

    Was vielleicht auch hier untergegangen ist: das was ich dort ausgebe oder teste ist das Request Handle, welches den ganzen Thread über benutzt wird. Die Daten (u.a. der Integer) sind vorhanden und werden in jedem Fall zur Funktion weitergereicht, welche dann den Segfault auslöst. Die Meldung des Debuggers mit der nach Überlauf klingenden Adresse deckt sich daher überhaupt nicht mit den extra eingebauten Debug Ausgaben.



  • ronny332 schrieb:

    Aber die Ausgabe funktionierte dennoch für unsere Zwecke so zutreffend, dass das "egal" war. Hätte ich korrigieren sollen, sorry.

    Sicher?
    Da werden 8 Byte auf den Stack geschoben (der Pointer), aber nur 4 Byte ausgewertet (%x).
    Dann werden für das %d die nächsten 4 Byte genommen. Nur gehören die dann noch zum Pointer und nicht zum dem int. Also siehst du da gar nicht dein request.ipcFd sondern die Highbytes vom Pointer.
    (Bei einem 64-Bit System)


  • Mod

    "Quasi keine" Speicheranforderungen sind immer noch einige. Werden die auch geprüft, wenn diese durch malloc erfolgen? Denk dran, dass diese auch in einer Bibliothek auftauchen können. Da eigentlich kein Programm, welches über ein Anfängerbeispiel hinausgeht, ohne dynamischen Speicher auskommt, kann ich mir nicht vorstellen, dass ihr wirklich alles losgeworden seid, ihr habt es bloß versteckt. Fakt ist doch, da taucht ein Nullzeiger auf. Da musst du also genau gucken und darfst nicht argumentieren, dass eigentlich alles richtig sein sollte. Dies ist eindeutig nicht der Fall.

    Valgrind findet auch keine Probleme, zeigt allerdings eine ganze Reihe "possibly lost" Meldungen, welche mit std::string Nutzungen zusammen hängen, dies allerdings nur, wenn die Software via Signal 11 abbricht.

    Das ist normal.

    Mich wurmt weiterhin, wie die Debug Ausgaben durchlaufen können, der Debugger allerdings schon weit vorher stoppt.

    Was genau meinst du?



  • Die Adressen Ausgabe habe ich soeben angpasst, ein Testlauf ist gerade im Gange, dann kann ich die richtigen Positionen melden.

    Ein richtiger Lauf der Ausgabe sieht jetzt so aus (mit den Kommentaren dahinter, von wo die Ausgabe stammt):

    Adresse    Wert (File Descriptor, welcher geschlossen werden soll)
    0x41e3c3b0 397 <-- kurz bevor das Request geschlossen wird in eigener Methode
    0x41e3c3b0 397 <-- FCGX_Free(), schon in der Library
    0x41e3beec 397 <-- OS_IpcClose(), Library (hier endet der Backtrace)
    0x41e3ba1c 397 <-- OS_Close(), Library (hier kommt der Backtrace gar nicht mehr hin)
    

    Mein Verständnis endet genau dort, wo der Backtrace bis OS_IpcClose() kommt, die Debug Ausgabe (Werte füge ich hier gleich an) aber bis OS_Close() problemlos alle Werte liefert und ausgibt.
    Würde die Ausgabe des Debuggers stimmen, so müsste doch auch die letzte Debug Ausgabe genau bei OS_IpcClose() abreißen.
    Auch besagt der Backtrace ja, dass der eigentlich übergebene Wert (File Descriptor) nicht verfügbar ist. Genau das sieht aber nach meinen Meldungen anders aus.

    Wie versprochen hier der Nachtrag vom Crash. Die letzte Debug Ausgabe der Adressen des File Descriptors, welchen die abstürzende Funktion schließen soll, und der neue Backtrace:

    0x4106a3b0 1408
    0x4106a3b0 1408
    0x41069eec 1408 <-- in OS_IpcClose()
    0x41069a1c 1408
    
    (gdb) bt
    #0  OS_IpcClose (ipcFd=<error reading variable: Cannot access memory at address 0xfffffffffffffffc>, ipcFd@entry=<error reading variable: Cannot access memory at address 0x8>, 
        shutdown=<error reading variable: Cannot access memory at address 0xfffffffffffffff8>, shutdown@entry=<error reading variable: Cannot access memory at address 0x8>) at os_unix.c:1264
    Cannot access memory at address 0x8
    (gdb)
    

  • Mod

    Wenn das verschiedene Threads sind, die die Meldungen schreiben, dann ist das durchaus normal, dass man noch Meldungen von anderen Threads bekommt, nachdem ein Thread schon abgestürzt ist.

    Ansonsten ist nun wohl der Zeitpunkt gekommen, mal ein bisschen Code zu zeigen.



  • :xmas2: über den code denke ich auch die ganze Zeit schon nach, da muss erstmal kräftig gekürzt werden, damit es überhaupt schnell verständlich wird. Die Software erstreckt sich in ihrem aktuellen Zustand über ca. 10 beteiligte Klassen, welche mehr oder weniger direkt in einander greifen. Diese Abstraktion wurde extra gewählt, um dem Spaghetti Code in einer alten Software-Version Herr zu werden (welche einst aus reinem C übernommen wurde). Das macht es mir aber gerade enorm schwer alles zu zeigen, aber dennoch den Thread nicht zu sprengen.


  • Mod

    Es könnte schon helfen, wenn du den fehlerverusachenden Code zeigst, insbesondere wie sich die Debugausgabe zu der Stelle verhält, die der Debugger als Absturzstelle anzeigt. Ganz toll wäre natürlich, wenn du irgendwie ein Minimalbeispiel basteln könntest (siehe dritter Link in meiner Signatur). Das ist bei dieser Art von Fehler natürlich sehr schwer. Unübersichtlichen Code zu Debuggen, noch dazu multithreaded, ist immer schwer. Aber wenn du das schaffst, dann kannst du fast mit Sicherheit davon ausgehen, eine hilfreiche Antwort zu bekommen oder sogar den Fehler dabei selber zu finden. Ansonsten werden die Antworten nämlich eher allgemeine Debugratschläge bleiben, so wie im bisherigen Threadverlauf.



  • Hallo nochmals :xmas1:,

    bei der Umsetzung einer reduzierten Quellcode Version kam mir immer mehr der Gedanke, dass kein Fehler vorliegen dürfte, zu viele unnötige Mutexe oder Umbauten waren mittlerweile in den Sourcen. Im libfcgi Code konnte ich auch nach langem Einlesen nichts finden, nur die Art wie dort auf neue Verbindungen reagiert wurde, kam mir irgendwie anders vor, als ich es machen würde (in einem eigenen kleinen Webserver nutze ich immer accept() anstelle von select(), die Bücher, aus welchen ich die Informationen dazu zog, wissen wohl auch sehr gut weshalb).

    Mit der Suche nach libfcgi und accept() fand dann Google endlich wonach ich nun 2,5 Wochen an der falschen Stelle gesucht habe.

    https://bugs.launchpad.net/ubuntu/+source/libfcgi/+bug/933417

    Somit lag der Fehler wirklich in der ursprünglich 1996 geschriebenen FastCGI Library, und ich habe Euch umsonst gefragt.

    Dennoch vielen Dank für die nette Unterstützung :xmas2:!

    Grüße,
    Ronny


Anmelden zum Antworten