From 17c6555a7fef4eb8711e826bf3401b8b9b6e93cf Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 21 Nov 2016 20:50:39 +0300 Subject: [PATCH 01/86] mdbx: add 'canary' support for libfpta. Change-Id: I62c68f149adf38d65aa9371a1fb3adac405d23ed --- mdb.c | 22 +++++++++++++++++++++- mdbx.c | 32 ++++++++++++++++++++++++++++++++ mdbx.h | 8 ++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/mdb.c b/mdb.c index edf088ec..45fcc5e6 100644 --- a/mdb.c +++ b/mdb.c @@ -778,6 +778,10 @@ typedef struct MDB_meta { volatile uint64_t mm_datasync_sign; #define META_IS_WEAK(meta) ((meta)->mm_datasync_sign == MDB_DATASIGN_WEAK) #define META_IS_STEADY(meta) ((meta)->mm_datasync_sign > MDB_DATASIGN_WEAK) + +#if MDBX_MODE_ENABLED + volatile mdbx_canary mm_canary; +#endif } MDB_meta; /** Buffer for a stack-allocated meta page. @@ -809,7 +813,7 @@ typedef struct MDB_dbx { * Every operation requires a transaction handle. */ struct MDB_txn { -#define MDBX_MT_SIGNATURE 0x706C553B +#define MDBX_MT_SIGNATURE 0x93D53A31 unsigned mt_signature; MDB_txn *mt_parent; /**< parent of a nested txn */ /** Nested txn under this txn, set together with flag #MDB_TXN_HAS_CHILD */ @@ -895,6 +899,10 @@ struct MDB_txn { * dirty_list into mt_parent after freeing hidden mt_parent pages. */ unsigned mt_dirty_room; + +#if MDBX_MODE_ENABLED + mdbx_canary mt_canary; +#endif }; /** Enough space for 2^32 nodes with minimum of 2 keys per node. I.e., plenty. @@ -2842,6 +2850,9 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) txn->mt_next_pgno = meta->mm_last_pg+1; /* Copy the DB info and flags */ memcpy(txn->mt_dbs, meta->mm_dbs, CORE_DBS * sizeof(MDB_db)); +#if MDBX_MODE_ENABLED + txn->mt_canary = meta->mm_canary; +#endif break; } } @@ -2858,6 +2869,9 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) pthread_mutex_lock(&tsan_mutex); #endif MDB_meta *meta = mdb_meta_head_w(env); +#if MDBX_MODE_ENABLED + txn->mt_canary = meta->mm_canary; +#endif txn->mt_txnid = meta->mm_txnid + 1; txn->mt_flags = flags; #ifdef __SANITIZE_THREAD__ @@ -3919,6 +3933,9 @@ mdb_txn_commit(MDB_txn *txn) meta.mm_dbs[MAIN_DBI] = txn->mt_dbs[MAIN_DBI]; meta.mm_last_pg = txn->mt_next_pgno - 1; meta.mm_txnid = txn->mt_txnid; +#if MDBX_MODE_ENABLED + meta.mm_canary = txn->mt_canary; +#endif rc = mdb_env_sync0(env, env->me_flags | txn->mt_flags, &meta); } @@ -4155,6 +4172,9 @@ mdb_env_sync0(MDB_env *env, unsigned flags, MDB_meta *pending) target->mm_dbs[FREE_DBI] = pending->mm_dbs[FREE_DBI]; target->mm_dbs[MAIN_DBI] = pending->mm_dbs[MAIN_DBI]; target->mm_last_pg = pending->mm_last_pg; +#if MDBX_MODE_ENABLED + target->mm_canary = pending->mm_canary; +#endif /* LY: 'commit' the meta */ target->mm_txnid = pending->mm_txnid; target->mm_datasync_sign = pending->mm_datasync_sign; diff --git a/mdbx.c b/mdbx.c index fb4aac85..cf5187fe 100644 --- a/mdbx.c +++ b/mdbx.c @@ -320,3 +320,35 @@ mdbx_env_pgwalk(MDB_txn *txn, MDBX_pgvisitor_func* visitor, void* user) rc = visitor(P_INVALID, 0, user, NULL, NULL, 0, 0, 0, 0); return rc; } + +int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary) +{ + if (unlikely(!txn)) + return EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + if (unlikely(F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))) + return EACCES; + + if (likely(canary)) { + txn->mt_canary.x = canary->x; + txn->mt_canary.y = canary->y; + txn->mt_canary.z = canary->z; + } + txn->mt_canary.v = txn->mt_txnid; + + return MDB_SUCCESS; +} + +size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary) +{ + if(unlikely(!txn || txn->mt_signature != MDBX_MT_SIGNATURE)) + return 0; + + if (likely(canary)) + *canary = txn->mt_canary; + + return txn->mt_txnid; +} diff --git a/mdbx.h b/mdbx.h index bcbd4f47..d494ef36 100644 --- a/mdbx.h +++ b/mdbx.h @@ -211,6 +211,14 @@ typedef int MDBX_pgvisitor_func(size_t pgno, unsigned pgnumber, void* ctx, const char* dbi, const char *type, int nentries, int payload_bytes, int header_bytes, int unused_bytes); int mdbx_env_pgwalk(MDB_txn *txn, MDBX_pgvisitor_func* visitor, void* ctx); + +typedef struct mdbx_canary { + size_t x, y, z, v; +} mdbx_canary; + +int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary); +size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary); + /** @} */ #ifdef __cplusplus From e381191c0fc3c6587b2379e26daeebb7b36792e9 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 27 Nov 2016 12:56:27 +0300 Subject: [PATCH 02/86] mdbx: fix typo. Change-Id: I46344bf13a71b04b32d84bf0e0bc0a34ae6ef162 --- mdb_chk.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mdb_chk.c b/mdb_chk.c index 4fe83ff0..b86b8f96 100644 --- a/mdb_chk.c +++ b/mdb_chk.c @@ -52,7 +52,7 @@ flagbit dbflags[] = { static volatile sig_atomic_t gotsignal; -static void signal_hanlder( int sig ) { +static void signal_handler( int sig ) { (void) sig; gotsignal = 1; } @@ -660,13 +660,13 @@ int main(int argc, char *argv[]) usage(prog); #ifdef SIGPIPE - signal(SIGPIPE, signal_hanlder); + signal(SIGPIPE, signal_handler); #endif #ifdef SIGHUP - signal(SIGHUP, signal_hanlder); + signal(SIGHUP, signal_handler); #endif - signal(SIGINT, signal_hanlder); - signal(SIGTERM, signal_hanlder); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); envname = argv[optind]; print("Running mdbx_chk for '%s' in %s mode...\n", From 533ad276bbf68b4c62f18a3325dc144ddd13cf4c Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Tue, 29 Nov 2016 19:19:45 +0000 Subject: [PATCH 03/86] mdbx: backport - more for cursor tracking after deletion (ITS#8406). xcursor fixup depends on init state Change-Id: I13139c401e2ae6bbe3d7e6b9fda3739f9ec789cf --- mdb.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index 408cebdb..6d713ba7 100644 --- a/mdb.c +++ b/mdb.c @@ -8451,6 +8451,11 @@ mdb_cursor_del0(MDB_cursor *mc) if (m3->mc_pg[mc->mc_top] == mp) { if (m3->mc_ki[mc->mc_top] == ki) { m3->mc_flags |= C_DEL; + if (mc->mc_db->md_flags & MDB_DUPSORT) { + /* Sub-cursor referred into dataset which is gone */ + m3->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF); + } + continue; } else if (m3->mc_ki[mc->mc_top] > ki) { m3->mc_ki[mc->mc_top]--; } @@ -8496,10 +8501,14 @@ mdb_cursor_del0(MDB_cursor *mc) if (mc->mc_db->md_flags & MDB_DUPSORT) { MDB_node *node = NODEPTR(m3->mc_pg[m3->mc_top], m3->mc_ki[m3->mc_top]); /* If this node is a fake page, it needs to be reinited - * because its data has moved. + * because its data has moved. But just reset mc_pg[0] + * if the xcursor is already live. */ if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) { - mdb_xcursor_init1(m3, node); + if (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) + m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); + else + mdb_xcursor_init1(m3, node); } } } From 2196a9b72c741fc922db1e5933df23f9b0e978ad Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Thu, 1 Dec 2016 21:17:42 +0100 Subject: [PATCH 04/86] mdbx: backport - factor out refreshing sub-page pointers. Change-Id: If2d3efde19ff751da208959f6f2834ece1f64e56 --- mdb.c | 88 ++++++++++++++++++++++++----------------------------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/mdb.c b/mdb.c index 6d713ba7..302a4256 100644 --- a/mdb.c +++ b/mdb.c @@ -973,6 +973,21 @@ typedef struct MDB_xcursor { unsigned char mx_dbflag; } MDB_xcursor; + /** Check if there is an inited xcursor, so #XCURSOR_REFRESH() is proper */ +#define XCURSOR_INITED(mc) \ + ((mc)->mc_xcursor && ((mc)->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) + + /** Update sub-page pointer, if any, in \b mc->mc_xcursor. Needed + * when the node which contains the sub-page may have moved. Called + * with \b mp = mc->mc_pg[mc->mc_top], \b ki = mc->mc_ki[mc->mc_top]. + */ +#define XCURSOR_REFRESH(mc, mp, ki) do { \ + MDB_page *xr_pg = (mp); \ + MDB_node *xr_node = NODEPTR(xr_pg, ki); \ + if ((xr_node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) \ + (mc)->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(xr_node); \ +} while (0) + /** State of FreeDB old pages, stored in the MDB_env */ typedef struct MDB_pgstate { pgno_t *mf_pghead; /**< Reclaimed freeDB pages, or NULL before use */ @@ -1445,7 +1460,7 @@ mdb_cursor_chk(MDB_cursor *mc) } if (unlikely(mc->mc_ki[i] >= NUMKEYS(mc->mc_pg[i]))) mdb_print("ack!\n"); - if (mc->mc_xcursor && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) { + if (XCURSOR_INITED(mc)) { node = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); if (((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) && mc->mc_xcursor->mx_cursor.mc_pg[0] != NODEDATA(node)) { @@ -2544,14 +2559,8 @@ done: if (m2 == mc) continue; if (m2->mc_pg[mc->mc_top] == mp) { m2->mc_pg[mc->mc_top] = np; - if ((mc->mc_db->md_flags & MDB_DUPSORT) && - IS_LEAF(np) && - (m2->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) - { - MDB_node *leaf = NODEPTR(np, m2->mc_ki[mc->mc_top]); - if ((leaf->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) - m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf); - } + if (XCURSOR_INITED(m2) && IS_LEAF(np)) + XCURSOR_REFRESH(m2, np, m2->mc_ki[mc->mc_top]); } } } @@ -6926,11 +6935,8 @@ new_sub: if (m3->mc_ki[i] >= mc->mc_ki[i] && insert_key) { m3->mc_ki[i]++; } - if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) { - MDB_node *n2 = NODEPTR(mp, m3->mc_ki[i]); - if ((n2->mn_flags & (F_SUBDATA|F_DUPDATA)) == F_DUPDATA) - m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2); - } + if (XCURSOR_INITED(m3)) + XCURSOR_REFRESH(m3, mp, m3->mc_ki[i]); } } } @@ -6981,9 +6987,7 @@ put_sub: if (m2->mc_ki[i] == mc->mc_ki[i]) { mdb_xcursor_init2(m2, mx, new_dupdata); } else if (!insert_key && m2->mc_ki[i] < nkeys) { - MDB_node *n2 = NODEPTR(mp, m2->mc_ki[i]); - if ((n2->mn_flags & (F_SUBDATA|F_DUPDATA)) == F_DUPDATA) - m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2); + XCURSOR_REFRESH(m2, mp, m2->mc_ki[i]); } } } @@ -7094,13 +7098,12 @@ mdb_cursor_del(MDB_cursor *mc, unsigned flags) if (m2 == mc || m2->mc_snum < mc->mc_snum) continue; if (!(m2->mc_flags & C_INITIALIZED)) continue; if (m2->mc_pg[mc->mc_top] == mp) { - if (m2->mc_ki[mc->mc_top] == mc->mc_ki[mc->mc_top]) { - m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf); - } else { - MDB_node *n2 = NODEPTR(mp, m2->mc_ki[mc->mc_top]); - if (!(n2->mn_flags & F_SUBDATA)) - m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2); + MDB_node *n2 = leaf; + if (m2->mc_ki[mc->mc_top] != mc->mc_ki[mc->mc_top]) { + n2 = NODEPTR(mp, m2->mc_ki[mc->mc_top]); + if (n2->mn_flags & F_SUBDATA) continue; } + m2->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(n2); } } } @@ -7962,12 +7965,8 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft) m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top]; m3->mc_ki[csrc->mc_top-1]++; } - if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) && - IS_LEAF(mps)) { - MDB_node *node = NODEPTR(m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]); - if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) - m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); - } + if (XCURSOR_INITED(m3) && IS_LEAF(mps)) + XCURSOR_REFRESH(m3, m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]); } } else /* Adding on the right, bump others down */ @@ -7988,12 +7987,8 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft) } else { m3->mc_ki[csrc->mc_top]--; } - if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) && - IS_LEAF(mps)) { - MDB_node *node = NODEPTR(m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]); - if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) - m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); - } + if (XCURSOR_INITED(m3) && IS_LEAF(mps)) + XCURSOR_REFRESH(m3, m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]); } } } @@ -8192,12 +8187,8 @@ mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst) m3->mc_ki[top-1] > csrc->mc_ki[top-1]) { m3->mc_ki[top-1]--; } - if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) && - IS_LEAF(psrc)) { - MDB_node *node = NODEPTR(m3->mc_pg[top], m3->mc_ki[top]); - if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) - m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); - } + if (XCURSOR_INITED(m3) && IS_LEAF(psrc)) + XCURSOR_REFRESH(m3, m3->mc_pg[top], m3->mc_ki[top]); } } { @@ -8459,11 +8450,8 @@ mdb_cursor_del0(MDB_cursor *mc) } else if (m3->mc_ki[mc->mc_top] > ki) { m3->mc_ki[mc->mc_top]--; } - if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) { - MDB_node *node = NODEPTR(m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]); - if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) - m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); - } + if (XCURSOR_INITED(m3)) + XCURSOR_REFRESH(m3, m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]); } } } @@ -8997,12 +8985,8 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno m3->mc_ki[ptop] >= mc->mc_ki[ptop]) { m3->mc_ki[ptop]++; } - if (m3->mc_xcursor && (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) && - IS_LEAF(mp)) { - MDB_node *node = NODEPTR(m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]); - if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) - m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node); - } + if (XCURSOR_INITED(m3) && IS_LEAF(mp)) + XCURSOR_REFRESH(m3, m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]); } } mdb_debug("mp left: %d, rp left: %d", SIZELEFT(mp), SIZELEFT(rp)); From 2fb5a5426427bd49f5e227e3173edd23914cbbf5 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sat, 3 Dec 2016 17:39:26 +0300 Subject: [PATCH 05/86] mdbx: minor simplify mc_signature. Change-Id: Ib3952853350d220dd62910bcd55ac74cf5f47886 --- mdb.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index 302a4256..6a2782ff 100644 --- a/mdb.c +++ b/mdb.c @@ -7548,7 +7548,6 @@ mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node) if (mx->mx_dbx.md_cmp == mdb_cmp_int && mx->mx_db.md_pad == sizeof(size_t)) mx->mx_dbx.md_cmp = mdb_cmp_clong; #endif */ - mc->mc_signature = MDBX_MC_SIGNATURE; } @@ -7587,6 +7586,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_next = NULL; mc->mc_backup = NULL; mc->mc_dbi = dbi; @@ -7610,7 +7610,6 @@ mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) if (*mc->mc_dbflag & DB_STALE) { mdb_page_search(mc, NULL, MDB_PS_ROOTONLY); } - mc->mc_signature = MDBX_MC_SIGNATURE; } int From f9f132671cc245cfe39519e5dbc9d5044b8bd973 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 6 Dec 2016 20:08:08 +0300 Subject: [PATCH 06/86] mdbx: explicit overwrite support for mdbx_put(). --- lmdb.h | 3 ++- mdb.c | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lmdb.h b/lmdb.h index 2d2aabb7..1237fdcd 100644 --- a/lmdb.h +++ b/lmdb.h @@ -349,7 +349,8 @@ typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *rel * For mdb_cursor_del: remove all duplicate data items. */ #define MDB_NODUPDATA 0x20 -/** For mdb_cursor_put: overwrite the current key/data pair */ +/** For mdb_cursor_put: overwrite the current key/data pair + * MDBX allows this flag for mdb_put() for explicit overwrite/update without insertion. */ #define MDB_CURRENT 0x40 /** For put: Just reserve space for data, don't copy it. Return a * pointer to the reserved space. diff --git a/mdb.c b/mdb.c index 5e1dcffc..21933378 100644 --- a/mdb.c +++ b/mdb.c @@ -9032,7 +9032,6 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi, { MDB_cursor mc; MDB_xcursor mx; - int rc; if (unlikely(!key || !data || !txn)) return EINVAL; @@ -9043,17 +9042,33 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi, if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) return EINVAL; - if (unlikely(flags & ~(MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND|MDB_APPENDDUP))) + if (unlikely(flags & ~(MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND|MDB_APPENDDUP + /* LY: MDB_CURRENT indicates explicit overwrite (update) for MDBX */ + | (MDBX_MODE_ENABLED ? MDB_CURRENT : 0)))) return EINVAL; 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 + /* LY: allows update (explicit overwrite) only for unique keys */ + if ((flags & MDB_CURRENT) && (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) + return EINVAL; +#endif /* MDBX_MODE_ENABLED */ + mdb_cursor_init(&mc, txn, dbi, &mx); mc.mc_next = txn->mt_cursors[dbi]; txn->mt_cursors[dbi] = &mc; - rc = mdb_cursor_put(&mc, key, data, flags); + int rc = MDB_SUCCESS; +#if MDBX_MODE_ENABLED + /* LY: support for update (explicit overwrite) */ + if (flags & MDB_CURRENT) + rc = mdb_cursor_get(&mc, key, NULL, MDB_SET); +#endif /* MDBX_MODE_ENABLED */ + if (likely(rc == MDB_SUCCESS)) + rc = mdb_cursor_put(&mc, key, data, flags); txn->mt_cursors[dbi] = mc.mc_next; + return rc; } From b980878b0727396fa9c9812c1d879059743a5288 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 6 Dec 2016 20:32:09 +0300 Subject: [PATCH 07/86] mdbx: adds mdbx_cursor_eof() for libfpta. --- mdbx.c | 11 +++++++++++ mdbx.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/mdbx.c b/mdbx.c index cf5187fe..d7001778 100644 --- a/mdbx.c +++ b/mdbx.c @@ -352,3 +352,14 @@ size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary) return txn->mt_txnid; } + +int mdbx_cursor_eof(MDB_cursor *mc) +{ + if (unlikely(mc == NULL)) + return EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + return (mc->mc_flags & (C_INITIALIZED | C_EOF)) != C_INITIALIZED ? 1 : 0; +} diff --git a/mdbx.h b/mdbx.h index d494ef36..8a5c238e 100644 --- a/mdbx.h +++ b/mdbx.h @@ -219,6 +219,10 @@ 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, + * 0 otherwise or less that zero in error case. */ +int mdbx_cursor_eof(MDB_cursor *mc); + /** @} */ #ifdef __cplusplus From ba688e63dccb7a613874375bfb97279aeab9feaa Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Wed, 7 Dec 2016 18:55:21 +0100 Subject: [PATCH 08/86] mdbx: backport (comments) - Note functions which must set MDB_TXN_ERROR on failure. Other functions depend on them to do so. For mdb_node_read(), instead remove such a dependence. Change-Id: I49c1c8bbb1c20527cbf76ef004cb7a1300ef465c --- mdb.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index 6a2782ff..d918fa79 100644 --- a/mdb.c +++ b/mdb.c @@ -1542,6 +1542,7 @@ mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b) /** Allocate memory for a page. * Re-use old malloc'd pages first for singletons, otherwise just malloc. + * Set #MDB_TXN_ERROR on failure. */ static MDB_page * mdb_page_malloc(MDB_txn *txn, unsigned num) @@ -2045,7 +2046,7 @@ mdb_page_dirty(MDB_txn *txn, MDB_page *mp) } /** Allocate page numbers and memory for writing. Maintain me_pglast, - * me_pghead and mt_next_pgno. + * me_pghead and mt_next_pgno. Set #MDB_TXN_ERROR on failure. * * If there are free pages available from older transactions, they * are re-used first. Otherwise allocate a new page at mt_next_pgno. @@ -2472,6 +2473,7 @@ mdb_page_unspill(MDB_txn *txn, MDB_page *mp, MDB_page **ret) } /** Touch a page: make it dirty and re-insert into tree with updated pgno. + * Set #MDB_TXN_ERROR on failure. * @param[in] mc cursor pointing to the page to be touched * @return 0 on success, non-zero on failure. */ @@ -5355,7 +5357,9 @@ mdb_cursor_pop(MDB_cursor *mc) } } -/** Push a page onto the top of the cursor's stack. */ +/** Push a page onto the top of the cursor's stack. + * Set #MDB_TXN_ERROR on failure. + */ static int mdb_cursor_push(MDB_cursor *mc, MDB_page *mp) { @@ -5375,6 +5379,7 @@ mdb_cursor_push(MDB_cursor *mc, MDB_page *mp) } /** Find the address of the page corresponding to a given page number. + * Set #MDB_TXN_ERROR on failure. * @param[in] txn the transaction for this access. * @param[in] pgno the page number for the page to retrieve. * @param[out] ret address of a pointer where the page's address will be stored. @@ -7148,6 +7153,7 @@ fail: } /** Allocate and initialize new pages for a database. + * Set #MDB_TXN_ERROR on failure. * @param[in] mc a cursor on the database being added to. * @param[in] flags flags defining what type of page is being allocated. * @param[in] num the number of pages to allocate. This is usually 1, @@ -7233,6 +7239,7 @@ mdb_branch_size(MDB_env *env, MDB_val *key) } /** Add a node to the page pointed to by the cursor. + * Set #MDB_TXN_ERROR on failure. * @param[in] mc The cursor for this operation. * @param[in] indx The index on the page where the new node should be added. * @param[in] key The key for the new node. @@ -7747,6 +7754,7 @@ mdb_cursor_dbi(MDB_cursor *mc) } /** Replace the key for a branch node with a new key. + * Set #MDB_TXN_ERROR on failure. * @param[in] mc Cursor pointing to the node to operate on. * @param[in] key The new key to use. * @return 0 on success, non-zero on failure. @@ -8577,6 +8585,7 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi, } /** Split a page and insert a new node. + * Set #MDB_TXN_ERROR on failure. * @param[in,out] mc Cursor pointing to the page and desired insertion index. * The cursor will be updated to point to the actual page and index where * the node got inserted after the split. From baf61da42bbd058d9da7c62e43420906d206fed4 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Wed, 7 Dec 2016 19:04:19 +0100 Subject: [PATCH 09/86] mdbx: backport (minor) - doxygen cleanup. Change-Id: Ide60614f4fc631aa2bfba3609115f39ec294b3de --- mdb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdb.c b/mdb.c index d918fa79..0208b73a 100644 --- a/mdb.c +++ b/mdb.c @@ -10276,7 +10276,7 @@ mdb_reader_check(MDB_env *env, int *dead) return mdb_reader_check0(env, 0, dead); } -/** As #mdb_reader_check(). rlocked = . */ +/** As #mdb_reader_check(). \b rlocked is set if caller locked #me_rmutex. */ static int __cold mdb_reader_check0(MDB_env *env, int rlocked, int *dead) { From 207f43003ce81a67c71571b71255d0eb6f0c2281 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Wed, 7 Dec 2016 19:06:11 +0100 Subject: [PATCH 10/86] mdbx: backport (comments) - MDB_CP_COMPACT comments. Change-Id: I4965d5e511395fceafcd922f513dcf0d5050c9b8 --- mdb.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index 0208b73a..07cbeb9a 100644 --- a/mdb.c +++ b/mdb.c @@ -9057,7 +9057,10 @@ typedef struct mdb_copy { HANDLE mc_fd; int mc_toggle; /**< Buffer number in provider */ int mc_new; /**< (0-2 buffers to write) | (#MDB_EOF at end) */ - volatile int mc_error; /**< Error code, never cleared if set */ + /** Error code. Never cleared if set. Both threads can set nonzero + * to fail the copy. Not mutex-protected, LMDB expects atomic int. + */ + volatile int mc_error; } mdb_copy; /** Dedicated writer thread for compacting copy. */ @@ -9135,7 +9138,11 @@ mdb_env_cthr_toggle(mdb_copy *my, int adjust) return my->mc_error; } - /** Depth-first tree traversal for compacting copy. */ + /** Depth-first tree traversal for compacting copy. + * @param[in] my control structure. + * @param[in,out] pg database root. + * @param[in] flags includes #F_DUPDATA if it is a sorted-duplicate sub-DB. + */ static int __cold mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags) { From c4f4d9ebf3f9690ff85151f66f2497b69b3da00a Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 8 Dec 2016 16:33:17 +0300 Subject: [PATCH 11/86] mdbx: rethink mdbx_cursor_eof() for libfpta. --- mdbx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdbx.c b/mdbx.c index d7001778..64c57683 100644 --- a/mdbx.c +++ b/mdbx.c @@ -361,5 +361,5 @@ int mdbx_cursor_eof(MDB_cursor *mc) if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; - return (mc->mc_flags & (C_INITIALIZED | C_EOF)) != C_INITIALIZED ? 1 : 0; + return (mc->mc_flags & C_INITIALIZED) ? 0 : 1; } From b33fe4a3f04397ad1bc016010041bb7ea631da35 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Tue, 12 Jan 2016 23:18:06 +0100 Subject: [PATCH 12/86] mdbx: backport - note about reserved vs. actual mem/disk usage. Change-Id: Ibd75bdafac646f4a577c7cbebda8173e5b7e5ef1 --- lmdb.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lmdb.h b/lmdb.h index 2d2aabb7..52abd372 100644 --- a/lmdb.h +++ b/lmdb.h @@ -66,6 +66,11 @@ * This does not use actual memory or disk space, but users may need * to understand the difference so they won't be scared off. * + * - An LMDB configuration will often reserve considerable \b unused + * memory address space and maybe file size for future growth. + * This does not use actual memory or disk space, but users may need + * to understand the difference so they won't be scared off. + * * - By default, in versions before 0.9.10, unused portions of the data * file might receive garbage data from memory freed by other code. * (This does not happen when using the #MDB_WRITEMAP flag.) As of From 7b773e6f2db24192dd0d22e7c031251cbaa1f36e Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Fri, 9 Dec 2016 00:03:36 +0100 Subject: [PATCH 13/86] mdbx: backport - Cleanup: Add flag DB_DUPDATA, drop DB_DIRTY hack. Change-Id: I5d30367104d025c1d2f8c39d29455faca59d7f19 --- mdb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mdb.c b/mdb.c index 07cbeb9a..1611e7fe 100644 --- a/mdb.c +++ b/mdb.c @@ -859,11 +859,12 @@ struct MDB_txn { * @ingroup internal * @{ */ -#define DB_DIRTY 0x01 /**< DB was modified or is DUPSORT data */ +#define DB_DIRTY 0x01 /**< DB was written in this txn */ #define DB_STALE 0x02 /**< Named-DB record is older than txnID */ #define DB_NEW 0x04 /**< Named-DB handle opened in this txn */ #define DB_VALID 0x08 /**< DB handle is valid, see also #MDB_VALID */ #define DB_USRVALID 0x10 /**< As #DB_VALID, but not set for #FREE_DBI */ +#define DB_DUPDATA 0x20 /**< DB is #MDB_DUPSORT data */ /** @} */ /** In write txns, array of cursors for each DB */ MDB_cursor **mt_cursors; @@ -6465,7 +6466,8 @@ mdb_cursor_touch(MDB_cursor *mc) { int rc = MDB_SUCCESS; - if (mc->mc_dbi >= CORE_DBS && !(*mc->mc_dbflag & DB_DIRTY)) { + if (mc->mc_dbi >= CORE_DBS && !(*mc->mc_dbflag & (DB_DIRTY|DB_DUPDATA))) { + /* Touch DB record of named DB */ MDB_cursor mc2; MDB_xcursor mcx; if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi)) @@ -7550,7 +7552,7 @@ mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node) } mdb_debug("Sub-db -%u root page %zu", mx->mx_cursor.mc_dbi, mx->mx_db.md_root); - mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DIRTY; /* DB_DIRTY guides mdb_cursor_touch */ + mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA; /* #if UINT_MAX < SIZE_MAX if (mx->mx_dbx.md_cmp == mdb_cmp_int && mx->mx_db.md_pad == sizeof(size_t)) mx->mx_dbx.md_cmp = mdb_cmp_clong; @@ -7576,7 +7578,7 @@ mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int new_dupdata) mx->mx_cursor.mc_top = 0; mx->mx_cursor.mc_flags |= C_INITIALIZED; mx->mx_cursor.mc_ki[0] = 0; - mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DIRTY; /* DB_DIRTY guides mdb_cursor_touch */ + 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 From ca97abb7f3d9c11cf03943581c157082fc0c04cc Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Sat, 10 Dec 2016 09:16:17 +0100 Subject: [PATCH 14/86] mdbx: backport - mdb_dbi_open(): Protect mainDB cursors (ITS#8542). Change-Id: I5bdd3727eddc16a518c4b88534a3e7253e9789fd --- mdb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mdb.c b/mdb.c index 1611e7fe..d520f5bf 100644 --- a/mdb.c +++ b/mdb.c @@ -9874,7 +9874,8 @@ int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned flags, MDB_dbi *dbi) memset(&dummy, 0, sizeof(dummy)); dummy.md_root = P_INVALID; dummy.md_flags = flags & PERSISTENT_FLAGS; - rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA); + WITH_CURSOR_TRACKING(mc, + rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA)); dbflag |= DB_DIRTY; } From fe4e9993d633e6019d9964f30de53691bd02be28 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Sun, 23 Aug 2015 20:33:02 +0200 Subject: [PATCH 15/86] mdbx: backport - Pass cursor to mdb_page_get(), mdb_node_read(). No change in behavior. Change-Id: I19054cfd96fa883970a0dc66a0088596a142ea07 --- mdb.c | 100 ++++++++++++++++++++++++++++----------------------------- mdbx.c | 11 +++++-- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/mdb.c b/mdb.c index d520f5bf..21983552 100644 --- a/mdb.c +++ b/mdb.c @@ -1092,10 +1092,10 @@ typedef struct MDB_ntxn { #define METAPAGE_2(env) \ (&((MDB_metabuf*) ((env)->me_map + env->me_psize))->mb_metabuf.mm_meta) -static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp, int flags); -static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp); -static int mdb_page_touch(MDB_cursor *mc); -static int mdb_cursor_touch(MDB_cursor *mc); +static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp, int flags); +static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp); +static int mdb_page_touch(MDB_cursor *mc); +static int mdb_cursor_touch(MDB_cursor *mc); #define MDB_END_NAMES {"committed", "empty-commit", "abort", "reset", \ "reset-tmp", "fail-begin", "fail-beginchild"} @@ -1108,16 +1108,16 @@ enum { #define MDB_END_UPDATE 0x10 /**< update env state (DBIs) */ #define MDB_END_FREE 0x20 /**< free txn unless it is #MDB_env.%me_txn0 */ #define MDB_END_SLOT MDB_NOTLS /**< release any reader slot if #MDB_NOTLS */ -static int mdb_txn_end(MDB_txn *txn, unsigned mode); +static int mdb_txn_end(MDB_txn *txn, unsigned mode); -static int mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **mp, int *lvl); -static int mdb_page_search_root(MDB_cursor *mc, +static int mdb_page_get(MDB_cursor *mc, pgno_t pgno, MDB_page **mp, int *lvl); +static int mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int modify); #define MDB_PS_MODIFY 1 #define MDB_PS_ROOTONLY 2 #define MDB_PS_FIRST 4 #define MDB_PS_LAST 8 -static int mdb_page_search(MDB_cursor *mc, +static int mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags); static int mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst); @@ -1125,17 +1125,17 @@ static int mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst); static int mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno, unsigned nflags); -static int mdb_env_read_header(MDB_env *env, MDB_meta *meta); -static int mdb_env_sync0(MDB_env *env, unsigned flags, MDB_meta *pending); -static void mdb_env_close0(MDB_env *env); +static int mdb_env_read_header(MDB_env *env, MDB_meta *meta); +static int mdb_env_sync0(MDB_env *env, unsigned flags, MDB_meta *pending); +static void mdb_env_close0(MDB_env *env); -static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp); -static int mdb_node_add(MDB_cursor *mc, indx_t indx, +static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp); +static int mdb_node_add(MDB_cursor *mc, indx_t indx, MDB_val *key, MDB_val *data, pgno_t pgno, unsigned flags); -static void mdb_node_del(MDB_cursor *mc, int ksize); -static void mdb_node_shrink(MDB_page *mp, indx_t indx); +static void mdb_node_del(MDB_cursor *mc, int ksize); +static void mdb_node_shrink(MDB_page *mp, indx_t indx); static int mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft); -static int mdb_node_read(MDB_txn *txn, MDB_node *leaf, MDB_val *data); +static int mdb_node_read(MDB_cursor *mc, MDB_node *leaf, MDB_val *data); static size_t mdb_leaf_size(MDB_env *env, MDB_val *key, MDB_val *data); static size_t mdb_branch_size(MDB_env *env, MDB_val *key); @@ -1161,8 +1161,8 @@ 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); +static void mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi); +static int mdb_reader_check0(MDB_env *env, int rlocked, int *dead); /** @cond */ static MDB_cmp_func mdb_cmp_memn, mdb_cmp_memnr, mdb_cmp_int_ai, mdb_cmp_int_a2, mdb_cmp_int_ua; @@ -1716,7 +1716,7 @@ mdb_pages_xkeep(MDB_cursor *mc, unsigned pflags, int all) { enum { Mask = P_SUBP|P_DIRTY|P_LOOSE|P_KEEP }; MDB_txn *txn = mc->mc_txn; - MDB_cursor *m3; + MDB_cursor *m3, *m0 = mc; MDB_xcursor *mx; MDB_page *dp, *mp; MDB_node *leaf; @@ -1759,7 +1759,7 @@ mdb_pages_xkeep(MDB_cursor *mc, unsigned pflags, int all) pgno_t pgno = txn->mt_dbs[i].md_root; if (pgno == P_INVALID) continue; - if ((rc = mdb_page_get(txn, pgno, &dp, &level)) != MDB_SUCCESS) + if (unlikely((rc = mdb_page_get(m0, pgno, &dp, &level)) != MDB_SUCCESS)) break; if ((dp->mp_flags & Mask) == pflags && level <= 1) dp->mp_flags ^= P_KEEP; @@ -2215,7 +2215,7 @@ mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp, int flags) np = m2.mc_pg[m2.mc_top]; leaf = NODEPTR(np, m2.mc_ki[m2.mc_top]); - if (unlikely((rc = mdb_node_read(txn, leaf, &data)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(&m2, leaf, &data)) != MDB_SUCCESS)) goto fail; if ((flags & MDBX_LIFORECLAIM) && !txn->mt_lifo_reclaimed) { @@ -5381,15 +5381,16 @@ mdb_cursor_push(MDB_cursor *mc, MDB_page *mp) /** Find the address of the page corresponding to a given page number. * Set #MDB_TXN_ERROR on failure. - * @param[in] txn the transaction for this access. + * @param[in] mc the cursor accessing the page. * @param[in] pgno the page number for the page to retrieve. * @param[out] ret address of a pointer where the page's address will be stored. * @param[out] lvl dirty_list inheritance level of found page. 1=current txn, 0=mapped page. * @return 0 on success, non-zero on failure. */ static int -mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **ret, int *lvl) +mdb_page_get(MDB_cursor *mc, pgno_t pgno, MDB_page **ret, int *lvl) { + MDB_txn *txn = mc->mc_txn; MDB_env *env = txn->mt_env; MDB_page *p = NULL; int level; @@ -5483,7 +5484,7 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) mdb_cassert(mc, i < NUMKEYS(mp)); node = NODEPTR(mp, i); - if (unlikely((rc = mdb_page_get(mc->mc_txn, NODEPGNO(node), &mp, NULL)) != 0)) + if (unlikely((rc = mdb_page_get(mc, NODEPGNO(node), &mp, NULL)) != 0)) return rc; mc->mc_ki[mc->mc_top] = i; @@ -5525,7 +5526,7 @@ mdb_page_search_lowest(MDB_cursor *mc) MDB_node *node = NODEPTR(mp, 0); int rc; - if (unlikely((rc = mdb_page_get(mc->mc_txn, NODEPGNO(node), &mp, NULL)) != 0)) + if (unlikely((rc = mdb_page_get(mc, NODEPGNO(node), &mp, NULL)) != 0)) return rc; mc->mc_ki[mc->mc_top] = 0; @@ -5577,7 +5578,7 @@ mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags) return MDB_NOTFOUND; if (unlikely((leaf->mn_flags & (F_DUPDATA|F_SUBDATA)) != F_SUBDATA)) return MDB_INCOMPATIBLE; /* not a named DB */ - rc = mdb_node_read(mc->mc_txn, leaf, &data); + rc = mdb_node_read(&mc2, leaf, &data); if (rc) return rc; memcpy(&flags, ((char *) data.mv_data + offsetof(MDB_db, md_flags)), @@ -5601,7 +5602,7 @@ mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags) mdb_cassert(mc, root > 1); if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root) - if (unlikely((rc = mdb_page_get(mc->mc_txn, root, &mc->mc_pg[0], NULL)) != 0)) + if (unlikely((rc = mdb_page_get(mc, root, &mc->mc_pg[0], NULL)) != 0)) return rc; mc->mc_snum = 1; @@ -5698,13 +5699,13 @@ release: } /** Return the data associated with a given node. - * @param[in] txn The transaction for this operation. + * @param[in] mc The cursor for this operation. * @param[in] leaf The node being read. * @param[out] data Updated to point to the node's data. * @return 0 on success, non-zero on failure. */ static MDBX_INLINE int -mdb_node_read(MDB_txn *txn, MDB_node *leaf, MDB_val *data) +mdb_node_read(MDB_cursor *mc, MDB_node *leaf, MDB_val *data) { MDB_page *omp; /* overflow page */ pgno_t pgno; @@ -5720,7 +5721,7 @@ mdb_node_read(MDB_txn *txn, MDB_node *leaf, MDB_val *data) */ data->mv_size = NODEDSZ(leaf); memcpy(&pgno, NODEDATA(leaf), sizeof(pgno)); - if (unlikely((rc = mdb_page_get(txn, pgno, &omp, NULL)) != 0)) { + if (unlikely((rc = mdb_page_get(mc, pgno, &omp, NULL)) != 0)) { mdb_debug("read overflow page %zu failed", pgno); return rc; } @@ -5801,7 +5802,7 @@ mdb_cursor_sibling(MDB_cursor *mc, int move_right) mdb_cassert(mc, IS_BRANCH(mc->mc_pg[mc->mc_top])); indx = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); - if (unlikely((rc = mdb_page_get(mc->mc_txn, NODEPGNO(indx), &mp, NULL)) != 0)) { + if (unlikely((rc = mdb_page_get(mc, NODEPGNO(indx), &mp, NULL)) != 0)) { /* mc will be inconsistent if caller does mc_snum++ as above */ mc->mc_flags &= ~(C_INITIALIZED|C_EOF); return rc; @@ -5884,7 +5885,7 @@ skip: mdb_xcursor_init1(mc, leaf); } if (data) { - if (unlikely((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)) return rc; if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { @@ -5967,7 +5968,7 @@ mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op) mdb_xcursor_init1(mc, leaf); } if (data) { - if (unlikely((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)) return rc; if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { @@ -6156,7 +6157,7 @@ set1: } } else if (op == MDB_GET_BOTH || op == MDB_GET_BOTH_RANGE) { MDB_val olddata; - if (unlikely((rc = mdb_node_read(mc->mc_txn, leaf, &olddata)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(mc, leaf, &olddata)) != MDB_SUCCESS)) return rc; rc = mc->mc_dbx->md_dcmp(data, &olddata); if (rc) { @@ -6169,7 +6170,7 @@ set1: } else { if (mc->mc_xcursor) mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF); - if (unlikely((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)) return rc; } } @@ -6218,7 +6219,7 @@ mdb_cursor_first(MDB_cursor *mc, MDB_val *key, MDB_val *data) if (unlikely(rc)) return rc; } else { - if (unlikely((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)) return rc; } } @@ -6263,7 +6264,7 @@ mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data) if (unlikely(rc)) return rc; } else { - if (unlikely((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS)) + if (unlikely((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)) return rc; } } @@ -6312,7 +6313,7 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { rc = mdb_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_GET_CURRENT); } else { - rc = mdb_node_read(mc->mc_txn, leaf, data); + rc = mdb_node_read(mc, leaf, data); } } } @@ -6429,7 +6430,7 @@ fetchm: 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_GET_KEY(leaf, key); - rc = mdb_node_read(mc->mc_txn, leaf, data); + rc = mdb_node_read(mc, leaf, data); break; } } @@ -6830,7 +6831,7 @@ current: int level, ovpages, dpages = OVPAGES(data->mv_size, env->me_psize); memcpy(&pg, olddata.mv_data, sizeof(pg)); - if (unlikely((rc2 = mdb_page_get(mc->mc_txn, pg, &omp, &level)) != 0)) + if (unlikely((rc2 = mdb_page_get(mc, pg, &omp, &level)) != 0)) return rc2; ovpages = omp->mp_pages; @@ -7141,7 +7142,7 @@ mdb_cursor_del(MDB_cursor *mc, unsigned flags) pgno_t pg; memcpy(&pg, NODEDATA(leaf), sizeof(pg)); - if (unlikely((rc = mdb_page_get(mc->mc_txn, pg, &omp, NULL)) || + if (unlikely((rc = mdb_page_get(mc, pg, &omp, NULL)) || (rc = mdb_ovpage_free(mc, omp)))) goto fail; } @@ -8313,7 +8314,7 @@ mdb_rebalance(MDB_cursor *mc) if (unlikely(rc)) return rc; mc->mc_db->md_root = NODEPGNO(NODEPTR(mp, 0)); - rc = mdb_page_get(mc->mc_txn,mc->mc_db->md_root,&mc->mc_pg[0],NULL); + rc = mdb_page_get(mc, mc->mc_db->md_root, &mc->mc_pg[0], NULL); if (unlikely(rc)) return rc; mc->mc_db->md_depth--; @@ -8374,7 +8375,7 @@ mdb_rebalance(MDB_cursor *mc) mdb_debug("reading right neighbor"); mn.mc_ki[ptop]++; node = NODEPTR(mc->mc_pg[ptop], mn.mc_ki[ptop]); - rc = mdb_page_get(mc->mc_txn,NODEPGNO(node),&mn.mc_pg[mn.mc_top],NULL); + rc = mdb_page_get(mc, NODEPGNO(node), &mn.mc_pg[mn.mc_top], NULL); if (unlikely(rc)) return rc; mn.mc_ki[mn.mc_top] = 0; @@ -8386,7 +8387,7 @@ mdb_rebalance(MDB_cursor *mc) mdb_debug("reading left neighbor"); mn.mc_ki[ptop]--; node = NODEPTR(mc->mc_pg[ptop], mn.mc_ki[ptop]); - rc = mdb_page_get(mc->mc_txn,NODEPGNO(node),&mn.mc_pg[mn.mc_top],NULL); + rc = mdb_page_get(mc, NODEPGNO(node), &mn.mc_pg[mn.mc_top], NULL); if (unlikely(rc)) return rc; mn.mc_ki[mn.mc_top] = NUMKEYS(mn.mc_pg[mn.mc_top]) - 1; @@ -9149,7 +9150,6 @@ static int __cold mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags) { MDB_cursor mc; - MDB_txn *txn = my->mc_txn; MDB_node *ni; MDB_page *mo, *mp, *leaf; char *buf, *ptr; @@ -9162,9 +9162,9 @@ mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags) memset(&mc, 0, sizeof(mc)); mc.mc_snum = 1; - mc.mc_txn = txn; + mc.mc_txn = my->mc_txn; - rc = mdb_page_get(txn, *pg, &mc.mc_pg[0], NULL); + rc = mdb_page_get(&mc, *pg, &mc.mc_pg[0], NULL); if (rc) return rc; rc = mdb_page_search_root(&mc, NULL, MDB_PS_FIRST); @@ -9209,7 +9209,7 @@ mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags) memcpy(&pg, NODEDATA(ni), sizeof(pg)); memcpy(NODEDATA(ni), &my->mc_next_pgno, sizeof(pgno_t)); - rc = mdb_page_get(txn, pg, &omp, NULL); + rc = mdb_page_get(&mc, pg, &omp, NULL); if (rc) goto done; if (my->mc_wlen[toggle] >= MDB_WBUF) { @@ -9259,7 +9259,7 @@ mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags) again: ni = NODEPTR(mp, mc.mc_ki[mc.mc_top]); pg = NODEPGNO(ni); - rc = mdb_page_get(txn, pg, &mp, NULL); + rc = mdb_page_get(&mc, pg, &mp, NULL); if (rc) goto done; mc.mc_top++; @@ -10006,7 +10006,7 @@ mdb_drop0(MDB_cursor *mc, int subs) MDB_page *omp; pgno_t pg; memcpy(&pg, NODEDATA(ni), sizeof(pg)); - rc = mdb_page_get(txn, pg, &omp, NULL); + rc = mdb_page_get(mc, pg, &omp, NULL); if (unlikely(rc)) goto done; mdb_cassert(mc, IS_OVERFLOW(omp)); diff --git a/mdbx.c b/mdbx.c index fb4aac85..9f07548e 100644 --- a/mdbx.c +++ b/mdbx.c @@ -182,7 +182,12 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee if (pg == P_INVALID) return MDB_SUCCESS; /* empty db */ - rc = mdb_page_get(ctx->mw_txn, pg, &mp, NULL); + MDB_cursor mc; + memset(&mc, 0, sizeof(mc)); + mc.mc_snum = 1; + mc.mc_txn = ctx->mw_txn; + + rc = mdb_page_get(&mc, pg, &mp, NULL); if (rc) return rc; if (pg != mp->mp_p.p_pgno) @@ -220,7 +225,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee } for (align_bytes = i = 0; i < nkeys; - align_bytes += ((payload_size + align_bytes) & 1), i++) { + align_bytes += ((payload_size + align_bytes) & 1), i++) { MDB_node *node; if (IS_LEAF2(mp)) { @@ -249,7 +254,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee payload_size += sizeof(pgno_t); opg = NODEDATA(node); - rc = mdb_page_get(ctx->mw_txn, *opg, &omp, NULL); + rc = mdb_page_get(&mc, *opg, &omp, NULL); if (rc) return rc; if (*opg != omp->mp_p.p_pgno) From fca2f4d9295a136c5d9e151f03dd077d71f990c2 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Sat, 10 Dec 2016 21:42:39 +0100 Subject: [PATCH 16/86] mdbx: backport - clean up and comment C_UNTRACK. Don't use it as a "cursor is tracked" hint in mdb_pages_xkeep(). It's been harmless so far, but would break after mdb_cursor_copy(). Checking m0 directly short-circuits better anyway. Change-Id: Ibf180214db603e08ed11e298cff85866eb79f4bb --- mdb.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/mdb.c b/mdb.c index 21983552..6e778cb0 100644 --- a/mdb.c +++ b/mdb.c @@ -1723,13 +1723,9 @@ mdb_pages_xkeep(MDB_cursor *mc, unsigned pflags, int all) unsigned i, j; int rc = MDB_SUCCESS, level; - /* Mark pages seen by cursors */ - if (mc->mc_flags & C_UNTRACK) - mc = NULL; /* will find mc in mt_cursors */ - for (i = txn->mt_numdbs;; mc = txn->mt_cursors[--i]) { - for (; mc; mc=mc->mc_next) { - if (!(mc->mc_flags & C_INITIALIZED)) - continue; + /* Mark pages seen by cursors: First m0, then tracked cursors */ + for (i = txn->mt_numdbs;; ) { + if (mc->mc_flags & C_INITIALIZED) { for (m3 = mc;; m3 = &mx->mx_cursor) { mp = NULL; for (j=0; jmc_snum; j++) { @@ -1748,10 +1744,13 @@ mdb_pages_xkeep(MDB_cursor *mc, unsigned pflags, int all) break; } } - if (i == 0) - break; + mc = mc->mc_next; + for (; !mc || mc == m0; mc = txn->mt_cursors[--i]) + if (i == 0) + goto mark_done; } +mark_done: if (all) { /* Mark dirty root pages */ for (i=0; imt_numdbs; i++) { @@ -7727,7 +7726,10 @@ mdb_cursor_close(MDB_cursor *mc) if (mc) { mdb_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE); if (!mc->mc_backup) { - /* remove from txn, if tracked */ + /* 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. + */ 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; @@ -8578,7 +8580,6 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi, * run out of space, triggering a split. We need this * cursor to be consistent until the end of the rebalance. */ - mc.mc_flags |= C_UNTRACK; mc.mc_next = txn->mt_cursors[dbi]; txn->mt_cursors[dbi] = &mc; rc = mdb_cursor_del(&mc, flags); From 362714512939ec5239787a7eb58ac134dbf0a3e9 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Sat, 10 Dec 2016 22:00:31 +0100 Subject: [PATCH 17/86] mdbx: backport - catch mdb_cursor_sibling() error (ITS#7377). Change-Id: I440ff1f9f92156e19935195d656f4d77b088f605 --- mdb.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mdb.c b/mdb.c index 6e778cb0..ce69a040 100644 --- a/mdb.c +++ b/mdb.c @@ -8678,7 +8678,6 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno split_indx = newindx; nkeys = 0; } else { - split_indx = (nkeys+1) / 2; if (IS_LEAF2(rp)) { @@ -8838,7 +8837,7 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno } else { /* find right page's left sibling */ mc->mc_ki[ptop] = mn.mc_ki[ptop]; - mdb_cursor_sibling(mc, 0); + rc = mdb_cursor_sibling(mc, 0); } } } else { @@ -8846,8 +8845,11 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno rc = mdb_node_add(&mn, mn.mc_ki[ptop], &sepkey, NULL, rp->mp_pgno, 0); mn.mc_top++; } - if (unlikely(rc != MDB_SUCCESS)) + if (unlikely(rc != MDB_SUCCESS)) { + if (rc == MDB_NOTFOUND) /* improper mdb_cursor_sibling() result */ + rc = MDB_PROBLEM; goto done; + } if (nflags & MDB_APPEND) { mc->mc_pg[mc->mc_top] = rp; mc->mc_ki[mc->mc_top] = 0; From cbff64757953b6ccb1ab8853a6d41491d2e94fc5 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 15 Dec 2016 20:09:07 +0300 Subject: [PATCH 18/86] mdbx: fix ov-pages copying in cursor_put(). I think I just lost one line of code. This bug was added by 09d790431710f6456cb80bcfc5962da5851893ed --- mdb.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mdb.c b/mdb.c index ce69a040..81dea96e 100644 --- a/mdb.c +++ b/mdb.c @@ -6864,13 +6864,8 @@ current: * parent txn, in case the user peeks at MDB_RESERVEd * or unused parts. Some users treat ovpages specially. */ -#if MDBX_MODE_ENABLED - /* LY: New page will contain only header from origin, - * but no any payload */ - memcpy(np, omp, PAGEHDRSZ); -#else size_t sz = (size_t) env->me_psize * ovpages, off; - if (!(flags & MDB_RESERVE)) { + if (MDBX_MODE_ENABLED || !(flags & MDB_RESERVE)) { /* Skip the part where LMDB will put *data. * Copy end of page, adjusting alignment so * compiler may copy words instead of bytes. @@ -6881,7 +6876,6 @@ current: sz = PAGEHDRSZ; } memcpy(np, omp, sz); /* Copy whole or header of page */ -#endif /* MDBX_MODE_ENABLED */ omp = np; } SETDSZ(leaf, data->mv_size); From 5865c74876578f55cf6bee9f94b2782b36655c49 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 15 Dec 2016 21:55:28 +0300 Subject: [PATCH 19/86] mdbx: rework overwrite support for mdbx_put(). --- mdb.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mdb.c b/mdb.c index ab72b9e6..16a24bbd 100644 --- a/mdb.c +++ b/mdb.c @@ -9042,20 +9042,23 @@ mdb_put(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 - /* LY: allows update (explicit overwrite) only for unique keys */ - if ((flags & MDB_CURRENT) && (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) - return EINVAL; -#endif /* MDBX_MODE_ENABLED */ - mdb_cursor_init(&mc, txn, dbi, &mx); mc.mc_next = txn->mt_cursors[dbi]; txn->mt_cursors[dbi] = &mc; int rc = MDB_SUCCESS; #if MDBX_MODE_ENABLED /* LY: support for update (explicit overwrite) */ - if (flags & MDB_CURRENT) + if (flags & MDB_CURRENT) { rc = mdb_cursor_get(&mc, key, NULL, MDB_SET); + if (likely(rc == MDB_SUCCESS) && (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) { + /* LY: allows update (explicit overwrite) only for unique keys */ + 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; + } + } + } #endif /* MDBX_MODE_ENABLED */ if (likely(rc == MDB_SUCCESS)) rc = mdb_cursor_put(&mc, key, data, flags); From 2956095c6ded72d22e10e6d1cad4a5410ea52994 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 15 Dec 2016 21:56:45 +0300 Subject: [PATCH 20/86] mdbx: rework MDB_CURRENT handling for libfpta. --- mdb.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mdb.c b/mdb.c index 447a618d..411f5ca2 100644 --- a/mdb.c +++ b/mdb.c @@ -6185,7 +6185,6 @@ set1: rc = 0; } *data = olddata; - } else { if (mc->mc_xcursor) mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF); @@ -6587,7 +6586,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, dkey.mv_size = 0; - if (flags == MDB_CURRENT) { + if (flags & MDB_CURRENT) { if (unlikely(!(mc->mc_flags & C_INITIALIZED))) return EINVAL; rc = MDB_SUCCESS; @@ -6778,6 +6777,7 @@ more: break; } /* FALLTHRU: Big enough MDB_DUPFIXED sub-page */ + case MDB_CURRENT | MDB_NODUPDATA: case MDB_CURRENT: fp->mp_flags |= P_DIRTY; COPY_PGNO(fp->mp_pgno, mp->mp_pgno); @@ -6975,12 +6975,15 @@ put_sub: xdata.mv_size = 0; xdata.mv_data = ""; leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + xflags = MDB_NOSPILL; + if (flags & MDB_NODUPDATA) + xflags |= MDB_NOOVERWRITE; + if (flags & MDB_APPENDDUP) + xflags |= MDB_APPEND; if (flags & MDB_CURRENT) { - xflags = MDB_CURRENT|MDB_NOSPILL; + xflags |= MDB_CURRENT; } else { mdb_xcursor_init1(mc, leaf); - xflags = (flags & MDB_NODUPDATA) ? - MDB_NOOVERWRITE|MDB_NOSPILL : MDB_NOSPILL; } if (sub_root) mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; @@ -7014,8 +7017,6 @@ put_sub: } } ecount = mc->mc_xcursor->mx_db.md_entries; - if (flags & MDB_APPENDDUP) - xflags |= MDB_APPEND; rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); if (flags & F_SUBDATA) { void *db = NODEDATA(leaf); From 70a138472b1ba6a9307fade117375b084a444997 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Thu, 15 Dec 2016 22:12:45 +0100 Subject: [PATCH 21/86] mdbx: backport - Mention MDB_PREV_MULTIPLE along with MDB_NEXT_MULTIPLE. Change-Id: I0c216203c3aa2005ef254293c1c472c9b7f257f3 --- lmdb.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lmdb.h b/lmdb.h index 52abd372..d0b8b37c 100644 --- a/lmdb.h +++ b/lmdb.h @@ -1149,8 +1149,9 @@ int mdb_txn_renew(MDB_txn *txn); * This flag may only be used in combination with #MDB_DUPSORT. This option * tells the library that the data items for this database are all the same * size, which allows further optimizations in storage and retrieval. When - * all data items are the same size, the #MDB_GET_MULTIPLE and #MDB_NEXT_MULTIPLE - * cursor operations may be used to retrieve multiple items at once. + * all data items are the same size, the #MDB_GET_MULTIPLE, #MDB_NEXT_MULTIPLE + * and #MDB_PREV_MULTIPLE cursor operations may be used to retrieve multiple + * items at once. *
  • #MDB_INTEGERDUP * This option specifies that duplicate data items are binary integers, * similar to #MDB_INTEGERKEY keys. From b950e39c10ace74431310a190aa3041adbd37d65 Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Thu, 20 Oct 2016 09:51:22 +0200 Subject: [PATCH 22/86] mdbx: backport - mdb_env_copyfd2(): Don't abort on SIGPIPE (ITS#8504). Return EPIPE instead. Never clear mc_error, we could lose a failure in the other thread. Change-Id: Ief08803ed56293309f07be116e69123c10907e77 --- mdb.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mdb.c b/mdb.c index 81dea96e..4c2cd1ed 100644 --- a/mdb.c +++ b/mdb.c @@ -9072,6 +9072,14 @@ mdb_env_copythr(void *arg) int toggle = 0, wsize, rc = 0; int len; +#ifdef SIGPIPE + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGPIPE); + if ((rc = pthread_sigmask(SIG_BLOCK, &set, NULL)) != 0) + my->mc_error = rc; +#endif + pthread_mutex_lock(&my->mc_mutex); for(;;) { while (!my->mc_new) @@ -9086,6 +9094,15 @@ again: len = write(my->mc_fd, ptr, wsize); if (len < 0) { rc = errno; +#ifdef SIGPIPE + if (rc == EPIPE) { + /* Collect the pending SIGPIPE, otherwise at least OS X + * gives it to the process on thread-exit (ITS#8504). + */ + int tmp; + sigwait(&set, &tmp); + } +#endif break; } else if (len > 0) { rc = MDB_SUCCESS; From 578fe9e2586cb7dbaa38a79ab1943388581782d8 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 18 Dec 2016 00:58:26 +0300 Subject: [PATCH 23/86] mdbx: allows zero-length keys for libfpta. --- mdb.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mdb.c b/mdb.c index 411f5ca2..83cd994c 100644 --- a/mdb.c +++ b/mdb.c @@ -6011,9 +6011,6 @@ mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_node *leaf = NULL; DKBUF; - if (unlikely(key->mv_size == 0)) - return MDB_BAD_VALSIZE; - if ( (mc->mc_db->md_flags & MDB_INTEGERKEY) && unlikely( key->mv_size != sizeof(unsigned) && key->mv_size != sizeof(size_t) )) { @@ -6556,7 +6553,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, if (unlikely(mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))) return (mc->mc_txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; - if (unlikely(key->mv_size-1 >= ENV_MAXKEY(env))) + if (unlikely(key->mv_size > ENV_MAXKEY(env))) return MDB_BAD_VALSIZE; #if SIZE_MAX > MAXDATASIZE From efcf60dfaa0ed33ecab5bd5ccfb93906dd96862c Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 18 Dec 2016 20:15:27 +0300 Subject: [PATCH 24/86] mdbx: fix MDB_GET_CURRENT for dupsort's subcursor. --- mdb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mdb.c b/mdb.c index 83cd994c..1f4cf026 100644 --- a/mdb.c +++ b/mdb.c @@ -6326,6 +6326,12 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_GET_KEY(leaf, key); if (data) { if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) { + mdb_xcursor_init1(mc, leaf); + rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + if (unlikely(rc)) + break; + } rc = mdb_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_GET_CURRENT); } else { rc = mdb_node_read(mc, leaf, data); From 7e682540510d5fb140fe42e4824a5587f0a3ec62 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 21 Dec 2016 20:29:19 +0300 Subject: [PATCH 25/86] mdbx: mdb_chk - don't close dbi-handles, set_maxdbs() instead. --- mdb_chk.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mdb_chk.c b/mdb_chk.c index b86b8f96..fd485fe1 100644 --- a/mdb_chk.c +++ b/mdb_chk.c @@ -432,7 +432,6 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent) fflush(NULL); } skipped_subdb++; - mdbx_dbi_close(env, dbi); return MDB_SUCCESS; } @@ -444,14 +443,12 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent) rc = mdbx_dbi_flags(txn, dbi, &flags); if (rc) { error(" - mdbx_dbi_flags failed, error %d %s\n", rc, mdbx_strerror(rc)); - mdbx_dbi_close(env, dbi); return rc; } rc = mdbx_stat(txn, dbi, &ms, sizeof(ms)); if (rc) { error(" - mdbx_stat failed, error %d %s\n", rc, mdbx_strerror(rc)); - mdbx_dbi_close(env, dbi); return rc; } @@ -475,7 +472,6 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent) rc = mdbx_cursor_open(txn, dbi, &mc); if (rc) { error(" - mdbx_cursor_open failed, error %d %s\n", rc, mdbx_strerror(rc)); - mdbx_dbi_close(env, dbi); return rc; } @@ -565,7 +561,6 @@ bailout: } mdbx_cursor_close(mc); - mdbx_dbi_close(env, dbi); return rc || problems_count; } @@ -686,7 +681,11 @@ int main(int argc, char *argv[]) } maxkeysize = rc; - mdbx_env_set_maxdbs(env, 3); + rc = mdbx_env_set_maxdbs(env, MAX_DBI); + if (rc < 0) { + error("mdbx_env_set_maxdbs failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } rc = mdbx_env_open_ex(env, envname, envflags, 0664, &exclusive); if (rc) { From 91bb3ab9fa1068bfbdb61455c9045b105f36095e Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 21 Dec 2016 20:31:46 +0300 Subject: [PATCH 26/86] mdbx: mdb_chk - cosmetics (no extra \n). --- mdb_chk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mdb_chk.c b/mdb_chk.c index fd485fe1..b61c06b7 100644 --- a/mdb_chk.c +++ b/mdb_chk.c @@ -746,7 +746,7 @@ int main(int argc, char *argv[]) meta_lt(info.me_meta1_txnid, info.me_meta1_sign, info.me_meta2_txnid, info.me_meta2_sign) ? "tail" : "head"); if (info.me_meta1_txnid > info.base.me_last_txnid) - print(", rolled-back %zu (%zu >>> %zu)\n", + print(", rolled-back %zu (%zu >>> %zu)", info.me_meta1_txnid - info.base.me_last_txnid, info.me_meta1_txnid, info.base.me_last_txnid); print("\n"); @@ -756,7 +756,7 @@ int main(int argc, char *argv[]) meta_lt(info.me_meta2_txnid, info.me_meta2_sign, info.me_meta1_txnid, info.me_meta1_sign) ? "tail" : "head"); if (info.me_meta2_txnid > info.base.me_last_txnid) - print(", rolled-back %zu (%zu >>> %zu)\n", + print(", rolled-back %zu (%zu >>> %zu)", info.me_meta2_txnid - info.base.me_last_txnid, info.me_meta2_txnid, info.base.me_last_txnid); print("\n"); From 9b38d8d422c79db54f4ba39685c956b44fa1a2da Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 21 Dec 2016 20:32:27 +0300 Subject: [PATCH 27/86] mdbx: adds mdbx_replace() for libfpta. --- mdbx.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mdbx.h | 3 ++ 2 files changed, 132 insertions(+) diff --git a/mdbx.c b/mdbx.c index ce930336..781450a5 100644 --- a/mdbx.c +++ b/mdbx.c @@ -368,3 +368,132 @@ int mdbx_cursor_eof(MDB_cursor *mc) return (mc->mc_flags & C_INITIALIZED) ? 0 : 1; } + +static int mdbx_is_samedata(MDB_val* a, MDB_val* b) { + return a->iov_len == b->iov_len + && memcmp(a->iov_base, b->iov_base, a->iov_len) == 0; +} + +/* Позволяет обновить или удалить существующую запись с получением + * в old_data предыдущего значения данных. При этом если new_data равен + * нулю, то выполняется удаление, иначе обновление/вставка. + * + * Текущее значение может находиться в уже измененной (грязной) странице. + * В этом случае страница будет перезаписана при обновлении, а само старое + * значение утрачено. Поэтому исходно в old_data должен быть передан + * дополнительный буфер для копирования старого значения. + * Если переданный буфер слишком мал, то функция вернет -1, установив + * old_data->iov_len в соответствующее значение. + * + * Для не-уникальных ключей также возможен второй сценарий использования, + * когда посредством old_data из записей с одинаковым ключом для + * удаления/обновления выбирается конкретная. Для выбора этого сценария + * во flags следует одновременно указать MDB_CURRENT и MDB_NOOVERWRITE. + * + * Функция может быть замещена соответствующими операциями с курсорами + * после двух доработок (TODO): + * - внешняя аллокация курсоров, в том числе на стеке (без malloc). + * - получения статуса страницы по адресу (знать о P_DIRTY). + */ +int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, + MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags) +{ + MDB_cursor mc; + MDB_xcursor mx; + + if (unlikely(!key || !old_data || !txn)) + return EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + if (unlikely(old_data->iov_base == NULL && old_data->iov_len)) + return EINVAL; + + if (unlikely(new_data == NULL && !(flags & MDB_CURRENT))) + return EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return EINVAL; + + if (unlikely(flags & ~(MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND|MDB_APPENDDUP|MDB_CURRENT))) + return EINVAL; + + if (unlikely(txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))) + return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; + + mdb_cursor_init(&mc, txn, dbi, &mx); + mc.mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = &mc; + + int rc; + MDB_val present_key = *key; + if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE) + && (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)) { + /* в old_data значение для выбора конкретного дубликата */ + 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; + } else { + /* в old_data буфер получения предыдущего значения */ + MDB_val present_data; + rc = mdbx_cursor_get(&mc, &present_key, &present_data, MDB_SET_KEY); + if (unlikely(rc != MDB_SUCCESS)) { + old_data->iov_base = NULL; + old_data->iov_len = rc; + if (rc != MDB_NOTFOUND || (flags & MDB_CURRENT)) + goto bailout; + } else if (flags & MDB_NOOVERWRITE) { + rc = MDB_KEYEXIST; + *old_data = present_data; + goto bailout; + } else { + MDB_page *page = mc.mc_pg[mc.mc_top]; + if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { + if (flags & MDB_CURRENT) { + /* для не-уникальных ключей позволяем update/delete только если ключ один */ + 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; + goto bailout; + } + /* если данные совпадают, то ничего делать не надо */ + if (new_data && mdbx_is_samedata(&present_data, new_data)) + goto bailout; + } else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) { + /* если данные совпадают и установлен MDB_NODUPDATA */ + rc = MDB_KEYEXIST; + goto bailout; + } + } else { + flags |= MDB_CURRENT; + } + + if (page->mp_flags & P_DIRTY) { + 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; + goto bailout; + } + memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len); + old_data->iov_len = present_data.iov_len; + } else { + *old_data = present_data; + } + } + } + + if (likely(new_data)) + rc = mdbx_cursor_put(&mc, key, new_data, flags); + else + rc = mdbx_cursor_del(&mc, 0); + +bailout: + txn->mt_cursors[dbi] = mc.mc_next; + return rc; +} diff --git a/mdbx.h b/mdbx.h index 8a5c238e..033db286 100644 --- a/mdbx.h +++ b/mdbx.h @@ -223,6 +223,9 @@ 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); +int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, + MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags); + /** @} */ #ifdef __cplusplus From ef375647c70ef5ee35f9c23a7fb15a6820ec7f80 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 22 Dec 2016 20:54:06 +0300 Subject: [PATCH 28/86] mdbx: fix mdbx_replace(). Always return `old_data`, even no changes. --- mdbx.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mdbx.c b/mdbx.c index 781450a5..3946a50c 100644 --- a/mdbx.c +++ b/mdbx.c @@ -369,7 +369,7 @@ int mdbx_cursor_eof(MDB_cursor *mc) return (mc->mc_flags & C_INITIALIZED) ? 0 : 1; } -static int mdbx_is_samedata(MDB_val* a, MDB_val* b) { +static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) { return a->iov_len == b->iov_len && memcmp(a->iov_base, b->iov_base, a->iov_len) == 0; } @@ -462,14 +462,21 @@ int mdbx_replace(MDB_txn *txn, MDB_dbi dbi, goto bailout; } /* если данные совпадают, то ничего делать не надо */ - if (new_data && mdbx_is_samedata(&present_data, new_data)) + if (new_data && mdbx_is_samedata(&present_data, new_data)) { + *old_data = *new_data; goto bailout; + } } else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) { /* если данные совпадают и установлен MDB_NODUPDATA */ rc = MDB_KEYEXIST; goto bailout; } } else { + /* если данные совпадают, то ничего делать не надо */ + if (new_data && mdbx_is_samedata(&present_data, new_data)) { + *old_data = *new_data; + goto bailout; + } flags |= MDB_CURRENT; } From ef00ee609583224b4daaabf47230dccb278f21eb Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Wed, 21 Dec 2016 16:33:47 +0100 Subject: [PATCH 29/86] mdbx: backport - Doxygen fixes. Use DISTRIBUTE_GROUP_DOC. - DISTRIBUTE_GROUP_DOC makes doxygen give several fields the same doc: mn_hi + mn_lo in MDB_node. - Don't hide a doxygen #name inside double quotes. Change-Id: I173ff54a78349344d38e175cf9de741efab29e82 --- Doxyfile | 2 +- mdb.c | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Doxyfile b/Doxyfile index 5047c0bb..5ca2cfe8 100644 --- a/Doxyfile +++ b/Doxyfile @@ -253,7 +253,7 @@ IDL_PROPERTY_SUPPORT = YES # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. -DISTRIBUTE_GROUP_DOC = NO +DISTRIBUTE_GROUP_DOC = YES # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a diff --git a/mdb.c b/mdb.c index 4c2cd1ed..cbee8489 100644 --- a/mdb.c +++ b/mdb.c @@ -614,19 +614,21 @@ typedef struct MDB_page { /** Header for a single key/data pair within a page. * Used in pages of type #P_BRANCH and #P_LEAF without #P_LEAF2. * We guarantee 2-byte alignment for 'MDB_node's. + * + * #mn_lo and #mn_hi are used for data size on leaf nodes, and for child + * pgno on branch nodes. On 64 bit platforms, #mn_flags is also used + * for pgno. (Branch nodes have no flags). Lo and hi are in host byte + * order in case some accesses can be optimized to 32-bit word access. */ typedef struct MDB_node { - /** lo and hi are used for data size on leaf nodes and for - * child pgno on branch nodes. On 64 bit platforms, flags - * is also used for pgno. (Branch nodes have no flags). - * They are in host byte order in case that lets some - * accesses be optimized into a 32-bit word access. - */ + /** part of data size or pgno + * @{ */ #if BYTE_ORDER == LITTLE_ENDIAN - unsigned short mn_lo, mn_hi; /**< part of data size or pgno */ + unsigned short mn_lo, mn_hi; #else unsigned short mn_hi, mn_lo; #endif + /** @} */ /** @defgroup mdb_node Node Flags * @ingroup internal * Flags for node headers. From 5bed0413f34c2e514d24bd544c92c54c838ca973 Mon Sep 17 00:00:00 2001 From: Hallvard Furuseth Date: Wed, 21 Dec 2016 21:40:14 +0100 Subject: [PATCH 30/86] mdbx: backport - More MDB_node doc. Change-Id: I2a4d2596af8d2c551d891b567e4d3366b2d6ad8c --- mdb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mdb.c b/mdb.c index cbee8489..27df5516 100644 --- a/mdb.c +++ b/mdb.c @@ -619,6 +619,11 @@ typedef struct MDB_page { * pgno on branch nodes. On 64 bit platforms, #mn_flags is also used * for pgno. (Branch nodes have no flags). Lo and hi are in host byte * order in case some accesses can be optimized to 32-bit word access. + * + * Leaf node flags describe node contents. #F_BIGDATA says the node's + * data part is the page number of an overflow page with actual data. + * #F_DUPDATA and #F_SUBDATA can be combined giving duplicate data in + * a sub-page/sub-database, and named databases (just #F_SUBDATA). */ typedef struct MDB_node { /** part of data size or pgno From bb7d3b4639495388ba9309ad15a2ca47033b1062 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Wed, 21 Dec 2016 13:02:00 +0000 Subject: [PATCH 31/86] mdbx: backport - More 0.9.19 updates. Change-Id: I4571ba200677124ab41f80a9c0cf37d620d5d230 --- CHANGES | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index cb19ff64..6ddd7d47 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,6 @@ MDBX Add MDB_PREV_MULTIPLE - Fix MDB_CP_COMPACT (ITS#8209) Add error MDB_PROBLEM, replace some MDB_CORRUPTED - Backport fixes for ITS#8406 LMDB 0.9.19 Release Engineering Fix mdb_env_cwalk cursor init (ITS#8424) @@ -11,6 +9,16 @@ LMDB 0.9.19 Release Engineering Optimize mdb_drop Fix xcursors after mdb_cursor_del (ITS#8406) Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412) + Fix mdb_cursor_put resetting C_EOF (ITS#8489) + Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504) + Fix mdb_env_copy with empty DB (ITS#8209) + Fix behaviors with fork (ITS#8505) + Fix mdb_dbi_open with mainDB cursors (ITS#8542) + Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682) + Documentation + Cleanup doxygen nits + Note reserved vs actual mem/disk usage + LMDB 0.9.18 Release (2016/02/05) already done for mdbx - Fix robust mutex detection on glibc 2.10-11 (ITS#8330) From dbc57d3eaf9025b8a5b3d14dd2ecbd0589d1c1b4 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 23 Dec 2016 15:35:42 +0300 Subject: [PATCH 32/86] mdbx: fix cursor_count() for libfpta. --- mdb.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/mdb.c b/mdb.c index 4976179c..323edf6d 100644 --- a/mdb.c +++ b/mdb.c @@ -7713,35 +7713,51 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc) int mdb_cursor_count(MDB_cursor *mc, size_t *countp) { - MDB_node *leaf; - if (unlikely(mc == NULL || countp == NULL)) return EINVAL; if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; - if (unlikely(mc->mc_xcursor == NULL)) - return MDB_INCOMPATIBLE; - if (unlikely(mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)) return MDB_BAD_TXN; if (unlikely(!(mc->mc_flags & C_INITIALIZED))) return EINVAL; +#if MDBX_MODE_ENABLED + MDB_page *mp = mc->mc_pg[mc->mc_top]; + int nkeys = NUMKEYS(mp); + if (!nkeys || mc->mc_ki[mc->mc_top] >= nkeys) { + *countp = 0; + return MDB_NOTFOUND; + } else if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { + *countp = 1; + } else { + 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 + *countp = mc->mc_xcursor->mx_db.md_entries; + } +#else + if (unlikely(mc->mc_xcursor == NULL)) + return MDB_INCOMPATIBLE; + if (unlikely(!mc->mc_snum || (mc->mc_flags & C_EOF))) return MDB_NOTFOUND; - leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], 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; - *countp = mc->mc_xcursor->mx_db.md_entries; } +#endif /* MDBX_MODE_ENABLED */ return MDB_SUCCESS; } From ec49580a843a8f05d2963a33cab7afe5c6cbe531 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 28 Dec 2016 23:48:04 +0300 Subject: [PATCH 33/86] mdbx: sync version timestamp with LMDB-0.9.19 Change-Id: I4a50b38042ebd57ea2f2fec5b837b16f8a2e3ff3 --- CHANGES | 2 +- lmdb.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 6ddd7d47..320c2393 100644 --- a/CHANGES +++ b/CHANGES @@ -2,7 +2,7 @@ MDBX Add MDB_PREV_MULTIPLE Add error MDB_PROBLEM, replace some MDB_CORRUPTED -LMDB 0.9.19 Release Engineering +LMDB 0.9.19 Release (2016/12/28) Fix mdb_env_cwalk cursor init (ITS#8424) Fix robust mutexes on Solaris 10/11 (ITS#8339) Fix MDB_GET_BOTH on non-dup record (ITS#8393) diff --git a/lmdb.h b/lmdb.h index d0b8b37c..a4558aac 100644 --- a/lmdb.h +++ b/lmdb.h @@ -209,7 +209,7 @@ typedef int mdb_filehandle_t; MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH) /** The release date of this library version */ -#define MDB_VERSION_DATE "2016-06-09" +#define MDB_VERSION_DATE "2016-12-28" /** A stringifier for the version info */ #define MDB_VERSTR(a,b,c,d) "MDBX " #a "." #b "." #c ": (" d ", https://github.com/ReOpen/libmdbx)" From 1edceae1a140b8e33f1851a0d47609fc87a1e027 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 29 Dec 2016 00:21:34 +0300 Subject: [PATCH 34/86] mdbx: adds travis-ci. Change-Id: Ic7034ae538bcc70ad1584f4aeae857594ceeded1 --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..27287a04 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: c +sudo: false +dist: trusty +cache: bundler +notifications: + email: false + +compiler: + - gcc + - clang + +os: + - linux + +script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make all lmdb check; fi From ddb3c32258e1f400b4f0d01fc144fc68146910ce Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 1 Jan 2017 14:56:00 +0300 Subject: [PATCH 35/86] mdbx: HNY 2017! Change-Id: Ie80fc1dc407ec45c828aa3b1bdc42933741a863a --- COPYRIGHT | 6 +++--- barriers.h | 4 ++-- intro.doc | 6 +++--- lmdb.h | 10 +++++----- mdb.c | 6 +++--- mdb_chk.c | 4 ++-- mdb_copy.1 | 4 +++- mdb_copy.c | 6 +++--- mdb_dump.1 | 6 +++--- mdb_dump.c | 6 +++--- mdb_load.1 | 6 ++++-- mdb_load.c | 6 +++--- mdb_stat.1 | 6 +++--- mdb_stat.c | 6 +++--- mdbx.c | 4 ++-- mdbx.h | 4 ++-- midl.c | 6 +++--- midl.h | 6 +++--- mtest0.c | 6 +++--- mtest1.c | 4 ++-- mtest2.c | 6 +++--- mtest3.c | 6 +++--- mtest4.c | 6 +++--- mtest5.c | 6 +++--- mtest6.c | 6 +++--- reopen.h | 4 ++-- sample-bdb.txt | 6 +++--- sample-mdb.txt | 6 +++--- wbench.c | 4 ++-- yota_test1.c | 5 ++--- yota_test2.c | 5 ++--- 31 files changed, 87 insertions(+), 85 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index e3af9b29..f4a7607c 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,6 +1,6 @@ -Copyright (c) 2015,2016 Leonid Yuriev . -Copyright (c) 2015,2016 Peter-Service R&D LLC. -Copyright 2011-2016 Howard Chu, Symas Corp. +Copyright 2015-2017 Leonid Yuriev . +Copyright 2011-2017 Howard Chu, Symas Corp. +Copyright 2015,2016 Peter-Service R&D LLC. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/barriers.h b/barriers.h index bce314e1..1e98730d 100644 --- a/barriers.h +++ b/barriers.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/intro.doc b/intro.doc index b0353bf3..9462df18 100644 --- a/intro.doc +++ b/intro.doc @@ -1,7 +1,7 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2015 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/lmdb.h b/lmdb.h index a4558aac..6e3a6557 100644 --- a/lmdb.h +++ b/lmdb.h @@ -125,17 +125,17 @@ * @author Leonid Yuriev, 'ReOpen' initiative . * Howard Chu, Symas Corp. All rights reserved. * - * @copyright 2015,2016 Leonid Yuriev . - * 2011-2016 Howard Chu, Symas Corp. All rights reserved. + * @copyright 2015-2017 Leonid Yuriev . + * 2011-2017 Howard Chu, Symas Corp. All rights reserved. * * --- * - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * @par Derived From: * This code is derived from LMDB engine written by Howard Chu, Symas Corporation. * - * Copyright 2011-2016 Howard Chu, Symas Corp. All rights reserved. + * Copyright 2011-2017 Howard Chu, Symas Corp. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP diff --git a/mdb.c b/mdb.c index 27df5516..f100f392 100644 --- a/mdb.c +++ b/mdb.c @@ -6,9 +6,9 @@ */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mdb_chk.c b/mdb_chk.c index b86b8f96..75a1959c 100644 --- a/mdb_chk.c +++ b/mdb_chk.c @@ -1,8 +1,8 @@ /* mdbx_chk.c - memory-mapped database check tool */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * * This file is part of libmdbx. * diff --git a/mdb_copy.1 b/mdb_copy.1 index 4e9ed7eb..157e741d 100644 --- a/mdb_copy.1 +++ b/mdb_copy.1 @@ -1,4 +1,6 @@ -.\" Copyright 2012-2016 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015-2017 Leonid Yuriev . +.\" Copyright 2012-2017 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copying restrictions apply. See COPYRIGHT/LICENSE. .TH MDB_COPY 1 "2014/06/20" "LMDB 0.9.14" .SH NAME diff --git a/mdb_copy.c b/mdb_copy.c index 6c898ac5..43bee869 100644 --- a/mdb_copy.c +++ b/mdb_copy.c @@ -1,9 +1,9 @@ /* mdb_copy.c - memory-mapped database backup tool */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2012-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2012-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mdb_dump.1 b/mdb_dump.1 index 4efebdf2..4c4553ce 100644 --- a/mdb_dump.1 +++ b/mdb_dump.1 @@ -1,6 +1,6 @@ -.\" Copyright (c) 2015,2016 Leonid Yuriev . -.\" Copyright (c) 2015,2016 Peter-Service R&D LLC . -.\" Copyright 2014-2016 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015-2017 Leonid Yuriev . +.\" Copyright 2014-2017 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copying restrictions apply. See COPYRIGHT/LICENSE. .TH MDB_DUMP 1 "2014/06/20" "LMDB 0.9.14" .SH NAME diff --git a/mdb_dump.c b/mdb_dump.c index c27419f3..0b5db58e 100644 --- a/mdb_dump.c +++ b/mdb_dump.c @@ -1,9 +1,9 @@ /* mdb_dump.c - memory-mapped database dump tool */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mdb_load.1 b/mdb_load.1 index 7b353e75..5e082f67 100644 --- a/mdb_load.1 +++ b/mdb_load.1 @@ -1,6 +1,8 @@ -.TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14" -.\" Copyright 2014-2016 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015-2017 Leonid Yuriev . +.\" Copyright 2014-2017 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14" .SH NAME mdb_load \- LMDB environment import tool .SH SYNOPSIS diff --git a/mdb_load.c b/mdb_load.c index 1e5378fd..625ef02d 100644 --- a/mdb_load.c +++ b/mdb_load.c @@ -1,9 +1,9 @@ /* mdb_load.c - memory-mapped database load tool */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mdb_stat.1 b/mdb_stat.1 index 2e054600..bb659744 100644 --- a/mdb_stat.1 +++ b/mdb_stat.1 @@ -1,6 +1,6 @@ -.\" Copyright (c) 2015,2016 Leonid Yuriev . -.\" Copyright (c) 2015,2016 Peter-Service R&D LLC . -.\" Copyright 2012-2016 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015-2017 Leonid Yuriev . +.\" Copyright 2012-2017 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . .\" Copying restrictions apply. See COPYRIGHT/LICENSE. .TH MDB_STAT 1 "2014/06/20" "LMDB 0.9.14" .SH NAME diff --git a/mdb_stat.c b/mdb_stat.c index 4c3c5453..d47ffe9e 100644 --- a/mdb_stat.c +++ b/mdb_stat.c @@ -1,9 +1,9 @@ /* mdb_stat.c - memory-mapped database status tool */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mdbx.c b/mdbx.c index 9f07548e..348003cd 100644 --- a/mdbx.c +++ b/mdbx.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mdbx.h b/mdbx.h index bcbd4f47..7bc762bb 100644 --- a/mdbx.h +++ b/mdbx.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/midl.c b/midl.c index c181f38c..6d2417ac 100644 --- a/midl.c +++ b/midl.c @@ -2,9 +2,9 @@ * @brief ldap bdb back-end ID List functions */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2000-2016 The OpenLDAP Foundation. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2000-2017 The OpenLDAP Foundation. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/midl.h b/midl.h index 7cc3baeb..1bdffce1 100644 --- a/midl.h +++ b/midl.h @@ -10,9 +10,9 @@ */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2000-2016 The OpenLDAP Foundation. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2000-2017 The OpenLDAP Foundation. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest0.c b/mtest0.c index 54b0d20c..efb583da 100644 --- a/mtest0.c +++ b/mtest0.c @@ -1,9 +1,9 @@ /* mtest.c - memory-mapped database tester/toy */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest1.c b/mtest1.c index ff063198..ffe79123 100644 --- a/mtest1.c +++ b/mtest1.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest2.c b/mtest2.c index 85f1576f..12b1e126 100644 --- a/mtest2.c +++ b/mtest2.c @@ -1,9 +1,9 @@ /* mtest2.c - memory-mapped database tester/toy */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest3.c b/mtest3.c index 245fd071..a55ec604 100644 --- a/mtest3.c +++ b/mtest3.c @@ -1,9 +1,9 @@ /* mtest3.c - memory-mapped database tester/toy */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest4.c b/mtest4.c index 56430876..3d67a0f9 100644 --- a/mtest4.c +++ b/mtest4.c @@ -1,9 +1,9 @@ /* mtest4.c - memory-mapped database tester/toy */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest5.c b/mtest5.c index 66a176f7..ed19f412 100644 --- a/mtest5.c +++ b/mtest5.c @@ -1,9 +1,9 @@ /* mtest5.c - memory-mapped database tester/toy */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/mtest6.c b/mtest6.c index bae731c4..d988c93c 100644 --- a/mtest6.c +++ b/mtest6.c @@ -1,9 +1,9 @@ /* mtest6.c - memory-mapped database tester/toy */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2011-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2011-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/reopen.h b/reopen.h index b85acde4..72f8e3ad 100644 --- a/reopen.h +++ b/reopen.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/sample-bdb.txt b/sample-bdb.txt index 9be26713..c4343e9e 100644 --- a/sample-bdb.txt +++ b/sample-bdb.txt @@ -4,9 +4,9 @@ */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2012-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2012-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/sample-mdb.txt b/sample-mdb.txt index f605f67d..24fccdb9 100644 --- a/sample-mdb.txt +++ b/sample-mdb.txt @@ -4,9 +4,9 @@ */ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. - * Copyright 2012-2016 Howard Chu, Symas Corp. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2012-2017 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/wbench.c b/wbench.c index db3b8457..e5fdc64a 100644 --- a/wbench.c +++ b/wbench.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2015,2016 Leonid Yuriev . - * Copyright (c) 2015,2016 Peter-Service R&D LLC. + * Copyright 2015-2017 Leonid Yuriev . + * Copyright 2015,2016 Peter-Service R&D LLC. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/yota_test1.c b/yota_test1.c index 178133f8..be727cbf 100644 --- a/yota_test1.c +++ b/yota_test1.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2016 Leonid Yuriev . - * Copyright (c) 2015 Vladimir Romanov , Yota Lab. + * Copyright 2016-2017 Leonid Yuriev . + * Copyright 2015 Vladimir Romanov , Yota Lab. * * This file is part of libmdbx. * @@ -16,7 +16,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * */ #include diff --git a/yota_test2.c b/yota_test2.c index a8bd6e61..753bea2f 100644 --- a/yota_test2.c +++ b/yota_test2.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2016 Leonid Yuriev . - * Copyright (c) 2015 Vladimir Romanov , Yota Lab. + * Copyright 2016-2017 Leonid Yuriev . + * Copyright 2015 Vladimir Romanov , Yota Lab. * * This file is part of libmdbx. * @@ -16,7 +16,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * */ #include From 5b160be1286483f158f27521775390e3b90e8c1a Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 3 Jan 2017 16:09:34 +0300 Subject: [PATCH 36/86] mdbx: assert_fail() when `INDXSIZE(key) > nodemax`. Change-Id: I2fadc68a3e682dd3d8b3b8b5f48ed096e9a92288 --- mdb.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index f100f392..3d442588 100644 --- a/mdb.c +++ b/mdb.c @@ -7232,10 +7232,11 @@ mdb_branch_size(MDB_env *env, MDB_val *key) size_t sz; sz = INDXSIZE(key); - if (sz > env->me_nodemax) { + if (unlikely(sz > env->me_nodemax)) { /* put on overflow page */ /* not implemented */ - /* sz -= key->size - sizeof(pgno_t); */ + mdb_assert_fail(env, "INDXSIZE(key) <= env->me_nodemax", __FUNCTION__, __LINE__); + sz -= key->mv_size - sizeof(pgno_t); } return sz + sizeof(indx_t); From 855c60a5547790acb99e7b96b021cfd3a400c269 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 3 Jan 2017 16:45:23 +0300 Subject: [PATCH 37/86] mdbx: remote extra LNs (cosmetics). Change-Id: I3ea352c9d088367756543f8313d6598de3b80bf0 --- mdb.c | 83 +++++++++++++++++++---------------------------------------- 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/mdb.c b/mdb.c index 3d442588..8681897d 100644 --- a/mdb.c +++ b/mdb.c @@ -1367,8 +1367,7 @@ mdb_dkey(MDB_val *key, char *buf) if (key->mv_size > DKBUF_MAXKEYSIZE) return "MDB_MAXKEYSIZE"; /* may want to make this a dynamic check: if the key is mostly - * printable characters, print it as-is instead of converting to hex. - */ + * printable characters, print it as-is instead of converting to hex. */ #if 1 buf[0] = '\0'; for (i=0; imv_size; i++) @@ -1576,8 +1575,7 @@ mdb_page_malloc(MDB_txn *txn, unsigned num) if ((env->me_flags & MDB_NOMEMINIT) == 0) { /* For a single page alloc, we init everything after the page header. * For multi-page, we init the final page; if the caller needed that - * many pages they will be filling in at least up to the last page. - */ + * many pages they will be filling in at least up to the last page. */ size_t skip = PAGEHDRSZ; if (num > 1) skip += (num - 1) * env->me_psize; @@ -1671,8 +1669,7 @@ mdb_page_loose(MDB_cursor *mc, MDB_page *mp) if (txn->mt_parent) { MDB_ID2 *dl = txn->mt_u.dirty_list; /* If txn has a parent, make sure the page is in our - * dirty list. - */ + * dirty list. */ if (dl[0].mid) { unsigned x = mdb_mid2l_search(dl, pgno); if (x <= dl[0].mid && dl[x].mid == pgno) { @@ -1862,8 +1859,7 @@ mdb_page_spill(MDB_cursor *m0, MDB_val *key, MDB_val *data) * turns out to be a lot of wasted effort because in a large txn many * of those pages will need to be used again. So now we spill only 1/8th * of the dirty pages. Testing revealed this to be a good tradeoff, - * better than 1/2, 1/4, or 1/10. - */ + * better than 1/2, 1/4, or 1/10. */ if (need < MDB_IDL_UM_MAX / 8) need = MDB_IDL_UM_MAX / 8; @@ -1875,8 +1871,7 @@ mdb_page_spill(MDB_cursor *m0, MDB_val *key, MDB_val *data) if (dp->mp_flags & (P_LOOSE|P_KEEP)) continue; /* Can't spill twice, make sure it's not already in a parent's - * spill list. - */ + * spill list. */ if (txn->mt_parent) { MDB_txn *tx2; for (tx2 = txn->mt_parent; tx2; tx2 = tx2->mt_parent) { @@ -2124,8 +2119,7 @@ mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp, int flags) pgno_t *idl; /* Seek a big enough contiguous page range. Prefer - * pages at the tail, just truncating the list. - */ + * pages at the tail, just truncating the list. */ if (likely(flags & MDBX_ALLOC_CACHE) && mop_len > n2 && ( !(flags & MDBX_COALESCE) || op == MDB_FIRST)) { @@ -2405,8 +2399,7 @@ mdb_page_copy(MDB_page *dst, MDB_page *src, unsigned psize) indx_t upper = src->mp_upper, lower = src->mp_lower, unused = upper-lower; /* If page isn't full, just copy the used portion. Adjust - * alignment so memcpy may copy words instead of bytes. - */ + * alignment so memcpy may copy words instead of bytes. */ if ((unused &= -Align) && !IS_LEAF2(src)) { upper = (upper + PAGEBASE) & -Align; memcpy(dst, src, (lower + PAGEBASE + (Align-1)) & -Align); @@ -2460,8 +2453,7 @@ mdb_page_unspill(MDB_txn *txn, MDB_page *mp, MDB_page **ret) if (tx2 == txn) { /* If in current txn, this page is no longer spilled. * If it happens to be the last page, truncate the spill list. - * Otherwise mark it as deleted by setting the LSB. - */ + * Otherwise mark it as deleted by setting the LSB. */ if (x == txn->mt_spill_pgs[0]) txn->mt_spill_pgs[0]--; else @@ -2521,8 +2513,7 @@ mdb_page_touch(MDB_cursor *mc) MDB_ID2 mid, *dl = txn->mt_u.dirty_list; pgno = mp->mp_pgno; /* If txn has a parent, make sure the page is in our - * dirty list. - */ + * dirty list. */ if (dl[0].mid) { unsigned x = mdb_mid2l_search(dl, pgno); if (x <= dl[0].mid && dl[x].mid == pgno) { @@ -2665,8 +2656,7 @@ mdb_cursor_shadow(MDB_txn *src, MDB_txn *dst) mc->mc_db = &dst->mt_dbs[i]; /* Kill pointers into src to reduce abuse: The * user may not use mc until dst ends. But we need a valid - * txn pointer here for cursor fixups to keep working. - */ + * txn pointer here for cursor fixups to keep working. */ mc->mc_txn = dst; mc->mc_dbflag = &dst->mt_dbflags[i]; if ((mx = mc->mc_xcursor) != NULL) { @@ -2826,8 +2816,7 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) * uses the reader table un-mutexed: First reset the * slot, next publish it in mti_numreaders. After * that, it is safe for mdb_env_close() to touch it. - * When it will be closed, we can finally claim it. - */ + * When it will be closed, we can finally claim it. */ r->mr_pid = 0; r->mr_txnid = ~(txnid_t)0; r->mr_tid = tid; @@ -3002,8 +2991,7 @@ mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned flags, MDB_txn **ret) size += tsize = sizeof(MDB_txn); } else { /* Reuse preallocated write txn. However, do not touch it until - * mdb_txn_renew0() succeeds, since it currently may be active. - */ + * mdb_txn_renew0() succeeds, since it currently may be active. */ txn = env->me_txn0; goto renew; } @@ -3289,8 +3277,7 @@ mdb_freelist_save(MDB_txn *txn) { /* env->me_pghead[] can grow and shrink during this call. * env->me_pglast and txn->mt_free_pgs[] can only grow. - * Page numbers cannot disappear from txn->mt_free_pgs[]. - */ + * Page numbers cannot disappear from txn->mt_free_pgs[]. */ MDB_cursor mc; MDB_env *env = txn->mt_env; int rc, maxfree_1pg = env->me_maxfree_1pg, more = 1; @@ -3315,8 +3302,7 @@ again: if (! lifo) { /* If using records from freeDB which we have not yet - * deleted, delete them and any we reserved for me_pghead. - */ + * deleted, delete them and any we reserved for me_pghead. */ while (pglast < env->me_pglast) { rc = mdb_cursor_first(&mc, &key, NULL); if (unlikely(rc)) @@ -3358,8 +3344,7 @@ again: if (unlikely(!env->me_pghead) && txn->mt_loose_pgs) { /* Put loose page numbers in mt_free_pgs, since - * we may be unable to return them to me_pghead. - */ + * we may be unable to return them to me_pghead. */ MDB_page *mp = txn->mt_loose_pgs; if (unlikely((rc = mdb_midl_need(&txn->mt_free_pgs, txn->mt_loose_count)) != 0)) return rc; @@ -3413,8 +3398,7 @@ again: /* Reserve records for me_pghead[]. Split it if multi-page, * to avoid searching freeDB for a page range. Use keys in - * range [1,me_pglast]: Smaller than txnid of oldest reader. - */ + * range [1,me_pglast]: Smaller than txnid of oldest reader. */ if (total_room >= mop_len) { if (total_room == mop_len || --more < 0) break; @@ -3491,8 +3475,7 @@ again: mdb_tassert(txn, cleanup_idx == (txn->mt_lifo_reclaimed ? txn->mt_lifo_reclaimed[0] : 0)); /* Return loose page numbers to me_pghead, though usually none are - * left at this point. The pages themselves remain in dirty_list. - */ + * left at this point. The pages themselves remain in dirty_list. */ if (txn->mt_loose_pgs) { MDB_page *mp = txn->mt_loose_pgs; unsigned count = txn->mt_loose_count; @@ -3766,8 +3749,7 @@ mdb_txn_commit(MDB_txn *txn) goto fail; mdb_midl_free(txn->mt_free_pgs); /* Failures after this must either undo the changes - * to the parent or set MDB_TXN_ERROR in the parent. - */ + * to the parent or set MDB_TXN_ERROR in the parent. */ parent->mt_next_pgno = txn->mt_next_pgno; parent->mt_flags = txn->mt_flags; @@ -5190,7 +5172,7 @@ mdb_cmp_int_ua(const MDB_val *a, const MDB_val *b) do { diff = *--pa - *--pb; - if (likely(diff)) break; + if (likely(diff != 0)) break; } while(pa != a->mv_data); return diff; } @@ -5410,8 +5392,7 @@ mdb_page_get(MDB_cursor *mc, pgno_t pgno, MDB_page **ret, int *lvl) /* Spilled pages were dirtied in this txn and flushed * because the dirty list got full. Bring this page * back in from the map (but don't unspill it here, - * leave that unless page_touch happens again). - */ + * leave that unless page_touch happens again). */ if (tx2->mt_spill_pgs) { MDB_ID pn = pgno << 1; x = mdb_midl_search(tx2->mt_spill_pgs, pn); @@ -6080,8 +6061,7 @@ mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data, } } /* If any parents have right-sibs, search. - * Otherwise, there's nothing further. - */ + * Otherwise, there's nothing further. */ for (i=0; imc_top; i++) if (mc->mc_ki[i] < NUMKEYS(mc->mc_pg[i])-1) @@ -6953,8 +6933,7 @@ new_sub: /* Now store the actual data in the child DB. Note that we're * storing the user data in the keys field, so there are strict * size limits on dupdata. The actual data fields of the child - * DB are all zero size. - */ + * DB are all zero size. */ if (do_sub) { int xflags, new_dupdata; size_t ecount; @@ -7018,8 +6997,7 @@ put_sub: if (unlikely(rc)) goto bad_sub; /* If we succeeded and the key didn't exist before, - * make sure the cursor is marked valid. - */ + * make sure the cursor is marked valid. */ mc->mc_flags |= C_INITIALIZED; } if (flags & MDB_MULTIPLE) { @@ -7561,7 +7539,6 @@ mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node) #endif */ } - /** Fixup a sorted-dups cursor due to underlying update. * Sets up some fields that depend on the data from the main cursor. * Almost the same as init1, but skips initialization steps if the @@ -7940,14 +7917,12 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft) csrc->mc_pg[csrc->mc_top]->mp_pgno, cdst->mc_ki[cdst->mc_top], cdst->mc_pg[cdst->mc_top]->mp_pgno); - /* Add the node to the destination page. - */ + /* Add the node to the destination page. */ rc = mdb_node_add(cdst, cdst->mc_ki[cdst->mc_top], &key, &data, srcpg, flags); if (unlikely(rc != MDB_SUCCESS)) return rc; - /* Delete the node from the source page. - */ + /* Delete the node from the source page. */ mdb_node_del(csrc, key.mv_size); { @@ -8008,8 +7983,7 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft) } } - /* Update the parent separators. - */ + /* Update the parent separators. */ if (csrc->mc_ki[csrc->mc_top] == 0) { if (csrc->mc_ki[csrc->mc_top-1] != 0) { if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { @@ -8808,8 +8782,7 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno mdb_debug("separator is %d [%s]", split_indx, DKEY(&sepkey)); - /* Copy separator key to the parent. - */ + /* Copy separator key to the parent. */ if (SIZELEFT(mn.mc_pg[ptop]) < mdb_branch_size(env, &sepkey)) { int snum = mc->mc_snum; mn.mc_snum--; @@ -10277,11 +10250,9 @@ mdb_pid_insert(pid_t *ids, pid_t pid) if( val < 0 ) { n = pivot; - } else if ( val > 0 ) { base = cursor; n -= pivot + 1; - } else { /* found, so it's a duplicate */ return -1; From ee4d5bb5d2fc3d2c893a08f15e711736d0dba51d Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 4 Jan 2017 00:09:42 +0300 Subject: [PATCH 38/86] mdbx: rework TLS cleanup on thread termination. Re-fix https://github.com/ReOpen/ReOpenLDAP/issues/48 Change-Id: Ie47d2ede0f47b382a30ab6a27546f249f56cf4f6 --- mdb.c | 183 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 65 deletions(-) diff --git a/mdb.c b/mdb.c index 8681897d..90ecc18b 100644 --- a/mdb.c +++ b/mdb.c @@ -431,8 +431,6 @@ typedef struct MDB_rxbody { volatile pid_t mrb_pid; /** The thread ID of the thread owning this txn. */ volatile pthread_t mrb_tid; - /** Pointer to the context for deferred cleanup reader thread. */ - struct MDB_rthc *mrb_rthc; } MDB_rxbody; /** The actual reader record, with cacheline padding. */ @@ -443,7 +441,6 @@ typedef struct MDB_reader { #define mr_txnid mru.mrx.mrb_txnid #define mr_pid mru.mrx.mrb_pid #define mr_tid mru.mrx.mrb_tid -#define mr_rthc mru.mrx.mrb_rthc /** cache line alignment */ char pad[(sizeof(MDB_rxbody)+CACHELINE_SIZE-1) & ~(CACHELINE_SIZE-1)]; } mru; @@ -1004,9 +1001,14 @@ typedef struct MDB_pgstate { /** Context for deferred cleanup of reader's threads. * to avoid https://github.com/ReOpen/ReOpenLDAP/issues/48 */ -struct MDB_rthc { +typedef struct MDBX_rthc { MDB_reader *rc_reader; -}; +} MDBX_rthc; + +static MDBX_rthc* mdbx_rthc_alloc(void); +static void mdbx_rthc_cleanup(MDBX_rthc*); +static int mdbx_rthc_assigned(MDBX_rthc* rthc); + /** The database environment. */ struct MDB_env { #define MDBX_ME_SIGNATURE (0x9A899641^MDBX_MODE_SALT) @@ -2754,7 +2756,7 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) } if (flags & MDB_TXN_RDONLY) { - struct MDB_rthc *rthc = NULL; + MDBX_rthc *rthc = NULL; MDB_reader *r = NULL; txn->mt_flags = MDB_TXN_RDONLY; @@ -2762,20 +2764,19 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) mdb_assert(env, !(env->me_flags & MDB_NOTLS)); rthc = pthread_getspecific(env->me_txkey); if (unlikely(! rthc)) { - rthc = calloc(1, sizeof(struct MDB_rthc)); + rthc = mdbx_rthc_alloc(); if (unlikely(! rthc)) - return ENOMEM; + return MDB_READERS_FULL; rc = pthread_setspecific(env->me_txkey, rthc); if (unlikely(rc)) { free(rthc); return rc; } } - r = rthc->rc_reader; - if (r) { + if (mdbx_rthc_assigned(rthc)) { + r = rthc->rc_reader; mdb_assert(env, r->mr_pid == env->me_pid); mdb_assert(env, r->mr_tid == pthread_self()); - mdb_assert(env, r->mr_rthc == rthc); } } else { mdb_assert(env, env->me_flags & MDB_NOTLS); @@ -2837,7 +2838,6 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) new_notls = MDB_END_SLOT; if (likely(rthc)) { rthc->rc_reader = r; - r->mr_rthc = rthc; new_notls = 0; } } @@ -4505,38 +4505,111 @@ mdb_env_open2(MDB_env *env, MDB_meta *meta) return MDB_SUCCESS; } -static pthread_mutex_t mdb_rthc_lock = PTHREAD_MUTEX_INITIALIZER; +/****************************************************************************/ /** Release a reader thread's slot in the reader lock table. * This function is called automatically when a thread exits. * @param[in] ptr This points to the MDB_rthc of a slot in the reader lock table. */ - -/* LY: TODO: Yet another problem is here - segfault in case if a DSO will - * be unloaded before a thread would been finished. */ -static ATTRIBUTE_NO_SANITIZE_THREAD +static __cold void mdb_env_reader_destr(void *ptr) { - struct MDB_rthc* rthc = ptr; - MDB_reader *reader; + MDBX_rthc* rthc = ptr; - mdb_ensure(NULL, pthread_mutex_lock(&mdb_rthc_lock) == 0); - reader = rthc->rc_reader; - if (reader && reader->mr_pid == getpid()) { - mdb_ensure(NULL, reader->mr_rthc == rthc); - rthc->rc_reader = NULL; - reader->mr_rthc = NULL; - mdbx_compiler_barrier(); - reader->mr_pid = 0; - mdbx_coherent_barrier(); - } - mdb_ensure(NULL, pthread_mutex_unlock(&mdb_rthc_lock) == 0); - free(rthc); + /* LY: Основная задача этого деструктора была и есть в освобождении + * слота таблицы читателей при завершении треда, но тут есть пара + * не очевидных сложностей: + * - Таблица читателей располагается в разделяемой памяти, поэтому + * во избежание segfault деструктор не должен что-либо делать после + * или одновременно с mdb_env_close(). + * - Действительно, mdb_env_close() вызовет pthread_key_delete() и + * после этого glibc не будет вызывать деструктор. + * - ОДНАКО, это никак не решает проблему гонок между mdb_env_close() + * и завершающимися тредами. Грубо говоря, при старте mdb_env_close() + * деструктор уже может выполняться в некоторых тредах, и завершиться + * эти выполнения могут во время или после окончания mdb_env_close(). + * - БОЛЕЕ ТОГО, схожая проблема может возникнуть при выгрузке dso/dll, + * в случае если пользователь не вызвал mdb_env_close(), на что он + * имеет право. + * - Исходное проявление проблемы было зафиксировано + * в https://github.com/ReOpen/ReOpenLDAP/issues/48 + * + * Решение посредством выделяемого динамически struct MDB_rthc было + * не удачным, так как порождало либо утечку памяти, либо вероятностное + * обращение к уже освобожденной памяти из этого деструктора. + */ + + mdbx_rthc_cleanup(rthc); } +static pthread_mutex_t mdbx_rthc_lock = PTHREAD_MUTEX_INITIALIZER; + +#define MDBX_RTHC_MAX 512 +static MDBX_rthc mdbx_rthc_tlb[MDBX_RTHC_MAX]; + +static MDBX_INLINE +int mdbx_rthc_assigned(MDBX_rthc* rthc) +{ + return rthc->rc_reader && rthc->rc_reader != (void*) rthc; +} + +static __cold +MDBX_rthc* mdbx_rthc_alloc(void) +{ + mdb_ensure(NULL, pthread_mutex_lock(&mdbx_rthc_lock) == 0); + for (size_t i = 0; i < MDBX_RTHC_MAX; ++i) { + if (mdbx_rthc_tlb[i].rc_reader == NULL) { + MDBX_rthc* rthc = mdbx_rthc_tlb + i; + rthc->rc_reader = (void *) rthc; + mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); + return rthc; + } + } + mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); + return NULL; +} + +static __cold +void mdbx_rthc_cleanup_locked(MDBX_rthc* rthc) +{ + if (mdbx_rthc_assigned(rthc)) { + if (rthc->rc_reader->mr_pid == getpid()) { + rthc->rc_reader->mr_pid = 0; + mdbx_coherent_barrier(); + } + rthc->rc_reader = NULL; + } +} + +static __cold +void mdbx_rthc_cleanup_env(MDB_env *env) +{ + mdb_ensure(env, pthread_mutex_lock(&mdbx_rthc_lock) == 0); + MDB_reader* begin = env->me_txns->mti_readers; + MDB_reader* end = env->me_txns->mti_readers + env->me_close_readers; + for (size_t i = 0; i < MDBX_RTHC_MAX; ++i) { + MDBX_rthc* rthc = mdbx_rthc_tlb + i; + if (rthc->rc_reader >= begin && rthc->rc_reader < end) + mdbx_rthc_cleanup_locked(rthc); + } + mdb_ensure(env, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); +} + +static __cold +void mdbx_rthc_cleanup(MDBX_rthc* rthc) +{ + if (rthc->rc_reader) { + mdb_ensure(NULL, pthread_mutex_lock(&mdbx_rthc_lock) == 0); + mdbx_rthc_cleanup_locked(rthc); + mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); + } +} + +/****************************************************************************/ + /** Downgrade the exclusive lock on the region back to shared */ -static int __cold -mdb_env_share_locks(MDB_env *env, int *excl) +static __cold +int mdb_env_share_locks(MDB_env *env, int *excl) { struct flock lock_info; int rc = 0; @@ -4991,11 +5064,7 @@ mdb_env_close0(MDB_env *env) mdb_midl_free(env->me_free_pgs); if (env->me_flags & MDB_ENV_TXKEY) { - struct MDB_rthc *rthc = pthread_getspecific(env->me_txkey); - if (rthc && pthread_setspecific(env->me_txkey, NULL) == 0) { - mdb_env_reader_destr(rthc); - } - pthread_key_delete(env->me_txkey); + mdb_ensure(env, pthread_key_delete(env->me_txkey) == 0); env->me_flags &= ~MDB_ENV_TXKEY; } @@ -5009,7 +5078,6 @@ mdb_env_close0(MDB_env *env) if (env->me_fd != INVALID_HANDLE_VALUE) (void) close(env->me_fd); - pid_t pid = env->me_pid; /* Clearing readers is done in this function because * me_txkey with its destructor must be disabled first. * @@ -5017,26 +5085,12 @@ mdb_env_close0(MDB_env *env) * data owned by this process (me_close_readers and * our readers), and clear each reader atomically. */ - if (pid == getpid()) { - mdb_ensure(env, pthread_mutex_lock(&mdb_rthc_lock) == 0); - for (i = env->me_close_readers; --i >= 0; ) { - MDB_reader *reader = &env->me_txns->mti_readers[i]; - if (reader->mr_pid == pid) { - struct MDB_rthc *rthc = reader->mr_rthc; - if (rthc) { - mdb_ensure(env, rthc->rc_reader == reader); - rthc->rc_reader = NULL; - reader->mr_rthc = NULL; - } - reader->mr_pid = 0; - } - } - mdbx_coherent_barrier(); - mdb_ensure(env, pthread_mutex_unlock(&mdb_rthc_lock) == 0); - } + if (env->me_pid == getpid()) + mdbx_rthc_cleanup_env(env); munmap((void *)env->me_txns, (env->me_maxreaders-1)*sizeof(MDB_reader)+sizeof(MDB_txninfo)); env->me_txns = NULL; + env->me_pid = 0; if (env->me_lfd != INVALID_HANDLE_VALUE) { (void) close(env->me_lfd); @@ -10318,15 +10372,14 @@ mdb_reader_check0(MDB_env *env, int rlocked, int *dead) j = rdrs; } } - for (; j Date: Wed, 4 Jan 2017 16:15:58 +0300 Subject: [PATCH 39/86] mdbx: enable C99. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ee32259d..dd782eee 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ suffix ?= CC ?= gcc XCFLAGS ?= -DNDEBUG=1 -DMDB_DEBUG=0 CFLAGS ?= -O2 -g3 -Wall -Werror -Wextra -CFLAGS += -pthread $(XCFLAGS) +CFLAGS += -std=gnu99 -pthread $(XCFLAGS) # LY: for ability to built with modern glibc, # but then run with the old From 02de457f3c6ab7a2fb751dcd32bdb4bc8f2bf11e Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 6 Jan 2017 02:09:08 +0300 Subject: [PATCH 40/86] mdbx: more rework TLS cleanup on thread termination. One more re-fix https://github.com/ReOpen/ReOpenLDAP/issues/48 Here is only part of the work for glibc >= 2.18 -- Unfortunately, the two bugs related to pthread_key_delete() are present in all glibc versions: 1) The race condition between pthread_key_delete() and thread's finalization path, from where a TSD-destructors are called. Therefore some TSD-destructor(s) could be executed after that the pthread_key_delete() was competed. 2) ld.so infrastructure does not tracks a TDS-destructors. Therefore a lib.so could be unloaded while corresponding a TSD-destructor(s) were even not completed or still were not called. Change-Id: I47eba97df57fd4c7a5bf3a8f9f12d72ba898cae5 --- mdb.c | 205 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 132 insertions(+), 73 deletions(-) diff --git a/mdb.c b/mdb.c index 90ecc18b..1d413ab7 100644 --- a/mdb.c +++ b/mdb.c @@ -1002,12 +1002,13 @@ typedef struct MDB_pgstate { /** Context for deferred cleanup of reader's threads. * to avoid https://github.com/ReOpen/ReOpenLDAP/issues/48 */ typedef struct MDBX_rthc { + struct MDBX_rthc *rc_next; + pthread_t rc_thread; MDB_reader *rc_reader; } MDBX_rthc; -static MDBX_rthc* mdbx_rthc_alloc(void); -static void mdbx_rthc_cleanup(MDBX_rthc*); -static int mdbx_rthc_assigned(MDBX_rthc* rthc); +static MDBX_rthc* mdbx_rthc_get(pthread_key_t key); +static void mdbx_rthc_cleanup(MDB_env *env); /** The database environment. */ struct MDB_env { @@ -2762,18 +2763,10 @@ mdb_txn_renew0(MDB_txn *txn, unsigned flags) txn->mt_flags = MDB_TXN_RDONLY; if (likely(env->me_flags & MDB_ENV_TXKEY)) { mdb_assert(env, !(env->me_flags & MDB_NOTLS)); - rthc = pthread_getspecific(env->me_txkey); - if (unlikely(! rthc)) { - rthc = mdbx_rthc_alloc(); - if (unlikely(! rthc)) - return MDB_READERS_FULL; - rc = pthread_setspecific(env->me_txkey, rthc); - if (unlikely(rc)) { - free(rthc); - return rc; - } - } - if (mdbx_rthc_assigned(rthc)) { + rthc = mdbx_rthc_get(env->me_txkey); + if (unlikely(! rthc)) + return ENOMEM; + if (likely(rthc->rc_reader)) { r = rthc->rc_reader; mdb_assert(env, r->mr_pid == env->me_pid); mdb_assert(env, r->mr_tid == pthread_self()); @@ -4507,15 +4500,48 @@ mdb_env_open2(MDB_env *env, MDB_meta *meta) /****************************************************************************/ +static pthread_mutex_t mdbx_rthc_mutex = PTHREAD_MUTEX_INITIALIZER; +static MDBX_rthc *mdbx_rthc_list; +static pthread_key_t mdbx_pthread_crutch_key; +static pthread_cond_t mdbx_rthc_cond = PTHREAD_COND_INITIALIZER; + +extern void *__dso_handle __attribute__ ((__weak__)); +extern int __cxa_thread_atexit_impl(void (*dtor)(void*), void *obj, void *dso_symbol); + +static __inline +void mdbx_rthc_lock(void) { + mdb_ensure(NULL, pthread_mutex_lock(&mdbx_rthc_mutex) == 0); +} + +static __inline +void mdbx_rthc_unlock(void) { + mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_mutex) == 0); +} + +static __attribute__((constructor)) __cold +void mdbx_pthread_crutch_ctor(void) { + mdbx_pthread_crutch_key = -1; + mdb_ensure(NULL, pthread_key_create(&mdbx_pthread_crutch_key, NULL) == 0); +} + +static __attribute__((destructor)) __cold +void mdbx_pthread_crutch_dtor(void) +{ + mdbx_rthc_lock(); + while (mdbx_rthc_list != NULL) + mdb_ensure(NULL, pthread_cond_wait(&mdbx_rthc_cond, &mdbx_rthc_mutex) == 0); + + pthread_key_delete(mdbx_pthread_crutch_key); + mdbx_pthread_crutch_key = -1; +} + /** Release a reader thread's slot in the reader lock table. * This function is called automatically when a thread exits. * @param[in] ptr This points to the MDB_rthc of a slot in the reader lock table. */ static __cold -void mdb_env_reader_destr(void *ptr) +void mdbx_rthc_dtor(void *ptr) { - MDBX_rthc* rthc = ptr; - /* LY: Основная задача этого деструктора была и есть в освобождении * слота таблицы читателей при завершении треда, но тут есть пара * не очевидных сложностей: @@ -4528,81 +4554,114 @@ void mdb_env_reader_destr(void *ptr) * и завершающимися тредами. Грубо говоря, при старте mdb_env_close() * деструктор уже может выполняться в некоторых тредах, и завершиться * эти выполнения могут во время или после окончания mdb_env_close(). - * - БОЛЕЕ ТОГО, схожая проблема может возникнуть при выгрузке dso/dll, - * в случае если пользователь не вызвал mdb_env_close(), на что он - * имеет право. + * - БОЛЕЕ ТОГО, схожая проблема возникает при выгрузке dso/dll, + * так как в текущей glibc (2.24) подсистема ld.so ничего не знает о + * TSD-деструкторах и поэтому может выгрузить lib.so до того как + * отработали все деструкторы. * - Исходное проявление проблемы было зафиксировано * в https://github.com/ReOpen/ReOpenLDAP/issues/48 * * Решение посредством выделяемого динамически struct MDB_rthc было * не удачным, так как порождало либо утечку памяти, либо вероятностное * обращение к уже освобожденной памяти из этого деструктора. + * + * Текущее решение достаточно "развесисто" и решает все описанные выше + * проблемы ценой переносимости, но без пенальти по производительности. */ - mdbx_rthc_cleanup(rthc); -} + MDBX_rthc* head = ptr; + mdbx_rthc_lock(); + mdb_ensure(NULL, head == pthread_getspecific(mdbx_pthread_crutch_key)); + mdb_ensure(NULL, pthread_setspecific(mdbx_pthread_crutch_key, NULL) == 0); -static pthread_mutex_t mdbx_rthc_lock = PTHREAD_MUTEX_INITIALIZER; + pid_t pid = getpid(); + pthread_t thread = pthread_self(); + for (MDBX_rthc** ref = &mdbx_rthc_list; *ref; ) { + MDBX_rthc* rthc = *ref; + if (rthc->rc_thread == thread) { + if (rthc->rc_reader && rthc->rc_reader->mr_pid == pid) { + rthc->rc_reader->mr_pid = 0; + mdbx_coherent_barrier(); + } + *ref = rthc->rc_next; + free(rthc); + } else { + ref = &(*ref)->rc_next; + } + } -#define MDBX_RTHC_MAX 512 -static MDBX_rthc mdbx_rthc_tlb[MDBX_RTHC_MAX]; - -static MDBX_INLINE -int mdbx_rthc_assigned(MDBX_rthc* rthc) -{ - return rthc->rc_reader && rthc->rc_reader != (void*) rthc; + if (mdbx_rthc_list == NULL) + pthread_cond_broadcast(&mdbx_rthc_cond); + mdbx_rthc_unlock(); } static __cold -MDBX_rthc* mdbx_rthc_alloc(void) +MDBX_rthc* mdbx_rthc_add(pthread_key_t key) { - mdb_ensure(NULL, pthread_mutex_lock(&mdbx_rthc_lock) == 0); - for (size_t i = 0; i < MDBX_RTHC_MAX; ++i) { - if (mdbx_rthc_tlb[i].rc_reader == NULL) { - MDBX_rthc* rthc = mdbx_rthc_tlb + i; - rthc->rc_reader = (void *) rthc; - mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); - return rthc; - } + MDBX_rthc *rthc = malloc(sizeof(MDBX_rthc)); + if (unlikely(rthc == NULL)) + goto bailout; + + rthc->rc_next = NULL; + rthc->rc_reader = NULL; + rthc->rc_thread = pthread_self(); + if (unlikely(pthread_setspecific(key, rthc) != 0)) + goto bailout_free; + + mdbx_rthc_lock(); + if (pthread_getspecific(mdbx_pthread_crutch_key) == NULL) { + void *dso_anchor = (&__dso_handle && __dso_handle) + ? __dso_handle : (void *)mdb_version; + if (unlikely(__cxa_thread_atexit_impl(mdbx_rthc_dtor, rthc, dso_anchor) != 0)) + goto bailout_unlock; + mdb_ensure(NULL, pthread_setspecific(mdbx_pthread_crutch_key, rthc) == 0); } - mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); + rthc->rc_next = mdbx_rthc_list; + mdbx_rthc_list = rthc; + mdbx_rthc_unlock(); + return rthc; + +bailout_unlock: + mdbx_rthc_unlock(); +bailout_free: + free(rthc); +bailout: return NULL; } -static __cold -void mdbx_rthc_cleanup_locked(MDBX_rthc* rthc) +static __inline +MDBX_rthc* mdbx_rthc_get(pthread_key_t key) { - if (mdbx_rthc_assigned(rthc)) { - if (rthc->rc_reader->mr_pid == getpid()) { - rthc->rc_reader->mr_pid = 0; - mdbx_coherent_barrier(); + MDBX_rthc *rthc = pthread_getspecific(key); + if (likely(rthc != NULL)) + return rthc; + return mdbx_rthc_add(key); +} + +static __cold +void mdbx_rthc_cleanup(MDB_env *env) +{ + mdbx_rthc_lock(); + MDB_reader *begin = env->me_txns->mti_readers; + MDB_reader *end = begin + env->me_close_readers; + + for (MDBX_rthc** ref = &mdbx_rthc_list; *ref; ) { + MDBX_rthc* rthc = *ref; + if (rthc->rc_reader >= begin && rthc->rc_reader < end) { + if (rthc->rc_reader->mr_pid == env->me_pid) { + rthc->rc_reader->mr_pid = 0; + mdbx_coherent_barrier(); + } + *ref = rthc->rc_next; + free(rthc); + } else { + ref = &(*ref)->rc_next; } - rthc->rc_reader = NULL; } -} -static __cold -void mdbx_rthc_cleanup_env(MDB_env *env) -{ - mdb_ensure(env, pthread_mutex_lock(&mdbx_rthc_lock) == 0); - MDB_reader* begin = env->me_txns->mti_readers; - MDB_reader* end = env->me_txns->mti_readers + env->me_close_readers; - for (size_t i = 0; i < MDBX_RTHC_MAX; ++i) { - MDBX_rthc* rthc = mdbx_rthc_tlb + i; - if (rthc->rc_reader >= begin && rthc->rc_reader < end) - mdbx_rthc_cleanup_locked(rthc); - } - mdb_ensure(env, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); -} - -static __cold -void mdbx_rthc_cleanup(MDBX_rthc* rthc) -{ - if (rthc->rc_reader) { - mdb_ensure(NULL, pthread_mutex_lock(&mdbx_rthc_lock) == 0); - mdbx_rthc_cleanup_locked(rthc); - mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_lock) == 0); - } + if (mdbx_rthc_list == NULL) + pthread_cond_broadcast(&mdbx_rthc_cond); + mdbx_rthc_unlock(); } /****************************************************************************/ @@ -4776,7 +4835,7 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl) fcntl(env->me_lfd, F_SETFD, fdflags); if (!(env->me_flags & MDB_NOTLS)) { - rc = pthread_key_create(&env->me_txkey, mdb_env_reader_destr); + rc = pthread_key_create(&env->me_txkey, NULL); if (rc) return rc; env->me_flags |= MDB_ENV_TXKEY; @@ -5086,7 +5145,7 @@ mdb_env_close0(MDB_env *env) * our readers), and clear each reader atomically. */ if (env->me_pid == getpid()) - mdbx_rthc_cleanup_env(env); + mdbx_rthc_cleanup(env); munmap((void *)env->me_txns, (env->me_maxreaders-1)*sizeof(MDB_reader)+sizeof(MDB_txninfo)); env->me_txns = NULL; From 9c02fad4cd4e33b068a1c7cbb3f6ec9e48d21ec9 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sat, 7 Jan 2017 20:58:18 +0300 Subject: [PATCH 41/86] mdbx: support glibc < 2.18 for TLS cleanup on thread termination. One more for https://github.com/ReOpen/ReOpenLDAP/issues/48 against https://sourceware.org/bugzilla/show_bug.cgi?id=21031 and https://sourceware.org/bugzilla/show_bug.cgi?id=21032 It is impossible to completely fix this issue for glibc prior to 2.18, in such case this fix just minimizes the probability of a crash. Change-Id: If2317275928e8f6d96c2682df99989da03aedaaa --- mdb.c | 132 ++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 40 deletions(-) diff --git a/mdb.c b/mdb.c index 1d413ab7..c288e9db 100644 --- a/mdb.c +++ b/mdb.c @@ -1008,7 +1008,6 @@ typedef struct MDBX_rthc { } MDBX_rthc; static MDBX_rthc* mdbx_rthc_get(pthread_key_t key); -static void mdbx_rthc_cleanup(MDB_env *env); /** The database environment. */ struct MDB_env { @@ -4500,13 +4499,17 @@ mdb_env_open2(MDB_env *env, MDB_meta *meta) /****************************************************************************/ +#ifndef MDBX_USE_THREAD_ATEXIT +# if __GLIBC_PREREQ(2,18) +# define MDBX_USE_THREAD_ATEXIT 1 +# else +# define MDBX_USE_THREAD_ATEXIT 0 +# endif +#endif + static pthread_mutex_t mdbx_rthc_mutex = PTHREAD_MUTEX_INITIALIZER; static MDBX_rthc *mdbx_rthc_list; static pthread_key_t mdbx_pthread_crutch_key; -static pthread_cond_t mdbx_rthc_cond = PTHREAD_COND_INITIALIZER; - -extern void *__dso_handle __attribute__ ((__weak__)); -extern int __cxa_thread_atexit_impl(void (*dtor)(void*), void *obj, void *dso_symbol); static __inline void mdbx_rthc_lock(void) { @@ -4518,29 +4521,12 @@ void mdbx_rthc_unlock(void) { mdb_ensure(NULL, pthread_mutex_unlock(&mdbx_rthc_mutex) == 0); } -static __attribute__((constructor)) __cold -void mdbx_pthread_crutch_ctor(void) { - mdbx_pthread_crutch_key = -1; - mdb_ensure(NULL, pthread_key_create(&mdbx_pthread_crutch_key, NULL) == 0); -} - -static __attribute__((destructor)) __cold -void mdbx_pthread_crutch_dtor(void) -{ - mdbx_rthc_lock(); - while (mdbx_rthc_list != NULL) - mdb_ensure(NULL, pthread_cond_wait(&mdbx_rthc_cond, &mdbx_rthc_mutex) == 0); - - pthread_key_delete(mdbx_pthread_crutch_key); - mdbx_pthread_crutch_key = -1; -} - /** Release a reader thread's slot in the reader lock table. * This function is called automatically when a thread exits. * @param[in] ptr This points to the MDB_rthc of a slot in the reader lock table. */ static __cold -void mdbx_rthc_dtor(void *ptr) +void mdbx_rthc_dtor(void) { /* LY: Основная задача этого деструктора была и есть в освобождении * слота таблицы читателей при завершении треда, но тут есть пара @@ -4561,18 +4547,16 @@ void mdbx_rthc_dtor(void *ptr) * - Исходное проявление проблемы было зафиксировано * в https://github.com/ReOpen/ReOpenLDAP/issues/48 * - * Решение посредством выделяемого динамически struct MDB_rthc было - * не удачным, так как порождало либо утечку памяти, либо вероятностное - * обращение к уже освобожденной памяти из этого деструктора. + * Предыдущее решение посредством выделяемого динамически MDB_rthc + * было не удачным, так как порождало либо утечку памяти, + * либо вероятностное обращение к уже освобожденной памяти + * из этого деструктора. * - * Текущее решение достаточно "развесисто" и решает все описанные выше - * проблемы ценой переносимости, но без пенальти по производительности. + * Текущее решение достаточно "развесисто", но решает все описанные выше + * проблемы без пенальти по производительности. */ - MDBX_rthc* head = ptr; mdbx_rthc_lock(); - mdb_ensure(NULL, head == pthread_getspecific(mdbx_pthread_crutch_key)); - mdb_ensure(NULL, pthread_setspecific(mdbx_pthread_crutch_key, NULL) == 0); pid_t pid = getpid(); pthread_t thread = pthread_self(); @@ -4590,11 +4574,79 @@ void mdbx_rthc_dtor(void *ptr) } } - if (mdbx_rthc_list == NULL) - pthread_cond_broadcast(&mdbx_rthc_cond); mdbx_rthc_unlock(); } +#if MDBX_USE_THREAD_ATEXIT + +extern void *__dso_handle __attribute__ ((__weak__)); +extern int __cxa_thread_atexit_impl(void (*dtor)(void*), void *obj, void *dso_symbol); + +static __cold +void mdbx_rthc__thread_atexit(void *ptr) { + mdb_ensure(NULL, ptr == pthread_getspecific(mdbx_pthread_crutch_key)); + mdb_ensure(NULL, pthread_setspecific(mdbx_pthread_crutch_key, NULL) == 0); + mdbx_rthc_dtor(); +} + +static __attribute__((constructor)) __cold +void mdbx_pthread_crutch_ctor(void) { + mdb_ensure(NULL, pthread_key_create( + &mdbx_pthread_crutch_key, NULL) == 0); +} + +#else /* MDBX_USE_THREAD_ATEXIT */ + +static __cold +void mdbx_rthc__thread_key_dtor(void *ptr) { + (void) ptr; + if (mdbx_pthread_crutch_key != (pthread_key_t) -1) + mdbx_rthc_dtor(); +} + +static __attribute__((constructor)) __cold +void mdbx_pthread_crutch_ctor(void) { + mdb_ensure(NULL, pthread_key_create( + &mdbx_pthread_crutch_key, mdbx_rthc__thread_key_dtor) == 0); +} + +static __attribute__((destructor)) __cold +void mdbx_pthread_crutch_dtor(void) +{ + pthread_key_delete(mdbx_pthread_crutch_key); + mdbx_pthread_crutch_key = -1; + + /* LY: Из-за race condition в pthread_key_delete() + * деструкторы уже могли начать выполняться. + * Уступая квант времени сразу после удаления ключа + * мы даем им шанс завершиться. */ + pthread_yield(); + + mdbx_rthc_lock(); + pid_t pid = getpid(); + while (mdbx_rthc_list != NULL) { + MDBX_rthc* rthc = mdbx_rthc_list; + mdbx_rthc_list = mdbx_rthc_list->rc_next; + if (rthc->rc_reader && rthc->rc_reader->mr_pid == pid) { + rthc->rc_reader->mr_pid = 0; + mdbx_coherent_barrier(); + } + free(rthc); + + /* LY: Каждый неудаленный элемент списка - это один + * не отработавший деструктор и потенциальный + * шанс получить segfault после выгрузки lib.so + * Поэтому на каждой итерации уступаем квант времени, + * в надежде что деструкторы успеют отработать. */ + mdbx_rthc_unlock(); + pthread_yield(); + mdbx_rthc_lock(); + } + mdbx_rthc_unlock(); + pthread_yield(); +} +#endif /* MDBX_USE_THREAD_ATEXIT */ + static __cold MDBX_rthc* mdbx_rthc_add(pthread_key_t key) { @@ -4610,10 +4662,14 @@ MDBX_rthc* mdbx_rthc_add(pthread_key_t key) mdbx_rthc_lock(); if (pthread_getspecific(mdbx_pthread_crutch_key) == NULL) { +#if MDBX_USE_THREAD_ATEXIT void *dso_anchor = (&__dso_handle && __dso_handle) ? __dso_handle : (void *)mdb_version; - if (unlikely(__cxa_thread_atexit_impl(mdbx_rthc_dtor, rthc, dso_anchor) != 0)) - goto bailout_unlock; + if (unlikely(__cxa_thread_atexit_impl(mdbx_rthc__thread_atexit, rthc, dso_anchor) != 0)) { + mdbx_rthc_unlock(); + goto bailout_free; + } +#endif /* MDBX_USE_THREAD_ATEXIT */ mdb_ensure(NULL, pthread_setspecific(mdbx_pthread_crutch_key, rthc) == 0); } rthc->rc_next = mdbx_rthc_list; @@ -4621,8 +4677,6 @@ MDBX_rthc* mdbx_rthc_add(pthread_key_t key) mdbx_rthc_unlock(); return rthc; -bailout_unlock: - mdbx_rthc_unlock(); bailout_free: free(rthc); bailout: @@ -4642,9 +4696,9 @@ static __cold void mdbx_rthc_cleanup(MDB_env *env) { mdbx_rthc_lock(); + MDB_reader *begin = env->me_txns->mti_readers; MDB_reader *end = begin + env->me_close_readers; - for (MDBX_rthc** ref = &mdbx_rthc_list; *ref; ) { MDBX_rthc* rthc = *ref; if (rthc->rc_reader >= begin && rthc->rc_reader < end) { @@ -4659,8 +4713,6 @@ void mdbx_rthc_cleanup(MDB_env *env) } } - if (mdbx_rthc_list == NULL) - pthread_cond_broadcast(&mdbx_rthc_cond); mdbx_rthc_unlock(); } From 478b7f00d981b540a063e905d14b4ec18623db4c Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sat, 7 Jan 2017 21:04:00 +0300 Subject: [PATCH 42/86] mdbx: refine `make ci` target. Change-Id: I4b50ded5cf4b03b7a10951e7d7ae2e08fd05d81b --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dd782eee..03105a62 100644 --- a/Makefile +++ b/Makefile @@ -199,13 +199,15 @@ bench: bench-lmdb.txt bench-mdbx.txt endif ci-rule = ( CC=$$(which $1); if [ -n "$$CC" ]; then \ - echo -n "probe by $2 ($$CC): " && \ + echo -n "probe by $2 ($$(readlink -f $$(which $$CC))): " && \ $(MAKE) clean >$1.log 2>$1.err && \ $(MAKE) CC=$$(readlink -f $$CC) XCFLAGS="-UNDEBUG -DMDB_DEBUG=2" all check 1>$1.log 2>$1.err && echo "OK" \ || ( echo "FAILED"; cat $1.err >&2; exit 1 ); \ else echo "no $2 ($1) for probe"; fi; ) ci: - @if [ "$(CC)" != "gcc" ]; then \ + @if [ "$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which gcc || echo /bin/false))" -a \ + "$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which clang || echo /bin/false))" -a \ + "$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which icc || echo /bin/false))" ]; then \ $(call ci-rule,$(CC),default C compiler); \ fi @$(call ci-rule,gcc,GCC) From 1fa026f332edda9fa7159c4083cee3eab4210e48 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sat, 7 Jan 2017 21:08:42 +0300 Subject: [PATCH 43/86] mdbx: adds thread's cleanup test into mtest0. Change-Id: I542425e5df1fb97d15030a681bc0e5173cf3c902 --- mtest0.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/mtest0.c b/mtest0.c index efb583da..c09019f1 100644 --- a/mtest0.c +++ b/mtest0.c @@ -23,6 +23,8 @@ #include #include "mdbx.h" +#include + #define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) #define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) #define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ @@ -32,6 +34,18 @@ # define DBPATH "./testdb" #endif +void* thread_entry(void *ctx) +{ + MDB_env *env = ctx; + MDB_txn *txn; + int rc; + + E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); + mdb_txn_abort(txn); + + return NULL; +} + int main(int argc,char * argv[]) { int i = 0, j = 0, rc; @@ -60,7 +74,7 @@ int main(int argc,char * argv[]) } E(mdb_env_create(&env)); - E(mdb_env_set_maxreaders(env, 1)); + E(mdb_env_set_maxreaders(env, 42)); E(mdb_env_set_mapsize(env, 10485760)); E(stat("/proc/self/exe", &exe_stat)?errno:0); @@ -184,6 +198,11 @@ int main(int argc,char * argv[]) mdb_cursor_close(cur2); E(mdb_txn_commit(txn)); + for(i = 0; i < 41; ++i) { + pthread_t thread; + pthread_create(&thread, NULL, thread_entry, env); + } + printf("Restarting cursor outside txn\n"); E(mdb_txn_begin(env, NULL, 0, &txn)); E(mdb_cursor_open(txn, dbi, &cursor)); From 234faf977087b0e77c8664f1eece5c800fca7991 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Fri, 6 Jan 2017 19:48:58 +0000 Subject: [PATCH 44/86] mdbx: backport - ITS#8558 fix mdb_load with escaped plaintext. Change-Id: I8646e876190529812476bac28e244a8eb6202336 --- CHANGES | 3 +++ mdb_load.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 320c2393..1cd39955 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,9 @@ MDBX Add MDB_PREV_MULTIPLE Add error MDB_PROBLEM, replace some MDB_CORRUPTED +LMDB 0.9.20 Release Engineering + Fix mdb_load with escaped plaintext (ITS#8558) + LMDB 0.9.19 Release (2016/12/28) Fix mdb_env_cwalk cursor init (ITS#8424) Fix robust mutexes on Solaris 10/11 (ITS#8339) diff --git a/mdb_load.c b/mdb_load.c index 625ef02d..e2cddd53 100644 --- a/mdb_load.c +++ b/mdb_load.c @@ -252,7 +252,8 @@ badend: c2 += 2; } } else { - c1++; c2++; + /* copies are redundant when no escapes were used */ + *c1++ = *c2++; } } } else { From 488ee065953321d431c9bad68bfe7ae20958a759 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 8 Jan 2017 21:47:26 +0300 Subject: [PATCH 45/86] mdbx: adds -ffunction-sections for CFLAGS. Change-Id: I38e24e9424b75424ff12e57f3906282338a95989 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 03105a62..66d7e278 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ suffix ?= CC ?= gcc XCFLAGS ?= -DNDEBUG=1 -DMDB_DEBUG=0 -CFLAGS ?= -O2 -g3 -Wall -Werror -Wextra +CFLAGS ?= -O2 -g3 -Wall -Werror -Wextra -ffunction-sections CFLAGS += -std=gnu99 -pthread $(XCFLAGS) # LY: for ability to built with modern glibc, From 17b8feac5728768bba642b867aebbdf4d3a84a57 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 8 Jan 2017 21:48:00 +0300 Subject: [PATCH 46/86] mdbx: adds README.md Initial for https://github.com/ReOpen/libmdbx/issues/2 Change-Id: I8e2afc659a8874405a85456da9904612c5bf8089 --- README.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lmdb.h | 4 +- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..5625d363 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +libmdbx +====================================== +Extended LMDB, aka "Расширенная LMDB". + +*The Future will Positive. Всё будет хорошо.* + +[![Build Status](https://travis-ci.org/leo-yuriev/libmdbx.svg?branch=devel)](https://travis-ci.org/leo-yuriev/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/README.md). + +## Кратко +libmdbx является встраиваемым key-value движком хранения со специфическим набором возможностей, которые при правильном применении позволяют создавать уникальные решения с чемпионской производительностью. + +## История +libmdbx является форком [Symas Lightning Memory-Mapped Database](https://symas.com/products/lightning-memory-mapped-database/) (известной под аббревиатурой [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с рядом перечисленных ниже доработок. + +Изначально модификация производилась в составе исходного кода проекта [ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы внесенные изменения приобрели самостоятельную ценность вне контекста ReOpenLDAP. + +Осенью 2015 доработанный движок был выделен в отдельный проект, который был [представлен на конференции Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html). + +## Общие характеристики оригинальной LMDB и MDBX. +* Данные хранятся в упорядоченном отображении (ordered map), ключи всегда отсортированы, поддерживается выборка диапазонов (range lookups). +* Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID) посредством [MVCC](https://ru.wikipedia.org/wiki/MVCC) и [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). +* Чтение [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F), мьютексы захватываются только при старте и завершении сеанса работы с БД. +* Читатели не конкурируют между собой, чтение масштабируется линейно по ядрам CPU. +* Изменения строго последовательны и не блокируются чтением, конфликты между транзакциями не возможны. +* Амортизационная стоимость любой операции Olog(N), [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). +* Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала транзакций, после сбоев не требуется восстановление. +* Не требуется компактификация или какое-либо периодическое обслуживание. +* Эффективное хранение дубликатов (ключей с несколькими значениями) с сортировкой. +* Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t). +* Поддержка горячего резервного копирования. +* Файл БД отображается в память. К ключам и данным обеспечивается прямой доступ (без копирования), они не меняются до завершения транзакции чтения. +* Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё необходимое выполняет ядро ОС. + +## Недостатки и Компромиссы +1. Единовременно может выполняться не более одной транзакция изменения данных (один писатель). Зато все изменения всегда последовательны, не может быть конфликтов или ошибок при откате транзакций. +2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) обуславливает относительно большой [WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация изменений на диске относительно дорога и является главным ограничителем для производительности по записи. В качестве компромисса предлагается несколько режимов ленивой и/или периодической фиксации. В том числе режим `WRITEMAP`, при котором изменения происходят только в памяти и асинхронно фиксируются на диске ядром ОС. +3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8) для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на уровне страниц в [B+ дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE). Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, что расходует [пропускную способность оперативной памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным ограничителем производительности в режиме `WRITEMAP`. +4. Проблема долгих чтений (зависших читателей), см. ниже. +5. Вероятность разрушения БД в режиме `WRITEMAP`, см ниже. + +### Проблема долгих чтений + +Понимание проблемы требует некоторых пояснений, которые изложены ниже, но могут быть сложны для быстрого восприятия. Поэтому, тезисно: + +* Изменение данных на фоне долгой операции чтения может приводить к исчерпанию места в БД. +* После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` до завершения долгой операции чтения. +* Характерными примерами долгих чтений являются горячее резервное копирования и отладка клиентского приложения при активной транзакции чтения. +* В оригинальной LMDB после этого будет наблюдаться устойчивая деградация производительности всех механизмов обратной записи на диск (в I/O контроллере, в гипервизоре, в ядре ОС). +* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. + +Операции чтения выполняются в контексте снимка данных (версии БД), который был актуальным на момент старта транзакции чтения. Такой читаемый снимок поддерживается неизменным до завершения операции. В свою очередь, это не позволяет повторно использовать страницы БД в последующих версиях (снимках БД). + +Другими словами, если обновление данных выполняется на фоне долгой операции чтения, то вместо повторного использования "старых" ненужных страниц будут выделяться новые, так как "старые" страницы составляют снимок БД, который еще используется долгой операцией чтения. + +В результате, при интенсивном изменении данных и достаточно длительной операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит создавать новые снимки/версии БД. Такая ситуация будет сохраняться до завершения операции чтения, которая использует старый снимок данных и препятствует повторному использованию страниц БД. + +Однако, на этом проблемы не заканчиваются. После описанной ситуации, все дополнительные страницы, которые были выделены пока переработка старых была невозможна, будут участвовать в цикле выделения/освобождения до конца жизни экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение количества циркулирующий страниц, с точки зрения механизмов кэширования и/или обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша при всех последующих изменениях данных. + +Для решения описанных проблемы в MDBX сделаны существенные доработки, см. ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример количественной оценки прироста производительности за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в MDBX. + + +## Доработки MDBX + +1. Режим `LIFO RECLAIM`. Для повторного использования выбираются не самые старые, а самые новые страницы из доступных. За счет этого цикл использования страниц всегда имеет минимальную длину и не зависит от общего числа выделенных страниц. В результате механизмы кэширования и обратной записи работают с максимально +возможной эффективностью. В случае использования контроллера дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно многократное увеличение производительности по записи (обновлению данных). + +2. Обработчик `OOM-KICK`. Посредством `mdbx_env_set_oomfunc()` может быть установлен внешний обработчик (callback), который будет вызван при исчерпания свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID и pthread_id. +В свою очередь обработчик может предпринять одно из действий: +* отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом; +* отменить или перезапустить проблемную операцию чтения, если операция выполняется одним из потоков текущего процесса; +* подождать некоторое время, в расчете что проблемная операция чтения будет штатно завершена; +* перервать текущую операцию изменения данных с возвратом кода ошибки. + +3. Гарантия сохранности БД в режиме `WRITEMAP`. +При работе в режиме `WRITEMAP` запись измененных страниц +выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС сохранит все изменения. +Однако, при аварийном отключении питания или сбоя в ядре ОС, на диске будет сохранена только часть измененных страниц БД. При этом с большой вероятностью может оказаться так, что будут сохранены мета-страницы с указателям на станицы с новыми версиями данных, но не сами данные. В этом случае БД будет безвозвратна разрушена, даже если до аварии производилась полная синхронизация данных (посредством `mdb_env_sync()`). +В MDBX эта проблема решена, на текущий момент как минимум частично. При завершении транзакций MDBX помечает точки фиксации как сильные (strong), либо как слабые (weak). Так в режиме `WRITEMAP` завершаемые транзакции помечаются как слабые, а при явной синхронизации данных как сильные. При открытии БД выполняется автоматический откат к последней сильной фиксации. Этим обеспечивается гарантия сохранности БД. +К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности данных, страницы формирующие крайний снимок с сильной фиксацией, не должны повторно использоваться (перезаписываться) до формирования следующей сильной точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше эффект "долгого чтения", с разницей в том, что при исчерпании свободных страниц автоматически будет сформирована новая точка сильной фиксации. +В последующих версиях MDBX будут предусмотрены средства для асинхронной записи данных на диск с формированием сильных точек фиксации. + +4. Возможность автоматического формирования контрольных точек (сброса данных на диск) при накоплении заданного объёма изменений, устанавливаемого функцией `mdbx_env_set_syncbytes()`. + +5. Возможность получить отставание текущей транзакции чтения от последней версии данных в БД посредством `mdbx_txn_straggler()`. + +6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для обхода всех страниц БД. + +7. Управление отладкой и получение отладочных сообщений посредством `mdbx_setup_debug()`. + +8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения посредством `mdbx_canary_get()`. + +9. Возможность узнать есть ли за текущей позицией курсора строка данных посредством `mdbx_cursor_eof()`. + +10. Возможность явно запросить обновление существующей записи, без создания новой посредством флажка `MDB_CURRENT` для `mdb_put()`. + +11. Возможность обновить или удалить запись с получением предыдущего значения данных посредством `mdbx_replace()`. + +12. Поддержка ключей нулевого размера. + +13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное количество дубликатов для всех типов таблиц и любого положения курсора. + +14. Возможность открыть БД в эксклюзивном режиме посредством `mdbx_env_open_ex()`, например в целях её проверки. + +15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. + +16. Возможность получить посредством `mdbx_env_info()` дополнительную информацию, включая номер самой старой версии БД (снимка данных), который используется одним из читателей. diff --git a/lmdb.h b/lmdb.h index 39275b6d..701f4b46 100644 --- a/lmdb.h +++ b/lmdb.h @@ -1,7 +1,7 @@ /** @file lmdb.h - * @brief Reliable Lightning memory-mapped database library + * @brief Extended Lightning memory-mapped database library * - * @mainpage Reliable Lightning Memory-Mapped Database Manager (MDBX) + * @mainpage Extended Lightning Memory-Mapped Database Manager (MDBX) * * @section intro_sec Introduction * MDBX is a Btree-based database management library modeled loosely on the From 81861084fc82461cfa50c23d0c8e295b30e175f3 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 9 Jan 2017 01:19:24 +0300 Subject: [PATCH 47/86] mdbx: cosmetics for README.md Change-Id: I02eca38563bb26f67a434dfbecaab3d066dd3d6d --- README.md | 274 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 212 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 5625d363..48a6bcf8 100644 --- a/README.md +++ b/README.md @@ -4,105 +4,255 @@ Extended LMDB, aka "Расширенная LMDB". *The Future will Positive. Всё будет хорошо.* -[![Build Status](https://travis-ci.org/leo-yuriev/libmdbx.svg?branch=devel)](https://travis-ci.org/leo-yuriev/libmdbx) +[![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/README.md). +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). ## Кратко -libmdbx является встраиваемым key-value движком хранения со специфическим набором возможностей, которые при правильном применении позволяют создавать уникальные решения с чемпионской производительностью. +libmdbx - это встраиваемый key-value движок хранения со специфическим +набором возможностей, которые при правильном применении позволяют создавать +уникальные решения с чемпионской производительностью. -## История -libmdbx является форком [Symas Lightning Memory-Mapped Database](https://symas.com/products/lightning-memory-mapped-database/) (известной под аббревиатурой [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с рядом перечисленных ниже доработок. +libmdbx является форком [Symas Lightning Memory-Mapped +Database](https://symas.com/products/lightning-memory-mapped-database/) +(известной под аббревиатурой +[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с +рядом существенных доработок, которые перечислены ниже. -Изначально модификация производилась в составе исходного кода проекта [ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы внесенные изменения приобрели самостоятельную ценность вне контекста ReOpenLDAP. +Изначально модификация производилась в составе исходного кода проекта +[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы +внесенные изменения приобрели самостоятельную ценность вне контекста +ReOpenLDAP. -Осенью 2015 доработанный движок был выделен в отдельный проект, который был [представлен на конференции Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html). +Осенью 2015 доработанный движок был выделен в отдельный проект, который был +[представлен на конференции Highload++ +2015](http://www.highload.ru/2015/abstracts/1831.html). + + +## Характеристики и ключевые особенности + +### Общее для оригинальной LMDB и MDBX + +* Данные хранятся в упорядоченном отображении (ordered map), ключи всегда + отсортированы, поддерживается выборка диапазонов (range lookups). + +* Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID), посредством + [MVCC](https://ru.wikipedia.org/wiki/MVCC) + и [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). + +* Чтение [без + блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), + без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). + Мьютексы захватываются только при старте и завершении сеанса работы с БД. -## Общие характеристики оригинальной LMDB и MDBX. -* Данные хранятся в упорядоченном отображении (ordered map), ключи всегда отсортированы, поддерживается выборка диапазонов (range lookups). -* Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID) посредством [MVCC](https://ru.wikipedia.org/wiki/MVCC) и [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). -* Чтение [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F), мьютексы захватываются только при старте и завершении сеанса работы с БД. * Читатели не конкурируют между собой, чтение масштабируется линейно по ядрам CPU. -* Изменения строго последовательны и не блокируются чтением, конфликты между транзакциями не возможны. -* Амортизационная стоимость любой операции Olog(N), [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). -* Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала транзакций, после сбоев не требуется восстановление. -* Не требуется компактификация или какое-либо периодическое обслуживание. -* Эффективное хранение дубликатов (ключей с несколькими значениями) с сортировкой. -* Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t). -* Поддержка горячего резервного копирования. -* Файл БД отображается в память. К ключам и данным обеспечивается прямой доступ (без копирования), они не меняются до завершения транзакции чтения. -* Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё необходимое выполняет ядро ОС. -## Недостатки и Компромиссы -1. Единовременно может выполняться не более одной транзакция изменения данных (один писатель). Зато все изменения всегда последовательны, не может быть конфликтов или ошибок при откате транзакций. -2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) обуславливает относительно большой [WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация изменений на диске относительно дорога и является главным ограничителем для производительности по записи. В качестве компромисса предлагается несколько режимов ленивой и/или периодической фиксации. В том числе режим `WRITEMAP`, при котором изменения происходят только в памяти и асинхронно фиксируются на диске ядром ОС. -3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8) для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на уровне страниц в [B+ дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE). Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, что расходует [пропускную способность оперативной памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным ограничителем производительности в режиме `WRITEMAP`. +* Изменения строго последовательны и не блокируются чтением, конфликты между + транзакциями не возможны. + +* Амортизационная стоимость любой операции Olog(N), + [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). + +* Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала + транзакций, после сбоев не требуется восстановление. + +* Не требуется компактификация или какое-либо периодическое обслуживание. + +* Эффективное хранение дубликатов (ключей с несколькими значениями) с + сортировкой значений. + +* Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t). + +* Поддержка горячего резервного копирования. + +* Файл БД отображается в память кажлого процесса, который работает с БД. К + ключам и данным обеспечивается прямой доступ (без копирования), они не + меняются до завершения транзакции чтения. + +* Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё + необходимое выполняет ядро ОС. + + +### Недостатки и Компромиссы + +1. Единовременно может выполняться не более одной транзакция изменения данных + (один писатель). Зато все изменения всегда последовательны, не может быть + конфликтов или ошибок при откате транзакций. + +2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) + обуславливает относительно большой + [WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация + изменений на диске относительно дорога и является главным ограничителем для + производительности по записи. В качестве компромисса предлагается несколько + режимов ленивой и/или периодической фиксации. В том числе режим `WRITEMAP`, + при котором изменения происходят только в памяти и асинхронно фиксируются на + диске ядром ОС. + +3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8) + для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на + уровне страниц в [B+ дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE). + Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, + что расходует [пропускную способность оперативной + памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным + ограничителем производительности в режиме `WRITEMAP`. + 4. Проблема долгих чтений (зависших читателей), см. ниже. + 5. Вероятность разрушения БД в режиме `WRITEMAP`, см ниже. -### Проблема долгих чтений -Понимание проблемы требует некоторых пояснений, которые изложены ниже, но могут быть сложны для быстрого восприятия. Поэтому, тезисно: +#### Проблема долгих чтений -* Изменение данных на фоне долгой операции чтения может приводить к исчерпанию места в БД. -* После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` до завершения долгой операции чтения. -* Характерными примерами долгих чтений являются горячее резервное копирования и отладка клиентского приложения при активной транзакции чтения. -* В оригинальной LMDB после этого будет наблюдаться устойчивая деградация производительности всех механизмов обратной записи на диск (в I/O контроллере, в гипервизоре, в ядре ОС). -* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. +Понимание проблемы требует некоторых пояснений, которые изложены ниже, но +могут быть сложны для быстрого восприятия. Поэтому, тезисно: -Операции чтения выполняются в контексте снимка данных (версии БД), который был актуальным на момент старта транзакции чтения. Такой читаемый снимок поддерживается неизменным до завершения операции. В свою очередь, это не позволяет повторно использовать страницы БД в последующих версиях (снимках БД). +* Изменение данных на фоне долгой операции чтения может приводить к исчерпанию + места в БД. -Другими словами, если обновление данных выполняется на фоне долгой операции чтения, то вместо повторного использования "старых" ненужных страниц будут выделяться новые, так как "старые" страницы составляют снимок БД, который еще используется долгой операцией чтения. +* После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` + до завершения долгой операции чтения. -В результате, при интенсивном изменении данных и достаточно длительной операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит создавать новые снимки/версии БД. Такая ситуация будет сохраняться до завершения операции чтения, которая использует старый снимок данных и препятствует повторному использованию страниц БД. +* Характерными примерами долгих чтений являются горячее резервное копирования + и отладка клиентского приложения при активной транзакции чтения. -Однако, на этом проблемы не заканчиваются. После описанной ситуации, все дополнительные страницы, которые были выделены пока переработка старых была невозможна, будут участвовать в цикле выделения/освобождения до конца жизни экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение количества циркулирующий страниц, с точки зрения механизмов кэширования и/или обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша при всех последующих изменениях данных. +* В оригинальной LMDB после этого будет наблюдаться устойчивая деградация + производительности всех механизмов обратной записи на диск (в I/O контроллере, + в гипервизоре, в ядре ОС). -Для решения описанных проблемы в MDBX сделаны существенные доработки, см. ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример количественной оценки прироста производительности за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в MDBX. +* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также + режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. + +Операции чтения выполняются в контексте снимка данных (версии БД), который был +актуальным на момент старта транзакции чтения. Такой читаемый снимок +поддерживается неизменным до завершения операции. В свою очередь, это не +позволяет повторно использовать страницы БД в последующих версиях (снимках +БД). + +Другими словами, если обновление данных выполняется на фоне долгой операции +чтения, то вместо повторного использования "старых" ненужных страниц будут +выделяться новые, так как "старые" страницы составляют снимок БД, который еще +используется долгой операцией чтения. + +В результате, при интенсивном изменении данных и достаточно длительной +операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит +создавать новые снимки/версии БД. Такая ситуация будет сохраняться до +завершения операции чтения, которая использует старый снимок данных и +препятствует повторному использованию страниц БД. + +Однако, на этом проблемы не заканчиваются. После описанной ситуации, все +дополнительные страницы, которые были выделены пока переработка старых была +невозможна, будут участвовать в цикле выделения/освобождения до конца жизни +экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по +принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение +количества циркулирующий страниц, с точки зрения механизмов кэширования и/или +обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, +однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому +эффекту вымывания I/O кэша при всех последующих изменениях данных. + +Для решения описанных проблемы в MDBX сделаны существенные доработки, см. +ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах +презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример +количественной оценки прироста производительности за счет эффективной работы +[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в +MDBX. ## Доработки MDBX -1. Режим `LIFO RECLAIM`. Для повторного использования выбираются не самые старые, а самые новые страницы из доступных. За счет этого цикл использования страниц всегда имеет минимальную длину и не зависит от общего числа выделенных страниц. В результате механизмы кэширования и обратной записи работают с максимально -возможной эффективностью. В случае использования контроллера дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно многократное увеличение производительности по записи (обновлению данных). +1. Режим `LIFO RECLAIM`. -2. Обработчик `OOM-KICK`. Посредством `mdbx_env_set_oomfunc()` может быть установлен внешний обработчик (callback), который будет вызван при исчерпания свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID и pthread_id. -В свою очередь обработчик может предпринять одно из действий: -* отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом; -* отменить или перезапустить проблемную операцию чтения, если операция выполняется одним из потоков текущего процесса; -* подождать некоторое время, в расчете что проблемная операция чтения будет штатно завершена; -* перервать текущую операцию изменения данных с возвратом кода ошибки. + Для повторного использования выбираются не самые старые, а самые новые + страницы из доступных. За счет этого цикл использования страниц всегда + имеет минимальную длину и не зависит от общего числа выделенных страниц. + + В результате механизмы кэширования и обратной записи работают с + максимально возможной эффективностью. В случае использования контроллера + дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) + возможно многократное увеличение производительности по записи + (обновлению данных). + +2. Обработчик `OOM-KICK`. + + Посредством `mdbx_env_set_oomfunc()` может быть + установлен внешний обработчик (callback), который будет вызван при исчерпания + свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID + и pthread_id. В свою очередь обработчик может предпринять одно из действий: + + * отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом; + * отменить или перезапустить проблемную операцию чтения, если операция + выполняется одним из потоков текущего процесса; + * подождать некоторое время, в расчете что проблемная операция чтения будет + штатно завершена; + * перервать текущую операцию изменения данных с возвратом кода ошибки. 3. Гарантия сохранности БД в режиме `WRITEMAP`. -При работе в режиме `WRITEMAP` запись измененных страниц -выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС сохранит все изменения. -Однако, при аварийном отключении питания или сбоя в ядре ОС, на диске будет сохранена только часть измененных страниц БД. При этом с большой вероятностью может оказаться так, что будут сохранены мета-страницы с указателям на станицы с новыми версиями данных, но не сами данные. В этом случае БД будет безвозвратна разрушена, даже если до аварии производилась полная синхронизация данных (посредством `mdb_env_sync()`). -В MDBX эта проблема решена, на текущий момент как минимум частично. При завершении транзакций MDBX помечает точки фиксации как сильные (strong), либо как слабые (weak). Так в режиме `WRITEMAP` завершаемые транзакции помечаются как слабые, а при явной синхронизации данных как сильные. При открытии БД выполняется автоматический откат к последней сильной фиксации. Этим обеспечивается гарантия сохранности БД. -К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности данных, страницы формирующие крайний снимок с сильной фиксацией, не должны повторно использоваться (перезаписываться) до формирования следующей сильной точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше эффект "долгого чтения", с разницей в том, что при исчерпании свободных страниц автоматически будет сформирована новая точка сильной фиксации. -В последующих версиях MDBX будут предусмотрены средства для асинхронной записи данных на диск с формированием сильных точек фиксации. -4. Возможность автоматического формирования контрольных точек (сброса данных на диск) при накоплении заданного объёма изменений, устанавливаемого функцией `mdbx_env_set_syncbytes()`. + При работе в режиме `WRITEMAP` запись измененных страниц выполняется ядром ОС, + что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС + сохранит все изменения. -5. Возможность получить отставание текущей транзакции чтения от последней версии данных в БД посредством `mdbx_txn_straggler()`. + Однако, при аварийном отключении питания или сбоя в ядре ОС, на диске будет + сохранена только часть измененных страниц БД. При этом с большой вероятностью + может оказаться так, что будут сохранены мета-страницы с указателям на станицы + с новыми версиями данных, но не сами данные. В этом случае БД будет + безвозвратна разрушена, даже если до аварии производилась полная синхронизация + данных (посредством `mdb_env_sync()`). -6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для обхода всех страниц БД. + В MDBX эта проблема решена, на текущий момент как минимум частично. При + завершении транзакций MDBX помечает точки фиксации как сильные (strong), либо + как слабые (weak). Так в режиме `WRITEMAP` завершаемые транзакции помечаются + как слабые, а при явной синхронизации данных как сильные. При открытии БД + выполняется автоматический откат к последней сильной фиксации. Этим + обеспечивается гарантия сохранности БД. -7. Управление отладкой и получение отладочных сообщений посредством `mdbx_setup_debug()`. + К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности + данных, страницы формирующие крайний снимок с сильной фиксацией, не должны + повторно использоваться (перезаписываться) до формирования следующей сильной + точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше + эффект "долгого чтения", с разницей в том, что при исчерпании свободных + страниц автоматически будет сформирована новая точка сильной фиксации. -8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения посредством `mdbx_canary_get()`. + В последующих версиях MDBX будут предусмотрены средства для асинхронной записи + данных на диск с формированием сильных точек фиксации. -9. Возможность узнать есть ли за текущей позицией курсора строка данных посредством `mdbx_cursor_eof()`. +4. Возможность автоматического формирования контрольных точек (сброса данных +на диск) при накоплении заданного объёма изменений, устанавливаемого функцией +`mdbx_env_set_syncbytes()`. -10. Возможность явно запросить обновление существующей записи, без создания новой посредством флажка `MDB_CURRENT` для `mdb_put()`. +5. Возможность получить отставание текущей транзакции чтения от последней +версии данных в БД посредством `mdbx_txn_straggler()`. -11. Возможность обновить или удалить запись с получением предыдущего значения данных посредством `mdbx_replace()`. +6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для обхода +всех страниц БД. + +7. Управление отладкой и получение отладочных сообщений посредством +`mdbx_setup_debug()`. + +8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных +маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения +посредством `mdbx_canary_get()`. + +9. Возможность узнать есть ли за текущей позицией курсора строка данных +посредством `mdbx_cursor_eof()`. + +10. Возможность явно запросить обновление существующей записи, без создания +новой посредством флажка `MDB_CURRENT` для `mdb_put()`. + +11. Возможность обновить или удалить запись с получением предыдущего значения +данных посредством `mdbx_replace()`. 12. Поддержка ключей нулевого размера. -13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное количество дубликатов для всех типов таблиц и любого положения курсора. +13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное +количество дубликатов для всех типов таблиц и любого положения курсора. -14. Возможность открыть БД в эксклюзивном режиме посредством `mdbx_env_open_ex()`, например в целях её проверки. +14. Возможность открыть БД в эксклюзивном режиме посредством +`mdbx_env_open_ex()`, например в целях её проверки. -15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. +15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и +формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. -16. Возможность получить посредством `mdbx_env_info()` дополнительную информацию, включая номер самой старой версии БД (снимка данных), который используется одним из читателей. +16. Возможность получить посредством `mdbx_env_info()` дополнительную +информацию, включая номер самой старой версии БД (снимка данных), который +используется одним из читателей. From c96cc9c567162e3ef10959450f16b838b2178d84 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 9 Jan 2017 02:04:31 +0300 Subject: [PATCH 48/86] mdbx: minor refine README.md Change-Id: I7e2253fc83240b7653eac1690493dcd3ee5ebe97 --- README.md | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 48a6bcf8..930699ca 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,7 @@ Database](https://symas.com/products/lightning-memory-mapped-database/) Изначально модификация производилась в составе исходного кода проекта [ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы -внесенные изменения приобрели самостоятельную ценность вне контекста -ReOpenLDAP. +внесенные изменения приобрели самостоятельную ценность. Осенью 2015 доработанный движок был выделен в отдельный проект, который был [представлен на конференции Highload++ @@ -192,19 +191,28 @@ MDBX. что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС сохранит все изменения. - Однако, при аварийном отключении питания или сбоя в ядре ОС, на диске будет + Однако, при аварийном отключении питания или сбое в ядре ОС, на диске будет сохранена только часть измененных страниц БД. При этом с большой вероятностью - может оказаться так, что будут сохранены мета-страницы с указателям на станицы - с новыми версиями данных, но не сами данные. В этом случае БД будет + может оказаться так, что будут сохранены мета-страницы со ссылками на страницы + с новыми версиями данных, но не сами новые данные. В этом случае БД будет безвозвратна разрушена, даже если до аварии производилась полная синхронизация данных (посредством `mdb_env_sync()`). - В MDBX эта проблема решена, на текущий момент как минимум частично. При - завершении транзакций MDBX помечает точки фиксации как сильные (strong), либо - как слабые (weak). Так в режиме `WRITEMAP` завершаемые транзакции помечаются - как слабые, а при явной синхронизации данных как сильные. При открытии БД - выполняется автоматический откат к последней сильной фиксации. Этим - обеспечивается гарантия сохранности БД. + В MDBX эта проблема решена путем полной переработки пути записи данных: + + * В режиме `WRITEMAP` MDBX не обновляет мета-страницы непосредственно, + а поддерживает их теневые копии с переносом изменений после фиксации + данных. + + * При завершении транзакций, в зависимости от состояния + синхронности данных между диском и оперативной память, MDBX помечает + точки фиксации либо как сильные (strong), либо как слабые (weak). Так + например, в режиме `WRITEMAP` завершаемые транзакции помечаются как + слабые, а при явной синхронизации данных как сильные. + + * При открытии БД + выполняется автоматический откат к последней сильной фиксации. Этим + обеспечивается гарантия сохранности БД. К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности данных, страницы формирующие крайний снимок с сильной фиксацией, не должны From 97e1d9b6850dd15ada332b6a224427df6c1d018c Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 12 Jan 2017 21:41:52 +0300 Subject: [PATCH 49/86] mdbx: fix mdb_cursor_last (ITS#8557). This is a port of http://www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=commit;h=d84dee516fa4cca41b5234e95c2105eb4737dfb3 HYC: Optimize mdb_page_search_root(PS_LAST) when cursor is already near last position, ignoring C_EOF flag for now. LY: Fixed C_EOF check. Don't ignore it, otherwise in some cases we got a "MDB_PAGE_NOTFOUND", instead of just "MDB_NOTFOUND". Change-Id: I2edbf6b64403abfa830a2fcb84162125634a85d0 --- CHANGES | 1 + mdb.c | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 1cd39955..f7146039 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ MDBX LMDB 0.9.20 Release Engineering Fix mdb_load with escaped plaintext (ITS#8558) + Fix mdb_cursor_last / mdb_put interaction (ITS#8557) LMDB 0.9.19 Release (2016/12/28) Fix mdb_env_cwalk cursor init (ITS#8424) diff --git a/mdb.c b/mdb.c index 16f8b691..c130d1d8 100644 --- a/mdb.c +++ b/mdb.c @@ -5636,8 +5636,17 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) if (flags & (MDB_PS_FIRST|MDB_PS_LAST)) { i = 0; - if (flags & MDB_PS_LAST) + if (flags & MDB_PS_LAST) { i = NUMKEYS(mp) - 1; + /* if already init'd, see if we're already in right place */ + if (mc->mc_flags & C_INITIALIZED) { + if (mc->mc_ki[mc->mc_top] == i) { + mp = mc->mc_pg[mc->mc_top]; + mc->mc_top = mc->mc_snum++; + goto ready; + } + } + } } else { int exact; node = mdb_node_search(mc, key, &exact); @@ -5663,6 +5672,7 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) if (unlikely(rc = mdb_cursor_push(mc, mp))) return rc; +ready: if (flags & MDB_PS_MODIFY) { if (unlikely((rc = mdb_page_touch(mc)) != 0)) return rc; @@ -6405,15 +6415,14 @@ mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data) mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF); if (likely(!(mc->mc_flags & C_EOF))) { - if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) { rc = mdb_page_search(mc, NULL, MDB_PS_LAST); if (unlikely(rc != MDB_SUCCESS)) return rc; } mdb_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); - } + mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]) - 1; mc->mc_flags |= C_INITIALIZED|C_EOF; leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); From 460eb64a6fb7819e7c469ae9239a2d6c75769e54 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Wed, 11 Jan 2017 10:33:28 +0000 Subject: [PATCH 50/86] mdbx: backport - Tweak cursor_next C_EOF check. Squash of relevant commits. HYC: Allow C_EOF flag to be stale. LY: Fixed parenthesis arrors. Change-Id: I5a26498763358a08f98481cacf582c881d59393d --- mdb.c | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/mdb.c b/mdb.c index c130d1d8..8cac1718 100644 --- a/mdb.c +++ b/mdb.c @@ -5641,8 +5641,8 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) /* if already init'd, see if we're already in right place */ if (mc->mc_flags & C_INITIALIZED) { if (mc->mc_ki[mc->mc_top] == i) { - mp = mc->mc_pg[mc->mc_top]; mc->mc_top = mc->mc_snum++; + mp = mc->mc_pg[mc->mc_top]; goto ready; } } @@ -6005,15 +6005,20 @@ mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op) MDB_node *leaf; int rc; - if ((mc->mc_flags & C_EOF) || - ((mc->mc_flags & C_DEL) && op == MDB_NEXT_DUP)) { + if ((mc->mc_flags & C_DEL) && op == MDB_NEXT_DUP) return MDB_NOTFOUND; - } + if (!(mc->mc_flags & C_INITIALIZED)) return mdb_cursor_first(mc, key, data); mp = mc->mc_pg[mc->mc_top]; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)-1) + return MDB_NOTFOUND; + mc->mc_flags ^= C_EOF; + } + if (mc->mc_db->md_flags & MDB_DUPSORT) { leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { @@ -6532,9 +6537,16 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, break; } rc = MDB_SUCCESS; - if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) || - (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF)) + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) break; + if (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF) { + MDB_cursor *mx = &mc->mc_xcursor->mx_cursor; + if (mx->mc_ki[mx->mc_top] >= NUMKEYS(mx->mc_pg[mx->mc_top])-1) { + rc = MDB_NOTFOUND; + break; + } + mx->mc_flags ^= C_EOF; + } goto fetchm; case MDB_NEXT_MULTIPLE: if (unlikely(data == NULL)) { @@ -7878,14 +7890,23 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) return EINVAL; #if MDBX_MODE_ENABLED - MDB_page *mp = mc->mc_pg[mc->mc_top]; - int nkeys = NUMKEYS(mp); - if (!nkeys || mc->mc_ki[mc->mc_top] >= nkeys) { + if (!mc->mc_snum) { *countp = 0; return MDB_NOTFOUND; - } else if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { + } + + MDB_page *mp = mc->mc_pg[mc->mc_top]; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) { + *countp = 0; + return MDB_NOTFOUND; + } + mc->mc_flags ^= C_EOF; + } + + if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { *countp = 1; - } else { + } else { MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) *countp = 1; @@ -7895,12 +7916,18 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) *countp = mc->mc_xcursor->mx_db.md_entries; } #else - if (unlikely(mc->mc_xcursor == NULL)) + if (unlikely(mc->mc_xcursor == NULL)) return MDB_INCOMPATIBLE; - if (unlikely(!mc->mc_snum || (mc->mc_flags & C_EOF))) + if (!mc->mc_snum) return MDB_NOTFOUND; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) + return MDB_NOTFOUND; + mc->mc_flags ^= C_EOF; + } + MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { *countp = 1; From 71ae2aba8dfbffc046f4f8c9c64d29636045c992 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 12 Jan 2017 22:36:52 +0300 Subject: [PATCH 51/86] mdbx: refine mdbx_cursor_eof(). Change-Id: I786c5f2eedb273f44fd2ef5065d200f63dfec84b --- mdbx.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mdbx.c b/mdbx.c index d2b14f51..ff2ba67a 100644 --- a/mdbx.c +++ b/mdbx.c @@ -366,7 +366,17 @@ int mdbx_cursor_eof(MDB_cursor *mc) if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; - return (mc->mc_flags & C_INITIALIZED) ? 0 : 1; + if ((mc->mc_flags & C_INITIALIZED) == 0) + return 1; + + if (mc->mc_snum == 0) + return 1; + + if ((mc->mc_flags & C_EOF) + && mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) + return 1; + + return 0; } static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) { From ad7113419bae7b5fecc7649b8cddd08e00a23a75 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 13 Jan 2017 00:53:22 +0300 Subject: [PATCH 52/86] mdbx: CHANGES for glibc bugs #21031 and #21032. Change-Id: I621a68161dc4f47ed30c3557f19e26a4e1db42a1 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index f7146039..93486855 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ MDBX Add MDB_PREV_MULTIPLE Add error MDB_PROBLEM, replace some MDB_CORRUPTED + Workarounds for glibc bugs: #21031 and 21032. LMDB 0.9.20 Release Engineering Fix mdb_load with escaped plaintext (ITS#8558) From c4142c9a351b61f0b287a8020de3c97fa894eca1 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 18 Jan 2017 16:40:56 +0300 Subject: [PATCH 53/86] mdbx: refine README. --- README.md | 399 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 232 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index 930699ca..d11e1ad7 100644 --- a/README.md +++ b/README.md @@ -6,71 +6,81 @@ Extended LMDB, aka "Расширенная LMDB". [![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&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel). + ## Кратко -libmdbx - это встраиваемый key-value движок хранения со специфическим -набором возможностей, которые при правильном применении позволяют создавать -уникальные решения с чемпионской производительностью. +_libmdbx_ - это встраиваемый key-value движок хранения со +специфическим набором возможностей, которые при правильном +применении позволяют создавать уникальные решения с чемпионской +производительностью, идеально сочетаясь с технологией +[MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory). -libmdbx является форком [Symas Lightning Memory-Mapped -Database](https://symas.com/products/lightning-memory-mapped-database/) -(известной под аббревиатурой -[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с -рядом существенных доработок, которые перечислены ниже. +_libmdbx_ обновляет совместно используемый набор данных, никак +не мешая при этом параллельным операциям чтения, не применяя +атомарных операций к самим данным, и обеспечивая +согласованность при аварийной остановке в любой момент. Поэтому +_libmdbx_ позволяя строить системы с линейным масштабированием +производительности чтения/поиска по ядрам CPU и амортизационной +стоимостью любых операций Olog(N). -Изначально модификация производилась в составе исходного кода проекта -[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы -внесенные изменения приобрели самостоятельную ценность. - -Осенью 2015 доработанный движок был выделен в отдельный проект, который был -[представлен на конференции Highload++ -2015](http://www.highload.ru/2015/abstracts/1831.html). +### История +_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). -## Характеристики и ключевые особенности +Характеристики и ключевые особенности +===================================== -### Общее для оригинальной LMDB и MDBX +_libmdbx_ наследует все ключевые возможности и особенности от +своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), +с устранением описанных далее проблем и архитектурных недочетов. -* Данные хранятся в упорядоченном отображении (ordered map), ключи всегда - отсортированы, поддерживается выборка диапазонов (range lookups). +### Общее для оригинальной _LMDB_ и _libmdbx_ -* Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID), посредством - [MVCC](https://ru.wikipedia.org/wiki/MVCC) - и [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). +1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда + отсортированы, поддерживается выборка диапазонов (range lookups). -* Чтение [без - блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), - без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). - Мьютексы захватываются только при старте и завершении сеанса работы с БД. +2. Данные отображается в память каждого работающего с БД процесса. + Ключам и данным обеспечивается прямой доступ без необходимости их + копирования, так как они защищены транзакцией чтения и не изменяются. -* Читатели не конкурируют между собой, чтение масштабируется линейно по ядрам CPU. +3. Транзакции согласно + [ACID](https://ru.wikipedia.org/wiki/ACID), посредством + [MVCC](https://ru.wikipedia.org/wiki/MVCC) и + [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). + Изменения строго последовательны и не блокируются чтением, + конфликты между транзакциями не возможны. -* Изменения строго последовательны и не блокируются чтением, конфликты между - транзакциями не возможны. +4. Чтение и поиск [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), + без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). + Читатели не блокируются операциями записи и не конкурируют + между собой, чтение масштабируется линейно по ядрам CPU. -* Амортизационная стоимость любой операции Olog(N), - [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). +5. Эффективное хранение дубликатов (ключей с несколькими + значениями), без дублирования ключей, с сортировкой значений, в + том числе целочисленных (для вторичных индексов). -* Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала - транзакций, после сбоев не требуется восстановление. +6. Эффективная поддержка ключей фиксированной длины, в том числе целочисленных. -* Не требуется компактификация или какое-либо периодическое обслуживание. +7. Амортизационная стоимость любой операции Olog(N), + [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). -* Эффективное хранение дубликатов (ключей с несколькими значениями) с - сортировкой значений. +8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала + транзакций, после сбоев не требуется восстановление. Не требуется компактификация + или какое-либо периодическое обслуживание. Поддерживается резервное копирование + "по горячему", на работающей БД без приостановки изменения данных. -* Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t). - -* Поддержка горячего резервного копирования. - -* Файл БД отображается в память кажлого процесса, который работает с БД. К - ключам и данным обеспечивается прямой доступ (без копирования), они не - меняются до завершения транзакции чтения. - -* Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё - необходимое выполняет ядро ОС. +9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё + необходимое штатно выполняет ядро ОС. ### Недостатки и Компромиссы @@ -82,9 +92,9 @@ Database](https://symas.com/products/lightning-memory-mapped-database/) 2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) обуславливает относительно большой [WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация - изменений на диске относительно дорога и является главным ограничителем для + изменений на диске может быть дорогой и является главным ограничителем для производительности по записи. В качестве компромисса предлагается несколько - режимов ленивой и/или периодической фиксации. В том числе режим `WRITEMAP`, + режимов ленивой и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором изменения происходят только в памяти и асинхронно фиксируются на диске ядром ОС. @@ -94,173 +104,228 @@ Database](https://symas.com/products/lightning-memory-mapped-database/) Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, что расходует [пропускную способность оперативной памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным - ограничителем производительности в режиме `WRITEMAP`. + ограничителем производительности в режиме `MAPASYNC`. -4. Проблема долгих чтений (зависших читателей), см. ниже. +4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей), + которая приводит к деградации производительности и переполнению БД. + В _libmdbx_ предложены средства для предотвращения, выхода из проблемной + ситуации и устранения её последствий. Подробности ниже. -5. Вероятность разрушения БД в режиме `WRITEMAP`, см ниже. +5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`. + В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы, + так и согласованность данных. При этом также, в качестве альтернативы, + предложен режим `UTTERLY_NOSYNC`. Подробности ниже. #### Проблема долгих чтений -Понимание проблемы требует некоторых пояснений, которые изложены ниже, но -могут быть сложны для быстрого восприятия. Поэтому, тезисно: +Понимание проблемы требует некоторых пояснений, которые +изложены ниже, но могут быть сложны для быстрого восприятия. +Поэтому, тезисно: -* Изменение данных на фоне долгой операции чтения может приводить к исчерпанию - места в БД. +* Изменение данных на фоне долгой операции чтения может + приводить к исчерпанию места в БД. -* После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` - до завершения долгой операции чтения. +* После чего любая попытка обновить данные будет приводить к + ошибке `MAP_FULL` до завершения долгой операции чтения. -* Характерными примерами долгих чтений являются горячее резервное копирования - и отладка клиентского приложения при активной транзакции чтения. +* Характерными примерами долгих чтений являются горячее + резервное копирования и отладка клиентского приложения при + активной транзакции чтения. -* В оригинальной LMDB после этого будет наблюдаться устойчивая деградация - производительности всех механизмов обратной записи на диск (в I/O контроллере, - в гипервизоре, в ядре ОС). +* В оригинальной _LMDB_ после этого будет наблюдаться + устойчивая деградация производительности всех механизмов + обратной записи на диск (в I/O контроллере, в гипервизоре, + в ядре ОС). -* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также - режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. +* В _libmdbx_ предусмотрен механизм аварийного прерывания таких + операций, а также режим `LIFO RECLAIM` устраняющий последующую + деградацию производительности. -Операции чтения выполняются в контексте снимка данных (версии БД), который был -актуальным на момент старта транзакции чтения. Такой читаемый снимок -поддерживается неизменным до завершения операции. В свою очередь, это не -позволяет повторно использовать страницы БД в последующих версиях (снимках -БД). +Операции чтения выполняются в контексте снимка данных (версии +БД), который был актуальным на момент старта транзакции чтения. +Такой читаемый снимок поддерживается неизменным до завершения +операции. В свою очередь, это не позволяет повторно +использовать страницы БД в последующих версиях (снимках БД). -Другими словами, если обновление данных выполняется на фоне долгой операции -чтения, то вместо повторного использования "старых" ненужных страниц будут -выделяться новые, так как "старые" страницы составляют снимок БД, который еще +Другими словами, если обновление данных выполняется на фоне +долгой операции чтения, то вместо повторного использования +"старых" ненужных страниц будут выделяться новые, так как +"старые" страницы составляют снимок БД, который еще используется долгой операцией чтения. -В результате, при интенсивном изменении данных и достаточно длительной -операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит -создавать новые снимки/версии БД. Такая ситуация будет сохраняться до -завершения операции чтения, которая использует старый снимок данных и -препятствует повторному использованию страниц БД. +В результате, при интенсивном изменении данных и достаточно +длительной операции чтения, в БД могут быть исчерпаны свободные +страницы, что не позволит создавать новые снимки/версии БД. +Такая ситуация будет сохраняться до завершения операции чтения, +которая использует старый снимок данных и препятствует +повторному использованию страниц БД. -Однако, на этом проблемы не заканчиваются. После описанной ситуации, все -дополнительные страницы, которые были выделены пока переработка старых была -невозможна, будут участвовать в цикле выделения/освобождения до конца жизни -экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по -принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение -количества циркулирующий страниц, с точки зрения механизмов кэширования и/или -обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, -однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому -эффекту вымывания I/O кэша при всех последующих изменениях данных. +Однако, на этом проблемы не заканчиваются. После описанной +ситуации, все дополнительные страницы, которые были выделены +пока переработка старых была невозможна, будут участвовать в +цикле выделения/освобождения до конца жизни экземпляра БД. В +оригинальной _LMDB_ этот цикл использования страниц работает по +принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому +увеличение количества циркулирующий страниц, с точки зрения +механизмов кэширования и/или обратной записи, выглядит как +увеличение рабочего набор данных. Проще говоря, однократное +попадание в ситуацию "уснувшего читателя" приводит к +устойчивому эффекту вымывания I/O кэша при всех последующих +изменениях данных. -Для решения описанных проблемы в MDBX сделаны существенные доработки, см. -ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах -презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример -количественной оценки прироста производительности за счет эффективной работы -[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в -MDBX. +Для устранения описанных проблемы в _libmdbx_ сделаны +существенные доработки, подробности ниже. Иллюстрации к +проблеме "долгих чтений" можно найти в [слайдах +презентации](http://www.slideshare.net/leoyuriev/lmdb). +Там же приведен пример количественной оценки прироста +производительности за счет эффективной работы +[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO +RECLAIM` в _libmdbx_. -## Доработки MDBX +#### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC` + +При работе в режиме `WRITEMAP+MAPSYNC` запись измененных +страниц выполняется ядром ОС, что имеет ряд преимуществ. Так +например, при крахе приложения, ядро ОС сохранит все изменения. + +Однако, при аварийном отключении питания или сбое в ядре ОС, на +диске будет сохранена только часть измененных страниц БД. При +этом с большой вероятностью может оказаться так, что будут +сохранены мета-страницы со ссылками на страницы с новыми +версиями данных, но не сами новые данные. В этом случае БД +будет безвозвратна разрушена, даже если до аварии производилась +полная синхронизация данных (посредством `mdb_env_sync()`). + +В _libmdbx_ эта проблема устранена, подробности ниже. + + +Доработки _libmdbx_ +=================== 1. Режим `LIFO RECLAIM`. - Для повторного использования выбираются не самые старые, а самые новые - страницы из доступных. За счет этого цикл использования страниц всегда - имеет минимальную длину и не зависит от общего числа выделенных страниц. + Для повторного использования выбираются не самые старые, а + самые новые страницы из доступных. За счет этого цикл + использования страниц всегда имеет минимальную длину и не + зависит от общего числа выделенных страниц. - В результате механизмы кэширования и обратной записи работают с - максимально возможной эффективностью. В случае использования контроллера - дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) - возможно многократное увеличение производительности по записи - (обновлению данных). + В результате механизмы кэширования и обратной записи работают с + максимально возможной эффективностью. В случае использования + контроллера дисков или системы хранения с + [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно + многократное увеличение производительности по записи + (обновлению данных). 2. Обработчик `OOM-KICK`. - Посредством `mdbx_env_set_oomfunc()` может быть - установлен внешний обработчик (callback), который будет вызван при исчерпания - свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID - и pthread_id. В свою очередь обработчик может предпринять одно из действий: + Посредством `mdbx_env_set_oomfunc()` может быть установлен + внешний обработчик (callback), который будет вызван при + исчерпания свободных страниц из-за долгой операцией чтения. + Обработчику будет передан PID и pthread_id. В свою очередь + обработчик может предпринять одно из действий: - * отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом; - * отменить или перезапустить проблемную операцию чтения, если операция - выполняется одним из потоков текущего процесса; - * подождать некоторое время, в расчете что проблемная операция чтения будет - штатно завершена; - * перервать текущую операцию изменения данных с возвратом кода ошибки. + * отправить сигнал kill (#9), если долгое чтение выполняется + сторонним процессом; -3. Гарантия сохранности БД в режиме `WRITEMAP`. + * отменить или перезапустить проблемную операцию чтения, если + операция выполняется одним из потоков текущего процесса; - При работе в режиме `WRITEMAP` запись измененных страниц выполняется ядром ОС, - что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС - сохранит все изменения. + * подождать некоторое время, в расчете что проблемная операция + чтения будет штатно завершена; - Однако, при аварийном отключении питания или сбое в ядре ОС, на диске будет - сохранена только часть измененных страниц БД. При этом с большой вероятностью - может оказаться так, что будут сохранены мета-страницы со ссылками на страницы - с новыми версиями данных, но не сами новые данные. В этом случае БД будет - безвозвратна разрушена, даже если до аварии производилась полная синхронизация - данных (посредством `mdb_env_sync()`). + * перервать текущую операцию изменения данных с возвратом кода + ошибки. - В MDBX эта проблема решена путем полной переработки пути записи данных: +3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`. - * В режиме `WRITEMAP` MDBX не обновляет мета-страницы непосредственно, - а поддерживает их теневые копии с переносом изменений после фиксации - данных. + При работе в режиме `WRITEMAP+MAPSYNC` запись измененных + страниц выполняется ядром ОС, что имеет ряд преимуществ. Так + например, при крахе приложения, ядро ОС сохранит все изменения. - * При завершении транзакций, в зависимости от состояния - синхронности данных между диском и оперативной память, MDBX помечает - точки фиксации либо как сильные (strong), либо как слабые (weak). Так - например, в режиме `WRITEMAP` завершаемые транзакции помечаются как - слабые, а при явной синхронизации данных как сильные. + Однако, при аварийном отключении питания или сбое в ядре ОС, на + диске будет сохранена только часть измененных страниц БД. При + этом с большой вероятностью может оказаться так, что будут + сохранены мета-страницы со ссылками на страницы с новыми + версиями данных, но не сами новые данные. В этом случае БД + будет безвозвратна разрушена, даже если до аварии производилась + полная синхронизация данных (посредством `mdb_env_sync()`). - * При открытии БД - выполняется автоматический откат к последней сильной фиксации. Этим - обеспечивается гарантия сохранности БД. + В _libmdbx_ эта проблема устранена путем полной переработки + пути записи данных: - К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности - данных, страницы формирующие крайний снимок с сильной фиксацией, не должны - повторно использоваться (перезаписываться) до формирования следующей сильной - точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше - эффект "долгого чтения", с разницей в том, что при исчерпании свободных - страниц автоматически будет сформирована новая точка сильной фиксации. + * В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет + мета-страницы непосредственно, а поддерживает их теневые копии + с переносом изменений после фиксации данных. - В последующих версиях MDBX будут предусмотрены средства для асинхронной записи - данных на диск с формированием сильных точек фиксации. + * При завершении транзакций, в зависимости от состояния + синхронности данных между диском и оперативной память, + _libmdbx_ помечает точки фиксации либо как сильные (strong), + либо как слабые (weak). Так например, в режиме + `WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как + слабые, а при явной синхронизации данных как сильные. -4. Возможность автоматического формирования контрольных точек (сброса данных -на диск) при накоплении заданного объёма изменений, устанавливаемого функцией + * При открытии БД выполняется автоматический откат к последней + сильной фиксации. Этим обеспечивается гарантия сохранности БД. + + К сожалению, такая гарантия надежности не дается бесплатно. Для + сохранности данных, страницы формирующие крайний снимок с + сильной фиксацией, не должны повторно использоваться + (перезаписываться) до формирования следующей сильной точки + фиксации. Таким образом, крайняя точка фиксации создает + описанный выше эффект "долгого чтения". Разница же здесь в том, + что при исчерпании свободных страниц ситуация будет + автоматически исправлена, посредством записи изменений на диск + и формированием новой сильной точки фиксации. + + В последующих версиях _libmdbx_ будут предусмотрены средства + для асинхронной записи данных на диск с автоматическим + формированием сильных точек фиксации. + +4. Возможность автоматического формирования контрольных точек +(сброса данных на диск) при накоплении заданного объёма +изменений, устанавливаемого функцией `mdbx_env_set_syncbytes()`. -5. Возможность получить отставание текущей транзакции чтения от последней -версии данных в БД посредством `mdbx_txn_straggler()`. +5. Возможность получить отставание текущей транзакции чтения от +последней версии данных в БД посредством +`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()`. +8. Возможность связать с каждой завершаемой транзакцией до 3 +дополнительных маркеров посредством `mdbx_canary_put()`, и +прочитать их в транзакции чтения посредством +`mdbx_canary_get()`. -9. Возможность узнать есть ли за текущей позицией курсора строка данных -посредством `mdbx_cursor_eof()`. +9. Возможность узнать есть ли за текущей позицией курсора +строка данных посредством `mdbx_cursor_eof()`. -10. Возможность явно запросить обновление существующей записи, без создания -новой посредством флажка `MDB_CURRENT` для `mdb_put()`. +10. Возможность явно запросить обновление существующей записи, +без создания новой посредством флажка `MDB_CURRENT` для +`mdb_put()`. -11. Возможность обновить или удалить запись с получением предыдущего значения -данных посредством `mdbx_replace()`. +11. Возможность обновить или удалить запись с получением +предыдущего значения данных посредством `mdbx_replace()`. 12. Поддержка ключей нулевого размера. -13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное -количество дубликатов для всех типов таблиц и любого положения курсора. +13. Исправленный вариант `mdb_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()` +дополнительную информацию, включая номер самой старой версии БД +(снимка данных), который используется одним из читателей. From 14b466bd2def54cb0516706e8de60e4b48eb1ad1 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 18 Jan 2017 17:15:51 +0300 Subject: [PATCH 54/86] mdbx: fix cursor EOF tricks. LY: Don't touch cursor's C_EOF flag in functions that don't moves the cursor. HYC: Further fix f8ce8a82717ddefdc912fa47c07f1bdee2a3336b Fully revert the change to GET_MULTIPLE Change-Id: Ia8e6dc0af04e5c7b2fd1a2fc9632ccfecc01819a --- mdb.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/mdb.c b/mdb.c index 8cac1718..092a0e89 100644 --- a/mdb.c +++ b/mdb.c @@ -6537,16 +6537,9 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, break; } rc = MDB_SUCCESS; - if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) || + (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF)) break; - if (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF) { - MDB_cursor *mx = &mc->mc_xcursor->mx_cursor; - if (mx->mc_ki[mx->mc_top] >= NUMKEYS(mx->mc_pg[mx->mc_top])-1) { - rc = MDB_NOTFOUND; - break; - } - mx->mc_flags ^= C_EOF; - } goto fetchm; case MDB_NEXT_MULTIPLE: if (unlikely(data == NULL)) { @@ -7896,12 +7889,9 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) } MDB_page *mp = mc->mc_pg[mc->mc_top]; - if (mc->mc_flags & C_EOF) { - if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) { - *countp = 0; - return MDB_NOTFOUND; - } - mc->mc_flags ^= C_EOF; + if ((mc->mc_flags & C_EOF) && mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) { + *countp = 0; + return MDB_NOTFOUND; } if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { @@ -7922,13 +7912,11 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) if (!mc->mc_snum) return MDB_NOTFOUND; - if (mc->mc_flags & C_EOF) { - if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) - return MDB_NOTFOUND; - mc->mc_flags ^= C_EOF; - } + MDB_page *mp = mc->mc_pg[mc->mc_top]; + if ((mc->mc_flags & C_EOF) && mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) + return MDB_NOTFOUND; - MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { *countp = 1; } else { From 5bb931f7c4b06028a146fffb2335e1c15f658e4d Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 18 Jan 2017 19:05:09 +0300 Subject: [PATCH 55/86] mdbx: fix xflags inside mdb_cursor_put(). Fix xflags preparation bug from 2956095c6ded72d22e10e6d1cad4a5410ea52994 --- mdb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mdb.c b/mdb.c index 092a0e89..08dccaaa 100644 --- a/mdb.c +++ b/mdb.c @@ -7143,15 +7143,13 @@ put_sub: xdata.mv_size = 0; xdata.mv_data = ""; leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); - xflags = MDB_NOSPILL; - if (flags & MDB_NODUPDATA) - xflags |= MDB_NOOVERWRITE; - if (flags & MDB_APPENDDUP) - xflags |= MDB_APPEND; if (flags & MDB_CURRENT) { - xflags |= MDB_CURRENT; + xflags = (flags & MDB_NODUPDATA) ? + MDB_CURRENT|MDB_NOOVERWRITE|MDB_NOSPILL : MDB_CURRENT|MDB_NOSPILL; } else { mdb_xcursor_init1(mc, leaf); + xflags = (flags & MDB_NODUPDATA) ? + MDB_NOOVERWRITE|MDB_NOSPILL : MDB_NOSPILL; } if (sub_root) mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; @@ -7185,6 +7183,8 @@ put_sub: } } ecount = mc->mc_xcursor->mx_db.md_entries; + if (flags & MDB_APPENDDUP) + xflags |= MDB_APPEND; rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); if (flags & F_SUBDATA) { void *db = NODEDATA(leaf); From 688b711e31c4333be8ce709a248d0b3c5c8c1a78 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 19 Jan 2017 17:11:30 +0300 Subject: [PATCH 56/86] 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 57/86] 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 58/86] 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 59/86] 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 60/86] 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 61/86] 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 62/86] 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 63/86] 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 64/86] 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 65/86] 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 66/86] 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 67/86] 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 68/86] 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 69/86] 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 70/86] 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 71/86] 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 72/86] 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 73/86] 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 74/86] 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 75/86] 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 76/86] 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) From acdaeeab5c7b2c9543936c48fd6d8d4ae5ebab3d Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 15 Feb 2017 19:50:23 +0300 Subject: [PATCH 77/86] mdbx: notes about free/reuse cursors. --- lmdb.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lmdb.h b/lmdb.h index b6a55f11..0fded84e 100644 --- a/lmdb.h +++ b/lmdb.h @@ -1039,8 +1039,16 @@ size_t mdb_txn_id(MDB_txn *txn); * * The transaction handle is freed. It and its cursors must not be used * again after this call, except with #mdb_cursor_renew(). - * @note Earlier documentation incorrectly said all cursors would be freed. + * + * @note 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. + * + * @note LMDB-compatible mode: + * Earlier documentation incorrectly said all cursors would be freed. * Only write-transactions free cursors. + * * @param[in] txn A transaction handle returned by #mdb_txn_begin() * @return A non-zero error value on failure and 0 on success. Some possible * errors are: @@ -1057,8 +1065,16 @@ int mdb_txn_commit(MDB_txn *txn); * * The transaction handle is freed. It and its cursors must not be used * again after this call, except with #mdb_cursor_renew(). - * @note Earlier documentation incorrectly said all cursors would be freed. + * + * @note 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. + * + * @note LMDB-compatible mode: + * Earlier documentation incorrectly said all cursors would be freed. * Only write-transactions free cursors. + * * @param[in] txn A transaction handle returned by #mdb_txn_begin() */ int mdb_txn_abort(MDB_txn *txn); From cf8ef06ebceba9c510d15c8f9b17ddccc3838631 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 15 Feb 2017 20:04:20 +0300 Subject: [PATCH 78/86] mdbx: 'unlikely' for DB_STALE. --- mdb.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mdb.c b/mdb.c index ce088c72..8edd5e5b 100644 --- a/mdb.c +++ b/mdb.c @@ -5763,7 +5763,7 @@ mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags) return MDB_BAD_TXN; } else { /* Make sure we're using an up-to-date root */ - if (*mc->mc_dbflag & DB_STALE) { + if (unlikely(*mc->mc_dbflag & DB_STALE)) { MDB_cursor mc2; if (unlikely(TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))) return MDB_BAD_DBI; @@ -7826,15 +7826,14 @@ mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) mc->mc_pg[0] = 0; mc->mc_flags = 0; mc->mc_ki[0] = 0; + mc->mc_xcursor = NULL; if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { mdb_tassert(txn, mx != NULL); mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE; mc->mc_xcursor = mx; mdb_xcursor_init0(mc); - } else { - mc->mc_xcursor = NULL; } - if (*mc->mc_dbflag & DB_STALE) { + if (unlikely(*mc->mc_dbflag & DB_STALE)) { mdb_page_search(mc, NULL, MDB_PS_ROOTONLY); } } @@ -10226,7 +10225,7 @@ mdbx_stat(MDB_txn *txn, MDB_dbi dbi, MDBX_stat *arg, size_t bytes) if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED)) return MDB_BAD_TXN; - if (txn->mt_dbflags[dbi] & DB_STALE) { + if (unlikely(txn->mt_dbflags[dbi] & DB_STALE)) { MDB_cursor mc; MDB_xcursor mx; /* Stale, must read the DB's root. cursor_init does it for us. */ From d47d8c25d8e2af129a67217559a21c107db2cdeb Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 15 Feb 2017 20:05:26 +0300 Subject: [PATCH 79/86] mdbx: drop unused 'flags' from mdb_env_walk(). --- mdbx.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mdbx.c b/mdbx.c index 71fd224b..3599d14b 100644 --- a/mdbx.c +++ b/mdbx.c @@ -172,7 +172,7 @@ typedef struct mdb_walk_ctx { /** Depth-first tree traversal. */ static int __cold -mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int deep) +mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int deep) { MDB_page *mp; int rc, i, nkeys; @@ -238,7 +238,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee payload_size += NODESIZE + node->mn_ksize; if (IS_BRANCH(mp)) { - rc = mdb_env_walk(ctx, dbi, NODEPGNO(node), flags, deep); + rc = mdb_env_walk(ctx, dbi, NODEPGNO(node), deep); if (rc) return rc; continue; @@ -286,7 +286,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee name[namelen] = 0; } rc = mdb_env_walk(ctx, (name && name[0]) ? name : dbi, - db->md_root, node->mn_flags & F_DUPDATA, deep + 1); + db->md_root, deep + 1); if (rc) return rc; } @@ -314,9 +314,9 @@ mdbx_env_pgwalk(MDB_txn *txn, MDBX_pgvisitor_func* visitor, void* user) rc = visitor(0, 2, user, "lmdb", "meta", 2, sizeof(MDB_meta)*2, PAGEHDRSZ*2, (txn->mt_env->me_psize - sizeof(MDB_meta) - PAGEHDRSZ) *2); if (! rc) - rc = mdb_env_walk(&ctx, "free", txn->mt_dbs[FREE_DBI].md_root, 0, 0); + rc = mdb_env_walk(&ctx, "free", txn->mt_dbs[FREE_DBI].md_root, 0); if (! rc) - rc = mdb_env_walk(&ctx, "main", txn->mt_dbs[MAIN_DBI].md_root, 0, 0); + rc = mdb_env_walk(&ctx, "main", txn->mt_dbs[MAIN_DBI].md_root, 0); if (! rc) rc = visitor(P_INVALID, 0, user, NULL, NULL, 0, 0, 0, 0); return rc; From 2b924524ecd58e7973f8df45df1c1d10f41526dc Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 15 Feb 2017 20:16:07 +0300 Subject: [PATCH 80/86] mdbx: initial mdbx_cursor_on_ first/last(). --- mdbx.c | 41 +++++++++++++++++++++++++++++++++++++++++ mdbx.h | 13 +++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/mdbx.c b/mdbx.c index 3599d14b..d32394a5 100644 --- a/mdbx.c +++ b/mdbx.c @@ -354,6 +354,47 @@ size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary) return txn->mt_txnid; } +int mdbx_cursor_on_first(MDB_cursor *mc) +{ + if (unlikely(mc == NULL)) + return EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + if (!(mc->mc_flags & C_INITIALIZED)) + return MDBX_RESULT_FALSE; + + unsigned i; + for(i = 0; i < mc->mc_snum; ++i) { + if (mc->mc_ki[i]) + return MDBX_RESULT_FALSE; + } + + return MDBX_RESULT_TRUE; +} + +int mdbx_cursor_on_last(MDB_cursor *mc) +{ + if (unlikely(mc == NULL)) + return EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDB_VERSION_MISMATCH; + + if (!(mc->mc_flags & C_INITIALIZED)) + return MDBX_RESULT_FALSE; + + unsigned i; + for(i = 0; i < mc->mc_snum; ++i) { + unsigned nkeys = NUMKEYS(mc->mc_pg[i]); + if (mc->mc_ki[i] != nkeys - 1) + return MDBX_RESULT_FALSE; + } + + return MDBX_RESULT_TRUE; +} + int mdbx_cursor_eof(MDB_cursor *mc) { if (unlikely(mc == NULL)) diff --git a/mdbx.h b/mdbx.h index 6a266395..515e819e 100644 --- a/mdbx.h +++ b/mdbx.h @@ -219,10 +219,19 @@ 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, - * 0 otherwise or less that zero in error case. */ +/* Returns: + * - MDBX_RESULT_TRUE when no more data available + * or cursor not positioned; + * - MDBX_RESULT_FALSE when data available; + * - Otherwise the error code. */ int mdbx_cursor_eof(MDB_cursor *mc); +/* Returns: MDBX_RESULT_TRUE, MDBX_RESULT_FALSE or Error code. */ +int mdbx_cursor_on_first(MDB_cursor *mc); + +/* Returns: MDBX_RESULT_TRUE, MDBX_RESULT_FALSE or Error code. */ +int mdbx_cursor_on_last(MDB_cursor *mc); + #define MDBX_EMULTIVAL (MDB_LAST_ERRCODE - 42) #define MDBX_RESULT_FALSE MDB_SUCCESS #define MDBX_RESULT_TRUE (-1) From f75fa27fe60837e88a634111508dc555f3eeafaf Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 15 Feb 2017 20:54:58 +0300 Subject: [PATCH 81/86] mdbx: don't close DBI-handles from R/O txn_abort(). This is related to: - https://github.com/ReOpen/ReOpenLDAP/issues/119 (its6794 regression test may fail) - https://github.com/ReOpen/ReOpenLDAP/issues/92 (rare test060-mt-hot failures) --- mdb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mdb.c b/mdb.c index 8edd5e5b..cf4a3e2d 100644 --- a/mdb.c +++ b/mdb.c @@ -3256,6 +3256,12 @@ mdb_txn_abort(MDB_txn *txn) if(unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) return MDB_VERSION_MISMATCH; +#if MDBX_MODE_ENABLED + if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) + /* LY: don't close DBI-handles in MDBX mode */ + return mdb_txn_end(txn, MDB_END_UPDATE|MDB_END_SLOT|MDB_END_FREE); +#endif /* MDBX_MODE_ENABLED */ + if (txn->mt_child) mdb_txn_abort(txn->mt_child); From 41576e553cbc68737be14220a4042a36d2dc8842 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 16 Feb 2017 14:07:38 +0300 Subject: [PATCH 82/86] mdbx: fix cursor-untrack bug. Fix segfault possibility, during closing the cursor after a write-txn. --- mdb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mdb.c b/mdb.c index cf4a3e2d..00597d92 100644 --- a/mdb.c +++ b/mdb.c @@ -2700,8 +2700,9 @@ mdb_cursors_eot(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 - || mc->mc_signature == MDBX_MC_WAIT4EOT); + unsigned stage = mc->mc_signature; + mdb_ensure(NULL, stage == MDBX_MC_SIGNATURE + || stage == MDBX_MC_WAIT4EOT); next = mc->mc_next; if ((bk = mc->mc_backup) != NULL) { if (merge) { @@ -2714,10 +2715,8 @@ mdb_cursors_eot(MDB_txn *txn, unsigned merge) if ((mx = mc->mc_xcursor) != NULL) mx->mx_cursor.mc_txn = bk->mc_txn; } else { - /* Abort nested txn, but save current cursor's stage */ - unsigned stage = mc->mc_signature; + /* Abort nested txn */ *mc = *bk; - mc->mc_signature = stage; if ((mx = mc->mc_xcursor) != NULL) *mx = *(MDB_xcursor *)(bk+1); } @@ -2725,11 +2724,12 @@ mdb_cursors_eot(MDB_txn *txn, unsigned merge) bk->mc_signature = 0; free(bk); } - if (mc->mc_signature == MDBX_MC_WAIT4EOT) { + if (stage == MDBX_MC_WAIT4EOT) { mc->mc_signature = 0; free(mc); } else { mc->mc_signature = MDBX_MC_READY4CLOSE; + mc->mc_flags = 0 /* reset C_UNTRACK */; } #else mc = bk; From 17080256510e23b6162db0cf2ca09e43d0dfac42 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 16 Feb 2017 14:15:47 +0300 Subject: [PATCH 83/86] mdbx: don't close/lost DBI-handles on ro-txn renew/reset. More for d0383e5aeeb29fc07b568a0d4b5287d539be5d0d Also this is related to: - https://github.com/ReOpen/ReOpenLDAP/issues/119 (its6794 regression test may fail) - https://github.com/ReOpen/ReOpenLDAP/issues/92 (rare test060-mt-hot failures) --- mdb.c | 7 ++++++- mdbx.c | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mdb.c b/mdb.c index 00597d92..06ec817b 100644 --- a/mdb.c +++ b/mdb.c @@ -3244,7 +3244,12 @@ mdb_txn_reset(MDB_txn *txn) if (unlikely(!(txn->mt_flags & MDB_TXN_RDONLY))) return EINVAL; +#if MDBX_MODE_ENABLED + /* LY: don't close DBI-handles in MDBX mode */ + return mdb_txn_end(txn, MDB_END_RESET|MDB_END_UPDATE); +#else return mdb_txn_end(txn, MDB_END_RESET); +#endif /* MDBX_MODE_ENABLED */ } int @@ -3259,7 +3264,7 @@ mdb_txn_abort(MDB_txn *txn) #if MDBX_MODE_ENABLED if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) /* LY: don't close DBI-handles in MDBX mode */ - return mdb_txn_end(txn, MDB_END_UPDATE|MDB_END_SLOT|MDB_END_FREE); + return mdb_txn_end(txn, MDB_END_ABORT|MDB_END_UPDATE|MDB_END_SLOT|MDB_END_FREE); #endif /* MDBX_MODE_ENABLED */ if (txn->mt_child) diff --git a/mdbx.c b/mdbx.c index d32394a5..42ad6242 100644 --- a/mdbx.c +++ b/mdbx.c @@ -369,7 +369,7 @@ int mdbx_cursor_on_first(MDB_cursor *mc) for(i = 0; i < mc->mc_snum; ++i) { if (mc->mc_ki[i]) return MDBX_RESULT_FALSE; - } + } return MDBX_RESULT_TRUE; } From d573c9d54e723d667e3fc4031b21582844a15fbf Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 17 Feb 2017 19:55:28 +0300 Subject: [PATCH 84/86] mdbx: fix mdbx_cursor_on_last(). --- mdbx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdbx.c b/mdbx.c index 42ad6242..66c377d8 100644 --- a/mdbx.c +++ b/mdbx.c @@ -388,7 +388,7 @@ int mdbx_cursor_on_last(MDB_cursor *mc) unsigned i; for(i = 0; i < mc->mc_snum; ++i) { unsigned nkeys = NUMKEYS(mc->mc_pg[i]); - if (mc->mc_ki[i] != nkeys - 1) + if (mc->mc_ki[i] < nkeys - 1) return MDBX_RESULT_FALSE; } From 9436a78e08f3a701297c5a52e8c3e0070c63cf72 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 17 Feb 2017 22:02:07 +0300 Subject: [PATCH 85/86] mdbx: update README. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index d1575cd6..f81dfef1 100644 --- a/README.md +++ b/README.md @@ -364,3 +364,12 @@ RECLAIM` в _libmdbx_. 23. Возможность посредством `mdbx_get_ex()` получить значение по заданному ключу, одновременно с количеством дубликатов. + +24. Наличие функций mdbx_cursor_on_first() и mdbx_cursor_on_last(), +которые позволяют быстро выяснить стоит ли курсор на первой/последней +позиции. + +25. При завершении читающих транзакций, открытые в них DBI-хендлы не +закрываются и не теряются при завершении таких транзакций посредством +mdb_txn_abort() или mdb_txn_reset(). Что позволяет избавится от ряда +сложно обнаруживаемых ошибок. From b950b010bf2321a3eea1a0163dbe2630b1d39a14 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 17 Feb 2017 22:04:38 +0300 Subject: [PATCH 86/86] mdbx: update version timestamp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://en.wikipedia.org/wiki/Institute_of_Mathematical_Problems_of_Biology 17 февраля 1972 — организован Научно-исследовательский вычислительный центр (НИВЦ) АН СССР. --- lmdb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmdb.h b/lmdb.h index 0fded84e..39031632 100644 --- a/lmdb.h +++ b/lmdb.h @@ -209,7 +209,7 @@ typedef int mdb_filehandle_t; MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH) /** The release date of this library version */ -#define MDB_VERSION_DATE "2016-12-28" +#define MDB_VERSION_DATE "2017-02-17" /** A stringifier for the version info */ #define MDB_VERSTR(a,b,c,d) "MDBX " #a "." #b "." #c ": (" d ", https://github.com/ReOpen/libmdbx)"