From 17c6555a7fef4eb8711e826bf3401b8b9b6e93cf Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Mon, 21 Nov 2016 20:50:39 +0300 Subject: [PATCH 01/26] 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 f9f132671cc245cfe39519e5dbc9d5044b8bd973 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 6 Dec 2016 20:08:08 +0300 Subject: [PATCH 02/26] 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 03/26] 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 c4f4d9ebf3f9690ff85151f66f2497b69b3da00a Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 8 Dec 2016 16:33:17 +0300 Subject: [PATCH 04/26] 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 5865c74876578f55cf6bee9f94b2782b36655c49 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 15 Dec 2016 21:55:28 +0300 Subject: [PATCH 05/26] 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 06/26] 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 578fe9e2586cb7dbaa38a79ab1943388581782d8 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Sun, 18 Dec 2016 00:58:26 +0300 Subject: [PATCH 07/26] 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 08/26] 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 09/26] 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 10/26] 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 11/26] 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 12/26] 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 dbc57d3eaf9025b8a5b3d14dd2ecbd0589d1c1b4 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 23 Dec 2016 15:35:42 +0300 Subject: [PATCH 13/26] 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 5b160be1286483f158f27521775390e3b90e8c1a Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Tue, 3 Jan 2017 16:09:34 +0300 Subject: [PATCH 14/26] 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 15/26] 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 16/26] 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 17/26] 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 18/26] 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 19/26] 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 20/26] 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 21/26] 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 22/26] 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 23/26] 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 24/26] 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 25/26] 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 26/26] 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` завершаемые транзакции помечаются как + слабые, а при явной синхронизации данных как сильные. + + * При открытии БД + выполняется автоматический откат к последней сильной фиксации. Этим + обеспечивается гарантия сохранности БД. К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности данных, страницы формирующие крайний снимок с сильной фиксацией, не должны