Борьба з витоками пам'яті (Memory Leaks) в AMX-скриптах
Стабільна безперервна робота сервера без перезавантажень протягом кількох днів або тижнів — ознака якісного ігрового мода та правильно налаштованого хостингу. Однак багато адміністраторів SA:MP/CRMP серверів стикаються з такою проблемою: після рестарту сервер працює ідеально, але через 12–24 години споживання оперативної пам'яті (RAM) контейнером хостингу виростає до максимуму, і панель керування примусово вимикає процес сервера з критичною помилкою Out Of Memory (OOM Killer).
Причиною цього є витоки пам'яті (Memory Leaks). У цій статті ми розберемо анатомію пам'яті скриптового рушія Pawn, знайдемо приховані пастки в коді, які непомітно «з'їдають» оперативну пам'ять, і навчимося їх усувати.
Як влаштована пам'ять у віртуальній машині AMX?
Мова Pawn є надбудовою над віртуальною машиною AMX. На відміну від C++ або C#, тут базова пам'ять статична. Коли ви компілюєте мод, компілятор заздалегідь виділяє фіксовані масиви під глобальні та локальні змінні (сегменти даних, стека та купи). З цього випливає важливий висновок:
Сам по собі чистий Pawn-код не може викликати витік пам'яті, оскільки розмір вашого файлу .amx та його базові вимоги до RAM фіксуються в момент компіляції.
Звідки ж беруться витоки? Вони виникають у двох випадках: при некоректному використанні динамічної пам'яті у сторонніх плагінах (MySQL, Стрімер, RakNet) та при переповненні пулу динамічних зон (наприклад, динамічних текстдравів або 3D-текстів).
Головні винуватці витоків пам'яті на ігровому сервері
1. Забуті кеші в плагіні MySQL (Найчастіша причина)
Сучасні версії плагіна MySQL (BlueG R39+) при відправці асинхронних запитів через mysql_tquery або mysql_pquery виділяють область пам'яті під результат відповіді (кеш). Якщо всередині вашого колбека ви не очистите цей кеш, пам'ять залишиться заблокованою назавжди.
Якщо на ваш сервер заходить 200 гравців на годину, і при кожному заході кеш авторизації не очищається, сервер втрачатиме по 50–100 МБ оперативної пам'яті щодня.
Як виправити: Наприкінці кожного колбека, який обробляє відповідь від бази даних, де використовується створення кастомного кешу, або якщо ви вручну перемикаєте контекст через cache_set_active, обов'язково викликайте функцію очищення:
forward OnPlayerLogin(playerid);
public OnPlayerLogin(playerid)
{
cache_delete(Cache:cache_id);
return 1;
}
2. Безкінечний спавн динамічних об'єктів у Стрімері (Incognito)
Плагін Streamer дозволяє обходити ліміти SA:MP на об'єкти, чекпоінти та 3D-тексти, створюючи їх динамічно. Однак, якщо ви створюєте об'єкт функцією CreateDynamicObject всередині циклічного таймера або при кожному вході гравця, але забуваєте його видалити при виході (DestroyDynamicObject), пам'ять плагіна почне стрімко забиватися структурами даних.
Приклад поганої практики: Створення персональної іконки на карті або 3D-тексту над головою при авторизації без подальшого видалення в OnPlayerDisconnect. Через 500 перепідключень гравців пам'ять сервера буде перевантажена тисячами «фантомних» об'єктів, які сервер продовжує прораховувати у фоні.
3. Форматування рядків та витоки в стеку (Stack/Heap Stretch)
Якщо у вашому моді активно використовуються рекурсивні функції або функції з величезними локальними масивами під форматування тексту (наприклад, new string[65536]; всередині таймера, що часто викликається), це призводить до розтягування сегмента Stack/Heap.
Рушій AMX автоматично розширює купу (Heap), якщо локальним змінним не вистачає місця, але він ніколи не звужує її назад у процесі роботи сервера. Один раз викликана функція з надлишковим розміром масиву назавжди забере цей шматок RAM у хостингу.
Правило оптимізації: Ніколи не створюйте масиви «із запасом». Для стандартного форматування рядка чату достатньо string[144], для діалогу — від 512 до 2048. Використовуйте оператор static для масивів у таймерах, щоб пам'ять під них виділялася один раз при запуску, а не плодилася кожен тік.
Методологія виявлення прихованих витоків
Якщо сервер уже лагає і «жере» пам'ять, знайти конкретний рядок коду вручну серед 50 000+ рядків мода неможливо. Використовуйте професійний алгоритм локалізації проблеми:
Крок 1: Підключення плагіна Profiler
Скачайте та встановіть плагін Profiler (від розробника Zeex) на ваш сервер хостингу. Він призначений для детального аудиту роботи AMX-машини.
- Завантажте
profiler.soдо папкиplugins/. - Пропишіть його першим у рядку
pluginsу файліserver.cfg. - Запустіть сервер на 2–3 години в режимі звичайного ігрового процесу.
Плагін запише файл логу, в якому покаже, які функції викликалися найчастіше і скільки пам'яті вони утилізували. Якщо ви побачите, що купа (Heap) росте після виклику певного колбека — ви знайшли джерело проблеми.
Крок 2: Моніторинг динамічних лімітів через консоль
Періодично вводьте в консоль сервера команди перевірки стану Стрімера, щоб побачити, чи не ростуть показники об'єктів безконтрольно:
Streamer_GetUpperBound(STREAMER_TYPE_OBJECT);— покаже поточну кількість динамічних об'єктів у пам'яті. Якщо це число збільшується при незмінному онлайні — в коді є витік створення об'єктів.
Крок 3: Перевірка логів MySQL на "завислі" кеші
Відкрийте файл mysql_log.txt (переконайтеся, че в mysql_log увімкнено режим error або warning). Якщо плагін MySQL фіксує витоки пам'яті, ви побачите системні попередження вигляду:
[WARNING] MySQL::Destroy - active cache was not deleted
Це пряма вказівка на те, що один із ваших SQL-запитів успішно виконався, але ви забули написати cache_delete у його колбеку. Перевірте всі останні додані системи (донат, інвентар, логування) — корінь зла знаходиться там.
| Тип ресурсу | Що викликає витік пам'яті? | Як правильно видаляти / очищати |
|---|---|---|
| MySQL Результати | Пропуск очищення відповідей СУБД | cache_delete(Cache:...) наприкінці колбека |
| Динамічні 3D Тексти | Створення поверх старих при спавні гравця | DestroyDynamic3DTextLabel перед створенням нового |
| Текстдрави (TextDraw) | Використання глобальних TD як персональних | Використовуйте CreatePlayerTextDraw (вони видаляються автоматично) |