From 688b711e31c4333be8ce709a248d0b3c5c8c1a78 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 19 Jan 2017 17:11:30 +0300 Subject: [PATCH 01/21] mdbx: adds mdbx_get_ex() for libfpta. --- mdbx.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mdbx.h | 5 ++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/mdbx.c b/mdbx.c index ff2ba67a..611a87e9 100644 --- a/mdbx.c +++ b/mdbx.c @@ -514,3 +514,56 @@ bailout: txn->mt_cursors[dbi] = mc.mc_next; return rc; } + +int +mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, + MDB_val *key, MDB_val *data, int* values_count) +{ + DKBUF; + mdb_debug("===> get db %u key [%s]", dbi, DKEY(key)); + + if (unlikely(!key || !data || !txn)) + return EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return EINVAL; + + if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED)) + return MDB_BAD_TXN; + + MDB_cursor mc; + MDB_xcursor mx; + mdb_cursor_init(&mc, txn, dbi, &mx); + + int exact = 0; + int rc = mdb_cursor_set(&mc, key, data, MDB_SET, &exact); + if (unlikely(rc != MDB_SUCCESS)) { + if (rc == MDB_NOTFOUND && values_count) + *values_count = 0; + return rc; + } + + if (values_count) { + if (mc.mc_xcursor == NULL) + *values_count = 1; + else { + MDB_page *mp = mc.mc_pg[mc.mc_top]; + if (IS_LEAF2(mp)) + *values_count = 1; + else { + MDB_node *leaf = NODEPTR(mp, mc.mc_ki[mc.mc_top]); + if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) + *values_count = 1; + else { + mdb_tassert(txn, mc.mc_xcursor == &mx); + mdb_tassert(txn, mx.mx_cursor.mc_flags & C_INITIALIZED); + *values_count = mx.mx_db.md_entries; + } + } + } + } + return MDB_SUCCESS; +} diff --git a/mdbx.h b/mdbx.h index 3a0eda23..7fd09d67 100644 --- a/mdbx.h +++ b/mdbx.h @@ -219,12 +219,15 @@ typedef struct mdbx_canary { int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary); size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary); -/** Returns 1 when no more data available or cursor not positioned, +/* Returns 1 when no more data available or cursor not positioned, * 0 otherwise or less that zero in error case. */ int mdbx_cursor_eof(MDB_cursor *mc); int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags); +/* Same as mdbx_get(), but also return the count + * of multi-values/duplicates for a given key. */ +int mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, int* values_count); /** @} */ From 8b045ab6264fbd49f2985073115095eacd04f286 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 24 Jan 2017 15:38:20 +0300 Subject: [PATCH 02/21] mdbx: MDBX_EMULTIVAL errcode for libfpta. --- mdb.c | 2 +- mdbx.c | 2 +- mdbx.h | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index 08dccaaa..f9a3737a 100644 --- a/mdb.c +++ b/mdb.c @@ -9254,7 +9254,7 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1); - rc = MDB_KEYEXIST; + rc = MDBX_EMULTIVAL; } } } diff --git a/mdbx.c b/mdbx.c index 611a87e9..6e3cb210 100644 --- a/mdbx.c +++ b/mdbx.c @@ -468,7 +468,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_node *leaf = NODEPTR(page, mc.mc_ki[mc.mc_top]); if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1); - rc = MDB_KEYEXIST; + rc = MDBX_EMULTIVAL; goto bailout; } /* если данные совпадают, то ничего делать не надо */ diff --git a/mdbx.h b/mdbx.h index 7fd09d67..83315e54 100644 --- a/mdbx.h +++ b/mdbx.h @@ -223,6 +223,8 @@ size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary); * 0 otherwise or less that zero in error case. */ int mdbx_cursor_eof(MDB_cursor *mc); +#define MDBX_EMULTIVAL (MDB_LAST_ERRCODE - 42) + int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags); /* Same as mdbx_get(), but also return the count From 6aa60c61c5230e471a854b40e4318ad4e43f61af Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 24 Jan 2017 20:17:20 +0300 Subject: [PATCH 03/21] mdbx: allows cursors to be free/reuse explicitly, regardless of transaction wr/ro type. --- lmdb.h | 8 ++++++++ mdb.c | 43 +++++++++++++++++++++++++++++-------------- mdbx.c | 2 +- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lmdb.h b/lmdb.h index 701f4b46..7463e4e8 100644 --- a/lmdb.h +++ b/lmdb.h @@ -1414,6 +1414,13 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data); * A cursor cannot be used when its database handle is closed. Nor * when its transaction has ended, except with #mdb_cursor_renew(). * It can be discarded with #mdb_cursor_close(). + * + * MDBX-mode: + * A cursor must be closed explicitly always, before + * or after its transaction ends. It can be reused with + * #mdb_cursor_renew() before finally closing it. + * + * LMDB-compatible mode: * A cursor in a write-transaction can be closed before its transaction * ends, and will otherwise be closed when its transaction ends. * A cursor in a read-only transaction must be closed explicitly, before @@ -1421,6 +1428,7 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data); * #mdb_cursor_renew() before finally closing it. * @note Earlier documentation said that cursors in every transaction * were closed when the transaction committed or aborted. + * * @param[in] txn A transaction handle returned by #mdb_txn_begin() * @param[in] dbi A database handle returned by #mdb_dbi_open() * @param[out] cursor Address where the new #MDB_cursor handle will be stored diff --git a/mdb.c b/mdb.c index f9a3737a..6db4d996 100644 --- a/mdb.c +++ b/mdb.c @@ -933,7 +933,8 @@ struct MDB_xcursor; * (A node with #F_DUPDATA but no #F_SUBDATA contains a subpage). */ struct MDB_cursor { -#define MDBX_MC_SIGNATURE (0xFE05D5B1^MDBX_MODE_SALT) +#define MDBX_MC_SIGNATURE_LIVE (0xFE05D5B1^MDBX_MODE_SALT) +#define MDBX_MC_SIGNATURE_CLOSED (0x90E297A7^MDBX_MODE_SALT) unsigned mc_signature; /** Next cursor on this DB in this txn */ MDB_cursor *mc_next; @@ -2695,6 +2696,8 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge) for (i = txn->mt_numdbs; --i >= 0; ) { for (mc = cursors[i]; mc; mc = next) { + mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE_LIVE + || mc->mc_signature == MDBX_MC_SIGNATURE_CLOSED); next = mc->mc_next; if ((bk = mc->mc_backup) != NULL) { if (merge) { @@ -2714,9 +2717,11 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge) } mc = bk; } +#if ! MDBX_MODE_ENABLED /* Only malloced cursors are permanently tracked. */ mc->mc_signature = 0; free(mc); +#endif } cursors[i] = NULL; } @@ -6465,7 +6470,7 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, if (unlikely(mc == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return MDB_VERSION_MISMATCH; if (unlikely(mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)) @@ -6699,7 +6704,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, if (unlikely(mc == NULL || key == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return MDB_VERSION_MISMATCH; env = mc->mc_txn->mt_env; @@ -7234,7 +7239,7 @@ mdb_cursor_del(MDB_cursor *mc, unsigned flags) if (unlikely(!mc)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return MDB_VERSION_MISMATCH; if (unlikely(mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))) @@ -7777,7 +7782,7 @@ mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int new_dupdata) static void mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) { - mc->mc_signature = MDBX_MC_SIGNATURE; + mc->mc_signature = MDBX_MC_SIGNATURE_LIVE; mc->mc_next = NULL; mc->mc_backup = NULL; mc->mc_dbi = dbi; @@ -7792,7 +7797,7 @@ mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) mc->mc_ki[0] = 0; if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { mdb_tassert(txn, mx != NULL); - mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE; + mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE_LIVE; mc->mc_xcursor = mx; mdb_xcursor_init0(mc); } else { @@ -7850,14 +7855,23 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc) return EINVAL; if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE - || mc->mc_signature != MDBX_MC_SIGNATURE)) + || mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return MDB_VERSION_MISMATCH; if (unlikely(!TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID))) return EINVAL; - if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) + if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) { +#if MDBX_MODE_ENABLED + MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; + while (*prev && *prev != mc) prev = &(*prev)->mc_next; + if (*prev == mc) + *prev = mc->mc_next; + mc->mc_signature = 0; +#else return EINVAL; +#endif + } if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED)) return MDB_BAD_TXN; @@ -7873,7 +7887,7 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) if (unlikely(mc == NULL || countp == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return MDB_VERSION_MISMATCH; if (unlikely(mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)) @@ -7932,12 +7946,11 @@ void mdb_cursor_close(MDB_cursor *mc) { if (mc) { - mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE); + mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE_LIVE); if (!mc->mc_backup) { /* Remove from txn, if tracked. * A read-only txn (!C_UNTRACK) may have been freed already, - * so do not peek inside it. Only write txns track cursors. - */ + * so do not peek inside it. Only write txns track cursors. */ if ((mc->mc_flags & C_UNTRACK) && mc->mc_txn->mt_cursors) { MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; while (*prev && *prev != mc) prev = &(*prev)->mc_next; @@ -7946,6 +7959,8 @@ mdb_cursor_close(MDB_cursor *mc) } mc->mc_signature = 0; free(mc); + } else { + mc->mc_signature = MDBX_MC_SIGNATURE_CLOSED; } } } @@ -7953,7 +7968,7 @@ mdb_cursor_close(MDB_cursor *mc) MDB_txn * mdb_cursor_txn(MDB_cursor *mc) { - if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return NULL; return mc->mc_txn; } @@ -7961,7 +7976,7 @@ mdb_cursor_txn(MDB_cursor *mc) MDB_dbi mdb_cursor_dbi(MDB_cursor *mc) { - if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return INT_MIN; return mc->mc_dbi; } diff --git a/mdbx.c b/mdbx.c index 6e3cb210..c7393c7b 100644 --- a/mdbx.c +++ b/mdbx.c @@ -363,7 +363,7 @@ int mdbx_cursor_eof(MDB_cursor *mc) if (unlikely(mc == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) return MDB_VERSION_MISMATCH; if ((mc->mc_flags & C_INITIALIZED) == 0) From cc6e1c5119038688ace7f2118f3658852a25a8be Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 24 Jan 2017 20:51:23 +0300 Subject: [PATCH 04/21] mdbx: fix MDB_CURRENT for mdb_cursor_put() with MDB_DUPSORT. --- mdb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mdb.c b/mdb.c index 6db4d996..2242109b 100644 --- a/mdb.c +++ b/mdb.c @@ -6902,8 +6902,11 @@ more: /* Was a single item before, must convert now */ if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { /* Just overwrite the current item */ - if (flags == MDB_CURRENT) + if (flags & MDB_CURRENT) { + if ((flags & MDB_NODUPDATA) && !mc->mc_dbx->md_dcmp(data, &olddata)) + return MDB_KEYEXIST; goto current; + } /* does data match? */ if (!mc->mc_dbx->md_dcmp(data, &olddata)) { From a148bcea70bb6d456aec4829d8d46ecb3fedb028 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 26 Jan 2017 23:12:31 +0300 Subject: [PATCH 05/21] mdbx: more checks for mdbx_replace(). --- mdbx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mdbx.c b/mdbx.c index c7393c7b..a6b9468f 100644 --- a/mdbx.c +++ b/mdbx.c @@ -411,7 +411,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_cursor mc; MDB_xcursor mx; - if (unlikely(!key || !old_data || !txn)) + if (unlikely(!key || !old_data || !txn || old_data == new_data)) return EINVAL; if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) @@ -449,6 +449,8 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, goto bailout; } else { /* в old_data буфер получения предыдущего значения */ + if (unlikely(new_data && old_data->iov_base == new_data->iov_base)) + return EINVAL; MDB_val present_data; rc = mdbx_cursor_get(&mc, &present_key, &present_data, MDB_SET_KEY); if (unlikely(rc != MDB_SUCCESS)) { From 72de33c8e9821163305329f6cf3da0379a417d86 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 27 Jan 2017 03:18:51 +0300 Subject: [PATCH 06/21] mdbx: use MDB_SET_KEY inside mdbx_replace() for libfpta. --- mdbx.c | 10 +++++----- mdbx.h | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mdbx.c b/mdbx.c index a6b9468f..6b988e58 100644 --- a/mdbx.c +++ b/mdbx.c @@ -448,7 +448,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, if (new_data && mdbx_is_samedata(old_data, new_data)) goto bailout; } else { - /* в old_data буфер получения предыдущего значения */ + /* в old_data буфер для сохранения предыдущего значения */ if (unlikely(new_data && old_data->iov_base == new_data->iov_base)) return EINVAL; MDB_val present_data; @@ -541,7 +541,7 @@ mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, mdb_cursor_init(&mc, txn, dbi, &mx); int exact = 0; - int rc = mdb_cursor_set(&mc, key, data, MDB_SET, &exact); + int rc = mdb_cursor_set(&mc, key, data, MDB_SET_KEY, &exact); if (unlikely(rc != MDB_SUCCESS)) { if (rc == MDB_NOTFOUND && values_count) *values_count = 0; @@ -561,11 +561,11 @@ mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, *values_count = 1; else { mdb_tassert(txn, mc.mc_xcursor == &mx); - mdb_tassert(txn, mx.mx_cursor.mc_flags & C_INITIALIZED); + mdb_tassert(txn, mx.mx_cursor.mc_flags & C_INITIALIZED); *values_count = mx.mx_db.md_entries; } } } - } - return MDB_SUCCESS; + } + return MDB_SUCCESS; } diff --git a/mdbx.h b/mdbx.h index 83315e54..73d325ed 100644 --- a/mdbx.h +++ b/mdbx.h @@ -227,8 +227,10 @@ int mdbx_cursor_eof(MDB_cursor *mc); int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags); -/* Same as mdbx_get(), but also return the count - * of multi-values/duplicates for a given key. */ +/* Same as mdbx_get(), but: + * 1) if values_count is not NULL, then returns the count + * of multi-values/duplicates for a given key. + * 2) updates the key for pointing to the actual key's data inside DB. */ int mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, int* values_count); /** @} */ From aacd468c054df93d287855e345d54ca7b2a42cb2 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 27 Jan 2017 19:24:43 +0300 Subject: [PATCH 07/21] mdbx: check __OPTIMIZE__ for __hot/__cold/__flatten. --- reopen.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reopen.h b/reopen.h index 72f8e3ad..dd214172 100644 --- a/reopen.h +++ b/reopen.h @@ -57,7 +57,7 @@ #endif /* __must_check_result */ #ifndef __hot -# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__)) +# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__)) # define __hot __attribute__((hot, optimize("O3"))) # elif defined(__GNUC__) /* cland case, just put frequently used functions in separate section */ @@ -68,7 +68,7 @@ #endif /* __hot */ #ifndef __cold -# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__)) +# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__)) # define __cold __attribute__((cold, optimize("Os"))) # elif defined(__GNUC__) /* cland case, just put infrequently used functions in separate section */ @@ -79,7 +79,7 @@ #endif /* __cold */ #ifndef __flatten -# if defined(NDEBUG) && (defined(__GNUC__) || defined(__clang__)) +# if defined(__OPTIMIZE__) && (defined(__GNUC__) || defined(__clang__)) # define __flatten __attribute__((flatten)) # else # define __flatten From ab6cc14480d9a363227f6239daae8281b89e4df4 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 27 Jan 2017 19:30:55 +0300 Subject: [PATCH 08/21] mdbx: zero-length key is not an error for MDBX. --- mdb_chk.c | 4 +--- mdbx.c | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mdb_chk.c b/mdb_chk.c index d23ce2cd..23e7da2f 100644 --- a/mdb_chk.c +++ b/mdb_chk.c @@ -487,9 +487,7 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent) goto bailout; } - if (key.mv_size == 0) { - problem_add("entry", record_count, "key with zero length", NULL); - } else if (key.mv_size > maxkeysize) { + if (key.mv_size > maxkeysize) { problem_add("entry", record_count, "key length exceeds max-key-size", "%zu > %zu", key.mv_size, maxkeysize); } else if ((flags & MDB_INTEGERKEY) diff --git a/mdbx.c b/mdbx.c index 6b988e58..24c867b4 100644 --- a/mdbx.c +++ b/mdbx.c @@ -245,8 +245,6 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee } assert(IS_LEAF(mp)); - if (node->mn_ksize < 1) - return MDB_CORRUPTED; if (node->mn_flags & F_BIGDATA) { MDB_page *omp; pgno_t *opg; From 6882d9c104348186fe8762a76ce96d66f4cbe047 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 30 Jan 2017 20:13:56 +0300 Subject: [PATCH 09/21] mdbx: add MDBX_RESULT_FALSE and MDBX_RESULT_TRUE for libfpta. --- mdbx.c | 10 +++++----- mdbx.h | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mdbx.c b/mdbx.c index 24c867b4..bab44d87 100644 --- a/mdbx.c +++ b/mdbx.c @@ -118,7 +118,7 @@ mdbx_env_set_syncbytes(MDB_env *env, size_t bytes) return MDB_VERSION_MISMATCH; env->me_sync_threshold = bytes; - return env->me_map ? mdb_env_sync(env, 0) : 0; + return env->me_map ? mdb_env_sync(env, 0) : MDB_SUCCESS; } void __cold @@ -365,16 +365,16 @@ int mdbx_cursor_eof(MDB_cursor *mc) return MDB_VERSION_MISMATCH; if ((mc->mc_flags & C_INITIALIZED) == 0) - return 1; + return MDBX_RESULT_TRUE; if (mc->mc_snum == 0) - return 1; + return MDBX_RESULT_TRUE; if ((mc->mc_flags & C_EOF) && mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) - return 1; + return MDBX_RESULT_TRUE; - return 0; + return MDBX_RESULT_FALSE; } static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) { diff --git a/mdbx.h b/mdbx.h index 73d325ed..4e85f55d 100644 --- a/mdbx.h +++ b/mdbx.h @@ -224,6 +224,8 @@ size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary); int mdbx_cursor_eof(MDB_cursor *mc); #define MDBX_EMULTIVAL (MDB_LAST_ERRCODE - 42) +#define MDBX_RESULT_FALSE MDB_SUCCESS +#define MDBX_RESULT_TRUE (-1) int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags); From 55c43291c62166fe6468864bd83055625b219212 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 30 Jan 2017 20:15:24 +0300 Subject: [PATCH 10/21] mdbx: undef NDEBUG when MDB_DEBUG != 0. --- mdb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mdb.c b/mdb.c index 2242109b..b9f8277e 100644 --- a/mdb.c +++ b/mdb.c @@ -70,6 +70,10 @@ # warning "ReOpenMDBX required at least GLIBC 2.12." #endif +#if MDB_DEBUG +# undef NDEBUG +#endif + #include "./reopen.h" #include "./barriers.h" From e080be163188e0c8f462c05eaea2da0d33498d8f Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 30 Jan 2017 20:16:12 +0300 Subject: [PATCH 11/21] mdbx: add mdbx_is_dirty() for libfpta. --- mdbx.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mdbx.h | 2 ++ 2 files changed, 89 insertions(+) diff --git a/mdbx.c b/mdbx.c index bab44d87..3e8f93d4 100644 --- a/mdbx.c +++ b/mdbx.c @@ -567,3 +567,90 @@ mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, } return MDB_SUCCESS; } + +/* Функция сообщает находится ли указанный адрес в "грязной" странице у + * заданной пишущей транзакции. В конечном счете это позволяет избавиться от + * лишнего копирования данных из НЕ-грязных страниц. + * + * "Грязные" страницы - это те, которые уже были изменены в ходе пишущей + * транзакции. Соответственно, какие-либо дальнейшие изменения могут привести + * к перезаписи таких страниц. Поэтому все функции, выполняющие изменения, в + * качестве аргументов НЕ должны получать указатели на данные в таких + * страницах. В свою очередь "НЕ грязные" страницы перед модификацией будут + * скопированы. + * + * Другими словами, данные из "грязных" страниц должны быть либо скопированы + * перед передачей в качестве аргументов для дальнейших модификаций, либо + * отвергнуты на стадии проверки корректности аргументов. + * + * Таким образом, функция позволяет как избавится от лишнего копирования, + * так и выполнить более полную проверку аргументов. + * + * ВАЖНО: Передаваемый указатель должен указывать на начало данных. Только + * так гарантируется что актуальный заголовок страницы будет физически + * расположен в той-же странице памяти, в том числе для многостраничных + * P_OVERFLOW страниц с длинными данными. */ +int mdbx_is_dirty(const MDB_txn *txn, const void* ptr) +{ + if (unlikely(!txn)) + return EINVAL; + + if(unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + if (unlikely(txn->mt_flags & MDB_TXN_RDONLY)) + return MDB_BAD_TXN; + + const MDB_env *env = txn->mt_env; + const uintptr_t mask = ~(uintptr_t) (env->me_psize - 1); + const MDB_page *page = (const MDB_page *) ((uintptr_t) ptr & mask); + + /* LY: Тут не всё хорошо с абсолютной достоверностью результата, + * так как флажок P_DIRTY в LMDB может означать не совсем то, + * что было исходно задумано, детали см в логике кода mdb_page_touch(). + * + * Более того, в режиме БЕЗ WRITEMAP грязные страницы выделяются через + * malloc(), т.е. находятся вне mmap-диаппазона. + * + * Тем не менее, однозначно страница "не грязная" если: + * - адрес находится внутри mmap-диаппазона и в заголовке страницы + * нет флажка P_DIRTY, то однозначно страница "не грязная". + * - адрес вне mmap-диаппазона и его нет среди списка "грязных" страниц. + */ + if (env->me_map < (char*) page) { + const size_t used_size = env->me_psize * txn->mt_next_pgno; + if (env->me_map + used_size > (char*) page) { + /* страница внутри диапазона */ + if (page->mp_flags & P_DIRTY) + return MDBX_RESULT_TRUE; + return MDBX_RESULT_FALSE; + } + /* Гипотетически здесь возможна ситуация, когда указатель адресует что-то + * в пределах mmap, но за границей распределенных страниц. Это тяжелая + * ошибка, которой не возможно добиться без каких-то мега-нарушений. + * Поэтому не проверяем этот случай кроме как assert-ом, ибо бестолку. */ + mdb_tassert(txn, env->me_map + env->me_mapsize > (char*) page); + } + /* Страница вне mmap-диаппазона */ + + if (env->me_flags & MDB_WRITEMAP) + /* Если MDB_WRITEMAP, то результат уже ясен. */ + return MDBX_RESULT_FALSE; + + /* Смотрим список грязных страниц у заданной транзакции. */ + MDB_ID2 *list = txn->mt_u.dirty_list; + if (list) { + unsigned i, n = list[0].mid; + for (i = 1; i <= n; i++) { + const MDB_page *dirty = list[i].mptr; + if (dirty == page) + return MDBX_RESULT_TRUE; + } + } + + /* При вложенных транзакциях, страница может быть в dirty-списке + * родительской транзакции, но в этом случае она будет скопирована перед + * изменением в текущей транзакции, т.е. относительно заданной транзакции + * проверяемый адрес "не грязный". */ + return MDBX_RESULT_FALSE; +} diff --git a/mdbx.h b/mdbx.h index 4e85f55d..f312d677 100644 --- a/mdbx.h +++ b/mdbx.h @@ -235,6 +235,8 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, * 2) updates the key for pointing to the actual key's data inside DB. */ int mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, int* values_count); +int mdbx_is_dirty(const MDB_txn *txn, const void* ptr); + /** @} */ #ifdef __cplusplus From 61e1efeb850ba4765780a9977bde801f469e3213 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 31 Jan 2017 02:47:54 +0300 Subject: [PATCH 12/21] mdbx: add mdbx_dbi_open_ex() for libfpta. --- mdb.c | 29 ++++++++++++++++++----------- mdbx.c | 13 +++++++++++++ mdbx.h | 3 +++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/mdb.c b/mdb.c index b9f8277e..5dc61cc8 100644 --- a/mdb.c +++ b/mdb.c @@ -1183,7 +1183,6 @@ static void mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node); static void mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int force); static int mdb_drop0(MDB_cursor *mc, int subs); -static void mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi); static int mdb_reader_check0(MDB_env *env, int rlocked, int *dead); /** @cond */ @@ -10024,6 +10023,21 @@ mdb_env_info(MDB_env *env, MDB_envinfo *arg) return mdbx_env_info(env, (MDBX_envinfo*) arg, sizeof(MDB_envinfo)); } +static MDB_cmp_func* +mdbx_default_keycmp(unsigned flags) +{ + return (flags & MDB_REVERSEKEY) ? mdb_cmp_memnr : + (flags & MDB_INTEGERKEY) ? mdb_cmp_int_a2 : mdb_cmp_memn; +} + +static MDB_cmp_func* +mdbx_default_datacmp(unsigned flags) +{ + return !(flags & MDB_DUPSORT) ? 0 : + ((flags & MDB_INTEGERDUP) ? mdb_cmp_int_ua : + ((flags & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn)); +} + /** Set the default comparison functions for a database. * Called immediately after a database is opened to set the defaults. * The user can then override them with #mdb_set_compare() or @@ -10034,16 +10048,9 @@ mdb_env_info(MDB_env *env, MDB_envinfo *arg) static void mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi) { - unsigned f = txn->mt_dbs[dbi].md_flags; - - txn->mt_dbxs[dbi].md_cmp = - (f & MDB_REVERSEKEY) ? mdb_cmp_memnr : - (f & MDB_INTEGERKEY) ? mdb_cmp_int_a2 : mdb_cmp_memn; - - txn->mt_dbxs[dbi].md_dcmp = - !(f & MDB_DUPSORT) ? 0 : - ((f & MDB_INTEGERDUP) ? mdb_cmp_int_ua : - ((f & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn)); + unsigned flags = txn->mt_dbs[dbi].md_flags; + txn->mt_dbxs[dbi].md_cmp = mdbx_default_keycmp(flags); + txn->mt_dbxs[dbi].md_dcmp = mdbx_default_datacmp(flags); } int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned flags, MDB_dbi *dbi) diff --git a/mdbx.c b/mdbx.c index 3e8f93d4..dd0bff5f 100644 --- a/mdbx.c +++ b/mdbx.c @@ -654,3 +654,16 @@ int mdbx_is_dirty(const MDB_txn *txn, const void* ptr) * проверяемый адрес "не грязный". */ return MDBX_RESULT_FALSE; } + +int mdbx_dbi_open_ex(MDB_txn *txn, const char *name, unsigned flags, + MDB_dbi *pdbi, MDB_cmp_func *keycmp, MDB_cmp_func *datacmp) +{ + int rc = mdbx_dbi_open(txn, name, flags, pdbi); + if (likely(rc == MDB_SUCCESS)) { + MDB_dbi dbi = *pdbi; + unsigned flags = txn->mt_dbs[dbi].md_flags; + txn->mt_dbxs[dbi].md_cmp = keycmp ? keycmp : mdbx_default_keycmp(flags); + txn->mt_dbxs[dbi].md_dcmp = datacmp ? datacmp : mdbx_default_datacmp(flags); + } + return rc; +} diff --git a/mdbx.h b/mdbx.h index f312d677..6a266395 100644 --- a/mdbx.h +++ b/mdbx.h @@ -237,6 +237,9 @@ int mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, int* val int mdbx_is_dirty(const MDB_txn *txn, const void* ptr); +int mdbx_dbi_open_ex(MDB_txn *txn, const char *name, unsigned flags, + MDB_dbi *dbi, MDB_cmp_func *keycmp, MDB_cmp_func *datacmp); + /** @} */ #ifdef __cplusplus From 7bf9d381eecdc1fd97ff5c44a99c909f230f543e Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 30 Jan 2017 22:12:47 +0300 Subject: [PATCH 13/21] mdbx: minor simplify mdb_del0(). --- mdb.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mdb.c b/mdb.c index 5dc61cc8..2c1d4f6e 100644 --- a/mdb.c +++ b/mdb.c @@ -8779,7 +8779,7 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi, MDB_cursor mc; MDB_xcursor mx; MDB_cursor_op op; - MDB_val rdata, *xdata; + MDB_val rdata; int rc, exact = 0; DKBUF; @@ -8790,13 +8790,12 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi, if (data) { op = MDB_GET_BOTH; rdata = *data; - xdata = &rdata; + data = &rdata; } else { op = MDB_SET; - xdata = NULL; flags |= MDB_NODUPDATA; } - rc = mdb_cursor_set(&mc, key, xdata, op, &exact); + rc = mdb_cursor_set(&mc, key, data, op, &exact); if (likely(rc == 0)) { /* let mdb_page_split know about this cursor if needed: * delete will trigger a rebalance; if it needs to move From d3518bf75b14df1f9989b83fd975c09cb361f39e Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 31 Jan 2017 18:30:32 +0300 Subject: [PATCH 14/21] mdbx: fix LEAF2-pages handling in mdb_cursor_count(). --- mdb.c | 13 +++++-------- mdbx.c | 22 +++++++--------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/mdb.c b/mdb.c index 2c1d4f6e..c7c5bc11 100644 --- a/mdb.c +++ b/mdb.c @@ -7914,16 +7914,13 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) return MDB_NOTFOUND; } - if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { - *countp = 1; - } else { + *countp = 1; + if (mc->mc_xcursor != NULL) { MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); - if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) - *countp = 1; - else if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) - return EINVAL; - else + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_cassert(mc, mc->mc_xcursor && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)); *countp = mc->mc_xcursor->mx_db.md_entries; + } } #else if (unlikely(mc->mc_xcursor == NULL)) diff --git a/mdbx.c b/mdbx.c index dd0bff5f..929154fe 100644 --- a/mdbx.c +++ b/mdbx.c @@ -547,21 +547,13 @@ mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, } if (values_count) { - if (mc.mc_xcursor == NULL) - *values_count = 1; - else { - MDB_page *mp = mc.mc_pg[mc.mc_top]; - if (IS_LEAF2(mp)) - *values_count = 1; - else { - MDB_node *leaf = NODEPTR(mp, mc.mc_ki[mc.mc_top]); - if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) - *values_count = 1; - else { - mdb_tassert(txn, mc.mc_xcursor == &mx); - mdb_tassert(txn, mx.mx_cursor.mc_flags & C_INITIALIZED); - *values_count = mx.mx_db.md_entries; - } + *values_count = 1; + if (mc.mc_xcursor != NULL) { + MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_tassert(txn, mc.mc_xcursor == &mx + && (mx.mx_cursor.mc_flags & C_INITIALIZED)); + *values_count = mx.mx_db.md_entries; } } } From 18654ccf2231329e0f168656cc421c4373416390 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 31 Jan 2017 18:39:16 +0300 Subject: [PATCH 15/21] mdbx: fix MDB_CURRENT for MDB_DUPSORT in mdbx_cursor_put() for libfpta. --- mdb.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mdb.c b/mdb.c index c7c5bc11..8b4afd00 100644 --- a/mdb.c +++ b/mdb.c @@ -6762,10 +6762,24 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, DDBI(mc), DKEY(key), key ? key->mv_size : 0, data->mv_size); dkey.mv_size = 0; - if (flags & MDB_CURRENT) { if (unlikely(!(mc->mc_flags & C_INITIALIZED))) return EINVAL; +#if MDBX_MODE_ENABLED + if (F_ISSET(mc->mc_db->md_flags, MDB_DUPSORT)) { + MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_cassert(mc, mc->mc_xcursor != NULL + && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)); + if (mc->mc_xcursor->mx_db.md_entries > 1) { + rc = mdbx_cursor_del(mc, 0); + if (rc != MDB_SUCCESS) + return rc; + flags -= MDB_CURRENT; + } + } + } +#endif /* MDBX_MODE_ENABLED */ rc = MDB_SUCCESS; } else if (mc->mc_db->md_root == P_INVALID) { /* new database, cursor has nothing to point to */ @@ -7772,9 +7786,7 @@ mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int new_dupdata) mx->mx_cursor.mc_flags |= C_INITIALIZED; mx->mx_cursor.mc_ki[0] = 0; mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA; -#if UINT_MAX < SIZE_MAX mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp; -#endif } else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) { return; } @@ -7867,7 +7879,7 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc) if (unlikely(!TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID))) return EINVAL; - if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) { + if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) { #if MDBX_MODE_ENABLED MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; while (*prev && *prev != mc) prev = &(*prev)->mc_next; From c9b7c0f4d14d6dec036ed47fd776b45117f279ba Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 31 Jan 2017 19:12:52 +0300 Subject: [PATCH 16/21] mdbx: rework mdbx_replace() for libfpta. --- mdbx.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/mdbx.c b/mdbx.c index 929154fe..4d249174 100644 --- a/mdbx.c +++ b/mdbx.c @@ -397,6 +397,8 @@ static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) { * когда посредством old_data из записей с одинаковым ключом для * удаления/обновления выбирается конкретная. Для выбора этого сценария * во flags следует одновременно указать MDB_CURRENT и MDB_NOOVERWRITE. + * Именно эта комбинация выбрана, так как она лишена смысла, и этим позволяет + * идентифицировать запрос такого сценария. * * Функция может быть замещена соответствующими операциями с курсорами * после двух доработок (TODO): @@ -436,15 +438,46 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, int rc; MDB_val present_key = *key; - if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE) - && (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) { + if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE)) { /* в old_data значение для выбора конкретного дубликата */ + if (unlikely(!(txn->mt_dbs[dbi].md_flags & MDB_DUPSORT))) { + rc = EINVAL; + goto bailout; + } + + /* убираем лишний бит, он был признаком запрошенного режима */ + flags -= MDB_NOOVERWRITE; + rc = mdbx_cursor_get(&mc, &present_key, old_data, MDB_GET_BOTH); if (rc != MDB_SUCCESS) goto bailout; - /* если данные совпадают, то ничего делать не надо */ - if (new_data && mdbx_is_samedata(old_data, new_data)) - goto bailout; + + if (new_data) { + /* обновление конкретного дубликата */ + if (mdbx_is_samedata(old_data, new_data)) + /* если данные совпадают, то ничего делать не надо */ + goto bailout; +#if 0 /* LY: исправлено в mdbx_cursor_put(), здесь в качестве памятки */ + MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); + if (F_ISSET(leaf->mn_flags, F_DUPDATA) + && mc.mc_xcursor->mx_db.md_entries > 1) { + /* Если у ключа больше одного значения, то + * сначала удаляем найденое "старое" значение. + * + * Этого можно не делать, так как MDBX уже + * обучен корректно обрабатывать такие ситуации. + * + * Однако, следует помнить, что в LMDB при + * совпадении размера данных, значение будет + * просто перезаписано с нарушением + * упорядоченности, что сломает поиск. */ + rc = mdbx_cursor_del(&mc, 0); + if (rc != MDB_SUCCESS) + goto bailout; + flags -= MDB_CURRENT; + } +#endif + } } else { /* в old_data буфер для сохранения предыдущего значения */ if (unlikely(new_data && old_data->iov_base == new_data->iov_base)) @@ -468,14 +501,20 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, MDB_node *leaf = NODEPTR(page, mc.mc_ki[mc.mc_top]); if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1); - rc = MDBX_EMULTIVAL; - goto bailout; + if (mc.mc_xcursor->mx_db.md_entries > 1) { + rc = MDBX_EMULTIVAL; + goto bailout; + } } /* если данные совпадают, то ничего делать не надо */ if (new_data && mdbx_is_samedata(&present_data, new_data)) { *old_data = *new_data; goto bailout; } + /* В оригинальной LMDB фладок MDB_CURRENT здесь приведет + * к замене данных без учета MDB_DUPSORT сортировки, + * но здесь это в любом случае допустимо, так как мы + * проверили что для ключа есть только одно значение. */ } else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) { /* если данные совпадают и установлен MDB_NODUPDATA */ rc = MDB_KEYEXIST; @@ -494,7 +533,7 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, if (unlikely(old_data->iov_len < present_data.iov_len)) { old_data->iov_base = NULL; old_data->iov_len = present_data.iov_len; - rc = -1; + rc = MDBX_RESULT_TRUE; goto bailout; } memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len); From 4681620e66bbd0973bb441edf4910e9c8a2d9c52 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 2 Feb 2017 13:15:16 +0300 Subject: [PATCH 17/21] mdbx: don't ignore `data` arg in mdb_del() for libfpta. --- lmdb.h | 10 +++++++++- mdb.c | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lmdb.h b/lmdb.h index 7463e4e8..b6a55f11 100644 --- a/lmdb.h +++ b/lmdb.h @@ -1,7 +1,7 @@ /** @file lmdb.h * @brief Extended Lightning memory-mapped database library * - * @mainpage Extended Lightning Memory-Mapped Database Manager (MDBX) + * @mainpage Extended Lightning Memory-Mapped Database (MDBX) * * @section intro_sec Introduction * MDBX is a Btree-based database management library modeled loosely on the @@ -1387,12 +1387,20 @@ int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, /** @brief Delete items from a database. * * This function removes key/data pairs from the database. + * + * MDBX-mode: + * The data parameter is NOT ignored regardless the database does + * support sorted duplicate data items or not. If the data parameter + * is non-NULL only the matching data item will be deleted. + * + * LMDB-compatible mode: * If the database does not support sorted duplicate data items * (#MDB_DUPSORT) the data parameter is ignored. * If the database supports sorted duplicates and the data parameter * is NULL, all of the duplicate data items for the key will be * deleted. Otherwise, if the data parameter is non-NULL * only the matching data item will be deleted. + * * This function will return #MDB_NOTFOUND if the specified key/data * pair is not in the database. * @param[in] txn A transaction handle returned by #mdb_txn_begin() diff --git a/mdb.c b/mdb.c index 8b4afd00..449ba82f 100644 --- a/mdb.c +++ b/mdb.c @@ -8773,10 +8773,12 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi, if (unlikely(txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))) return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; +#if ! MDBX_MODE_ENABLED if (!F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) { /* must ignore any data */ data = NULL; } +#endif return mdb_del0(txn, dbi, key, data, 0); } From 16fe998f7cb9c5b5304d7449ecc6e4959c7d992d Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 7 Feb 2017 20:56:51 +0300 Subject: [PATCH 18/21] mdbx: fix losing a zero-length value of sorted-dups (for libfpta). --- mdb.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mdb.c b/mdb.c index 449ba82f..c627203c 100644 --- a/mdb.c +++ b/mdb.c @@ -6761,7 +6761,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, mdb_debug("==> put db %d key [%s], size %zu, data size %zu", DDBI(mc), DKEY(key), key ? key->mv_size : 0, data->mv_size); - dkey.mv_size = 0; + int dupdata_flag = 0; if (flags & MDB_CURRENT) { if (unlikely(!(mc->mc_flags & C_INITIALIZED))) return EINVAL; @@ -6934,6 +6934,7 @@ more: } /* Back up original data item */ + dupdata_flag = 1; dkey.mv_size = olddata.mv_size; dkey.mv_data = memcpy(fp+1, olddata.mv_data, olddata.mv_size); @@ -7162,7 +7163,7 @@ new_sub: * size limits on dupdata. The actual data fields of the child * DB are all zero size. */ if (do_sub) { - int xflags, new_dupdata; + int xflags; size_t ecount; put_sub: xdata.mv_size = 0; @@ -7178,9 +7179,8 @@ put_sub: } if (sub_root) mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; - new_dupdata = (int)dkey.mv_size; /* converted, write the original data first */ - if (dkey.mv_size) { + if (dupdata_flag) { rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags); if (unlikely(rc)) goto bad_sub; @@ -7200,7 +7200,7 @@ put_sub: if (!(m2->mc_flags & C_INITIALIZED)) continue; if (m2->mc_pg[i] == mp) { if (m2->mc_ki[i] == mc->mc_ki[i]) { - mdb_xcursor_init2(m2, mx, new_dupdata); + mdb_xcursor_init2(m2, mx, dupdata_flag); } else if (!insert_key && m2->mc_ki[i] < nkeys) { XCURSOR_REFRESH(m2, mp, m2->mc_ki[i]); } From ed925640c8141c484078b6038e1a996f6fe99593 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 8 Feb 2017 16:46:05 +0300 Subject: [PATCH 19/21] mdbx: zero-length data is not an error for MDBX. --- mdb_chk.c | 2 +- mdbx.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mdb_chk.c b/mdb_chk.c index 23e7da2f..1422eea1 100644 --- a/mdb_chk.c +++ b/mdb_chk.c @@ -264,7 +264,7 @@ static int pgvisitor(size_t pgno, unsigned pgnumber, void* ctx, const char* dbi, problem_add("page", pgno, "illegal header-length", "%zu < %i < %zu", sizeof(long), header_bytes, stat.base.ms_psize - sizeof(long)); if (payload_bytes < 1) { - if (nentries > 0) { + if (nentries > 1) { problem_add("page", pgno, "zero size-of-entry", "payload %i bytes, %i entries", payload_bytes, nentries); if ((size_t) header_bytes + unused_bytes < page_size) { diff --git a/mdbx.c b/mdbx.c index 4d249174..e3c86693 100644 --- a/mdbx.c +++ b/mdbx.c @@ -279,8 +279,6 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee MDB_db *db = NODEDATA(node); char* name = NULL; - if (NODEDSZ(node) < 1) - return MDB_CORRUPTED; if (! (node->mn_flags & F_DUPDATA)) { name = NODEKEY(node); int namelen = (char*) db - name; From eb4eda636803954e79d63b6bc46095ccfb5dd5ee Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 8 Feb 2017 17:52:05 +0300 Subject: [PATCH 20/21] mdbx: append README. --- README.md | 133 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index d11e1ad7..2cbe08cb 100644 --- a/README.md +++ b/README.md @@ -3,38 +3,36 @@ libmdbx Extended LMDB, aka "Расширенная LMDB". *The Future will Positive. Всё будет хорошо.* - [![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=devel)](https://travis-ci.org/ReOpen/libmdbx) - -English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel). +English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel). ## Кратко -_libmdbx_ - это встраиваемый key-value движок хранения со -специфическим набором возможностей, которые при правильном -применении позволяют создавать уникальные решения с чемпионской -производительностью, идеально сочетаясь с технологией -[MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory). -_libmdbx_ обновляет совместно используемый набор данных, никак -не мешая при этом параллельным операциям чтения, не применяя -атомарных операций к самим данным, и обеспечивая -согласованность при аварийной остановке в любой момент. Поэтому -_libmdbx_ позволяя строить системы с линейным масштабированием -производительности чтения/поиска по ядрам CPU и амортизационной -стоимостью любых операций Olog(N). +_libmdbx_ - это встраиваемый key-value движок хранения со специфическим +набором возможностей, которые при правильном применении позволяют +создавать уникальные решения с чемпионской производительностью, идеально +сочетаясь с технологией [MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory). + +_libmdbx_ обновляет совместно используемый набор данных, никак не мешая +при этом параллельным операциям чтения, не применяя атомарных операций к +самим данным, и обеспечивая согласованность при аварийной остановке в +любой момент. Поэтому _libmdbx_ позволяя строить системы с линейным +масштабированием производительности чтения/поиска по ядрам CPU и +амортизационной стоимостью любых операций Olog(N). ### История + _libmdbx_ является потомком "Lightning Memory-Mapped Database", известной под аббревиатурой [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). Изначально доработка производилась в составе проекта -[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за -год работы внесенные изменения приобрели самостоятельную -ценность. Осенью 2015 доработанный движок был выделен в -отдельный проект, который был [представлен на конференции -Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html). +[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год +работы внесенные изменения приобрели самостоятельную ценность. Осенью +2015 доработанный движок был выделен в отдельный проект, который был +[представлен на конференции Highload++ +2015](http://www.highload.ru/2015/abstracts/1831.html). Характеристики и ключевые особенности @@ -284,48 +282,85 @@ RECLAIM` в _libmdbx_. формированием сильных точек фиксации. 4. Возможность автоматического формирования контрольных точек -(сброса данных на диск) при накоплении заданного объёма -изменений, устанавливаемого функцией -`mdbx_env_set_syncbytes()`. +(сброса данных на диск) при накоплении заданного объёма изменений, +устанавливаемого функцией `mdbx_env_set_syncbytes()`. 5. Возможность получить отставание текущей транзакции чтения от -последней версии данных в БД посредством -`mdbx_txn_straggler()`. +последней версии данных в БД посредством `mdbx_txn_straggler()`. -6. Утилита mdbx_chk для проверки БД и функция -`mdbx_env_pgwalk()` для обхода всех страниц БД. +6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для +обхода всех страниц БД. -7. Управление отладкой и получение отладочных сообщений -посредством `mdbx_setup_debug()`. +7. Управление отладкой и получение отладочных сообщений посредством +`mdbx_setup_debug()`. 8. Возможность связать с каждой завершаемой транзакцией до 3 -дополнительных маркеров посредством `mdbx_canary_put()`, и -прочитать их в транзакции чтения посредством -`mdbx_canary_get()`. +дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их +в транзакции чтения посредством `mdbx_canary_get()`. -9. Возможность узнать есть ли за текущей позицией курсора -строка данных посредством `mdbx_cursor_eof()`. +9. Возможность узнать есть ли за текущей позицией курсора строка данных +посредством `mdbx_cursor_eof()`. -10. Возможность явно запросить обновление существующей записи, -без создания новой посредством флажка `MDB_CURRENT` для -`mdb_put()`. +10. Возможность явно запросить обновление существующей записи, без +создания новой посредством флажка `MDB_CURRENT` для `mdbx_put()`. -11. Возможность обновить или удалить запись с получением -предыдущего значения данных посредством `mdbx_replace()`. +11. Возможность обновить или удалить запись с получением предыдущего +значения данных посредством `mdbx_replace()`. -12. Поддержка ключей нулевого размера. +12. Поддержка ключей и значений нулевой длины. Включая сортированные +дубликаты, в том числе вне зависимости от порядка их добавления или +обновления. -13. Исправленный вариант `mdb_cursor_count()`, возвращающий -корректное количество дубликатов для всех типов таблиц и любого -положения курсора. +13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное +количество дубликатов для всех типов таблиц и любого положения курсора. 14. Возможность открыть БД в эксклюзивном режиме посредством `mdbx_env_open_ex()`, например в целях её проверки. -15. Возможность закрыть БД в "грязном" состоянии (без сброса -данных и формирования сильной точки фиксации) посредством -`mdbx_env_close_ex()`. +15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и +формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. -16. Возможность получить посредством `mdbx_env_info()` -дополнительную информацию, включая номер самой старой версии БД -(снимка данных), который используется одним из читателей. +16. Возможность получить посредством `mdbx_env_info()` дополнительную +информацию, включая номер самой старой версии БД (снимка данных), +который используется одним из читателей. + +17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий) +аргумент `data` для таблиц без дубликатов (без флажка `MDB_DUPSORT`), а +при его ненулевом значении всегда использует его для сверки с удаляемой +записью. + +18. Возможность открыть dbi-таблицу, одновременно с установкой +компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`. + +19. Возможность посредством `mdbx_is_dirty()` определить находятся ли +некоторый ключ или данные в "грязной" странице БД. Таким образом избегаю +лишнего копирования данных перед выполнением модифицирующих операций +(значения в размещенные "грязных" страницах могут быть перезаписаны при +изменениях, иначе они будут неизменны). + +20. Корректное обновление текущей записи, в том числе сортированного +дубликата, при использовании режима `MDB_CURRENT` в `mdbx_cursor_put()`. + +21. Все курсоры, как в транзакциях только для чтения, так и в пишущих, +могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ +ОСВОБОЖДАТЬСЯ ЯВНО. + > + > ## _ВАЖНО_, Обратите внимание! + > + > Это единственное изменение в API, которое значимо меняет + > семантику управления курсорами и может приводить к утечкам + > памяти. Следует отметить, что это изменение вынужденно. + > Так устраняется неоднозначность с массой тяжких последствий: + > + > - обращение к уже освобожденной памяти; + > - попытки повторного освобождения памяти; + > - memory corruption and segfaults. + +22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из +`mdbx_put()` и `mdbx_replace()` при попытке выполнять неоднозначное +обновление или удаления одного из нескольких значений с одним ключом, +т.е. когда невозможно однозначно идентифицировать одно целевое значение +из нескольких. + +23. Возможность посредством `mdbx_get_ex()` получить значение по +заданному ключу, одновременно с количеством дубликатов. From 6f600845f383a464420c65b9036c443409be00fc Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 8 Feb 2017 19:43:22 +0300 Subject: [PATCH 21/21] mdbx: more for robustness free/reuse of cursors. Change-Id: I6c4056bc47aea28d39701713f97694a4ebe9b582 --- mdb.c | 68 ++++++++++++++++++++++++++++++++++++++-------------------- mdbx.c | 2 +- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/mdb.c b/mdb.c index c627203c..ce088c72 100644 --- a/mdb.c +++ b/mdb.c @@ -937,8 +937,9 @@ struct MDB_xcursor; * (A node with #F_DUPDATA but no #F_SUBDATA contains a subpage). */ struct MDB_cursor { -#define MDBX_MC_SIGNATURE_LIVE (0xFE05D5B1^MDBX_MODE_SALT) -#define MDBX_MC_SIGNATURE_CLOSED (0x90E297A7^MDBX_MODE_SALT) +#define MDBX_MC_SIGNATURE (0xFE05D5B1^MDBX_MODE_SALT) +#define MDBX_MC_READY4CLOSE (0x2817A047^MDBX_MODE_SALT) +#define MDBX_MC_WAIT4EOT (0x90E297A7^MDBX_MODE_SALT) unsigned mc_signature; /** Next cursor on this DB in this txn */ MDB_cursor *mc_next; @@ -2691,7 +2692,7 @@ mdb_cursor_shadow(MDB_txn *src, MDB_txn *dst) * @return 0 on success, non-zero on failure. */ static void -mdb_cursors_close(MDB_txn *txn, unsigned merge) +mdb_cursors_eot(MDB_txn *txn, unsigned merge) { MDB_cursor **cursors = txn->mt_cursors, *mc, *next, *bk; MDB_xcursor *mx; @@ -2699,8 +2700,8 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge) for (i = txn->mt_numdbs; --i >= 0; ) { for (mc = cursors[i]; mc; mc = next) { - mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE_LIVE - || mc->mc_signature == MDBX_MC_SIGNATURE_CLOSED); + mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE + || mc->mc_signature == MDBX_MC_WAIT4EOT); next = mc->mc_next; if ((bk = mc->mc_backup) != NULL) { if (merge) { @@ -2713,14 +2714,26 @@ mdb_cursors_close(MDB_txn *txn, unsigned merge) if ((mx = mc->mc_xcursor) != NULL) mx->mx_cursor.mc_txn = bk->mc_txn; } else { - /* Abort nested txn */ + /* Abort nested txn, but save current cursor's stage */ + unsigned stage = mc->mc_signature; *mc = *bk; + mc->mc_signature = stage; if ((mx = mc->mc_xcursor) != NULL) *mx = *(MDB_xcursor *)(bk+1); } +#if MDBX_MODE_ENABLED + bk->mc_signature = 0; + free(bk); + } + if (mc->mc_signature == MDBX_MC_WAIT4EOT) { + mc->mc_signature = 0; + free(mc); + } else { + mc->mc_signature = MDBX_MC_READY4CLOSE; + } +#else mc = bk; } -#if ! MDBX_MODE_ENABLED /* Only malloced cursors are permanently tracked. */ mc->mc_signature = 0; free(mc); @@ -3171,7 +3184,7 @@ mdb_txn_end(MDB_txn *txn, unsigned mode) pgno_t *pghead = env->me_pghead; if (!(mode & MDB_END_UPDATE)) /* !(already closed cursors) */ - mdb_cursors_close(txn, 0); + mdb_cursors_eot(txn, 0); if (!(env->me_flags & MDB_WRITEMAP)) { mdb_dlist_free(txn); } @@ -3769,7 +3782,7 @@ mdb_txn_commit(MDB_txn *txn) parent->mt_flags = txn->mt_flags; /* Merge our cursors into parent's and close them */ - mdb_cursors_close(txn, 1); + mdb_cursors_eot(txn, 1); /* Update parent's DB table. */ memcpy(parent->mt_dbs, txn->mt_dbs, txn->mt_numdbs * sizeof(MDB_db)); @@ -3888,7 +3901,7 @@ mdb_txn_commit(MDB_txn *txn) goto fail; } - mdb_cursors_close(txn, 0); + mdb_cursors_eot(txn, 0); if (!txn->mt_u.dirty_list[0].mid && !(txn->mt_flags & (MDB_TXN_DIRTY|MDB_TXN_SPILLS))) @@ -6473,7 +6486,7 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, if (unlikely(mc == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; if (unlikely(mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)) @@ -6707,7 +6720,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, if (unlikely(mc == NULL || key == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; env = mc->mc_txn->mt_env; @@ -7259,7 +7272,7 @@ mdb_cursor_del(MDB_cursor *mc, unsigned flags) if (unlikely(!mc)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; if (unlikely(mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))) @@ -7800,7 +7813,7 @@ mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int new_dupdata) static void mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) { - mc->mc_signature = MDBX_MC_SIGNATURE_LIVE; + mc->mc_signature = MDBX_MC_SIGNATURE; mc->mc_next = NULL; mc->mc_backup = NULL; mc->mc_dbi = dbi; @@ -7815,7 +7828,7 @@ mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) mc->mc_ki[0] = 0; if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { mdb_tassert(txn, mx != NULL); - mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE_LIVE; + mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE; mc->mc_xcursor = mx; mdb_xcursor_init0(mc); } else { @@ -7872,20 +7885,26 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc) if (unlikely(!mc || !txn)) return EINVAL; - if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE - || mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) return MDB_VERSION_MISMATCH; + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE + && mc->mc_signature != MDBX_MC_READY4CLOSE)) + return EINVAL; + if (unlikely(!TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID))) return EINVAL; + if (unlikely(mc->mc_backup)) + return EINVAL; + if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) { #if MDBX_MODE_ENABLED MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; while (*prev && *prev != mc) prev = &(*prev)->mc_next; if (*prev == mc) *prev = mc->mc_next; - mc->mc_signature = 0; + mc->mc_signature = MDBX_MC_READY4CLOSE; #else return EINVAL; #endif @@ -7905,7 +7924,7 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) if (unlikely(mc == NULL || countp == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; if (unlikely(mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)) @@ -7961,7 +7980,8 @@ void mdb_cursor_close(MDB_cursor *mc) { if (mc) { - mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE_LIVE); + mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE + || mc->mc_signature == MDBX_MC_READY4CLOSE); if (!mc->mc_backup) { /* Remove from txn, if tracked. * A read-only txn (!C_UNTRACK) may have been freed already, @@ -7975,7 +7995,9 @@ mdb_cursor_close(MDB_cursor *mc) mc->mc_signature = 0; free(mc); } else { - mc->mc_signature = MDBX_MC_SIGNATURE_CLOSED; + /* cursor closed before nested txn ends */ + mdb_cassert(mc, mc->mc_signature == MDBX_MC_SIGNATURE); + mc->mc_signature = MDBX_MC_WAIT4EOT; } } } @@ -7983,7 +8005,7 @@ mdb_cursor_close(MDB_cursor *mc) MDB_txn * mdb_cursor_txn(MDB_cursor *mc) { - if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE)) return NULL; return mc->mc_txn; } @@ -7991,7 +8013,7 @@ mdb_cursor_txn(MDB_cursor *mc) MDB_dbi mdb_cursor_dbi(MDB_cursor *mc) { - if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE)) return INT_MIN; return mc->mc_dbi; } diff --git a/mdbx.c b/mdbx.c index e3c86693..71fd224b 100644 --- a/mdbx.c +++ b/mdbx.c @@ -359,7 +359,7 @@ int mdbx_cursor_eof(MDB_cursor *mc) if (unlikely(mc == NULL)) return EINVAL; - if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE_LIVE)) + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; if ((mc->mc_flags & C_INITIALIZED) == 0)