Отложенное освобождение позволяет реализовать безопасное выполнение
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, то
переключение на новую мета-страницу могло не давать эффекта либо
приводить к появлению двух мета-страниц с одинаковым номером транзакции,
что является ошибочной ситуацией.
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением
недочетов, в день 100-летнего юбилея спортивного клуба [«ЦСКА»](https://ru.wikipedia.org/wiki/Центральный_спортивный_клуб_Армии).
Мелочи:
-------
- Обновление патча для старых версий buildroot.
- Использование clang-format-16.
- Использование `enum`-типов вместо `int` для устранения предупреждений GCC 13,
что могло ломать сборку в Fedora 38.
14 files changed, 117 insertions(+), 83 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением
недочетов, в день 100-летнего юбилея спортивного общества [«Динамо»](https://ru.wikipedia.org/wiki/Динамо_(спортивное_общество)).
Благодарности:
--------------
- Max <maxc0d3r@protonmail.com> за сообщение о проблеме экспорта из DSO/DLL
устаревших функций API.
- [`@calvin3721`](https://t.me/calvin3721) за сообщение о проблеме работы
`MainDB` с флагами не по-умолчанию.
Исправления:
------------
- Поправлен экспорт из DSO/DLL устаревших функций,
которые заменены на inline в текущем API.
- Устранено использование неверного компаратора при создании или пересоздании
`MainDB` с флагами/опциями предполагающим использование специфического
компаратора (не по-умолчанию).
Мелочи:
-------
- Удалена дублирующая диагностика внутри `node_read_bigdata()`.
- Исправлены ссылки в описании `mdbx_env_set_geometry()`.
- Добавлен отдельный тест `extra/upsert_alldups` для специфического
сценария замены/перезаписи одним значением всех multi-значений
соответствующих ключу, т.е. замена всех «дубликатов» одним значением.
- В C++ API добавлены варианты `buffer::key_from()` с явным именованием по типу данных.
- Добавлен отдельный тест `extra/maindb_ordinal` для специфического
сценария создания `MainDB` с флагами требующими использования
компаратора не по-умолчанию.
- Рефакторинг проверки "когерентности" мета-страниц.
- Корректировка `osal_vasprintf()` для устранения предупреждений статических анализаторов.
16 files changed, 686 insertions(+), 247 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Стабилизирующий выпуск с исправлением обнаруженных ошибок, устранением
недочетов и технических долгов. Ветка 0.12 считается готовой к
продуктовому использованию, получает статус стабильной и далее будет
получать только исправление ошибок. Разработка будет продолжена в ветке
0.13, а ветка 0.11 становится архивной.
Благодарности:
--------------
- Max <maxc0d3r@protonmail.com> за сообщение о проблеме ERROR_SHARING_VIOLATION
в режиме MDBX_EXCLUSIVE на Windows.
- Alisher Ashyrov <https://t.me/a1is43ras4> за сообщение о проблеме
с assert-проверкой и содействие в отладке.
- Masatoshi Fukunaga <https://gitflic.ru/user/mah0x211> за сообщение о проблеме
`put(MDBX_UPSERT+MDBX_ALLDUPS)` для случая замены всех значений в subDb.
Исправления (без корректировок новых функций):
----------------------------------------------
- Устранен регресс после коммита 474391c83c5f81def6fdf3b0b6f5716a87b78fbf,
приводящий к возврату ERROR_SHARING_VIOLATION в Windows при открытии БД
в режиме MDBX_EXCLUSIVE для чтения-записи.
- Добавлено ограничение размера отображения при коротком read-only файле, для
предотвращения ошибки ERROR_NOT_ENOUGH_MEMORY в Windows, которая возникает
в этом случае и совсем не информативна для пользователя.
- Произведен рефакторинг `dxb_resize()`, в том числе, для устранения срабатывания
assert-проверки `size_bytes == env->me_dxb_mmap.current` в специфических
многопоточных сценариях использования. Проверка срабатывала только в
отладочных сборках, при специфическом наложении во времени читающей и
пишущей транзакции в разных потоках, одновременно с изменением размера БД.
Кроме срабатывание проверки, каких-либо других последствий не возникало.
- Устранена проблема в `put(MDBX_UPSERT+MDBX_ALLDUPS)` для случая замены
всех значений единственного ключа в subDb. В ходе этой операции subDb
становится полностью пустой, без каких-либо страниц и именно эта
ситуация не была учтена в коде, что приводило к повреждению БД
при фиксации такой транзакции.
- Устранена излишняя assert-проверка внутри `override_meta()`.
Что в отладочных сборках могло приводить к ложным срабатываниям
при восстановлении БД, в том числе при автоматическом откате слабых
мета-страниц.
- Скорректированы макросы `__cold`/`__hot`, в том числе для устранения проблемы
`error: inlining failed in call to ‘always_inline FOO(...)’: target specific option mismatch`
при сборке посредством GCC >10.x для SH4.
Ликвидация технических долгов и мелочи:
---------------------------------------
- Исправлены многочисленные опечатки в документации.
- Доработан тест для полной стохастической проверки `MDBX_EKEYMISMATCH` в режиме `MDBX_APPEND`.
- Расширены сценарии запуска `mdbx_chk` из CMake-тестов для проверки как в обычном,
так и эксклюзивном режимах чтения-записи.
- Уточнены спецификаторы `const` и `noexcept` для нескольких методов в C++ API.
- Устранено использование стека под буферы для `wchar`-преобразования путей.
- Для Windows добавлена функция `mdbx_env_get_path()` для получения пути к БД
в формате многобайтных символов.
- Добавлены doxygen-описания для API с широкими символами.
- Устранены предупреждения статического анализатора MSVC,
все они были несущественные, либо ложные.
- Устранено ложное предупреждение GCC при сборке для SH4.
- Добавлена поддержка ASAN (Address Sanitizer) при сборке посредством MSVC.
- Расширен набор перебираемых режимов в скрипте `test/long_stochastic.sh`,
добавлена опция `--extra`.
- В C++ API добавлена поддержка расширенных опций времени выполнения `mdbx::extra_runtime_option`,
аналогично `enum MDBX_option_t` из C API.
- Вывод всех счетчиков page-operations в `mdbx_stat`.
63 files changed, 1161 insertions(+), 569 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Никаких значимых изменений, только обход "странностей" в MSVC.
Как оказалось MSVC распространяет действие директивы
`pragma(warning(supppress:#))` строго на следующую строку, даже если эта
строка является продолжением комментария начатого в самой директиве
и/или не содержит синтаксических конструкций языка.
Поэтому большинство из добавленных ранее директив для подавления ложных
предупреждений, перестало работать после переформатирования исходного
кода.
В том числе для устранения проблемы
`error: inlining failed in call to ‘always_inline FOO(...)’: target specific option mismatch`
при сборке посредством GCC >10.x для SH4.
Fixed cursor_put_nochecklen() internals for case when dupsort'ed named subDb
contains a single key with multiple values (aka duplicates), which are replaced
with a single value by put-operation with the `MDBX_UPSERT+MDBX_ALLDUPS` flags.
In this case, the database becomes completely empty, without any pages.
However exactly this condition was not considered and
thus wasn't handled correctly.
Fixes https://gitflic.ru/project/erthink/libmdbx/issue/8
Thanks Masatoshi Fukunaga <https://gitflic.ru/user/mah0x211> for reporting.
Устранение регресса после a484a1f89bcbf38aeb7a81d6080605f86ddc7933.
Проверка `prev_limit_pgno >= used_pgno` правомочна только в части сценариев,
но не в общем случае.
В том числе, для устранения срабатывания assert-проверки
`size_bytes == env->me_dxb_mmap.current` в специфических многопоточных
сценариях использования.
Проверка срабатывала только в отладочных сборках, при специфическом
наложении во времени читающей и пишущей транзакции в разных потоках,
одновременно с изменением размера БД.
Кроме срабатывание проверки, каких-либо других последствий не возникало.
Цель в предотвращении ошибки ERROR_NOT_ENOUGH_MEMORY в Windows, которая
совсем не информативна для пользователя и возникает в этом случае (когда
файл открыт read-only и короче запрошенного размера).
Цель в том, чтобы уменьшить кол-во условных и безусловных переходов при
сравнениях равно/неравно, в том числе избегать вызовов задаваемых
кастомных компаратаров и memcmp() для коротких ключей/значений.
Существует проблема https://libmdbx.dqdkfa.ru/dead-github/issues/269,
которая проявляется только при специфической неупорядоченности внутри
ядра ОС, когда страницы, записанные в файл отображенный в память,
становятся видны в памяти посредством работы unified page cache:
- если записанная последней мета-страница "обгоняет" ранее записанные,
т.е. когда записанное в файл позже становится видимым в отображении
раньше, чем записанное ранее.
Теперь, вместо постоянной полной сверки записываемых страниц,
выполняется легковесная проверка при старте транзакций, с переключением
в режим "как раньше" при обнаружении проблемы.
В результате, в некоторых сценариях возвращается 5-10%
производительности, а в отдельных синтетических тестах до 30%.
Два существенных изменения:
1. Инкремент и обновление LRU происходит при изменении страницы,
но не при доступе к ней.
2. Устранен регресс, из-за которого страницы в стеке курсора хоть
помечались, но могли быть ошибочно пролиты на диск,
так как dpl_age() возвращал не 0.
Суть в избавлении от лишнего вызова msync(MS_ASYNC) в режимах
MDBX_WRITEMAP+MDBX_SAFE_NOSYNC и т.п.
Гипотетически могут быть системы/платформы, на которых изменения в
разделяемой памяти не видны другим процессам до вызова msync(MS_ASYNC)
и/или до этого вызова не будет инициироваться вытеснение/запись таких
страниц на диск.
Поэтому использование msync(MS_ASYNC) вынесено под опцию
MDBX_MMAP_USE_MS_ASYNC, которая по-умолчанию включена только на системах
с MDBX_MMAP_INCOHERENT_FILE_WRITE или MDBX_MMAP_INCOHERENT_CPU_CACHE.
Это вынужденный читинг для "починки" сравнительных бенчмарков при
размещении БД в /dev/shm.
Проблема в том, что актуальные ядра Linux для файлов размещенных в tmpfs
возвращают mincore=false. В результате, в простейших бенчмарках видно
двукратное снижение производительности, просто из-за вызовов write()
выполняемых для prefault.
Из-за этого, в таких синтетических тестах, новая libmdbx становится
существенно медленнее предыдущих версий, в том числе LMDB.
Ошибка не была замечена ранее из-за много-ходового сценария воспроизведения:
1. Создаём экземпляр MDBX_env посредством mdbx_env_create();
2. Пытаемся открыть БД посредством mdbx_env_open() в режиме
чтения-записи и эта попытка должны быть неудачной;
3. Не освобождая экземпляр MDBX_env повторно открываем его в режиме
только-чтение;
4. Закрываем среду посредством mdbx_env_close().
Падение происходит на пункте 4, либо на пункте 3, если попытка
повторного открытия будет не успешной.
Причина в том, что внутренний экземпляр osal_ioring_t инициализировался
только для режимов чтения-записи, а разрушался всегда. При этом после
первого разрушения намеренно оставался в состоянии вызывающем падение
при использовании без инициализации.
[Simon Leier](https://t.me/leisim) сообщал об этой проблеме (теперь
понятно что это было), но из-за сложности сценария проблему не удалось
воспроизвести и идентифицировать.
Ранее упущенный не очевидный момент: При работе БД в режимах
не-синхронной/отложенной фиксации на диске, все процессы-писатели должны
иметь одинаковый режим MDBX_WRITEMAP.
В противном случае, сброс на диск следует выполнять дважды: сначала
msync(), затем fdatasync(). При этом msync() не обязан отрабатывать в
процессах без MDBX_WRITEMAP, так как файл в память отображен только для
чтения. Поэтому, в общем случае, различия по MDBX_WRITEMAP не позволяют
выполнить фиксацию данных на диск, после их изменения в другом процессе.
В режиме MDBX_UTTERLY_NOSYNC позволять совместную работу с MDBX_WRITEMAP
также не следует, поскольку никакой процесс (в том числе последний) не
может гарантированно сбросить данные на диск, а следовательно не должен
помечать какую-либо транзакцию как steady.
В результате, требуется либо запретить совместную работу процессам с
разным MDBX_WRITEMAP в режиме отложенной записи, либо отслеживать такое
смешивание и блокировать steady-пометки - что контрпродуктивно.