Bekämpfung von Speicherlecks (Memory Leaks) in AMX-Skripten
Ein stabiler, kontinuierlicher Serverbetrieb ohne tägliche Neustarts ist das primäre Merkmal eines optimierten Spielmodus-Skripts und korrekter Hosting-Einstellungen. Viele SA:MP/CRMP-Administratoren kennen jedoch das Problem: Nach dem geplanten Neustart läuft der Server perfekt, doch nach 12–24 Stunden schnellt die Arbeitsspeicherauslastung (RAM) des Hosting-Containers auf das Maximum. Dies führt zu einer erzwungenen Beendigung durch den Kernel-Watchdog des Hostings via Out Of Memory (OOM Killer)-Fehler.
Die Ursache hierfür sind sogenannte Speicherlecks (Memory Leaks). In diesem Artikel analysieren wir die Speicher-Anatomie der virtuellen AMX-Maschine der Pawn-Engine, decken versteckte Code-Fallen auf, die unbemerkt RAM verbrauchen, und lernen, wie man sie dauerhaft behebt.
Wie ist der Speicher in der virtuellen AMX-Maschine strukturiert?
Die Skriptsprache Pawn fungiert als High-Level-Ebene über einer abstrakten virtuellen AMX-Maschine. Im Gegensatz zu C++ oder C# є ihre grundlegende Speicherverwaltung vollständig statisch. Wenn Sie ein Skript kompilieren, reserviert der Pawn-Compiler im Voraus feste Speicherblöcke für globale und lokale Variablen (Daten-, Stack- und Heap-Segmente). Daraus folgt eine wesentliche architektonische Erkenntnis:
Reiner Pawn-Code kann für sich genommen kein Speicherleck verursachen, da die Größe Ihrer kompilierten .amx-Datei und deren RAM-Anforderungen zum Kompilierungszeitpunkt festgeschrieben sind.
Woher kommen also die Speicherlecks? Sie entstehen in zwei spezifischen Fällen: Durch die fehlerhafte Nutzung von dynamischem Speicher innerhalb externer Plugins (wie MySQL, Streamer oder RakNet) und durch das Überlaufen dynamischer Entitäten-Pools (z. B. dynamische TextDraws oder 3D-Text-Labels).
Die Hauptverursacher von Speicherlecks auf Spielservern
1. Verwaiste Cache-Handles im MySQL-Plugin (Die häufigste Ursache)
Moderne Versionen des BlueG MySQL-Plugins (ab Version R39+) reservieren dynamischen Heap-Speicher für SQL-Abfrageergebnisse, wenn Sie asynchrone Abfragen via mysql_tquery oder mysql_pquery senden. Wenn Sie diese Datenstrukturen am Ende Ihres Callbacks nicht wieder freigeben, bleibt das entsprechende RAM-Segment dauerhaft blockiert.
Wenn Ihr Server stündlich 200 Spieler-Verbindungen verarbeitet und das Autorisierungs-Skript vergisst, die Cache-Instanzen zu löschen, verliert der Server täglich etwa 50–100 MB Arbeitsspeicher.
Fehlerbehebung: Rufen Sie am Ende jedes Datenbank-Callbacks, in dem ein benutzerdefinierter Cache-Index generiert wird (oder wenn Sie den Thread-Kontext via cache_set_active manuell wechseln), zwingend die Bereinigungsfunktion auf:
forward OnPlayerLogin(playerid);
public OnPlayerLogin(playerid)
{
cache_delete(Cache:cache_id);
return 1;
}
2. Unbegrenztes Erstellen dynamischer Objekte im Streamer (Incognito)
Das Streamer-Plugin ermöglicht es Entwicklern, die nativen SA:MP-Engine-Limits für Kartenobjekte, Checkpoints und 3D-Texte durch Virtualisierung komplett zu umgehen. Wenn Ihr Code jedoch in einem wiederkehrenden Timer-Loop oder bei jedem Spieler-Login CreateDynamicObject ausführt, ohne die Instanz beim Verlassen (DestroyDynamicObject) wieder zu entfernen, füllt sich die Heap-Struktur des Plugins unaufhaltsam.
Beispiel für schlechte Praxis: Das Erstellen eines individuellen Karten-Icons oder eines schwebenden 3D-Textes über einem Spieler beim Login, ohne diesen Kontext in OnPlayerDisconnect wieder freizugeben. Nach 500 kumulativen Verbindungen ist der Server mit Tausenden von „Geister-Referenzen“ überlastet, die im Hintergrund weiterhin verarbeitet werden.
3. String-Formatierung und Heap-Dehnung (Stack/Heap Stretch)
Wenn Ihr Mod rechenintensive rekursive Schleifen verwendet oder übermäßig große lokale Arrays für Textformatierungen deklariert (z. B. new string[65536]; in einem häufig aufgerufenen Timer), führt dies zu einer strukturellen Ausdehnung des Stack/Heap-Segments der virtuellen Maschine.
Die AMX-Engine erweitert den Heap automatisch, falls der lokale Speicherplatz für Variablen nicht ausreicht, reduziert diese Grenze während des laufenden Betriebs jedoch nie wieder. Ein einziger Aufruf einer Funktion mit einem überdimensionierten Array nimmt dem Hosting-Container diesen RAM-Block dauerhaft weg.
Optimierungsregel: Erstellen Sie Arrays niemals „auf Vorrat“. Für normale Chat-Formatierungen reicht eine Array-Größe від string[144]; für Dialog-Interfaces begrenzen Sie die Dimensionen auf Werte zwischen 512 und 2048 Zellen. Nutzen Sie den Operator static für Arrays in Timer-Schleifen, damit der Speicher einmalig beim Start reserviert wird, statt sich mit jedem Tick neu aufzubauen.
Methodik zur Lokalisierung versteckter Speicherlecks
Wenn ein Live-Server spürbar laggt und RAM blockiert, є eine manuelle Überprüfung von über 50.000 Codezeilen unpraktisch. Nutzen Sie stattdessen diesen professionellen Diagnose-Ablauf:
Schritt 1: Integration des Profiler-Plugins von Zeex
Installieren Sie das spezielle Profiler-Plugin (entwickelt von Zeex) direkt in Ihrer Serverumgebung, um eine detaillierte Überwachung der virtuellen AMX-Maschine auszuführt.
- Laden Sie die kompilierte Datei
profiler.soin Ihrenplugins/-Ordner hoch. - Tragen Sie das Plugin als absolut ersten Eintrag in der
plugins-Zeile innerhalb derserver.cfgein. - Lassen Sie den Server für 2–3 Stunden unter normaler Auslastung laufen.
Das Plugin generiert ein detailliertes Protokoll, das zeigt, welche Callback-Funktionen wie oft aufgerufen wurden und wie viel Speicher sie beansprucht haben. Wenn Sie feststellen, dass der Heap-Wert nach dem Aufruf eines bestimmten Callbacks kontinuierlich ansteigt, haben Sie das Problem isoliert.
Schritt 2: Überwachung dynamischer Limits via Konsole
Senden Sie regelmäßig Abfragebefehle an das Server-Terminal, um die Obergrenzen Ihrer Streamer-Pools zu überprüfen. So stellen Sie sicher, dass die Entitäten-Listen nicht unkontrolliert anwachsen:
Streamer_GetUpperBound(STREAMER_TYPE_OBJECT);— Liefert die aktuelle Anzahl der aktiven dynamischen Kartenobjekte im Speicher. Wenn dieser Wert bei gleichbleibender Spielerzahl kontinuierlich steigt, liegt ein Objektleck vor.
Schritt 3: MySQL-Logs auf verwaiste Cache-Handles prüfen
Öffnen Sie das Protokoll mysql_log.txt (stellen Sie sicher, dass für mysql_log der Modus error oder warning aktiv є). Wenn der Treiber Speicherlecks registriert, gibt er folgende standardisierte Warnung aus:
[WARNING] MySQL::Destroy - active cache was not deleted
Dies є ein direkter Beweis dafür, dass eine asynchrone Abfrage ausgeführt wurde, aber die Bereinigungsfunktion cache_delete am Ende des Callbacks vergessen wurde. Überprüfen Sie Ihre neuesten Skripte (wie Inventar-Systeme, Spenden-Updates oder Aktivitäts-Logs), щоб das Leck zu schließen.
| Ressourcen-Typ | Ursache des Speicherlecks | Korrekte Bereinigung / Löschung |
|---|---|---|
| MySQL Cache-Sets | Fehlen von Löschroutinen am Callback-Ende | cache_delete(Cache:...) vor dem Callback-Exit aufrufen |
| Dynamische 3D-Texte | Erstellen neuer Tags über alte Instanzen beim Spieler-Spawn | DestroyDynamic3DTextLabel vor dem Kompilieren eines neuen Tags aufrufen |
| TextDraw Elemente | Verwendung globaler TextDraw-Zeiger als individuelle Spieler-Assets | Nutzen Sie CreatePlayerTextDraw (wird von der Engine automatisch geleert) |