From 6cb9305b31b9f38bbdfa7c7cc56055aec450afc6 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: Sat, 19 Jul 2025 01:26:37 +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=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D0=BD=D0=B5=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=B0=20`MDBX=5FDBS=5FFULL`=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B8=20DBI-=D0=B4?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20(backport).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В lockfree-пути открытия DBI-дескрипторов, при просмотре уже открытых таблиц, пропускались элементы отличающиеся не только по имени, но также и при несовпадении запрашиваемых флагов и актуальных флагов уже открытой таблицы. Если при этом уже было достигнуто (ранее заданное) максимальное количество открытых DBI-дескрипторов, то возвращалась ошибка `MDBX_DBS_FULL`, в том числе в ситуациях когда результат должен быть другим. Спасибо [Артёму Воротникову](https://github.com/vorot93) за сообщение о проблеме! --- src/dbi.c | 76 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/src/dbi.c b/src/dbi.c index e5051604..385aa1d9 100644 --- a/src/dbi.c +++ b/src/dbi.c @@ -385,7 +385,7 @@ static int dbi_open_locked(MDBX_txn *txn, unsigned user_flags, MDBX_dbi *dbi, MD slot = (slot < scan) ? slot : scan; continue; } - if (!env->kvs[MAIN_DBI].clc.k.cmp(&name, &env->kvs[scan].name)) { + if (env->kvs[MAIN_DBI].clc.k.cmp(&name, &env->kvs[scan].name) == 0) { slot = scan; int err = dbi_check(txn, slot); if (err == MDBX_BAD_DBI && txn->dbi_state[slot] == (DBI_OLDEN | DBI_LINDO)) { @@ -541,54 +541,68 @@ int dbi_open(MDBX_txn *txn, const MDBX_val *const name, unsigned user_flags, MDB #if MDBX_ENABLE_DBI_LOCKFREE /* Is the DB already open? */ const MDBX_env *const env = txn->env; - size_t free_slot = env->n_dbi; + bool have_free_slot = env->n_dbi < env->max_dbi; for (size_t i = CORE_DBS; i < env->n_dbi; ++i) { - retry: if ((env->dbs_flags[i] & DB_VALID) == 0) { - free_slot = i; + have_free_slot = true; continue; } - const uint32_t snap_seq = atomic_load32(&env->dbi_seqs[i], mo_AcquireRelease); - const uint16_t snap_flags = env->dbs_flags[i]; + struct dbi_snap_result snap = dbi_snap(env, i); const MDBX_val snap_name = env->kvs[i].name; - if (user_flags != MDBX_ACCEDE && - (((user_flags ^ snap_flags) & DB_PERSISTENT_FLAGS) || (keycmp && keycmp != env->kvs[i].clc.k.cmp) || - (datacmp && datacmp != env->kvs[i].clc.v.cmp))) - continue; const uint32_t main_seq = atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease); MDBX_cmp_func *const snap_cmp = env->kvs[MAIN_DBI].clc.k.cmp; - if (unlikely(!(snap_flags & DB_VALID) || !snap_name.iov_base || !snap_name.iov_len || !snap_cmp)) - continue; + if (unlikely(!(snap.flags & DB_VALID) || !snap_name.iov_base || !snap_name.iov_len || !snap_cmp)) + /* похоже на столкновение с параллельно работающим обновлением */ + goto slowpath_locking; const bool name_match = snap_cmp(&snap_name, name) == 0; - osal_flush_incoherent_cpu_writeback(); - if (unlikely(snap_seq != atomic_load32(&env->dbi_seqs[i], mo_AcquireRelease) || + if (unlikely(snap.sequence != atomic_load32(&env->dbi_seqs[i], mo_AcquireRelease) || main_seq != atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease) || - snap_flags != env->dbs_flags[i] || snap_name.iov_base != env->kvs[i].name.iov_base || + snap.flags != env->dbs_flags[i] || snap_name.iov_base != env->kvs[i].name.iov_base || snap_name.iov_len != env->kvs[i].name.iov_len)) - goto retry; - if (name_match) { + /* похоже на столкновение с параллельно работающим обновлением */ + goto slowpath_locking; + + if (!name_match) + continue; + + osal_flush_incoherent_cpu_writeback(); + if (user_flags != MDBX_ACCEDE && + (((user_flags ^ snap.flags) & DB_PERSISTENT_FLAGS) || (keycmp && keycmp != env->kvs[i].clc.k.cmp) || + (datacmp && datacmp != env->kvs[i].clc.v.cmp))) + /* есть подозрение что пользователь открывает таблицу с другими флагами/атрибутами + * или другими компараторами, поэтому уходим в безопасный режим */ + goto slowpath_locking; + + rc = dbi_check(txn, i); + if (rc == MDBX_BAD_DBI && txn->dbi_state[i] == (DBI_OLDEN | DBI_LINDO)) { + /* хендл использовался, стал невалидным, + * но теперь явно пере-открывается в этой транзакци */ + eASSERT(env, !txn->cursors[i]); + txn->dbi_state[i] = DBI_LINDO; rc = dbi_check(txn, i); - if (rc == MDBX_BAD_DBI && txn->dbi_state[i] == (DBI_OLDEN | DBI_LINDO)) { - /* хендл использовался, стал невалидным, - * но теперь явно пере-открывается в этой транзакци */ - eASSERT(env, !txn->cursors[i]); - txn->dbi_state[i] = DBI_LINDO; - rc = dbi_check(txn, i); - } - if (likely(rc == MDBX_SUCCESS)) { - rc = dbi_bind(txn, i, user_flags, keycmp, datacmp); - if (likely(rc == MDBX_SUCCESS)) - *dbi = (MDBX_dbi)i; - } - return rc; } + if (likely(rc == MDBX_SUCCESS)) { + if (unlikely(snap.sequence != atomic_load32(&env->dbi_seqs[i], mo_AcquireRelease) || + main_seq != atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease) || + snap.flags != env->dbs_flags[i] || snap_name.iov_base != env->kvs[i].name.iov_base || + snap_name.iov_len != env->kvs[i].name.iov_len)) + /* похоже на столкновение с параллельно работающим обновлением */ + goto slowpath_locking; + rc = dbi_bind(txn, i, user_flags, keycmp, datacmp); + if (likely(rc == MDBX_SUCCESS)) + *dbi = (MDBX_dbi)i; + } + return rc; } /* Fail, if no free slot and max hit */ - if (unlikely(free_slot >= env->max_dbi)) + if (unlikely(!have_free_slot)) return MDBX_DBS_FULL; + +slowpath_locking: + #endif /* MDBX_ENABLE_DBI_LOCKFREE */ rc = osal_fastmutex_acquire(&txn->env->dbi_lock);