Das deep_copy() Memleak

30. Juli 2009

Seit circa einem Monat leidet Avalon unter einem Memleak, was den tägliche garbage collection Durchgang ewig dauern liess und das bekannte Problem verursachte, dass das MUD in den Morgenstunden unspielbar wurde. (siehe die Spielerzeitung)

Wir waren letztlich gezwungen den täglichen GC übergangsweise abzuschalten und wieder den wöchentlichen Armageddon einzuführen, damit unsere Frühaufsteher überhaupt etwas vom MUD haben können.

Der geniale Baba (noch so ein verschollener Administrator) hat wohl schon etwas gewittert und sich in den letzten zwei Wochen der Sache angenommen. Und gestern hatten wir dann den Durchbruch!

Selbstreferenzierende Mappings, die durch ein deep_copy() geschickt werden, haben ungültige Referenzzählerwerte. Der Speicherplatz für diese wird deswegen erst beim nächsten GC freigegeben. Das ist die Ursache. Der Anlass: Änderungen am VItem-Code benutzen und benötigen solch eine Selbstreferenz, weil VItems mit Unter-VItems („v_item“-Eintrag) alle einen „environment“-Eintrag halten, der das Mutter-VItem enthält.

Wir denken nun also, dass wir langsam verstehen, weshalb das Memleak entsteht, jetzt müssen wir uns überlegen, was wir dagegen tun können. Bislang sieht es ja so aus, als wenn wir auf einen Driverbug gestossen sind, aber auch auf corelib-Ebene muss ich mir die betroffenen Passagen nochmal konzentriert anschauen. Vielleicht lässt sich die ganze ungünstige Konstellation auch vermeiden.

Hut ab, Baba!

Die letzte Woche hatte ich das Glück gleich zwei Altadmins im MUD anzutreffen und länger mit ihnen zu quatschen. Das ist immer sehr angenehm. Nicht nur kennt man sich zum Teil noch privat und freut sich Neuigkeiten zu hören, sondern es ist auch einfach interessant das MUD aus Adminaugen zu sehen, die einfach nicht mehr tagtäglich involviert sind. Ein paar alte Geschichten gibt es dann auch immer.

Grüsse an die Aachener!
Und dem einen Kollegen, man sieht sofort, dass hier jemand anwesend war, der die Funktionsweise des MUDs kennt, hat natürlich gleich einen neuen (das heisst: bislang ungemeldeten, unbekannten) Parserbug gefunden:

Nachdem ich mich im April mit der Deklination von Mengenbezeichnungen in Items herumgeschlagen habe, die grammatikalisch immer als Plural gekennzeichnet waren (aber das wegen der Mengenbezeichnung nicht wirklich waren), bereiten mir diese Pluralitems jetzt beim Parser Schwierigkeiten. Im Augenblick kommen wir nämlich nicht damit klar, wenn eine Spielereingabe einen Gegenstand im Plural kennzeichnet, aber zugleich „normale“ Items vorhanden sind, die diese Eingabe (korrekt) als Plural-ID führen sowie Plural:1-Gegenstände, die diese Eingabe als nicht explizite Plural-ID (sondern als übliche ID) führen und aber als Plural:1 gelten.

Die Lösung hierfür ist so etwas wie „implizite Pluralfünde“ einzuführen: Gegenstände, die zwar auf eine Singular-ID reagierten, aber als plural:1 gelten. Das ist mein Ansatz und ein Bugfix war zum Teil schon fertig, aber ein Schnellschuss gestern Abend war dann doch nicht ausreichend getestet. Ich war ganz erstaunt, dass nicht alle möglichen Fälle in den Parser-Unittests enthalten sind ;) Aber lange wird ein Bugfix nicht auf sich warten lassen.

Demnächst wird man also in Ashar besser mit den Kokosnüssen umgehen können.

Bis vor zwei-drei Wochen hatte ich nicht genug Zeit um so ein grösseres Projekt weiterzutreiben wie die Verstärker/Enhancer-Sachen, an denen ich letztens gearbeitet hatte, also habe ich mich, als ich dann mal etwas programmieren wollte, mich auf eines meiner anderen Lieblingsprojekte zurückgesinnt: Den (Kern-)parser.

Na, schon gelangweilt? Egal, mich interessiert das Ding.

Was ich so nebenbei angefangen habe, ist dann zu einer sehr umfangreichen Änderung geworden. Seit langem hatte ich vor die wichtigste intere Datenstruktur des Parsers von einem stackbasierten Aufbau zu einer (Multi-)Liste umzustellen (Notiz: letztendlich dann als doppelte Liste umgesetzt). Das führte zu dem grössten Umbau seit, äh, 2004 (?), dem Datum, wo der jetztige Parser zum ersten mal die grundlegenden Parsingaufgaben im MUD übernahm.

Dabei ist natürlich erstmal herausgekommen, dass ich mein Homemud geschrottet habe und ein bis zwei Wochen lang auf den ganz alten Parser zurückstellen musste (ja, Avalongeschichtsfreaks, die erste Implementation von parse_com() gurkt noch irgendwo in der Lib herum für genau solche Notfälle). Die Umstellung dieser Datenstruktur brachte natürlich eine Änderung aller prozeduralen Abläufe mit sich und dann immer mehr und mehr Umbau und Kontrolldurchgänge.

Und wenn man bedenkt, dass eine andere Datenstruktur keine neuen Funktionen für Spieler mit sich bringt, könnte man fragen, wieso ich das überhaupt angegangen bin (und wieso ich das so lange schon vorhatte). Prinzipiell schaut sich der Parser alle Knoten (Wörter) in Dreiergruppen an und schickt abgehakte Knoten (mit denen er nichts weiteres mehr anfangen kann) in einen „Ausgangskorb“ und holt sich den nächsten Knoten vom „Eingangskorb“, damit er sich wieder eine Dreiergruppe von Knoten (Wörtern) angucken kann. Das war das ursprüngliche Design von diesem Kernparser. Das stiess auf Probleme, als IDs  und Adjektive bestehend aus mehreren Wörtern möglich werden sollten. Mehrere Wörter sollten also teils einzeln in Knoten mit je einem Wort und teils zusammgefasst in Knoten mit mehreren Wörtern betrachtet werden. (man vergesse hier nicht, dass der Kernparser schnell sein muss und nicht alle möglichen Permutationen durcharbeiten kann und dann immer ein vollständiges Backtracking durchführen kann). Seitdem konnte der Parser Knoten bei Bedarf „zusammenfalten“ oder „assimilieren“, wie ich es damals genannt habe. Mehrere Wörter konnten zu einem Knoten zusammengefaltet/assimiliert oder getrennt betrachtet werden und die Grammatik (die Regeln des Parsers) bekamen Regeln, wann solch eine Assimilation durchzuführen sei und wann sie aufzulösen sei (alles anhand des syntaktisch und semantischen Kontextes möglichst früh entschieden). Ok, aber dafür war die alte stackbasierte Struktur nicht mehr so wirklich geeignet. Die assimilierten Knoten mussten vom Stack genommen und wieder mittendrin eingefügt werden können. Das klappte die letzten Jahre zwar, aber prinzipiell wurde der Stack dabei wie eine Liste behandelt.

Meine letzte Umstellung ist nun eben ein Umbau auf eine von grundauf als solches realisierte Liste (doppelt, weil in verschiedenen Durchgängen die Eingabe des Spielers von links nach rechts und von rechts nach links analysiert wird) und die assimilierten Knoten werden orthogonal in einer zweiten Liste an ihrem Aufhängeknoten (sozusagen als 2. Dimension) gehalten. Statt eines Stacks, der wie eine Liste behandelt wird, haben wir nun eine Liste, die etwas wie ein Stack behandelt wird.

Das ganze ist deshalb vom Modell her näher an dem, was es tun soll. Das ist schöner. Ausserdem müssen in manchen Fällen mehr als 3 Knoten betrachtet werden und die Listenstruktur macht ein Vor- und Zurückspulen einfacher. Auch sind manche Abarbeitungen jetzt in konstanter Zeit möglich statt mit der Anzahl betroffener Knoten zu skalieren. Leider macht mir LPC als Sprache einen Strich durch die Rechnung, da direkte Structaufrufe evalmässig teurer sind als ich dachte (dazu muss ich wohl noch einen Fehler für LDMUD absetzen), so dass der Parser nach dem Umbau zwar insgesamt besser skaliert, aber bei den häufigsten Anwendungsfällen (ganz kurze Eingaben) weniger Leistung bringt.

Weniger Leistung? Naja, letztendlich sind das Peanuts. Und nach einigen morgentlichen „Programmieren, bevor ich los muss – wo ist mein Kaffee?“-Sitzungen konnte ich meine neuen Routinen verbessern und auch andernorts Evals einsparen, so dass wir uns wieder in im April erreichten Geschwindigkeitsbereichen befinden (es ist eine Frage der Ehre!).

Wohin geht die Reise? Der Parser hat einige Bugs, was lange Adjektive angeht (also bestehend aus mehreren Wörtern), die mit Objekten mit Adjektiven kollidieren, die ein Teil des langen Adjektives sind: Wenn man ein „halb volles Fass“ und ein „volles Fass“ dabei hat, wird nicht immer richtig entschieden. Das stört offensichtlich vor allem Druiden und bei der Verwendung von „alles nicht vollste fass“. Das muss behoben werden.

Danach werde ich, da der Parser jetzt besser vor- und zurückspulen kann, andere, aber weniger dringliche Doppeldeutigkeiten angehen, die auch nicht immer befriedigend gelöst werden. Diese können jetzt früher erkannt und entschieden werden.

All diese Änderungen werde ich jetzt noch eine Zeitlang testen und dann können Avalonier sie hoffentlich in wenigen Wochen erleben. Nicht, dass es gross zu spüren wäre. In so einem Fall hofft man eher, dass sich erstmal nichts wahrnehmbares verändert, weil das heisst, dass sich keine neuen Fehler eingeschlichen haben.

ps: Nebenbei sind wieder einige Fehlermeldungen schöner geworden und ein paar Fehler in den Deklinationsroutinen entdeckt und behoben worden.