mdbx: рефакторинг mdbx_txn_commit_ex() 5/5 (вычленение txn_basal_end()).

This commit is contained in:
Леонид Юрьев (Leonid Yuriev) 2025-01-10 12:13:47 +03:00
parent 6d92a778a5
commit 10e7e5c899
3 changed files with 116 additions and 127 deletions

View File

@ -271,8 +271,8 @@ int mdbx_txn_begin_ex(MDBX_env *env, MDBX_txn *parent, MDBX_txn_flags_t flags, M
}
static void latency_gcprof(MDBX_commit_latency *latency, const MDBX_txn *txn) {
if (latency && MDBX_ENABLE_PROFGC) {
MDBX_env *const env = txn->env;
MDBX_env *const env = txn->env;
if (latency && likely(env->lck) && MDBX_ENABLE_PROFGC) {
pgop_stat_t *const ptr = &env->lck->pgops;
latency->gc_prof.work_counter = ptr->gc_prof.work.spe_counter;
latency->gc_prof.work_rtime_monotonic = osal_monotime_to_16dot16(ptr->gc_prof.work.rtime_monotonic);
@ -336,12 +336,9 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
struct commit_timestamp ts;
latency_init(latency, &ts);
/* txn_end() mode for a commit which writes nothing */
unsigned end_mode = TXN_END_PURE_COMMIT | TXN_END_UPDATE | TXN_END_SLOT | TXN_END_FREE;
int rc = check_txn(txn, MDBX_TXN_FINISHED);
if (unlikely(rc != MDBX_SUCCESS)) {
if (rc == MDBX_BAD_TXN && (txn->flags & MDBX_TXN_RDONLY)) {
if (rc == MDBX_BAD_TXN && F_ISSET(txn->flags, MDBX_TXN_FINISHED | MDBX_TXN_RDONLY)) {
rc = MDBX_RESULT_TRUE;
goto fail;
}
@ -355,19 +352,18 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
return LOG_IFERR(rc);
}
if (unlikely(txn->flags & MDBX_TXN_RDONLY)) {
if (txn->flags & MDBX_TXN_RDONLY) {
if (unlikely(txn->parent || (txn->flags & MDBX_TXN_HAS_CHILD) || txn == env->txn || txn == env->basal_txn)) {
ERROR("attempt to commit %s txn %p", "strange read-only", (void *)txn);
return MDBX_PROBLEM;
}
if (txn->flags & MDBX_TXN_ERROR) {
rc = MDBX_RESULT_TRUE;
goto fail;
}
latency_gcprof(latency, txn);
rc = (txn->flags & MDBX_TXN_ERROR) ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
txn_end(txn, TXN_END_PURE_COMMIT | TXN_END_UPDATE | TXN_END_SLOT | TXN_END_FREE);
goto done;
}
if (!txn->parent && (txn->flags & MDBX_NOSTICKYTHREADS) && unlikely(txn->owner != osal_thread_self())) {
if ((txn->flags & MDBX_NOSTICKYTHREADS) && txn == env->basal_txn && unlikely(txn->owner != osal_thread_self())) {
txn->flags |= MDBX_TXN_ERROR;
rc = MDBX_THREAD_MISMATCH;
return LOG_IFERR(rc);
@ -375,7 +371,12 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
if (unlikely(txn->flags & MDBX_TXN_ERROR)) {
rc = MDBX_RESULT_TRUE;
goto fail;
fail:
latency_gcprof(latency, txn);
int err = txn_abort(txn);
if (unlikely(err != MDBX_SUCCESS))
rc = err;
goto done;
}
if (txn->nested) {
@ -395,46 +396,27 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
ERROR("attempt to commit %s txn %p", "strange nested", (void *)txn);
return MDBX_PROBLEM;
}
latency_gcprof(latency, txn);
rc = txn_nested_join(txn, latency ? &ts : nullptr);
if (likely(rc == MDBX_SUCCESS))
goto provide_latency;
if (rc == MDBX_RESULT_TRUE) {
/* fast completion of pure nested transaction */
end_mode = TXN_END_PURE_COMMIT | TXN_END_SLOT | TXN_END_FREE;
goto done;
}
goto fail;
goto done;
}
rc = txn_basal_commit(txn, latency ? &ts : nullptr);
end_mode = TXN_END_COMMITTED | TXN_END_UPDATE;
if (likely(rc == MDBX_SUCCESS))
goto done;
if (rc == MDBX_RESULT_TRUE) {
#if MDBX_NOSUCCESS_PURE_COMMIT
rc = txn_end(txn, end_mode);
if (unlikely(rc != MDBX_SUCCESS))
goto fail;
rc = MDBX_RESULT_TRUE;
goto provide_latency;
#else
goto done;
#endif /* MDBX_NOSUCCESS_PURE_COMMIT */
latency_gcprof(latency, txn);
int end = TXN_END_COMMITTED | TXN_END_UPDATE;
if (unlikely(rc != MDBX_SUCCESS)) {
end = TXN_END_ABORT;
if (rc == MDBX_RESULT_TRUE) {
end = TXN_END_PURE_COMMIT | TXN_END_UPDATE;
rc = MDBX_NOSUCCESS_PURE_COMMIT ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
}
}
fail:
txn->flags |= MDBX_TXN_ERROR;
if (latency)
latency_gcprof(latency, txn);
txn_abort(txn);
goto provide_latency;
int err = txn_end(txn, end);
if (unlikely(err != MDBX_SUCCESS))
rc = err;
done:
if (latency)
latency_gcprof(latency, txn);
rc = txn_end(txn, end_mode);
provide_latency:
latency_done(latency, &ts);
return LOG_IFERR(rc);
}

View File

@ -78,6 +78,8 @@ struct commit_timestamp {
};
MDBX_INTERNAL int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts);
MDBX_INTERNAL int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts);
MDBX_INTERNAL int txn_basal_end(MDBX_txn *txn, unsigned mode);
MDBX_INTERNAL int txn_rdo_end(MDBX_txn *txn, unsigned mode);
/* env.c */
MDBX_INTERNAL int env_open(MDBX_env *env, mdbx_mode_t mode);

167
src/txn.c
View File

@ -439,6 +439,7 @@ int txn_abort(MDBX_txn *txn) {
txn_abort(txn->nested);
tASSERT(txn, (txn->flags & MDBX_TXN_ERROR) || dpl_check(txn));
txn->flags |= /* avoid merge cursors' state */ MDBX_TXN_ERROR;
return txn_end(txn, TXN_END_ABORT | TXN_END_SLOT | TXN_END_FREE);
}
@ -839,8 +840,9 @@ bailout:
return rc;
}
static int txn_ro_end(MDBX_txn *txn, unsigned mode) {
int txn_ro_end(MDBX_txn *txn, unsigned mode) {
MDBX_env *const env = txn->env;
tASSERT(txn, (txn->flags & txn_may_have_cursors) == 0);
txn->n_dbi = 0; /* prevent further DBI activity */
if (txn->to.reader) {
reader_slot_t *slot = txn->to.reader;
@ -899,43 +901,14 @@ int txn_end(MDBX_txn *txn, unsigned mode) {
txn->dbs[MAIN_DBI].root, txn->dbs[FREE_DBI].root);
tASSERT(txn, txn->signature == txn_signature && !txn->nested && !(txn->flags & MDBX_TXN_HAS_CHILD));
if (txn->flags & txn_may_have_cursors) {
txn->flags |= /* avoid merge cursors' state */ MDBX_TXN_ERROR;
if (txn->flags & txn_may_have_cursors)
txn_done_cursors(txn);
}
MDBX_env *const env = txn->env;
MDBX_txn *const parent = txn->parent;
if (txn == env->basal_txn) {
tASSERT(txn, !parent && !(txn->flags & MDBX_TXN_RDONLY));
tASSERT(txn, (txn->flags & MDBX_TXN_FINISHED) == 0 && txn->owner);
if (unlikely(txn->flags & MDBX_TXN_FINISHED))
return MDBX_SUCCESS;
ENSURE(env, txn->txnid >= /* paranoia is appropriate here */ env->lck->cached_oldest.weak);
dxb_sanitize_tail(env, nullptr);
txn->flags = MDBX_TXN_FINISHED;
env->txn = nullptr;
pnl_free(txn->tw.spilled.list);
txn->tw.spilled.list = nullptr;
eASSERT(env, txn->parent == nullptr);
pnl_shrink(&txn->tw.retired_pages);
pnl_shrink(&txn->tw.repnl);
if (!(env->flags & MDBX_WRITEMAP))
dpl_release_shadows(txn);
/* Export or close DBI handles created in this txn */
int err = dbi_update(txn, (mode & TXN_END_UPDATE) != 0);
if (unlikely(err != MDBX_SUCCESS)) {
ERROR("unexpected error %d during export the state of dbi-handles to env", err);
err = MDBX_PROBLEM;
}
/* The writer mutex was locked in mdbx_txn_begin. */
lck_txn_unlock(env);
return err;
tASSERT(txn, !parent && !(txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED)) && txn->owner);
return txn_basal_end(txn, mode);
}
if (txn->flags & MDBX_TXN_RDONLY) {
@ -1072,7 +1045,7 @@ int txn_unpark(MDBX_txn *txn) {
return err ? err : MDBX_OUSTED;
}
MDBX_txn *txn_basal_create(const size_t max_dbi) {
__cold MDBX_txn *txn_basal_create(const size_t max_dbi) {
MDBX_txn *txn = nullptr;
const intptr_t bitmap_bytes =
#if MDBX_ENABLE_DBI_SPARSE
@ -1107,7 +1080,7 @@ MDBX_txn *txn_basal_create(const size_t max_dbi) {
return txn;
}
void txn_basal_destroy(MDBX_txn *txn) {
__cold void txn_basal_destroy(MDBX_txn *txn) {
dpl_free(txn);
txl_free(txn->tw.gc.retxl);
pnl_free(txn->tw.retired_pages);
@ -1266,6 +1239,7 @@ int txn_nested_create(MDBX_txn *parent, const MDBX_txn_flags_t flags) {
void txn_nested_abort(MDBX_txn *nested) {
MDBX_txn *const parent = nested->parent;
tASSERT(nested, !(nested->flags & txn_may_have_cursors));
nested->signature = 0;
nested->owner = 0;
@ -1314,7 +1288,7 @@ int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts) {
tASSERT(txn, txn->tw.loose_count == 0);
VERBOSE("fast-complete pure nested txn %" PRIaTXN, txn->txnid);
return MDBX_RESULT_TRUE;
return txn_end(txn, TXN_END_PURE_COMMIT | TXN_END_SLOT | TXN_END_FREE);
}
/* Preserve space for spill list to avoid parent's state corruption
@ -1396,6 +1370,7 @@ int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts) {
txn_merge(parent, txn, parent_retired_len);
env->txn = parent;
parent->nested = nullptr;
parent->flags &= ~MDBX_TXN_HAS_CHILD;
tASSERT(parent, dpl_check(parent));
#if MDBX_ENABLE_REFUND
@ -1419,6 +1394,35 @@ int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts) {
return MDBX_SUCCESS;
}
int txn_basal_end(MDBX_txn *txn, unsigned mode) {
MDBX_env *const env = txn->env;
tASSERT(txn, (txn->flags & (MDBX_TXN_FINISHED | txn_may_have_cursors)) == 0 && txn->owner);
ENSURE(env, txn->txnid >= /* paranoia is appropriate here */ env->lck->cached_oldest.weak);
dxb_sanitize_tail(env, nullptr);
txn->flags = MDBX_TXN_FINISHED;
env->txn = nullptr;
pnl_free(txn->tw.spilled.list);
txn->tw.spilled.list = nullptr;
eASSERT(env, txn->parent == nullptr);
pnl_shrink(&txn->tw.retired_pages);
pnl_shrink(&txn->tw.repnl);
if (!(env->flags & MDBX_WRITEMAP))
dpl_release_shadows(txn);
/* Export or close DBI handles created in this txn */
int err = dbi_update(txn, (mode & TXN_END_UPDATE) != 0);
if (unlikely(err != MDBX_SUCCESS)) {
ERROR("unexpected error %d during export the state of dbi-handles to env", err);
err = MDBX_PROBLEM;
}
/* The writer mutex was locked in mdbx_txn_begin. */
lck_txn_unlock(env);
return err;
}
int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts) {
MDBX_env *const env = txn->env;
tASSERT(txn, txn == env->basal_txn && !txn->parent && !txn->nested);
@ -1432,8 +1436,53 @@ int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts) {
if (txn->flags & txn_may_have_cursors)
txn_done_cursors(txn);
bool need_flush_for_nometasync = false;
const meta_ptr_t head = meta_recent(env, &txn->tw.troika);
const uint32_t meta_sync_txnid = atomic_load32(&env->lck->meta_sync_txnid, mo_Relaxed);
/* sync prev meta */
if (head.is_steady && meta_sync_txnid != (uint32_t)head.txnid) {
/* Исправление унаследованного от LMDB недочета:
*
* Всё хорошо, если все процессы работающие с БД не используют WRITEMAP.
* Тогда мета-страница (обновленная, но не сброшенная на диск) будет
* сохранена в результате fdatasync() при записи данных этой транзакции.
*
* Всё хорошо, если все процессы работающие с БД используют WRITEMAP
* без MDBX_AVOID_MSYNC.
* Тогда мета-страница (обновленная, но не сброшенная на диск) будет
* сохранена в результате msync() при записи данных этой транзакции.
*
* Если же в процессах работающих с БД используется оба метода, как sync()
* в режиме MDBX_WRITEMAP, так и записи через файловый дескриптор, то
* становится невозможным обеспечить фиксацию на диске мета-страницы
* предыдущей транзакции и данных текущей транзакции, за счет одной
* sync-операцией выполняемой после записи данных текущей транзакции.
* Соответственно, требуется явно обновлять мета-страницу, что полностью
* уничтожает выгоду от NOMETASYNC. */
const uint32_t txnid_dist = ((txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC) ? MDBX_NOMETASYNC_LAZY_FD
: MDBX_NOMETASYNC_LAZY_WRITEMAP;
/* Смысл "магии" в том, чтобы избежать отдельного вызова fdatasync()
* или msync() для гарантированной фиксации на диске мета-страницы,
* которая была "лениво" отправлена на запись в предыдущей транзакции,
* но не сброшена на диск из-за активного режима MDBX_NOMETASYNC. */
if (
#if defined(_WIN32) || defined(_WIN64)
!env->ioring.overlapped_fd &&
#endif
meta_sync_txnid == (uint32_t)head.txnid - txnid_dist)
need_flush_for_nometasync = true;
else {
int err = meta_sync(env, head);
if (unlikely(err != MDBX_SUCCESS)) {
ERROR("txn-%s: error %d", "presync-meta", err);
return err;
}
}
}
if ((!txn->tw.dirtylist || txn->tw.dirtylist->length == 0) &&
(txn->flags & (MDBX_TXN_DIRTY | MDBX_TXN_SPILLS)) == 0) {
(txn->flags & (MDBX_TXN_DIRTY | MDBX_TXN_SPILLS | MDBX_TXN_NOSYNC | MDBX_TXN_NOMETASYNC)) == 0 &&
!need_flush_for_nometasync && !head.is_steady && !AUDIT_ENABLED()) {
TXN_FOREACH_DBI_ALL(txn, i) { tASSERT(txn, !(txn->dbi_state[i] & DBI_DIRTY)); }
/* fast completion of pure transaction */
return MDBX_NOSUCCESS_PURE_COMMIT ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
@ -1497,50 +1546,6 @@ int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts) {
return rc;
}
bool need_flush_for_nometasync = false;
const meta_ptr_t head = meta_recent(env, &txn->tw.troika);
const uint32_t meta_sync_txnid = atomic_load32(&env->lck->meta_sync_txnid, mo_Relaxed);
/* sync prev meta */
if (head.is_steady && meta_sync_txnid != (uint32_t)head.txnid) {
/* Исправление унаследованного от LMDB недочета:
*
* Всё хорошо, если все процессы работающие с БД не используют WRITEMAP.
* Тогда мета-страница (обновленная, но не сброшенная на диск) будет
* сохранена в результате fdatasync() при записи данных этой транзакции.
*
* Всё хорошо, если все процессы работающие с БД используют WRITEMAP
* без MDBX_AVOID_MSYNC.
* Тогда мета-страница (обновленная, но не сброшенная на диск) будет
* сохранена в результате msync() при записи данных этой транзакции.
*
* Если же в процессах работающих с БД используется оба метода, как sync()
* в режиме MDBX_WRITEMAP, так и записи через файловый дескриптор, то
* становится невозможным обеспечить фиксацию на диске мета-страницы
* предыдущей транзакции и данных текущей транзакции, за счет одной
* sync-операцией выполняемой после записи данных текущей транзакции.
* Соответственно, требуется явно обновлять мета-страницу, что полностью
* уничтожает выгоду от NOMETASYNC. */
const uint32_t txnid_dist = ((txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC) ? MDBX_NOMETASYNC_LAZY_FD
: MDBX_NOMETASYNC_LAZY_WRITEMAP;
/* Смысл "магии" в том, чтобы избежать отдельного вызова fdatasync()
* или msync() для гарантированной фиксации на диске мета-страницы,
* которая была "лениво" отправлена на запись в предыдущей транзакции,
* но не сброшена на диск из-за активного режима MDBX_NOMETASYNC. */
if (
#if defined(_WIN32) || defined(_WIN64)
!env->ioring.overlapped_fd &&
#endif
meta_sync_txnid == (uint32_t)head.txnid - txnid_dist)
need_flush_for_nometasync = true;
else {
rc = meta_sync(env, head);
if (unlikely(rc != MDBX_SUCCESS)) {
ERROR("txn-%s: error %d", "presync-meta", rc);
return rc;
}
}
}
if (txn->tw.dirtylist) {
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
tASSERT(txn, txn->tw.loose_count == 0);