Skip to content

Commit 8333413

Browse files
committed
mdbx: merge nested read-only txns feature (v0.14.1-342-g630b73ee).
2026-01-25 mdbx: adds using nested txn and C++ lambda into example. 2026-01-25 mdbx-test: add trivia cases for nested transactions to `extra/txn`. 2026-01-25 mdbx: merge `nested read-only txns` feature. 2026-01-24 mdbx++: more for nested transactions. 2026-01-24 mdbx: initial support for read-only nested transactions. 2026-01-21 mdbx++: add `txn::checkpoint()` and a few `env::is_xyz()` methods. 2026-01-20 mdbx: add `txn_ro_nested` and `txn_ro_both`. 2026-01-20 mdbx: rename `remap_lock` (cosmetic). 2026-01-20 mdbx: refactoring `mdbx_txn_begin()`, drop `txn_renew()` and `txn_end()`. 2026-01-20 mdbx: refactoring `txn_renew()` into `txn_setup_primal()`, `txn_ro_start()` and `txn_basal_start()`. 2026-01-20 mdbx-doc: update TODO. 2026-01-19 mdbx: cleanup `txn_commit()` of end-mode and `txn_end()`. 2026-01-19 mdbx: cleanup `txn_basal_end()` of end-mode. 2026-01-18 mdbx: refactoring `txn_ro_end()` into `txn_ro_reset()` and `txn_ro_free()`. 2026-01-17 mdbx: drop using txn_end() for nested-txn. 2026-01-17 mdbx: remove signature checks nested-txn code.
1 parent 5e8679f commit 8333413

File tree

11 files changed

+678
-651
lines changed

11 files changed

+678
-651
lines changed

VERSION.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "git_describe": "v0.14.1-326-g72e0af44", "git_timestamp": "2026-01-25T00:52:18+03:00", "git_tree": "32db7628fa7e56137220952cf3328075093e8efc", "git_commit": "72e0af44e90a9e678e21ab84ce4b7fe9275fc730", "semver": "0.14.1.326" }
1+
{ "git_describe": "v0.14.1-342-g630b73ee", "git_timestamp": "2026-01-25T01:41:08+03:00", "git_tree": "b089c2d8bc73633c3be446f12b6bffb9eba9f35a", "git_commit": "630b73eed683e2ede5c8620b23671afc772da335", "semver": "0.14.1.342" }

mdbx.c

Lines changed: 554 additions & 581 deletions
Large diffs are not rendered by default.

mdbx.c++

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2026
33
/* clang-format off */
44

5-
#define MDBX_BUILD_SOURCERY d0c652016ea3e3d314181251772ed3ad35b30d8654a3e0e3f7a1dfc73f6406b4_v0_14_1_326_g72e0af44
5+
#define MDBX_BUILD_SOURCERY bceb4cffb452beb8b344b66517dbfcc12ae8e80220a05240377f9d076620e4d6_v0_14_1_342_g630b73ee
66

77
#define LIBMDBX_INTERNALS
88
#define MDBX_DEPRECATED
@@ -3851,26 +3851,6 @@ MDBX_MAYBE_UNUSED static inline void dxb_sanitize_tail(MDBX_env *env, MDBX_txn *
38513851
}
38523852
#endif /* ENABLE_MEMCHECK || __SANITIZE_ADDRESS__ */
38533853

3854-
/* txn.c */
3855-
#define TXN_END_NAMES \
3856-
{"committed", "pure-commit", "abort", "reset", "fail-begin", "fail-begin-nested", "ousted", nullptr}
3857-
enum {
3858-
/* txn_end operation number, for logging */
3859-
TXN_END_COMMITTED /* 0 */,
3860-
TXN_END_PURE_COMMIT /* 1 */,
3861-
TXN_END_ABORT /* 2 */,
3862-
TXN_END_RESET /* 3 */,
3863-
TXN_END_FAIL_BEGIN /* 4 */,
3864-
TXN_END_FAIL_BEGIN_NESTED /* 5 */,
3865-
TXN_END_OUSTED /* 6 */,
3866-
TXN_END_OPMASK = 0x07 /* mask for txn_end() operation number */,
3867-
3868-
TXN_END_UPDATE = 0x10 /* update env state (DBIs) */,
3869-
TXN_END_FREE = 0x20 /* free txn unless it is env.basal_txn */,
3870-
TXN_END_SLOT = 0x40 /* release any reader slot if NOSTICKYTHREADS */,
3871-
TXN_END_LOCK = 0x80 /* release writer mutex */
3872-
};
3873-
38743854
struct commit_timestamp {
38753855
uint64_t start, prep, gc, audit, write, sync, gc_cpu;
38763856
};
@@ -3881,14 +3861,13 @@ MDBX_INTERNAL int txn_check_badbits_parked(const MDBX_txn *txn, int bad_bits);
38813861
MDBX_INTERNAL void txn_done_cursors(MDBX_txn *txn);
38823862
MDBX_INTERNAL int txn_shadow_cursors(const MDBX_txn *parent, const size_t dbi);
38833863

3884-
MDBX_INTERNAL MDBX_txn *txn_alloc(const MDBX_txn_flags_t flags, MDBX_env *env);
3864+
MDBX_INTERNAL MDBX_txn *txn_alloc(const unsigned flags, MDBX_env *env);
38853865
MDBX_INTERNAL int txn_abort(MDBX_txn *txn);
38863866
MDBX_INTERNAL int txn_commit(MDBX_txn *txn, struct commit_timestamp *);
3887-
MDBX_INTERNAL int txn_renew(MDBX_txn *txn, unsigned flags);
3888-
MDBX_INTERNAL int txn_end(MDBX_txn *txn, unsigned mode);
38893867
#if !(defined(_WIN32) || defined(_WIN64))
38903868
MDBX_INTERNAL void txn_abort_after_resurrect(MDBX_txn *txn);
38913869
#endif /* Windows */
3870+
MDBX_INTERNAL int txn_setup_primal(MDBX_txn *txn);
38923871

38933872
MDBX_INTERNAL int txn_nested_create(MDBX_txn *parent, const MDBX_txn_flags_t flags);
38943873
MDBX_INTERNAL int txn_nested_abort(MDBX_txn *nested);
@@ -3899,16 +3878,16 @@ MDBX_INTERNAL MDBX_txn *txn_basal_create(const size_t max_dbi);
38993878
MDBX_INTERNAL void txn_basal_destroy(MDBX_txn *txn);
39003879
MDBX_INTERNAL int txn_basal_start(MDBX_txn *txn, unsigned flags);
39013880
MDBX_INTERNAL int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts);
3902-
MDBX_INTERNAL int txn_basal_end(MDBX_txn *txn, unsigned mode);
3881+
MDBX_INTERNAL int txn_basal_end(MDBX_txn *txn, bool unlock);
39033882
MDBX_INTERNAL int txn_basal_checkpoint(MDBX_txn *txn, MDBX_txn_flags_t weakening_durability,
39043883
struct commit_timestamp *ts);
39053884

39063885
MDBX_INTERNAL int txn_ro_park(MDBX_txn *txn, bool autounpark);
39073886
MDBX_INTERNAL int txn_ro_unpark(MDBX_txn *txn);
3908-
MDBX_INTERNAL int txn_ro_start(MDBX_txn *txn, unsigned flags);
3909-
MDBX_INTERNAL int txn_ro_end(MDBX_txn *txn, unsigned mode);
3887+
MDBX_INTERNAL int txn_ro_start(MDBX_txn *txn, bool prepare);
39103888
MDBX_INTERNAL int txn_ro_clone(const MDBX_txn *const source, MDBX_txn *const clone);
39113889
MDBX_INTERNAL int txn_ro_reset(MDBX_txn *txn);
3890+
MDBX_INTERNAL void txn_ro_free(MDBX_txn *txn);
39123891

39133892
/* env.c */
39143893
MDBX_INTERNAL int env_open(MDBX_env *env, mdbx_mode_t mode);
@@ -4499,6 +4478,9 @@ enum dbi_state {
44994478
};
45004479

45014480
enum txn_flags {
4481+
txn_ro_flat = MDBX_TXN_RDONLY,
4482+
txn_ro_nested = UINT32_C(0x0800),
4483+
txn_ro_both = txn_ro_flat | txn_ro_nested,
45024484
txn_ro_begin_flags = MDBX_TXN_RDONLY | MDBX_TXN_RDONLY_PREPARE,
45034485
txn_rw_begin_flags = MDBX_TXN_NOMETASYNC | MDBX_TXN_NOSYNC | MDBX_TXN_TRY,
45044486
txn_rw_checkpoint = MDBX_TXN_RDONLY_PREPARE & ~MDBX_TXN_RDONLY,
@@ -4828,14 +4810,14 @@ struct MDBX_env {
48284810
osal_ioring_t ioring;
48294811

48304812
#if defined(_WIN32) || defined(_WIN64)
4831-
osal_srwlock_t remap_guard;
4813+
osal_srwlock_t remap_lock;
48324814
/* Workaround for LockFileEx and WriteFile multithread bug */
48334815
CRITICAL_SECTION lck_event_cs;
48344816
CRITICAL_SECTION dxb_event_cs;
48354817
char *pathname_char; /* cache of multi-byte representation of pathname
48364818
to the DB files */
48374819
#else
4838-
osal_fastmutex_t remap_guard;
4820+
osal_fastmutex_t remap_lock;
48394821
#endif
48404822

48414823
/* ------------------------------------------------- stub for lck-less mode */
@@ -5628,7 +5610,7 @@ static __always_inline int check_txn_anythread(const MDBX_txn *txn, int bad_bits
56285610
return MDBX_EPERM;
56295611

56305612
if (unlikely(txn->flags & bad_bits)) {
5631-
if ((bad_bits & MDBX_TXN_RDONLY) && unlikely(txn->flags & MDBX_TXN_RDONLY))
5613+
if ((bad_bits & txn_ro_both) && unlikely(txn->flags & txn_ro_both))
56325614
return MDBX_EACCESS;
56335615
if ((bad_bits & MDBX_TXN_PARKED) == 0)
56345616
return MDBX_BAD_TXN;
@@ -5646,7 +5628,7 @@ static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
56465628
#if MDBX_TXN_CHECKOWNER
56475629
if (err == MDBX_SUCCESS && (txn->flags & (MDBX_NOSTICKYTHREADS | MDBX_TXN_FINISHED)) != MDBX_NOSTICKYTHREADS &&
56485630
!(bad_bits /* abort/reset/txn-break */ == 0 &&
5649-
((txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED)) == (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED))) &&
5631+
((txn->flags & (txn_ro_flat | MDBX_TXN_FINISHED)) == (txn_ro_flat | MDBX_TXN_FINISHED))) &&
56505632
unlikely(txn->owner != osal_thread_self()))
56515633
err = txn->owner ? MDBX_THREAD_MISMATCH : MDBX_BAD_TXN;
56525634
#endif /* MDBX_TXN_CHECKOWNER */
@@ -5655,14 +5637,14 @@ static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
56555637
}
56565638

56575639
static inline int check_txn_rw(const MDBX_txn *txn, int bad_bits) {
5658-
return check_txn(txn, (bad_bits | MDBX_TXN_RDONLY) & ~MDBX_TXN_PARKED);
5640+
return check_txn(txn, (bad_bits | txn_ro_both) & ~MDBX_TXN_PARKED);
56595641
}
56605642

56615643
MDBX_NOTHROW_CONST_FUNCTION static inline txnid_t txn_basis_snapshot(const MDBX_txn *txn) {
5662-
STATIC_ASSERT(((MDBX_TXN_RDONLY >> ((xMDBX_TXNID_STEP == 2) ? 16 : 17)) & xMDBX_TXNID_STEP) == xMDBX_TXNID_STEP);
5644+
STATIC_ASSERT(((txn_ro_flat >> ((xMDBX_TXNID_STEP == 2) ? 16 : 17)) & xMDBX_TXNID_STEP) == xMDBX_TXNID_STEP);
56635645
const txnid_t committed_txnid =
56645646
txn->txnid - xMDBX_TXNID_STEP + ((txn->flags >> ((xMDBX_TXNID_STEP == 2) ? 16 : 17)) & xMDBX_TXNID_STEP);
5665-
tASSERT(txn, committed_txnid == ((txn->flags & MDBX_TXN_RDONLY) ? txn->txnid : txn->txnid - xMDBX_TXNID_STEP));
5647+
tASSERT(txn, committed_txnid == ((txn->flags & txn_ro_flat) ? txn->txnid : txn->txnid - xMDBX_TXNID_STEP));
56665648
return committed_txnid;
56675649
}
56685650

@@ -6018,7 +6000,7 @@ static inline int cursor_check_ro(const MDBX_cursor *mc) { return cursor_check(m
60186000

60196001
/* для записи данных. */
60206002
static inline int cursor_check_rw(const MDBX_cursor *mc) {
6021-
return cursor_check(mc, (MDBX_TXN_BLOCKED - MDBX_TXN_PARKED) | MDBX_TXN_RDONLY);
6003+
return cursor_check(mc, (MDBX_TXN_BLOCKED - MDBX_TXN_PARKED) | txn_ro_both);
60226004
}
60236005

60246006
MDBX_INTERNAL MDBX_cursor *cursor_eot(MDBX_cursor *cursor, MDBX_txn *txn);
@@ -6142,7 +6124,7 @@ MDBX_INTERNAL dpl_t *dpl_reserve(MDBX_txn *txn, size_t size);
61426124
MDBX_INTERNAL __noinline dpl_t *dpl_sort_slowpath(const MDBX_txn *txn);
61436125

61446126
static inline dpl_t *dpl_sort(const MDBX_txn *txn) {
6145-
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
6127+
tASSERT(txn, (txn->flags & txn_ro_both) == 0);
61466128
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
61476129

61486130
dpl_t *dl = txn->wr.dirtylist;
@@ -6168,7 +6150,7 @@ MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t dpl_endpgno(const dpl_t *dl, siz
61686150
}
61696151

61706152
MDBX_NOTHROW_PURE_FUNCTION static inline bool dpl_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npages) {
6171-
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
6153+
tASSERT(txn, (txn->flags & txn_ro_both) == 0);
61726154
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
61736155

61746156
dpl_t *dl = txn->wr.dirtylist;
@@ -6214,7 +6196,7 @@ MDBX_INTERNAL int __must_check_result dpl_append(MDBX_txn *txn, pgno_t pgno, pag
62146196
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool dpl_check(MDBX_txn *txn);
62156197

62166198
MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t dpl_age(const MDBX_txn *txn, size_t i) {
6217-
tASSERT(txn, (txn->flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP)) == 0);
6199+
tASSERT(txn, (txn->flags & (txn_ro_both | MDBX_WRITEMAP)) == 0);
62186200
const dpl_t *dl = txn->wr.dirtylist;
62196201
assert((intptr_t)i > 0 && i <= dl->length);
62206202
size_t *const ptr = ptr_disp(dl->items[i].ptr, -(ptrdiff_t)sizeof(size_t));
@@ -6578,7 +6560,7 @@ static inline bool spill_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npag
65786560
}
65796561

65806562
static inline int txn_spill(MDBX_txn *const txn, MDBX_cursor *const m0, const size_t need) {
6581-
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
6563+
tASSERT(txn, (txn->flags & txn_ro_both) == 0);
65826564
tASSERT(txn, !m0 || cursor_is_tracked(m0));
65836565

65846566
const intptr_t wanna_spill_entries = txn->wr.dirtylist ? (need - txn->wr.dirtyroom - txn->wr.loose_count) : 0;
@@ -6704,7 +6686,7 @@ MDBX_INTERNAL int page_retire_ex(MDBX_cursor *mc, const pgno_t pgno, page_t *mp
67046686
static inline int page_retire(MDBX_cursor *mc, page_t *mp) { return page_retire_ex(mc, mp->pgno, mp, mp->flags); }
67056687

67066688
static inline void page_wash(MDBX_txn *txn, size_t di, page_t *const mp, const size_t npages) {
6707-
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
6689+
tASSERT(txn, (txn->flags & txn_ro_both) == 0);
67086690
mp->txnid = INVALID_TXNID;
67096691
mp->flags = P_BAD;
67106692

@@ -8498,7 +8480,7 @@ __cold MDBX_env_flags_t env::operate_parameters::make_flags(bool accede, bool us
84988480
flags |= MDBX_VALIDATION;
84998481

85008482
if (mode != readonly) {
8501-
if (options.nested_write_transactions)
8483+
if (options.nested_transactions)
85028484
flags &= ~MDBX_WRITEMAP;
85038485
if (reclaiming.coalesce)
85048486
flags |= MDBX_COALESCE;
@@ -8545,7 +8527,7 @@ env::reclaiming_options::reclaiming_options(MDBX_env_flags_t flags) noexcept
85458527

85468528
env::operate_options::operate_options(MDBX_env_flags_t flags) noexcept
85478529
: no_sticky_threads(((flags & (MDBX_NOSTICKYTHREADS | MDBX_EXCLUSIVE)) == MDBX_NOSTICKYTHREADS) ? true : false),
8548-
nested_write_transactions((flags & (MDBX_WRITEMAP | MDBX_RDONLY)) ? false : true),
8530+
nested_transactions((flags & (MDBX_WRITEMAP | MDBX_RDONLY)) ? false : true),
85498531
exclusive((flags & MDBX_EXCLUSIVE) ? true : false), disable_readahead((flags & MDBX_NORDAHEAD) ? true : false),
85508532
disable_clear_memory((flags & MDBX_NOMEMINIT) ? true : false) {}
85518533

@@ -8667,7 +8649,7 @@ __cold env_managed::env_managed(const char *pathname, const operate_parameters &
86678649
setup(op.max_maps, op.max_readers);
86688650
error::success_or_throw(::mdbx_env_open(handle_, pathname, op.make_flags(accede), 0));
86698651

8670-
if (op.options.nested_write_transactions && !get_options().nested_write_transactions)
8652+
if (op.options.nested_transactions && !get_options().nested_transactions)
86718653
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
86728654
}
86738655

@@ -8679,7 +8661,7 @@ __cold env_managed::env_managed(const char *pathname, const env_managed::create_
86798661
error::success_or_throw(
86808662
::mdbx_env_open(handle_, pathname, op.make_flags(accede, cp.use_subdirectory), cp.file_mode_bits));
86818663

8682-
if (op.options.nested_write_transactions && !get_options().nested_write_transactions)
8664+
if (op.options.nested_transactions && !get_options().nested_transactions)
86838665
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
86848666
}
86858667

@@ -8696,7 +8678,7 @@ __cold env_managed::env_managed(const wchar_t *pathname, const operate_parameter
86968678
setup(op.max_maps, op.max_readers);
86978679
error::success_or_throw(::mdbx_env_openW(handle_, pathname, op.make_flags(accede), 0));
86988680

8699-
if (op.options.nested_write_transactions && !get_options().nested_write_transactions)
8681+
if (op.options.nested_transactions && !get_options().nested_transactions)
87008682
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
87018683
}
87028684

@@ -8708,7 +8690,7 @@ __cold env_managed::env_managed(const wchar_t *pathname, const env_managed::crea
87088690
error::success_or_throw(
87098691
::mdbx_env_openW(handle_, pathname, op.make_flags(accede, cp.use_subdirectory), cp.file_mode_bits));
87108692

8711-
if (op.options.nested_write_transactions && !get_options().nested_write_transactions)
8693+
if (op.options.nested_transactions && !get_options().nested_transactions)
87128694
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
87138695
}
87148696

@@ -8731,10 +8713,13 @@ __cold env_managed::env_managed(const MDBX_STD_FILESYSTEM_PATH &pathname, const
87318713

87328714
//------------------------------------------------------------------------------
87338715

8734-
txn_managed txn::start_nested() {
8716+
txn_managed txn::start_nested() { return start_nested(false); }
8717+
8718+
txn_managed txn::start_nested(bool readonly) {
87358719
MDBX_txn *nested;
87368720
error::throw_on_nullptr(handle_, MDBX_BAD_TXN);
8737-
error::success_or_throw(::mdbx_txn_begin(mdbx_txn_env(handle_), handle_, MDBX_TXN_READWRITE, &nested));
8721+
error::success_or_throw(
8722+
::mdbx_txn_begin(mdbx_txn_env(handle_), handle_, readonly ? MDBX_TXN_RDONLY : MDBX_TXN_READWRITE, &nested));
87388723
assert(nested != nullptr);
87398724
return txn_managed(nested);
87408725
}
@@ -8768,6 +8753,16 @@ void txn_managed::commit(commit_latency *latency) {
87688753
MDBX_CXX20_UNLIKELY err.throw_exception();
87698754
}
87708755

8756+
bool txn_managed::checkpoint(commit_latency *latency) {
8757+
const error err = static_cast<MDBX_error_t>(::mdbx_txn_checkpoint(handle_, MDBX_TXN_NOWEAKING, latency));
8758+
if (MDBX_UNLIKELY(err.is_failure())) {
8759+
if (err.code() != MDBX_THREAD_MISMATCH && err.code() != MDBX_EINVAL)
8760+
handle_ = nullptr;
8761+
MDBX_CXX20_UNLIKELY err.throw_exception();
8762+
}
8763+
return err.is_result_true();
8764+
}
8765+
87718766
void txn_managed::commit_embark_read() {
87728767
auto env = handle_->env;
87738768
commit();
@@ -9037,8 +9032,8 @@ __cold ::std::ostream &operator<<(::std::ostream &out, const env::operate_option
90379032
out << delimiter << "no_sticky_threads";
90389033
delimiter = comma;
90399034
}
9040-
if (it.nested_write_transactions) {
9041-
out << delimiter << "nested_write_transactions";
9035+
if (it.nested_transactions) {
9036+
out << delimiter << "nested_transactions";
90429037
delimiter = comma;
90439038
}
90449039
if (it.exclusive) {

0 commit comments

Comments
 (0)