Система отладки синхронизации
Система отладки синхронизации позволяет размещать точки синхронизации в коде сервера с помощью макроса DEBUG_SYNC:
open_tables(...) DEBUG_SYNC(thd, "after_open_tables"); lock_tables(...)
При активации точка синхронизации может
- Выдать сигнал и/или
- Подождать сигнала
| Наименование | Описание |
|---|---|
| сигнал | Значение глобальной переменной, которая сохраняется до перезаписи новым сигналом. Глобальную переменную также можно рассматривать как «пост сигнала» или «флаговый мачта». Тогда сигнал — это то, что прикреплено к «посту сигнала» или «флаговому мачту». |
| выдать сигнал | Присвоить значение (сигнал) глобальной переменной («установить флаг») и транслировать глобальное условие, чтобы разбудить те, кто ожидает сигнала. |
| ждать сигнала | Циклически ожидать глобального условия, пока глобальное значение не совпадёт со значением ожидаемого сигнала. |
По умолчанию все точки синхронизации неактивны. Они ничего не делают (кроме того, что тратят несколько циклов процессора на проверку своей активности).
Точка синхронизации становится активной, когда для неё запрашивается действие. Для этого поместите строку такого вида в файл тестового случая:
SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
Это активирует точку синхронизации 'after_open_tables'. Она запрашивает выдачу сигнала 'opened' и ожидание другого потока, который выдает сигнал 'flushed' при выполнении потока через точку синхронизации.
Для каждой точки синхронизации может быть только одно действие на поток. Каждый поток может запросить несколько действий, но только одно на точку синхронизации. Другими словами, поток может активировать несколько точек синхронизации.
Вот пример, как активировать и использовать точки синхронизации:
--connection conn1
SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
send INSERT INTO t1 VALUES(1);
--connection conn2
SET DEBUG_SYNC= 'now WAIT_FOR opened';
SET DEBUG_SYNC= 'after_abort_locks SIGNAL flushed';
FLUSH TABLE t1;
Когда conn1 проходит через оператор INSERT, он достигает точки синхронизации 'after_open_tables'. Он замечает, что она активна, и выполняет своё действие. Он выдает сигнал 'opened' и ожидает, пока другой поток выдаст сигнал 'flushed'.
conn2 немедленно ожидает у специальной точки синхронизации 'now' другого потока для выдачи сигнала 'opened'.
Сигнал остаётся в силе до его перезаписи. Если conn1 сигнализирует 'opened' до того, как conn2 достигнет 'now', conn2 всё ещё найдёт сигнал 'opened'. В этом случае он не ждёт.
Когда conn2 достигает 'after_abort_locks', он сигнализирует 'flushed', что позволяет conn1 проснуться.
Обычно активация точки синхронизации сбрасывается после её выполнения. Иногда необходимо сохранить точку синхронизации активной для другого выполнения. Вы можете добавить счётчик выполнения к действию:
SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 3';
Это устанавливает счётчик активации точки сигнала в 3. Каждое выполнение уменьшает счётчик. После третьего выполнения точка синхронизации становится неактивной.
Одной из основных целей этой системы является устранение задержек из набора тестов. В большинстве случаев можно переписать тестовые случаи таким образом, чтобы им не пришлось спать. (Но эта система не может синхронизировать несколько процессов.) Однако, для поддержки разработки тестов и в качестве последнего средства, время ожидания точки синхронизации имеет таймаут. Есть значение таймаута по умолчанию, но его можно переопределить:
SET DEBUG_SYNC= 'name WAIT_FOR sig TIMEOUT 10 EXECUTE 2';
TIMEOUT 0 является специальным: если сигнал отсутствует, ожидание немедленно завершается с таймаутом.
Когда ожидание завершилось с таймаутом (даже на TIMEOUT 0), генерируется предупреждение, чтобы оно отображалось в результатах теста.
Вы можете вывести сообщение об ошибке и остановить запрос, когда точка синхронизации достигается определённое количество раз:
SET DEBUG_SYNC= 'name HIT_LIMIT 3';
Или объединить её с сигналом и/или ожиданием:
SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 2 HIT_LIMIT 3';
Здесь первые два срабатывания выдают сигнал, третье — возвращает сообщение об ошибке и останавливает запрос.
В случаях, когда вы не уверены, что действие выполнено и, следовательно, в любом случае сброшено, вы можете принудительно сбросить (деактивировать) точку синхронизации:
SET DEBUG_SYNC= 'name CLEAR';
Если вы хотите очистить все действия и сбросить глобальный сигнал, используйте:
SET DEBUG_SYNC= 'RESET';
Это единственный способ сбросить глобальный сигнал до пустой строки.
Для тестирования самой системы вы можете выполнить точку синхронизации так, как будто она была достигнута:
SET DEBUG_SYNC= 'name TEST';
Формальный синтаксис
Строка, «присваиваемая» переменной DEBUG_SYNC, может содержать:
RESET |
<sync point name> TEST |
<sync point name> CLEAR |
<sync point name> {{SIGNAL <signal name> |
WAIT_FOR <signal name> [TIMEOUT <seconds>]}
[EXECUTE <count>] &| HIT_LIMIT <count>}
Здесь '&|' означает 'и/или'. Это означает, что один из разделов, разделенных '&|', должен быть присутствующим или оба.
Активация/Деактивация
С помощью MariaDB для отладки, её можно включить с помощью параметра командной строки mysqld:
--debug-sync-timeout[=default_wait_timeout_value_in_seconds]
'default_wait_timeout_value_in_seconds' — это значение таймаута по умолчанию для действия WAIT_FOR. Если установлено в ноль, система остаётся отключённой.
Система по умолчанию включена в наборе тестов, но может быть отключена с помощью:
mariadb-test-run.pl ... --debug-sync-timeout=0 ...
Аналогично, значение таймаута ожидания по умолчанию можно установить:
mariadb-test-run.pl ... --debug-sync-timeout=10 ...
Параметр командной строки влияет на читаемое значение системной переменной debug_sync.
- Если система не скомпилирована, системная переменная не существует.
- Если
--debug-sync-timeout=0значение переменной читается как"OFF".
- В противном случае значение читается как
"ON - current signal: "за которым следует текущая строка сигнала, которая может быть пустой.
Читаемое значение переменной одинаково, независимо от того, читается ли оно как глобальное или сессионное значение.
Установка системной переменной debug_sync требует привилегии 'SUPER'. Вы никогда не сможете считать обратно строку, которую вы присвоили переменной, если не присвоите значение, которое уже есть у переменной. Но это даст ошибку разбора. Синтаксически правильная строка разбирается в действие отладки синхронизации и сохраняется отдельно от значения переменной.
Реализация
Псевдокод для точки синхронизации:
#define DEBUG_SYNC(thd, sync_point_name)
if (unlikely(opt_debug_sync_timeout))
debug_sync(thd, STRING_WITH_LEN(sync_point_name))
Точка синхронизации выполняет двоичный поиск в отсортированном массиве действий для этого потока.
Оператор SET DEBUG_SYNC добавляет запрошенное действие в массив или перезаписывает существующее действие для той же точки синхронизации. При добавлении нового действия массив сортируется заново.
Типичный шаблон синхронизации
В MariaDB и MySQL есть много мест, где мы используем такой шаблон синхронизации:
mysql_mutex_lock(&mutex); thd->enter_cond(&condition_variable, &mutex, new_message); #if defined(ENABLE_DEBUG_SYNC) if (!thd->killed && !end_of_wait_condition) DEBUG_SYNC(thd, "sync_point_name"); #endif while (!thd->killed && !end_of_wait_condition) mysql_cond_wait(&condition_variable, &mutex); thd->exit_cond(old_message);
Вот некоторые пояснения:
thd->enter_cond() используется для регистрации переменной условия и мьютекса в thd->mysys_var. Это делается для того, чтобы позволить потоку быть прерванным (уничтоженным) во время сна. Другой поток может найти переменную условия для сигнализации и мьютекс для использования в потоке THD::mysys_var.
thd->enter_cond() требует приобретения мьютекса заранее.
thd->exit_cond() аннулирует переменную условия и мьютекс и освобождает мьютекс.
Если вы хотите иметь точку синхронизации Debug Sync с ожиданием, поместите её после enter_cond(). Только тогда вы можете безопасно определить, будет ли взято ожидание. Также у вас будет THD::proc_info правильным, когда точка синхронизации выдает сигнал. DEBUG_SYNC устанавливает свою собственную proc_info, но восстанавливает предыдущую перед освобождением своего внутреннего мьютекса. Как только другой поток увидит сигнал, он также увидит proc_info до входа в точку синхронизации. В этом случае это будет «new_message», которое ассоциируется с ожиданием, которое должно быть синхронизировано.
В приведённом выше примере условие ожидания повторяется перед точкой синхронизации. Это сделано для пропуска точки синхронизации, если ожидание не происходит. Точка синхронизации находится перед циклом (не внутри цикла), чтобы она срабатывала только один раз. Возможно, что переменная условия сигнализируется несколько раз, не выполняя условие ожидания.
Несколько вне темы: в некоторых местах цикл повторяется вокруг всего шаблона синхронизации:
while (!thd->killed && !end_of_wait_condition)
{
mysql_mutex_lock(&mutex);
thd->enter_cond(&condition_variable, &mutex, new_message);
if (!thd->killed [&& !end_of_wait_condition])
{
[DEBUG_SYNC(thd, "sync_point_name");]
mysql_cond_wait(&condition_variable, &mutex);
}
thd->exit_cond(old_message);
}
Обратите внимание, что важно повторить тест для thd->killed после enter_cond(). В противном случае поток, выполняющий убийство, может убить этот поток после проверки thd->killed в условии цикла и перед регистрацией переменной условия и мьютекса в enter_cond(). В этом случае поток, выполняющий убийство, не знает, что этот поток собирается ожидать переменной условия. Он просто установит THD::killed. Но если мы не проверим это снова, мы уснём, хотя мы убиты. Если поток, выполняющий убийство, убьёт нас, когда мы находимся после второго теста, но всё ещё до засыпания, мы удерживаем мьютекс, который зарегистрирован в mysys_var. Поток, выполняющий убийство, попытается получить мьютекс перед сигнализацией переменной условия. Поскольку мьютекс освобождается только неявно в mysql_cond_wait(), сигнализация происходит в нужном месте. У нас есть безопасная синхронизация.
Работа с системой DBUG
При запуске набора тестов MariaDB с параметром командной строки --debug-dbug, система отладки синхронизации записывает сообщения трассировки в трассировку DBUG. Следующие команды оболочки оказались очень полезными для извлечения релевантной информации:
egrep 'query:|debug_sync_exec:' mysql-test/var/log/mysqld.1.trace
Он отображает все выполненные SQL-запросы и все действия, выполненные точками синхронизации.
Иногда полезно видеть, какие точки синхронизации были пройдены (достигнуты) с выполнением действий или без. Тогда добавьте "|debug_sync_point:" к шаблону egrep.
Синхронизация действий DEBUG_SYNC
Тесты могут потребовать дополнительных механизмов синхронизации между действиями DEBUG_SYNC, поскольку определённые комбинации действий могут привести к потере сигналов. Более конкретно, как только действие SIGNAL выполнено, оно сохраняется в глобальной переменной для потоков ожидания, чтобы определить, зависят ли они от этого сигнала для продолжения. Однако, если последующее действие перезаписывает эту переменную до того, как ожидающий поток сможет проверить её, исходный сигнал теряется. Примеры действий, которые изменяли бы состояние переменной, — это другое действие SIGNAL или RESET. Поэтому перед выполнением этих команд разработчик тестов должен убедиться, что предыдущий сигнал был подтверждён. Следующие фрагменты кода показывают пример проблемного шаблона и потенциального решения.
SET DEBUG_SYNC='now SIGNAL sig'; SET DEBUG_SYNC='RESET'; # Problematic because sig can be cleared before a waiting thread can acknowledge it
SET DEBUG_SYNC='now SIGNAL sig'; # Don't issue the RESET until we have proven the waiting thread has received the signal let $wait_condition= select count(*)=0 from information_schema.processlist where state like "debug sync point%"; source include/wait_condition.inc; SET DEBUG_SYNC='RESET'; # Now this is safe
© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/the-debug-sync-facility/