(Function) Pointers nach Plugin unload



  • Hi,
    ich schreibe gerade eine hoch dynamische Pluginsoftware in der viele Funktions/Speicher Pointer rumgeschoben werden (müssen).
    Jede Pluginbibliothek kann weitere Abhängigkeiten (run/compile time) haben und mit diesen Pointer austauschen.

    Wenn ein Plugin nun ausgeladen wird, werden x andere ebenso ausgeladen.
    Jetzt fliegen immernoch die Pointer durchs System.
    Jeder von Ihnen wird von mir mit einem Bibliothekshinweis gespeichert.
    Unter Windows kann ich so ordentlich vorher aufräumen, da es eine (versteckte) Win32 load/unload callback Registrierung gibt, die für ALLE Bibliotheken, die der Prozess lädt, aufgerufen wird. (siehe LDRREGISTERDLLNOTIFICATION).

    1. Gibt es etwas Vergleichbares unter Linux/Unix?

    2. Was passiert mit Funktionspointern entladener Bibliotheken?
      (Memory Pointer sollten unter Linux weiter gültig sein, während unter Windows hingegen jede DLL ihren eigenen Speicherbereich hat, der mit ihr untergeht.)

    Problem ist: Ich kann weder VOR Unload Aufräumen, da ich nicht weiß welche Bibliotheken sonst noch beim Entladen der zentralen Pluginbibliothek mitgerissen werden, noch will ich es DANACH machen, weil ich sonst unendlich viele (Threading-)Sicherheitssysteme einbauen muss.

    1. Wie sollten daher unter Linux/Unix die Pointer am Besten aufgeräumt werden?

    Habor

    PS: Bitte kein Hinterfragen bzgl. des "Warum" beim Pointer Sharing.



  • Was hat das mit C++ zu tun?



  • Wo sollte ich diese Fragen posten?
    Es geht um ein C++ Project.



  • @Habor sagte in (Function) Pointers nach Plugin unload:

    Problem ist: Ich kann weder VOR Unload Aufräumen, da ich nicht weiß welche Bibliotheken sonst noch beim Entladen der zentralen Pluginbibliothek mitgerissen werden, noch will ich es DANACH machen, weil ich sonst unendlich viele (Threading-)Sicherheitssysteme einbauen muss.

    So ganz folgen kann ich dir nicht. Was spielt es für eine Rolle, wenn du das selber vor dem Unload machst, oder wenn du eine Notification bekommst, dass eine Dll entladen wurde?
    Du weißt doch hoffentlich, dass du ein Plugin entladen willst, und kannst an der Stelle sauber aufräumen. Alles andere würde ich zumindest unsauber finden.

    Solche Callbacks/Notifications sind mir unter Linux nicht bekannt. Du könntest evtl. irgendwelche Hacks versuchen, wie dlclose dynamisch zu patchen, aber sauberer wird das alles dadurch definitiv nicht.



  • @Mechanics sagte in (Function) Pointers nach Plugin unload:

    So ganz folgen kann ich dir nicht. Was spielt es für eine Rolle, wenn du das selber vor dem Unload machst, oder wenn du eine Notification bekommst, dass eine Dll entladen wurde?
    Du weißt doch hoffentlich, dass du ein Plugin entladen willst, und kannst an der Stelle sauber aufräumen. Alles andere würde ich zumindest unsauber finden.

    Ich weiß nur welche Bibliothek ich direkt(!) entlade, nicht aber was dadurch sonst noch alles abgeräumt wird. Eine Bibliothek kann ja selbst noch weitere Abhängigkeiten haben (nicht alles läuft via dlopen/dlclose). Und selbst wenn ich wüsste welche Bibliotheken potenziell(!) abgeräumt werden KÖNNTEN, kann ich mir nicht sicher sein.

    Beispiel:
    Plugin A nutzt Bibliothek C
    Plugin B nutzt auch Bibliothek C

    Entlade ich nur Plugin A bleibt C weiterhin geladen.

    Das Windows Callback sagt mir zu A, B und C einzeln Bescheid.
    Was tue ich dagegen unter Linux?



  • Du könntest z.B. einen Snapshot der geladenen shared objects erstellen, vorher und nacher. Siehe z.B. hier:

    http://syprog.blogspot.com/2011/12/listing-loaded-shared-objects-in-linux.html

    Mir ist aber immer noch nicht ganz klar (und deswegen vermute ich, dass das irgendwie unsauber ist), was es dich interessiert, welche Abhängigen Bibliotheken es sonst gibt, und was die machen. Wenn dein Plugin etwas nachlädt, dann muss es sich auch selber darum kümmern, sauber aufzuräumen. Das sehe ich nicht als Aufgabe des übergeordneten Frameworks.



  • Wirklich toller Link. Dass ein Handle unter Linux tatsächlich ein Pointer ist, kann u.U. noch woanders helfen. Aber ich glaub da gibts Funktionen für, die das sauberer machen.
    Davon abgesehen gibt es auch die Funktion dl_iterate_phdr (https://linux.die.net/man/3/dl_iterate_phdr), mit der sich die aktuell geladenen Bibliotheken auflisten lassen sollen.

    Ich will nur eigentlich nicht so "aktiv" scannen, da:
    "Problem ist: Ich kann weder VOR Unload Aufräumen, da ich nicht weiß welche Bibliotheken sonst noch beim Entladen der zentralen Pluginbibliothek mitgerissen werden, noch will ich es DANACH machen, weil ich sonst unendlich viele (Threading-)Sicherheitssysteme einbauen muss." ("Danach"-Teil beachten)

    Auch die "C" Bibliotheken (siehe ABC-Beispiel oben) verteilen im System Pointer auf ihre Funktionen und Speicher.
    (Ja, ich weiß, das wird nicht gern gesehen, aber das Potenzial eines komplett dynamischen Programms, in dem Plugins miteinander "reden" können, ohne einander zu kennen, nur via Reflection und eine "Funktionsdatenbank", ist einfach zu groß, um es ungetestet zu lassen.)
    Nun, das Plugin A, nutzt mit B zusammen dynamisch C (beide haben eine Linker-Time Dependency darauf). A weiß daher nicht, ob es C ebenfalls entladen wird.
    Um "C" Aufzuräumen gibt es jetzt mehrere Möglichkeiten:

    1. Beste: Ich habe ein Systemcallback, was mir über JEDEN bevorstehenden Unload Bescheid sagt.
    2. Ich halte vor dem Unload eines Plugins das GANZE System (alle Threads) an, mache einen Snapshot, entlade das Plugin, mache wieder einen Snapshot und bereinige alle Pointer der nun fehlenden dritten Bibliotheken (z.B. "C").
    3. Keine Ahnung ob das geht: Ich baue in den Destruktor einer statischen Variable in JEDER "C" Bibliothek die notwendigen Bereinigungsvorgänge ein.

    Letzteres (3.) ist "ekelig" und z.T. auch ganz und gar unmöglich, da ich auf die Bibliotheksinhalte keinen Zugriff habe.
    2. ist extrem aufwendig, da viele Threads (z.B. für Sockets) vorliegen, die ich z.T. auch garnicht kenne (in anderen Plugins), und die ich nicht einfach "einfrieren" kann. Auch Mutexe/Spinlocks sind keine Option, da Locks vor bis nach dem Unload den Entladevorgang Deadlocken kann (Stichwort: Destruktoren).
    Aber wie es scheint, gibt es unter Linux keine Lösung für 1. Oder habe ich noch eine Möglichkeit übersehen?



  • Nochmal zum Verständnis, wenn "C" kein Plugin ist, was interessiert es dich dann, was weißt du darüber, was willst du aufräumen?



  • @Habor Die übliche Lösung wäre dass die Objekte die die Zeiger enthalten die DLL referenzieren (ref-count). Also dafür sorgen dass die DLL eben nicht zu früh entladen wird. Entweder direkt (ref-counting Mechanismus des OS für DLLs/SOs) oder auch indirekt über die entsprechenden Funktionen deines Plugin Systems. Andernfalls kannst du sowieso nie garantieren dass es beim Entladen nicht kracht.

    noch will ich es DANACH machen, weil ich sonst unendlich viele (Threading-)Sicherheitssysteme einbauen muss.

    Sobald Threads im Spiel sind die einfach so in die diversen DLLs rein-rufen können, hast du sowieso ein Problem. Weil du nicht sicherstellen kannst dass gerade kein Code in der DLL ausgeführt wird während sie entladen wird.

    Und selbst wenn du es könntest, hilft es dir nix. Weil du im Unload-Hook nicht sinnvoll darauf warten kannst bis die Threads den DLL Code wieder "verlassen" haben. Weil du da im Loader-Lock bist. Und im Loader-Lock auf sowas zu warten - damit hast du dann schnell einen Deadlock beinander.



  • @Habor sagte in (Function) Pointers nach Plugin unload:

    PS: Bitte kein Hinterfragen bzgl. des "Warum" beim Pointer Sharing.

    Dir ist klar dass das auf unseren Bildschirmen als "ich weiss dass das doof ist aber ich will es trotzdem machen" angezeigt wird, ja?



  • "As to "why do you need that?" - why do you care? Poor guy's asking a question here so let's assume he knows what he's doing." - http://syprog.blogspot.com/2011/12/listing-loaded-shared-objects-in-linux.html (siehe Oben) ^^

    In jedem Fall vielen Dank "hustbaer". Deine Bedenken und Problemdarstellungen haben mich, nach langem Nachdenken, auf den richtigen Weg gestoßen und eine Lösung entwickeln lassen.

    1. In meiner, in der Executable verwalteten "SharedLibrary"(Plugin-Manager)-Klasse, lasse ich mir von dem Plugin zunächsteinmal durch einen Reset()-Aufruf garantieren, dass nichts mehr darin lebt und das Plugin alles in seiner Macht stehende dafür getan hat.
    2. Ich locke hart den (globalen) Shared Mutex der "SharedLibrary"-Klasse.
    3. Ich entlade das Plugin und damit implizit u.U. Teile seiner Dependencies (wenn diese nicht noch von andere Plugins genutzt werden).
    4. Ich wische feucht durch und lösche alles invalide.
    5. Ich entlocke den Shared Mutex der "SharedLibrary"-Klasse.

    Erklärung dazu:
    Die "überall rumfliegenden" Funktions- und Speicherpointer befinden sich ausschließlich(!) in den Klassen "Variant" (ähnlich, wenn auch VIEL mächtiger, als "any" Typen; wer sie kennt) und "Reflection" (enthält Klasseninformationen und Funktionspointer für registrierte Typen).
    Diese beide Klassen werden ab sofort vor und vor allem WÄHREND JEDER Anwendung der intern gespeicherten Pointer (inkl. delete()/free() Aufrufen), zunächst den Shared Mutex der "SharedLibrary"-Klasse read-locken und prüfen, ob die Bibliothek des Pointers noch geladen ist.
    Es wird garantiert, dass ein Pluginentladevorgang niemals innerhalb eines Read-Locks des Shared Mutex der "SharedLibrary"-Klasse geschieht.

    Vermutlich kapier das Geschriebene nur ich, aber das ist schon die simple Version. ^^
    Ziemlich komplexes System, das jetzt hoffentlich noch potenter wird.

    Ich danke allen Beteiligten für die Unterstützung und große Hilfe. 🙂
    Habor

    Ich poste hier später fürs Archiv, ob's geklappt hat.



  • @Habor sagte in (Function) Pointers nach Plugin unload:

    Vermutlich kapier das Geschriebene nur ich, aber das ist schon die simple Version. ^^

    Ja, sowas kenn ich. Wir haben im Grunde auch etwas vergleichbares in der Arbeit (Linux wird aber nicht mehr unterstützt). Wir haben auch schon seit Jahren mit Problemen was wir wann geladen/entladen zu kämpfen. Kommt aber auch dazu, dass auch unsere Software selber als Plugin in anderen Systemen geladen werden kann, die das immer irgendwie unterschiedlich machen.
    Jedenfalls könnte bei uns auch niemand einfach so erklären, wie das funktioniert. Wir haben bei vielen Details eher etwas im Hinterkopf wie, aber da war doch mal was, wenn man...



  • @Habor sagte in (Function) Pointers nach Plugin unload:

    Die "überall rumfliegenden" Funktions- und Speicherpointer befinden sich ausschließlich(!) in den Klassen "Variant" (ähnlich, wenn auch VIEL mächtiger, als "any" Typen; wer sie kennt) und "Reflection" (enthält Klasseninformationen und Funktionspointer für registrierte Typen).

    OK. D.h. du willst erlauben dass die DLL entladen wird während noch Variants existieren die von der DLL produziert wurden. Weil es schwer/unmöglich zu garantieren wäre dass sie alle weg sind wenn das Programm das Plugin entfernen möchte.

    Um die DLL entladen zu können markierst du die daraus erzeugten Variants dann quasi als "abgelaufen" - womit sie nicht mehr verwendbar sind. Und das ganze halt thread-safe durch den von dir beschriebenen Locking-Mechanismus.

    Soweit richtig?

    Frage: Warum muss die Plugin-DLL überhaupt zu dem Zeitpunkt entladen werden wo das Plugin logisch "entfernt" wird? Ist das eine Anforderung? Also z.B. weil danach das DLL File löschbar/verschiebbar sein muss. Oder weil die Anwendung garantieren muss dass Variants die von dem Plugin erzeugt wurden garantiert nicht mehr verwendbar sind nachdem das Plugin logisch entfernt wurde.

    Wenn es so eine Anforderung gibt, dann macht dein Vorhaben vermutlich Sinn.
    Wenn es so eine Anforderung nicht gibt, dann würde ich wie schon beschrieben eher überall wo ein Zeiger auf etwas in der DLL ist auch eine Referenz ("ref-count") auf die DLL halten. Ein shared_ptr/intrusive_ptr tut es dafür.

    Das Plugin aus der Plugin-Liste entfernen und die dortige Referenz auf die DLL freigeben kannst du dann ja trotzdem wann auch immer du willst. Wenn es sonst keine Referenzen auf die DLL gibt dann wird die auch zu genau dem Zeitpunkt entladen. Und wenn doch, dann bleibt sie erstmal noch geladen. Genau bis zu dem Zeitpunkt wo die letzte Referenz auf die DLL stirbt.

    ps:

    Ich poste hier später fürs Archiv, ob's geklappt hat.

    👍 Ja, bitte tu das 🙂



  • @hustbaer sagte in (Function) Pointers nach Plugin unload:

    OK. D.h. du willst erlauben dass die DLL entladen wird während noch Variants existieren die von der DLL produziert wurden. Weil es schwer/unmöglich zu garantieren wäre dass sie alle weg sind wenn das Programm das Plugin entfernen möchte.

    Korrekt.

    Um die DLL entladen zu können markierst du die daraus erzeugten Variants dann quasi als "abgelaufen" - womit sie nicht mehr verwendbar sind. Und das ganze halt thread-safe durch den von dir beschriebenen Locking-Mechanismus.

    Ich markiere nichts. Während und nach dem Entladen ist alles hart gelocked. Ich gehe danach alle Pointer in den beiden Klassen durch und lass über die beiliegenden Informationen prüfen, ob sie nun noch valide sind. Wenn nicht, lösche ich sie direkt.
    Der normale Zugriff auf die Pointer geschieht in Zukunft nurnoch über einen Read-Lock. (Reflection & Co. sind eh nicht für High-Performance geeignet.)

    Frage: Warum muss die Plugin-DLL überhaupt zu dem Zeitpunkt entladen werden wo das Plugin logisch "entfernt" wird? Ist das eine Anforderung? Also z.B. weil danach das DLL File löschbar/verschiebbar sein muss. Oder weil die Anwendung garantieren muss dass Variants die von dem Plugin erzeugt wurden garantiert nicht mehr verwendbar sind nachdem das Plugin logisch entfernt wurde.

    Ich fahre eine äußerst harte Schiene von Konstruktor/Destruktor, Initialize/Reset, Start/Stop, etc. Funktionspaaren.
    Ausnahmen sind Systemschwachpunkte. Wenn ich eine Bibliothek entlade, dann soll sie auch entladen sein und nicht noch bis Ende des Programms irgendwo rumgeistern.

    Wenn es so eine Anforderung nicht gibt, dann würde ich wie schon beschrieben eher überall wo ein Zeiger auf etwas in der DLL ist auch eine Referenz ("ref-count") auf die DLL halten. Ein shared_ptr/intrusive_ptr tut es dafür.

    Das Plugin aus der Plugin-Liste entfernen und die dortige Referenz auf die DLL freigeben kannst du dann ja trotzdem wann auch immer du willst. Wenn es sonst keine Referenzen auf die DLL gibt dann wird die auch zu genau dem Zeitpunkt entladen. Und wenn doch, dann bleibt sie erstmal noch geladen. Genau bis zu dem Zeitpunkt wo die letzte Referenz auf die DLL stirbt.

    Ich verstehe diesen "umgedrehten" Lösch- und "weichen" Entladeansatz, möchte aber ein hartes Entladen ermöglichen.
    Ansonsten kommt man später noch in des Teufels Küche.
    Davon abgesehen werden diese Pointer normalerweise im System nicht gelöscht. 😉



  • @Habor sagte in (Function) Pointers nach Plugin unload:

    Ich markiere nichts. Während und nach dem Entladen ist alles hart gelocked. Ich gehe danach alle Pointer in den beiden Klassen durch und lass über die beiliegenden Informationen prüfen, ob sie nun noch valide sind. Wenn nicht, lösche ich sie direkt.
    Der normale Zugriff auf die Pointer geschieht in Zukunft nurnoch über einen Read-Lock. (Reflection & Co. sind eh nicht für High-Performance geeignet.)

    Du löscht die Variants oder die Pointer? Wenn du die Pointer meinst: das meinte ich mit "markieren" - damit dass du die Pointer aus den Variants "rauslöscht", markierst du indirekt den Variant als "abgelaufen".



  • Ah. Ja, ich lösche die Pointer und muss daher ein "invalid" Flag setzen. Richtig.
    Ich kann ja nicht überall hingreifen und die Variants selbst löschen, wenn sie ieine krude Instanze an sich gerissen hat.



  • "&typeid(T)" ergibt einen immer gleichen Pointer für einen Typ.
    Liegt der Speicher dafür im Process Memory oder in der Bibliothek der Typedefinition?
    (Wenn im Process Memory fragt sich was nach Plugin Unload mit dem Eintrag für einen Plugintypen passiert.)



  • @Habor sagte in (Function) Pointers nach Plugin unload:

    oder in der Bibliothek der Typedefinition?

    dort.
    Du kannst aber den String kopieren den du von .name() zurückbekommst, der sollte eindeutig sein.



  • Das bringt aber auch nichts wenn ich die Bibliothek schließe und dann wieder öffne.
    String wäre dann gleich, aber was auch immer neben dieser Typinfo liegt, ist invalide und dann umso gefährlicher.
    Ich werd also die Infos ebenfalls schützen/nullen müssen.
    Davon abgesehen kann der Compiler da auch einfach nurn Klassennamen reinschreiben.



  • Wie versprochen das Ergebnis: Trommelwirbel
    ...

    ...
    Es funzt. ^^

    Zwischenzeitlich nochn tollen MSVC ICE bekommen (für '17 und unter '19), aber es geht jetzt.
    Linux ist noch ungetestet, aber dafür müssen da eh noch zwei andere Projekte zum Laufen gebracht werden (Chromium und OpenGL/Vulkan Engine). Wird also noch ne Weile dauern.

    Nochmals Danke für die Hilfe!


Log in to reply