From 07265e9930f279f95f974375e63fc88a9ab6f47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=D0=AE=D1=80=D1=8C?= =?UTF-8?q?=D0=B5=D0=B2=20=28Leonid=20Yuriev=29?= Date: Wed, 16 Jul 2025 18:57:29 +0300 Subject: [PATCH] =?UTF-8?q?mdbx:=20=D1=83=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B5=D0=BE=D0=B6=D0=B8=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20`MDBX=5FBAD=5FDBI`=20=D0=BF=D1=80=D0=B8=20=D0=B3=D0=BE?= =?UTF-8?q?=D0=BD=D0=BA=D0=B0=D1=85=20=D0=B2=D0=BD=D1=83=D1=82=D1=80=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Запуск читающих и пишущих транзакций взаимно не блокируется. Однако, внутри одного процесса, DBI-хендлы и атрибуты таблиц используются совместно всеми транзакциями (в рамках экземпляра среды работы с БД). Поэтому после изменения атрибутов таблиц, в том числе при первоначальном чтении актуальных атрибутов MainDB, может возникать состояние гонок при одновременном старте нескольких транзакций. Этим коммитом исправляются недочеты в обработке ситуации таких гонок, из-за чего могла возвращается неожиданная (с точки зрения пользователя) ошибка `MDBX_BAD_DBI`. Формально ошибка присутствовала начиная с коммита `e6af7d7c53428ca2892bcbf7eec1c2acee06fd44` от 2023-11-05. Однако, до этого (исторически, как было унаследовано от LMDB) отсутствовал какой-либо контроль смены атрибутов MainDB во время старта и/или работы транзакций. Поэтому вместо возврата каких-либо ошибок подобные состояние гонок и/или связанные с изменением атрибутов MainDB оставались необработанными/незамеченными, либо проявлялись как редкие неуловимые сбои пользовательских приложений. Спасибо [Артёму Воротникову](https://github.com/vorot93) за сообщение о проблеме! --- src/dbi.c | 21 +++++++++++++-------- src/txn.c | 21 +++++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/dbi.c b/src/dbi.c index 871632a8..ef7a1411 100644 --- a/src/dbi.c +++ b/src/dbi.c @@ -84,7 +84,7 @@ __noinline int dbi_import(MDBX_txn *txn, const size_t dbi) { /* dbi-слот еще не инициализирован в транзакции, а хендл не использовался */ txn->cursors[dbi] = nullptr; MDBX_txn *const parent = txn->parent; - if (parent) { + if (unlikely(parent)) { /* вложенная пишущая транзакция */ int rc = dbi_check(parent, dbi); /* копируем состояние dbi-хендла очищая new-флаги. */ @@ -100,26 +100,31 @@ __noinline int dbi_import(MDBX_txn *txn, const size_t dbi) { txn->dbi_state[dbi] = DBI_LINDO; } else { eASSERT(env, txn->dbi_seqs[dbi] != env->dbi_seqs[dbi].weak); - if (unlikely((txn->dbi_state[dbi] & (DBI_VALID | DBI_OLDEN)) || txn->cursors[dbi])) { - /* хендл уже использовался в транзакции, но был закрыт или переоткрыт, - * либо при явном пере-открытии хендла есть висячие курсоры */ - eASSERT(env, (txn->dbi_state[dbi] & DBI_STALE) == 0); + if (unlikely(txn->cursors[dbi])) { + /* хендл уже использовался в транзакции и остались висячие курсоры */ txn->dbi_seqs[dbi] = env->dbi_seqs[dbi].weak; txn->dbi_state[dbi] = DBI_OLDEN | DBI_LINDO; - return txn->cursors[dbi] ? MDBX_DANGLING_DBI : MDBX_BAD_DBI; + return MDBX_DANGLING_DBI; + } + if (unlikely(txn->dbi_state[dbi] & (DBI_OLDEN | DBI_VALID))) { + /* хендл уже использовался в транзакции, но был закрыт или переоткрыт, + * висячих курсоров нет */ + txn->dbi_seqs[dbi] = env->dbi_seqs[dbi].weak; + txn->dbi_state[dbi] = DBI_OLDEN | DBI_LINDO; + return MDBX_BAD_DBI; } } /* хендл не использовался в транзакции, либо явно пере-отрывается при * отсутствии висячих курсоров */ - eASSERT(env, (txn->dbi_state[dbi] & DBI_LINDO) && !txn->cursors[dbi]); + eASSERT(env, (txn->dbi_state[dbi] & (DBI_LINDO | DBI_VALID)) == DBI_LINDO && !txn->cursors[dbi]); /* читаем актуальные флаги и sequence */ struct dbi_snap_result snap = dbi_snap(env, dbi); txn->dbi_seqs[dbi] = snap.sequence; if (snap.flags & DB_VALID) { txn->dbs[dbi].flags = snap.flags & DB_PERSISTENT_FLAGS; - txn->dbi_state[dbi] = DBI_LINDO | DBI_VALID | DBI_STALE; + txn->dbi_state[dbi] = (dbi >= CORE_DBS) ? DBI_LINDO | DBI_VALID | DBI_STALE : DBI_LINDO | DBI_VALID; return MDBX_SUCCESS; } return MDBX_BAD_DBI; diff --git a/src/txn.c b/src/txn.c index 93f3d66f..9b70367e 100644 --- a/src/txn.c +++ b/src/txn.c @@ -155,7 +155,7 @@ int txn_renew(MDBX_txn *txn, unsigned flags) { txn->dbi_seqs[FREE_DBI] = 0; txn->dbi_seqs[MAIN_DBI] = atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease); - if (unlikely(env->dbs_flags[MAIN_DBI] != (DB_VALID | txn->dbs[MAIN_DBI].flags))) { + if (unlikely(env->dbs_flags[MAIN_DBI] != (DB_VALID | txn->dbs[MAIN_DBI].flags) || !txn->dbi_seqs[MAIN_DBI])) { const bool need_txn_lock = env->basal_txn && env->basal_txn->owner != osal_thread_self(); bool should_unlock = false; if (need_txn_lock) { @@ -167,24 +167,24 @@ int txn_renew(MDBX_txn *txn, unsigned flags) { } rc = osal_fastmutex_acquire(&env->dbi_lock); if (likely(rc == MDBX_SUCCESS)) { - uint32_t seq = dbi_seq_next(env, MAIN_DBI); /* проверяем повторно после захвата блокировки */ + uint32_t seq = atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease); if (env->dbs_flags[MAIN_DBI] != (DB_VALID | txn->dbs[MAIN_DBI].flags)) { - if (!need_txn_lock || should_unlock || - /* если нет активной пишущей транзакции, - * то следующая будет ждать на dbi_lock */ - !env->txn) { - if (env->dbs_flags[MAIN_DBI] != 0 || MDBX_DEBUG) + if (!(env->dbs_flags[MAIN_DBI] & DB_VALID) || !need_txn_lock || should_unlock || + /* если нет активной пишущей транзакции, * то следующая будет ждать на dbi_lock */ !env->txn) { + if (env->dbs_flags[MAIN_DBI] & DB_VALID) { NOTICE("renew MainDB for %s-txn %" PRIaTXN " since db-flags changes 0x%x -> 0x%x", (txn->flags & MDBX_TXN_RDONLY) ? "ro" : "rw", txn->txnid, env->dbs_flags[MAIN_DBI] & ~DB_VALID, txn->dbs[MAIN_DBI].flags); - env->dbs_flags[MAIN_DBI] = DB_POISON; - atomic_store32(&env->dbi_seqs[MAIN_DBI], seq, mo_AcquireRelease); + seq = dbi_seq_next(env, MAIN_DBI); + env->dbs_flags[MAIN_DBI] = DB_POISON; + atomic_store32(&env->dbi_seqs[MAIN_DBI], seq, mo_AcquireRelease); + } rc = tbl_setup(env, &env->kvs[MAIN_DBI], &txn->dbs[MAIN_DBI]); if (likely(rc == MDBX_SUCCESS)) { seq = dbi_seq_next(env, MAIN_DBI); env->dbs_flags[MAIN_DBI] = DB_VALID | txn->dbs[MAIN_DBI].flags; - txn->dbi_seqs[MAIN_DBI] = atomic_store32(&env->dbi_seqs[MAIN_DBI], seq, mo_AcquireRelease); + atomic_store32(&env->dbi_seqs[MAIN_DBI], seq, mo_AcquireRelease); } } else { ERROR("MainDB db-flags changes 0x%x -> 0x%x ahead of read-txn " @@ -193,6 +193,7 @@ int txn_renew(MDBX_txn *txn, unsigned flags) { rc = MDBX_INCOMPATIBLE; } } + txn->dbi_seqs[MAIN_DBI] = seq; ENSURE(env, osal_fastmutex_release(&env->dbi_lock) == MDBX_SUCCESS); } else { DEBUG("dbi_lock failed, err %d", rc);