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) { 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; pgop_stat_t *const ptr = &env->lck->pgops;
latency->gc_prof.work_counter = ptr->gc_prof.work.spe_counter; 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); 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; struct commit_timestamp ts;
latency_init(latency, &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); int rc = check_txn(txn, MDBX_TXN_FINISHED);
if (unlikely(rc != MDBX_SUCCESS)) { 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; rc = MDBX_RESULT_TRUE;
goto fail; goto fail;
} }
@ -355,19 +352,18 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
return LOG_IFERR(rc); 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)) { 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); ERROR("attempt to commit %s txn %p", "strange read-only", (void *)txn);
return MDBX_PROBLEM; return MDBX_PROBLEM;
} }
if (txn->flags & MDBX_TXN_ERROR) { latency_gcprof(latency, txn);
rc = MDBX_RESULT_TRUE; rc = (txn->flags & MDBX_TXN_ERROR) ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
goto fail; txn_end(txn, TXN_END_PURE_COMMIT | TXN_END_UPDATE | TXN_END_SLOT | TXN_END_FREE);
}
goto done; 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; txn->flags |= MDBX_TXN_ERROR;
rc = MDBX_THREAD_MISMATCH; rc = MDBX_THREAD_MISMATCH;
return LOG_IFERR(rc); 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)) { if (unlikely(txn->flags & MDBX_TXN_ERROR)) {
rc = MDBX_RESULT_TRUE; 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) { 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); ERROR("attempt to commit %s txn %p", "strange nested", (void *)txn);
return MDBX_PROBLEM; return MDBX_PROBLEM;
} }
latency_gcprof(latency, txn);
rc = txn_nested_join(txn, latency ? &ts : nullptr); rc = txn_nested_join(txn, latency ? &ts : nullptr);
if (likely(rc == MDBX_SUCCESS)) goto done;
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;
} }
rc = txn_basal_commit(txn, latency ? &ts : nullptr); rc = txn_basal_commit(txn, latency ? &ts : nullptr);
end_mode = TXN_END_COMMITTED | TXN_END_UPDATE; latency_gcprof(latency, txn);
if (likely(rc == MDBX_SUCCESS)) int end = TXN_END_COMMITTED | TXN_END_UPDATE;
goto done; if (unlikely(rc != MDBX_SUCCESS)) {
if (rc == MDBX_RESULT_TRUE) { end = TXN_END_ABORT;
#if MDBX_NOSUCCESS_PURE_COMMIT if (rc == MDBX_RESULT_TRUE) {
rc = txn_end(txn, end_mode); end = TXN_END_PURE_COMMIT | TXN_END_UPDATE;
if (unlikely(rc != MDBX_SUCCESS)) rc = MDBX_NOSUCCESS_PURE_COMMIT ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
goto fail; }
rc = MDBX_RESULT_TRUE;
goto provide_latency;
#else
goto done;
#endif /* MDBX_NOSUCCESS_PURE_COMMIT */
} }
int err = txn_end(txn, end);
fail: if (unlikely(err != MDBX_SUCCESS))
txn->flags |= MDBX_TXN_ERROR; rc = err;
if (latency)
latency_gcprof(latency, txn);
txn_abort(txn);
goto provide_latency;
done: done:
if (latency)
latency_gcprof(latency, txn);
rc = txn_end(txn, end_mode);
provide_latency:
latency_done(latency, &ts); latency_done(latency, &ts);
return LOG_IFERR(rc); 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_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_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 */ /* env.c */
MDBX_INTERNAL int env_open(MDBX_env *env, mdbx_mode_t mode); 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); txn_abort(txn->nested);
tASSERT(txn, (txn->flags & MDBX_TXN_ERROR) || dpl_check(txn)); 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); return txn_end(txn, TXN_END_ABORT | TXN_END_SLOT | TXN_END_FREE);
} }
@ -839,8 +840,9 @@ bailout:
return rc; 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; MDBX_env *const env = txn->env;
tASSERT(txn, (txn->flags & txn_may_have_cursors) == 0);
txn->n_dbi = 0; /* prevent further DBI activity */ txn->n_dbi = 0; /* prevent further DBI activity */
if (txn->to.reader) { if (txn->to.reader) {
reader_slot_t *slot = 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); txn->dbs[MAIN_DBI].root, txn->dbs[FREE_DBI].root);
tASSERT(txn, txn->signature == txn_signature && !txn->nested && !(txn->flags & MDBX_TXN_HAS_CHILD)); tASSERT(txn, txn->signature == txn_signature && !txn->nested && !(txn->flags & MDBX_TXN_HAS_CHILD));
if (txn->flags & txn_may_have_cursors) { if (txn->flags & txn_may_have_cursors)
txn->flags |= /* avoid merge cursors' state */ MDBX_TXN_ERROR;
txn_done_cursors(txn); txn_done_cursors(txn);
}
MDBX_env *const env = txn->env; MDBX_env *const env = txn->env;
MDBX_txn *const parent = txn->parent; MDBX_txn *const parent = txn->parent;
if (txn == env->basal_txn) { if (txn == env->basal_txn) {
tASSERT(txn, !parent && !(txn->flags & MDBX_TXN_RDONLY)); tASSERT(txn, !parent && !(txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED)) && txn->owner);
tASSERT(txn, (txn->flags & MDBX_TXN_FINISHED) == 0 && txn->owner); return txn_basal_end(txn, mode);
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;
} }
if (txn->flags & MDBX_TXN_RDONLY) { if (txn->flags & MDBX_TXN_RDONLY) {
@ -1072,7 +1045,7 @@ int txn_unpark(MDBX_txn *txn) {
return err ? err : MDBX_OUSTED; 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; MDBX_txn *txn = nullptr;
const intptr_t bitmap_bytes = const intptr_t bitmap_bytes =
#if MDBX_ENABLE_DBI_SPARSE #if MDBX_ENABLE_DBI_SPARSE
@ -1107,7 +1080,7 @@ MDBX_txn *txn_basal_create(const size_t max_dbi) {
return txn; return txn;
} }
void txn_basal_destroy(MDBX_txn *txn) { __cold void txn_basal_destroy(MDBX_txn *txn) {
dpl_free(txn); dpl_free(txn);
txl_free(txn->tw.gc.retxl); txl_free(txn->tw.gc.retxl);
pnl_free(txn->tw.retired_pages); 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) { void txn_nested_abort(MDBX_txn *nested) {
MDBX_txn *const parent = nested->parent; MDBX_txn *const parent = nested->parent;
tASSERT(nested, !(nested->flags & txn_may_have_cursors));
nested->signature = 0; nested->signature = 0;
nested->owner = 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); tASSERT(txn, txn->tw.loose_count == 0);
VERBOSE("fast-complete pure nested txn %" PRIaTXN, txn->txnid); 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 /* 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); txn_merge(parent, txn, parent_retired_len);
env->txn = parent; env->txn = parent;
parent->nested = nullptr; parent->nested = nullptr;
parent->flags &= ~MDBX_TXN_HAS_CHILD;
tASSERT(parent, dpl_check(parent)); tASSERT(parent, dpl_check(parent));
#if MDBX_ENABLE_REFUND #if MDBX_ENABLE_REFUND
@ -1419,6 +1394,35 @@ int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts) {
return MDBX_SUCCESS; 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) { int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts) {
MDBX_env *const env = txn->env; MDBX_env *const env = txn->env;
tASSERT(txn, txn == env->basal_txn && !txn->parent && !txn->nested); 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) if (txn->flags & txn_may_have_cursors)
txn_done_cursors(txn); 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) && 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)); } TXN_FOREACH_DBI_ALL(txn, i) { tASSERT(txn, !(txn->dbi_state[i] & DBI_DIRTY)); }
/* fast completion of pure transaction */ /* fast completion of pure transaction */
return MDBX_NOSUCCESS_PURE_COMMIT ? MDBX_RESULT_TRUE : MDBX_SUCCESS; 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; 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) { if (txn->tw.dirtylist) {
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC); tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
tASSERT(txn, txn->tw.loose_count == 0); tASSERT(txn, txn->tw.loose_count == 0);