From 10e7e5c8991edbeece986777220cf00bf130e005 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: Fri, 10 Jan 2025 12:13:47 +0300 Subject: [PATCH] =?UTF-8?q?mdbx:=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20`mdbx=5Ftxn=5Fcommit=5Fex()`?= =?UTF-8?q?=205/5=20(=D0=B2=D1=8B=D1=87=D0=BB=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20`txn=5Fbasal=5Fend()`).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api-txn.c | 74 +++++++++------------- src/proto.h | 2 + src/txn.c | 167 ++++++++++++++++++++++++++------------------------ 3 files changed, 116 insertions(+), 127 deletions(-) diff --git a/src/api-txn.c b/src/api-txn.c index 4a343d50..2c56e08e 100644 --- a/src/api-txn.c +++ b/src/api-txn.c @@ -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); } diff --git a/src/proto.h b/src/proto.h index 10d02254..a77cc478 100644 --- a/src/proto.h +++ b/src/proto.h @@ -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); diff --git a/src/txn.c b/src/txn.c index 7dc2aa78..88565912 100644 --- a/src/txn.c +++ b/src/txn.c @@ -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);