mirror of
https://github.com/isar/libmdbx.git
synced 2025-07-19 15:24:44 +08:00
mdbx: устранение неожиданной ошибки MDBX_BAD_DBI
при гонках внутри процесса.
Запуск читающих и пишущих транзакций взаимно не блокируется. Однако, внутри одного процесса, DBI-хендлы и атрибуты таблиц используются совместно всеми транзакциями (в рамках экземпляра среды работы с БД). Поэтому после изменения атрибутов таблиц, в том числе при первоначальном чтении актуальных атрибутов MainDB, может возникать состояние гонок при одновременном старте нескольких транзакций. Этим коммитом исправляются недочеты в обработке ситуации таких гонок, из-за чего могла возвращается неожиданная (с точки зрения пользователя) ошибка `MDBX_BAD_DBI`. Формально ошибка присутствовала начиная с коммита `e6af7d7c53428ca2892bcbf7eec1c2acee06fd44` от 2023-11-05. Однако, до этого (исторически, как было унаследовано от LMDB) отсутствовал какой-либо контроль смены атрибутов MainDB во время старта и/или работы транзакций. Поэтому вместо возврата каких-либо ошибок подобные состояние гонок и/или связанные с изменением атрибутов MainDB оставались необработанными/незамеченными, либо проявлялись как редкие неуловимые сбои пользовательских приложений. Спасибо [Артёму Воротникову](https://github.com/vorot93) за сообщение о проблеме!
This commit is contained in:
parent
e9d47291b0
commit
07265e9930
21
src/dbi.c
21
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;
|
||||
|
21
src/txn.c
21
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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user