Повреждение БД и/или потери данных не происходило, проблема лишь в
возврате ложной ошибки.
Благодарю пользователя/разработчика @Dvirsw (https://t.me/Dvirsw) за
сообщения о проблеме и предоставление минимального/оптимального сценария
воспроизведения.
--
Проблема была из-за излишнего условия при контроле внутренего поля
mp_upper в ходе проверки структуры страниц БД.
Поле mp_upper указывает на нижнуюю границу заполнения страницы от конца
к началу. Вследствие того, что значения ключей выравниваетня на четную
границу, это поле четно во всех случаях за исключением LEAF2-страницы
(листовая страница вложенного дерева для множественных значений
финсированной/одинаковой длины одного ключа), на которой размещено
нечетное количество значений нечетной длины.
Ошибка не проявлялась в большинстве случаев (в том числе в
стохастических тестах), так как штатно лишняя проверка производилась
только при чтении страницы и перебалансировке ключей, но не при каждом
добавлении значения. Тем не менее, сценарии тестов требуют
доработки/расширения для явного добавления нечетных dupfixed-сценариев.
После f0d523c507042cc70eeeb690778c9b2be6a8b33f, при использовании
добавленного API блокировок, возможно ложно-положительное определение
состояние "внутри транзакции".
Когда rp_augment_limit не задан пользователем посредством
`MDBX_opt_rp_augment_limit`, то как и ранее он подстраивается в
зависимости от текущего размера БД (актуального кол-ва страниц).
Теперь-же авто-устанавливаемое значение rp_augment_limit вычисляется
обратно-пропорционально `MDBX_opt_gc_time_limit`:
- Если gc_time_limit == 0, то rp_augment_limit устанавливается в 1/3 от
общего кол-ва страниц БД, но не меньше рационального минимума.
Это соответствует прежнему поведению и обеспечивает достаточно глубокую
переработку GC во всех не-экстремальных сценариях.
- При gc_time_limit >= 16_секунд
rp_augment_limit устанавливается в минимальное значение.
- Когда 0 < gc_time_limit < 16_секунд
rp_augment_limit устанавливается между минимумом и 1/3 от размера БД
пропорционально остатку gc_time_limit до 16 секунд.
Соответственно, при больших значениях gc_time_limit, выбирается меньшее
значение rp_augment_limit, и контроль глубины переработки GC
ограничивается в основном по-времени.
Отложенное освобождение позволяет реализовать безопасное выполнение
fastpath/lockfree при повторном открытии из других потоков/транзакцйий
уже открытых subDB, что и происходит при активации добавленной опции
сборки `MDBX_ENABLE_DBI_LOCKFREE`.
Ранее инициализация в транзакциях структур данных, связанных с
dbi-хендлами и subDb, выполнялась непосредственно при запуске
транзакций. Что в сценариях с большим кол-вом dbi-дексприторов (например
libfpta) порождало заметные накладные расходы, которые расли линейно от
общего кол-ва открытых subDb, а не от реально используемых в транзакции.
При использовании одной-двух сотен хендлов, при старте каждой транзакции
могли копироваться и/или обнуляться десятки килобайт. Теперь этот
недостаток устранен.
Изменена схема инициализации, валидации и импорта хендлов открытых после
старта транзакции:
1) Инициализация теперь выполняется отложенна, а при старте транзации
обнуляется только массив с однобайтовыми статустами dbi-хендлов.
При этом доступнва опция сборки `MDBX_ENABLE_DBI_SPARSE`, при активации
которой используется битовая карты, что снижает объем инициализации
при старте транзакции в 8 раз (CHAR_BIT).
2) Переработана валидация dbi-хендлов на входах API, с уменьшением кол-ва
проверок и ветвлений до теоретического минимума.
3) Переработ импорт dbi-хендов открытых после старта транзакци, теперь
при этом не захватывается мьютекс.
Планируется очистка от функций и возможностей ранее объявленных
устаревшими. В частности, будет удалена поддержка пользовательских
функций сравнения, которые были обьявлены устаревшими начиная с версии
0.9, более 33 месяцев назад.
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением недочетов,
в день 100-летия со дня рождения выдающегося советского и российского ученого и конструктора [Влади́мира Фёдоровича У́ткина](https://ru.wikipedia.org/wiki/Уткин,_Владимир_Фёдорович).
Исправления и доработки:
------------------------
- Устранение регресса/ошибки в пути обработки `put(MDBX_MULTIPLE)` при пакетном/оптовом
помещении в БД множественных значений одного ключа (aka multi-value или dupsort).
Проявление проблемы зависит от компилятора и опций оптимизации/кодогенерации, но с большой вероятностью возвращется
ошибка `MDBX_BAD_VALSIZE` (`-30781`), а в отладочных сборках срабатывает проверка `cASSERT(mc, !"Invalid key-size")`.
Сценарии приводящие к другим проявлениям на данный момент не известны.
- Реализована перезапись в `mdbx_put(MDBX_CURRENT)` всех текущих мульти-значений ключа
при отсутствии флага `MDBX_NOOVERWRITE`. Ранее в такой ситуации возвращалась ошибка `MDBX_EMULTIVAL`.
В текущем понимании новое поведение более удобно и не создаёт проблем совместимости с ранее написанным кодом.
- Добавлена возможность использовать `mdbx_cursor_get(MDBX_GET_MULTIPLE)` без предварительной установки
курсора, совмещая операцию пакетного получения данных с позиционированием курсора на передаваемый ключ.
- Микрооптимизация и рефакторинг `cursor_put_nochecklen()` в продолжение исправления
регресса/ошибки в пути обработки `put(MDBX_MULTIPLE)`.
- Уточнение формулировок в описании API, в том числе пояснений о `SIGSEGV`
и недопустимости прямого изменения данных.
Более подробная информация в [ChangeLog](https://libmdbx.dqdkfa.ru/md__change_log.html).
git diff' stat: 24 commits, 18 files changed, 624 insertions(+), 94 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Достаточно запутано:
- Внутри `update_gc()` используется создание записей с резервированием
посредством `put(MDBX_RESERVE)` в циклах с ранним выходом и последующим
заполнением.
- При этом в случае раннего выхода (из цикла из-за изменения набора
страниц) зарезервированное место в добавленных записях остается
незаполненным/неиницилизированным (подкрашенным в Valgrind или ASAN).
- Чтение этих незаполненных/неиницилизированных данных штатно не
происходит, но в отладочных сборках при включении детального уровне
логирования выполняется отладочный вывод значений ключей и данных при
позиционировании курсоров.
- В свою очередь, `update_gc()` либо удаляет, либо заполняет
зарезервированные записи, но для этого требуется позиционирование
курсора, что в отладочных сборках приводит к чтению
незаполненных/неиницилизированных записей и печали Valgrind/ASAN.
Теперь внутри `update_gc()` в отладочных сборках с поддержкой Valgrind
или ASAN место в резервируемых записях явно инициализируется.
- Обеспечении терминирующего нуля даже при нехватке буфера и
опосредованных предупреждений Valgrind из-за чтения внутри strlen()
неинициализированных данных при последующем логировании/печати.
- Ускорение за счет отказа от использования snpruintf().
Достаточно запутанно:
- Для полноценного контроля при использовании Valgrind или ASAN
требуется закрашивать/отравлять отображение файла БД выше границы
распределенных страниц.
- Производить такое подкрашивание/отравление необходимо в синхронизации
с пишущими транзакциями и запросами на изменение геометрии, в том числе
при изменении размера БД и/или геометрии другим процессом.
- Для такой синхронизации логично и проще всего использовать основной
мьютекс/механизм блокировки пишущих транзакций, что и происходит внутри
txn_valgrind().
- Однако, в этой схеме может возникать ошибка EDEADLK, когда
txn_valgrind() вызывается при завершении читающей транзакции
выполняющейся с дополнительной блокировкой пишущих транзакций.
- Как таковая ошибка EDEADLK при этом проблем не создаёт и поэтому
просто игнорируется. Но утилита mdbx_chk при работе в кооперативном
(не эксклюзивном) режиме чтения-записи использует именно такой сценарий,
а возникающую при этом ошибку EDEADLK засчитывает как проблему при
проверке.
= В результате, при использовании Valgrind или ASAN утилита mdbx_chk
запущенная с опциями `-wc` всегда завершается неудачей из-за как минимум
одной проблемы в ходе проверки. Что внешне выглядит как
недочет/ошибка/регресс и создает проблемы при автоматизированном
тестировании.
Добавленный костыль использует atomic-счетчик, который инкремируется до
и декремируется после попытки захвата блокировки изнутри txn_valgrind().
В свою очередь, код обрабатывающий ошибку захвата блокировки, игнорирует
EDEADLK при ненулевом значении счетчика. Активируется костыль только при
сборке с поддержкой Valgrind или включенном ASAN, и не оказывает
никакого влияния в остальных случаях.
Маркер steady/weak в прототипе/заготовке мета-страницы не
инициализировался, но опосредованно читался кодом проверки
когерентности unified buffer/page cache.
Прочитанное не-инициализированное/случайное значение использовалось в
условии одного из ветвлений, но не оказывало какого-либо влияния, так
как в данном контексте все пути приводят к одному инварианту результата.
- удалены переменные-флаги dupdata_flag и do_sub;
- вместо dupdata_flag используется условие dkey.iov_base != nullptr;
- вместо do_sub используется условие flags & F_DUPDATA;
- очищено использование dkey, добавлена инициализация dkey.iov_base в ключевых точках;
- декларация части переменных перенеса ближе к месту использования.
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением недочетов,
в день основания международного детского центра [«Арте́к»](https://ru.wikipedia.org/wiki/Артек).
Исправления и доработки:
------------------------
- Исправление опечатки в имени переменной внутри `mdbx_env_turn_for_recovery()`.
- Обходное решение проблем сборки посредством GCC с использование опций `-m32 -arch=i686 -Ofast`.
- Доработка режима "восстановления" БД и переключения на заданную мета-страницу.
Более подробная информация в [ChangeLog](https://libmdbx.dqdkfa.ru/md__change_log.html).
Мелочи:
-------
- Незначительное уточнение CMake-пробника для `std::filesystem`,
проверяющего необходимость линковки с дополнительными библиотеками C++.
- Устранение минорных предупреждений старых компиляторов в тестах.
- Устранение причины ложно-позитивного предупреждения новых версий GCC в C++ API.
- Исправление ссылки на репозиторий бенчмарка ioarena.
- Добавление перекрестных ссылок в doxygen-документацию по C++ API.
- Уточнение ограничений в разделе [Restrictions & Caveats](https://libmdbx.dqdkfa.ru/intro.html#restrictions).
- Исправление ссылок на описание `mdbx_canary_put()`.
14 files changed, 222 insertions(+), 56 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Обходное решение проблем сборки посредством GCC с использование опций `-m32 -arch=i686 -Ofast`.
Проблема обусловлена ошибкой GCC, из-за которой конструкция `__attribute__((__target__("sse2")))`
не включает полноценное использование инструкций SSE и SSE2, если это не было сделано посредством
опций командной строки, но была использована опция `-Ofast`.
В результате сборка заканчивалась сообщением об ошибке:
gcc/i686-buildroot-linux-gnu/12.2.0/include/xmmintrin.h: In function 'diffcmp2mask_sse2':
gcc/i686-buildroot-linux-gnu/12.2.0/include/xmmintrin.h:814:1: error: inlining failed in call to 'always_inline' '_mm_movemask_ps': target specific option mismatch
814 | _mm_movemask_ps (__m128 __A)
Это позволяет обезопасить БД (снизить шанс её разрушения) если
пользователь при попытке восстановления, либо просто в качестве
эксперимента, задал утилите `mdbx_chk` неверную или опасную комбинацию
параметров.
При этом обычная проверка, как и явное переключение мета-страниц,
работают по-прежнему.
Полная сверка геометрии на совпадение (включая geo.next) не является
ошибкой, но может приводить к выводу бессмысленного предупреждения о
пропуске обновлении/перезаписи геометрии при открытии БД в режиме
восстановления (с явным указанием мета-страницы).
Исправление опечатки в имени переменной внутри `mdbx_env_turn_for_recovery()`,
что приводило к неверному поведению в некоторых ситуациях.
С точки зрения пользователя, с учетом актуальных сценариев использования
утилиты `mdbx_chk`, был только один специфический/редкий сценарий
проявления ошибки/проблемы - когда выполнялась проверка и активация
слабой/weak мета-страницы с НЕ-последней транзакцией после системной
аварии машины, где БД использовалась в хрупком/небезопасном режиме.
В сценарии, при успешной проверке целевой страницы и её последующей
активации выводилось сообщение об ошибке, связанной со срабатыванием
механизма контроля не-когерентности кэша файловой системы и отображенных
в ОЗУ данных БД. При этом БД успешно восстанавливалось и не было
каких-либо негативных последствия, кроме самого сообщения об ошибке.
Технически же ошибка проявлялась при "переключении" на мета-страницу,
когда у хотя-бы одной из двух других мета-страниц номер транзакции был
больше:
* Если содержимое других мета-страниц было корректным, а номера
связанных транзакций были больше, то результирующий номер транзакции в
целевой/активируемой мета-страницы устанавливается без учета этих
мета-страниц и мог быть меньше-или-равным.
* В результате, если такие мета-страницы были в статусе слабых/weak, то
при закрытии БД после переключения могла срабатывать защита от
не-когерентности unified buffer/page cache, а в отладочных сборках могла
срабатывать assert-проверка.
* Если же такие мета-страницы были в статусе сильных/steady, то
переключение на новую мета-страницу могло не давать эффекта либо
приводить к появлению двух мета-страниц с одинаковым номером транзакции,
что является ошибочной ситуацией.