(Function) Pointers nach Plugin unload



  • @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!


Anmelden zum Antworten