Тестовая запись, чтобы не забыть.. а то пока я закончу править исходники, забуду с чего начиналось.. хотя я и так забыл, зачем я полез на сервер СУБД...
Вспомнил. Начала падать MariaDB, после случайного обновления до
Сейчас тут остались только следы от builk-insert-ов статистики, поскольку в своем модуле я уже все пофиксил, но первоначально я тут увидел и следы своих "служебных" таблиц, что и заставило меня дальше разбираться с этой проблемой.
ССУТЬ ПРОБЛЕМЫ:
Изначально мой модуль
Так вот эти запросы формируют в памяти драйвера СУБД наборы
Более того, у меня еще есть режим асинхронной работы с базой (это чтобы супер-шустро было) - и там я ОБЯЗАН, не только прочитать все
Ну ладно, у себя я поправил.. Теперь будем править
Основная проблема у них та же, что была и у меня... хитрый кодер, который это писал, вероятно, тоже делал много дел одновременно из-за чего его вспышки патсазнания то давали ему идею сделать обработку
В общем он даже сделал попытку корректного чтения всех наборов result-set в деструкторе:
Но это не помогло, потому что стата открывает "1 подключение" на несколько секунд и юзает его "в хвост и в гриву", выполняя
В общем смысл в том, что поскольку в коде
на стороне MariaDB, мы увидили в логах правильную цепочку для всех запросов: connect + запрос + quit.
Поэтому мы лезем в код, быстренько разбираемся, что нам нужна
Было:
Правим еще 2 косячка попутно.. Получаем:
MysqlQuery.cpp
MysqlDatabase.h
MysqlDatabase.cpp
Добавляем в начало MysqlDatabase.cpp
Меняем тег в
Компилируем "по спецификации". Бац, получили набор сошек и персональную редакцию:
Во вложении *.so. По факту нужна только
Исходники тут: https://disk.yandex.ru/d/j0NCn9eXDHH2Zg
PS: Результат пока проверил только на тестовом.. Ошибки ушли. Завтра Оки "сломает" сервер МСК и будет понятно, работает или нет...
Вспомнил. Начала падать MariaDB, после случайного обновления до
10.11.6... Практически с любыми настройками в конфиге вы гарантированно получаете OOM Kill из-за SEGV в ha_maria::drop_table. Обновление MariaDB до 10.11.18 решило проблему... Однако черт меня надоумил выполнить команду:
C-подобный:
journalctl -u mariadb --since "2 hour ago" | grep "62.122.213.230"
C-подобный:
Sep 25 12:35:36 *.academy-cs.ru mariadbd[4755]: 2026-09-25 12:35:36 167662 [Warning] Aborted connection 167662 to db: 'db_msk_stats' user: '***SECRET***' host: '62.122.213.230' (Unknown error)
Sep 25 12:35:36 *.academy-cs.ru mariadbd[4755]: 2026-09-25 12:35:36 167664 [Warning] Aborted connection 167664 to db: 'db_msk_stats' user: '***SECRET***' host: '62.122.213.230' (Got an error writing communication packets)
Sep 25 12:36:23 *.academy-cs.ru mariadbd[4755]: 2026-09-25 12:36:23 168037 [Warning] Aborted connection 168037 to db: 'db_msk_stats' user: '***SECRET***' host: '62.122.213.230' (Unknown error)
Sep 25 12:37:14 *.academy-cs.ru mariadbd[4755]: 2026-09-25 12:37:14 168576 [Warning] Aborted connection 168576 to db: 'db_msk_stats' user: '***SECRET***' host: '62.122.213.230' (Unknown error)
Сейчас тут остались только следы от builk-insert-ов статистики, поскольку в своем модуле я уже все пофиксил, но первоначально я тут увидел и следы своих "служебных" таблиц, что и заставило меня дальше разбираться с этой проблемой.
ССУТЬ ПРОБЛЕМЫ:
Изначально мой модуль
refsapi и штатный mysqlx некорректно закрывали соединения после потоковых запросов, типа: insert ..; insert ...; insert ...; или update ...; update ...; update ...; либо ЛЮБОЙ другой набор multi-statement запросов (insert ...; select ...; delete ...; update ...; - и все в одном запросе).Так вот эти запросы формируют в памяти драйвера СУБД наборы
result-set, которые "по спецификации" - вы ОБЯЗАНЫ ВСЕ ПРОЧИТАТЬ до закрытия соединения. Поскольку я спецификации читаю редко, только когда что-то сломалось или не получается сделать "по наитию", то я этого не знал, как и, очевидно, разработчики mysqlx. Никогда в жизни я такой фигней не занимался, и всегда делал просто - mysql_close(). И все, дальше не мои проблемы.Более того, у меня еще есть режим асинхронной работы с базой (это чтобы супер-шустро было) - и там я ОБЯЗАН, не только прочитать все
result-set-ы, но еще и прочитать в них все строки mysqlx.. последней версии с github.Основная проблема у них та же, что была и у меня... хитрый кодер, который это писал, вероятно, тоже делал много дел одновременно из-за чего его вспышки патсазнания то давали ему идею сделать обработку
multi-statmen-запросов полноценной, то наотрез отшибали - и он ставил заглушки, вроде return false.В общем он даже сделал попытку корректного чтения всех наборов result-set в деструкторе:
C-подобный:
MysqlResultSet::~MysqlResultSet()
{
if (m_pRes == NULL)
{
return;
}
mysql_free_result(m_pRes);
while (mysql_next_result(m_pMySQL) == 0)
{
m_pRes = mysql_store_result(m_pMySQL);
if (m_pRes != NULL)
{
mysql_free_result(m_pRes);
}
}
}
Но это не помогло, потому что стата открывает "1 подключение" на несколько секунд и юзает его "в хвост и в гриву", выполняя
multi-statmen-запросы чанками/пачками. И к моменту прихода в деструктор m_pMySQL может содержать m_pRes от совсем другого запроса.В общем смысл в том, что поскольку в коде
mysqlx тоже через жопу реализована работа с multi-statmen-запросами, считаем, что все они это bulk-(update или insert), и нам не важны наборы данных (кроме первого), присылаемые драйвером СУБД, нам главное корректно все это отработать "по спецификации" и корректно закрыть соединения, чтобы вот в этой команде:
C-подобный:
SET GLOBAL general_log = 1;
Поэтому мы лезем в код, быстренько разбираемся, что нам нужна
MysqlQuery::ExecuteR(), после чего лезем туда и на манер моей (::get_result()) вносим аналогичные изменения, поскольку закрывать multi-statmen наборы данных -> нужно именно здесь, а не в деструкторе. Почему? Потому!Было:
C-подобный:
bool MysqlQuery::ExecuteR(QueryInfo *info, char *error, size_t maxlength)
{
int err;
if ( (err=mysql_real_query(m_pDatabase->m_pMysql, m_QueryString, (unsigned long)m_QueryLen)) )
{
info->errorcode = mysql_errno(m_pDatabase->m_pMysql);
info->success = false;
info->affected_rows = 0;
info->rs = NULL;
if (error && maxlength)
{
ke::SafeSprintf(error, maxlength, "%s", mysql_error(m_pDatabase->m_pMysql));
}
}
else
{
MYSQL_RES *res = mysql_store_result(m_pDatabase->m_pMysql);
if (!res)
{
if (mysql_field_count(m_pDatabase->m_pMysql) > 0)
{
//error !111!!11
info->errorcode = mysql_errno(m_pDatabase->m_pMysql);
info->success = false;
info->affected_rows = 0;
info->rs = NULL;
} else {
info->errorcode = 0;
info->success = true;
info->affected_rows = mysql_affected_rows(m_pDatabase->m_pMysql);
info->rs = NULL;
}
} else {
info->errorcode = 0;
info->success = true;
info->affected_rows = mysql_affected_rows(m_pDatabase->m_pMysql);
MysqlResultSet *rs = new MysqlResultSet(res, m_pDatabase->m_pMysql);
info->rs = rs;
}
}
return info->success;
}
Правим еще 2 косячка попутно.. Получаем:
MysqlQuery.cpp
C-подобный:
bool MysqlQuery::ExecuteR(QueryInfo *info, char *error, size_t maxlength)
{
MYSQL *mysql = m_pDatabase->m_pMysql;
// При срабатывании MYSQL_OPT_RECONNECT кодировка соединения сбрасывается.
// Проверяем, не было ли тихого реконнекта, и при необходимости восстанавливаем её.
m_pDatabase->EnsureConnection();
int err;
if ((err = mysql_real_query(mysql, m_QueryString, (unsigned long)m_QueryLen)))
{
info->errorcode = mysql_errno(mysql);
info->success = false;
info->affected_rows = 0;
info->rs = NULL;
if (error && maxlength)
{
ke::SafeSprintf(error, maxlength, "%s", mysql_error(mysql));
}
return false;
}
info->errorcode = 0;
info->success = true;
info->affected_rows = 0;
info->rs = NULL;
int status;
// Обрабатываем результат первого стейтмента, затем ОБЯЗАТЕЛЬНО дренажируем
// все остальные. Без этого при CLIENT_MULTI_STATEMENTS непрочитанные
// ответы остаются в буфере, и следующий запрос получает
// 2014 "Commands out of sync".
do
{
MYSQL_RES *res = mysql_store_result(mysql);
if (res)
{
// Первый встретившийся результирующий набор отдаём вызывающему,
// остальные — освобождаем, чтобы не оставлять их в соединении.
if (info->rs == NULL)
{
info->rs = new MysqlResultSet(res, mysql);
}
else
{
mysql_free_result(res);
}
info->affected_rows += mysql_affected_rows(mysql);
}
else if (mysql_field_count(mysql) == 0)
{
// Стейтмент без результирующего набора (INSERT / UPDATE / DELETE и т.п.).
// Это штатная ситуация: накапливаем затронутые строки.
info->affected_rows += mysql_affected_rows(mysql);
}
else
{
// store_result вернул NULL при ненулевом числе полей — ошибка
// получения результата (например, обрыв соединения или нехватка памяти).
info->errorcode = mysql_errno(mysql);
info->success = false;
if (error && maxlength)
{
ke::SafeSprintf(error, maxlength, "%s", mysql_error(mysql));
}
}
// Переходим к следующему результату.
// 0 = есть ещё результат, -1 = результатов больше нет, >0 = ошибка.
status = mysql_next_result(mysql);
if (status > 0)
{
info->errorcode = mysql_errno(mysql);
info->success = false;
if (error && maxlength)
{
ke::SafeSprintf(error, maxlength, "%s", mysql_error(mysql));
}
}
} while (status == 0);
return info->success;
}
MysqlDatabase.h
C-подобный:
class MysqlDatabase : public IDatabase
{
friend class MysqlQuery;
public:
MysqlDatabase(MYSQL *mysql, MysqlDriver *drvr);
~MysqlDatabase();
public:
void FreeHandle();
ISQLDriver *Driver();
public:
IQuery *PrepareQueryFmt(const char *fmt, ...);
IQuery *PrepareQueryFmt(const char *fmt, va_list ap);
IQuery *PrepareQuery(const char *query);
int QuoteString(const char *str, char buffer[], size_t maxlen, size_t *newsize);
bool SetCharacterSet(const char *characterset);
// НОВОЕ: проверка на тихий авто-реконнект и восстановление кодировки.
void EnsureConnection();
private:
void Disconnect();
private:
MYSQL *m_pMysql;
MysqlDriver *m_pParent;
// НОВОЕ: желаемая кодировка и thread_id на момент её установки.
char m_charset[64];
unsigned long m_threadId;
};
MysqlDatabase.cpp
C-подобный:
MysqlDatabase::MysqlDatabase(MYSQL *mysql, MysqlDriver *drvr)
: m_pMysql(mysql), m_pParent(drvr)
{
m_charset[0] = '\0';
m_threadId = mysql_thread_id(m_pMysql);
}
bool MysqlDatabase::SetCharacterSet(const char *characterset)
{
if (mysql_set_character_set(m_pMysql, characterset) == 0)
{
// Запоминаем кодировку и текущий thread_id, чтобы после
// возможного авто-реконнекта уметь восстановить её.
ke::SafeStrcpy(m_charset, sizeof(m_charset), characterset);
m_threadId = mysql_thread_id(m_pMysql);
return true;
}
return false;
}
void MysqlDatabase::EnsureConnection()
{
unsigned long currentThreadId = mysql_thread_id(m_pMysql);
if (currentThreadId != m_threadId)
{
// Сменился thread_id => библиотека тихо переподключилась.
// При авто-реконнекте теряются: кодировка, сессионные переменные,
// транзакции, prepared statements. Восстанавливаем хотя бы кодировку.
m_threadId = currentThreadId;
if (m_charset[0] != '\0')
{
mysql_set_character_set(m_pMysql, m_charset);
}
}
}
int MysqlDatabase::QuoteString(const char *str, char buffer[], size_t maxlen, size_t *newsize)
{
unsigned long size = static_cast<unsigned long>(strlen(str));
// Худший случай: каждый символ экранируется (2 байта) + нуль-терминатор.
size_t needed = size * 2 + 1;
if (maxlen < needed)
{
return static_cast<int>(needed);
}
// Используем хендл соединения: экранирование учитывает реальную
// кодировку соединения (в отличие от mysql_escape_string).
unsigned long result = mysql_real_escape_string(m_pMysql, buffer, str, size);
if (result == static_cast<unsigned long>(-1))
{
return -1; // ошибка (например, некорректная кодировка)
}
if (newsize)
{
*newsize = static_cast<size_t>(result);
}
return 0;
}
void MysqlDatabase::Disconnect()
{
if (m_pMysql)
{
mysql_close(m_pMysql);
m_pMysql = NULL;
}
}
Добавляем в начало MysqlDatabase.cpp
C-подобный:
#include <amtl/am-string.h>
Меняем тег в
public/amxmodx_version.h
C-подобный:
#define AMXX_BUILD_TAG "refs"
Компилируем "по спецификации". Бац, получили набор сошек и персональную редакцию:
C-подобный:
[ 8] AMX Mod X RUN - amxmodx_mm_i386.so v1.10.0-refs ini Start ANY
Во вложении *.so. По факту нужна только
mysql_amxx_i386.so. Но я замутил свой релиз Исходники тут: https://disk.yandex.ru/d/j0NCn9eXDHH2Zg
PS: Результат пока проверил только на тестовом.. Ошибки ушли. Завтра Оки "сломает" сервер МСК и будет понятно, работает или нет...
Вложения
Последнее редактирование: