From 3e7459b428373404e3c8ce238bd4061fa9d18f79 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 5 Dec 2020 09:45:03 +0300 Subject: [PATCH 1/8] mdbx-windows: fix `mdbx_realloc()` for nullptr and `MDBX_AVOID_CRT=ON`. Change-Id: I129221186d65254da5b1d84747e5c59d53864b70 --- ChangeLog.md | 1 + src/osal.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7d5134eb..66c5de37 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -21,6 +21,7 @@ Added features: Fixes: - Fixed missing cleanup (null assigned) in the C++ commit/abort (https://github.com/erthink/libmdbx/pull/143). + - Fixed `mdbx_realloc()` for case of nullptr and `MDBX_AVOID_CRT=ON` for Windows. ## v0.9.2 scheduled at 2020-11-27 diff --git a/src/osal.h b/src/osal.h index a1c8c1f6..be02cf11 100644 --- a/src/osal.h +++ b/src/osal.h @@ -181,7 +181,8 @@ static inline void *mdbx_calloc(size_t nelem, size_t size) { #ifndef mdbx_realloc static inline void *mdbx_realloc(void *ptr, size_t bytes) { - return LocalReAlloc(ptr, bytes, LMEM_MOVEABLE); + return ptr ? LocalReAlloc(ptr, bytes, LMEM_MOVEABLE) + : LocalAlloc(LMEM_FIXED, bytes); } #endif /* mdbx_realloc */ From 3758e7697e40040afbc7104701dc549547fb67a0 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Tue, 15 Dec 2020 15:06:17 +0300 Subject: [PATCH 2/8] mdbx: more checks inside `mdbx_cursor_close()`. Related to https://github.com/erthink/libmdbx/issues/146 Change-Id: I7b90a0e515aa6320b0e89ec52fe01bc0be126071 --- src/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core.c b/src/core.c index 8c305ea4..d9e7f7b9 100644 --- a/src/core.c +++ b/src/core.c @@ -14273,16 +14273,18 @@ again: } void mdbx_cursor_close(MDBX_cursor *mc) { - if (mc) { + if (likely(mc)) { mdbx_ensure(NULL, mc->mc_signature == MDBX_MC_LIVE || mc->mc_signature == MDBX_MC_READY4CLOSE); + MDBX_txn *const txn = mc->mc_txn; if (!mc->mc_backup) { + mc->mc_txn = NULL; /* Remove from txn, if tracked. * A read-only txn (!C_UNTRACK) may have been freed already, * so do not peek inside it. Only write txns track cursors. */ if (mc->mc_flags & C_UNTRACK) { - mdbx_cassert(mc, !(mc->mc_txn->mt_flags & MDBX_TXN_RDONLY)); - MDBX_cursor **prev = &mc->mc_txn->tw.cursors[mc->mc_dbi]; + mdbx_ensure(txn->mt_env, check_txn_rw(txn, 0) == MDBX_SUCCESS); + MDBX_cursor **prev = &txn->tw.cursors[mc->mc_dbi]; while (*prev && *prev != mc) prev = &(*prev)->mc_next; mdbx_cassert(mc, *prev == mc); @@ -14294,6 +14296,7 @@ void mdbx_cursor_close(MDBX_cursor *mc) { } else { /* Cursor closed before nested txn ends */ mdbx_cassert(mc, mc->mc_signature == MDBX_MC_LIVE); + mdbx_ensure(txn->mt_env, check_txn_rw(txn, 0) == MDBX_SUCCESS); mc->mc_signature = MDBX_MC_WAIT4EOT; } } From 166ed1c7d4e4b926de781a3550b1f2e2aef995ec Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Tue, 15 Dec 2020 15:43:19 +0300 Subject: [PATCH 3/8] mdbx: refine prev commit (avoids `SIGSEGV` but assertion failure). More related to https://github.com/erthink/libmdbx/issues/146 Change-Id: Ie5277a8cc56421d20a7c9aad83724991b2efdc2d --- src/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core.c b/src/core.c index d9e7f7b9..6689fafe 100644 --- a/src/core.c +++ b/src/core.c @@ -14277,13 +14277,14 @@ void mdbx_cursor_close(MDBX_cursor *mc) { mdbx_ensure(NULL, mc->mc_signature == MDBX_MC_LIVE || mc->mc_signature == MDBX_MC_READY4CLOSE); MDBX_txn *const txn = mc->mc_txn; + MDBX_env *const env = txn ? txn->mt_env : NULL; if (!mc->mc_backup) { mc->mc_txn = NULL; /* Remove from txn, if tracked. * A read-only txn (!C_UNTRACK) may have been freed already, * so do not peek inside it. Only write txns track cursors. */ if (mc->mc_flags & C_UNTRACK) { - mdbx_ensure(txn->mt_env, check_txn_rw(txn, 0) == MDBX_SUCCESS); + mdbx_ensure(env, check_txn_rw(txn, 0) == MDBX_SUCCESS); MDBX_cursor **prev = &txn->tw.cursors[mc->mc_dbi]; while (*prev && *prev != mc) prev = &(*prev)->mc_next; @@ -14296,7 +14297,7 @@ void mdbx_cursor_close(MDBX_cursor *mc) { } else { /* Cursor closed before nested txn ends */ mdbx_cassert(mc, mc->mc_signature == MDBX_MC_LIVE); - mdbx_ensure(txn->mt_env, check_txn_rw(txn, 0) == MDBX_SUCCESS); + mdbx_ensure(env, check_txn_rw(txn, 0) == MDBX_SUCCESS); mc->mc_signature = MDBX_MC_WAIT4EOT; } } From cda64ca663d0a8d64cdf3dc9971ed64ab47dfbc5 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Thu, 17 Dec 2020 01:21:48 +0300 Subject: [PATCH 4/8] mdbx: rework open/import/export of DBI-handles for robustness Resolve https://github.com/erthink/libmdbx/issues/146 Change-Id: Idd18dc0d038eeba47668983ecf4ff46eabd16de5 --- src/core.c | 102 +++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/src/core.c b/src/core.c index 6689fafe..14f55431 100644 --- a/src/core.c +++ b/src/core.c @@ -6816,8 +6816,45 @@ int mdbx_txn_flags(const MDBX_txn *txn) { return txn->mt_flags; } +/* Check for misused dbi handles */ +#define TXN_DBI_CHANGED(txn, dbi) \ + ((txn)->mt_dbiseqs[dbi] != (txn)->mt_env->me_dbiseqs[dbi]) + +static void dbi_import_locked(MDBX_txn *txn) { + MDBX_env *const env = txn->mt_env; + const unsigned n = env->me_numdbs; + for (unsigned i = CORE_DBS; i < n; ++i) { + if (i >= txn->mt_numdbs) { + txn->mt_dbistate[i] = 0; + if (!(txn->mt_flags & MDBX_TXN_RDONLY)) + txn->tw.cursors[i] = NULL; + } + if ((env->me_dbflags[i] & DB_VALID) && + !(txn->mt_dbistate[i] & DBI_USRVALID)) { + txn->mt_dbiseqs[i] = env->me_dbiseqs[i]; + txn->mt_dbs[i].md_flags = env->me_dbflags[i] & DB_PERSISTENT_FLAGS; + txn->mt_dbistate[i] = DBI_VALID | DBI_USRVALID | DBI_STALE; + mdbx_tassert(txn, txn->mt_dbxs[i].md_cmp != NULL); + } + } + txn->mt_numdbs = n; +} + +/* Import DBI which opened after txn started into context */ +static __cold bool dbi_import(MDBX_txn *txn, MDBX_dbi dbi) { + if (dbi < CORE_DBS || dbi >= txn->mt_env->me_numdbs) + return false; + + mdbx_ensure(txn->mt_env, mdbx_fastmutex_acquire(&txn->mt_env->me_dbi_lock) == + MDBX_SUCCESS); + dbi_import_locked(txn); + mdbx_ensure(txn->mt_env, mdbx_fastmutex_release(&txn->mt_env->me_dbi_lock) == + MDBX_SUCCESS); + return txn->mt_dbistate[dbi] & DBI_USRVALID; +} + /* Export or close DBI handles opened in this txn. */ -static void mdbx_dbis_update(MDBX_txn *txn, int keep) { +static void dbi_update(MDBX_txn *txn, int keep) { mdbx_tassert(txn, !txn->mt_parent && txn == txn->mt_env->me_txn0); MDBX_dbi n = txn->mt_numdbs; if (n) { @@ -6931,7 +6968,7 @@ static int mdbx_txn_end(MDBX_txn *txn, unsigned mode) { if (txn == env->me_txn0) { mdbx_assert(env, txn->mt_parent == NULL); /* Export or close DBI handles created in this txn */ - mdbx_dbis_update(txn, mode & MDBX_END_UPDATE); + dbi_update(txn, mode & MDBX_END_UPDATE); mdbx_pnl_shrink(&txn->tw.retired_pages); mdbx_pnl_shrink(&txn->tw.reclaimed_pglist); /* The writer mutex was locked in mdbx_txn_begin. */ @@ -8032,42 +8069,13 @@ __hot static int mdbx_page_flush(MDBX_txn *txn, const unsigned keep) { return MDBX_SUCCESS; } -/* Check for misused dbi handles */ -#define TXN_DBI_CHANGED(txn, dbi) \ - ((txn)->mt_dbiseqs[dbi] != (txn)->mt_env->me_dbiseqs[dbi]) - -/* Import DBI which opened after txn started into context */ -static __cold bool mdbx_txn_import_dbi(MDBX_txn *txn, MDBX_dbi dbi) { - MDBX_env *env = txn->mt_env; - if (dbi < CORE_DBS || dbi >= env->me_numdbs) - return false; - - mdbx_ensure(env, mdbx_fastmutex_acquire(&env->me_dbi_lock) == MDBX_SUCCESS); - const unsigned snap_numdbs = env->me_numdbs; - mdbx_compiler_barrier(); - for (unsigned i = CORE_DBS; i < snap_numdbs; ++i) { - if (i >= txn->mt_numdbs) - txn->mt_dbistate[i] = 0; - if (!(txn->mt_dbistate[i] & DBI_USRVALID) && - (env->me_dbflags[i] & DB_VALID)) { - txn->mt_dbs[i].md_flags = env->me_dbflags[i] & DB_PERSISTENT_FLAGS; - txn->mt_dbistate[i] = DBI_VALID | DBI_USRVALID | DBI_STALE; - mdbx_tassert(txn, txn->mt_dbxs[i].md_cmp != NULL); - } - } - txn->mt_numdbs = snap_numdbs; - - mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); - return txn->mt_dbistate[dbi] & DBI_USRVALID; -} - /* Check txn and dbi arguments to a function */ static __always_inline bool mdbx_txn_dbi_exists(MDBX_txn *txn, MDBX_dbi dbi, unsigned validity) { if (likely(dbi < txn->mt_numdbs && (txn->mt_dbistate[dbi] & validity))) return true; - return mdbx_txn_import_dbi(txn, dbi); + return dbi_import(txn, dbi); } int mdbx_txn_commit(MDBX_txn *txn) { return __inline_mdbx_txn_commit(txn); } @@ -17533,18 +17541,8 @@ static int dbi_open(MDBX_txn *txn, const char *table_name, unsigned user_flags, goto early_bailout; } - if (txn->mt_numdbs < env->me_numdbs) { - /* Import handles from env */ - for (unsigned i = txn->mt_numdbs; i < env->me_numdbs; ++i) { - txn->mt_dbistate[i] = 0; - if (env->me_dbflags[i] & DB_VALID) { - txn->mt_dbs[i].md_flags = env->me_dbflags[i] & DB_PERSISTENT_FLAGS; - txn->mt_dbistate[i] = DBI_VALID | DBI_USRVALID | DBI_STALE; - mdbx_tassert(txn, txn->mt_dbxs[i].md_cmp != NULL); - } - } - txn->mt_numdbs = env->me_numdbs; - } + /* Import handles from env */ + dbi_import_locked(txn); /* Rescan after mutex acquisition & import handles */ for (slot = scan = txn->mt_numdbs; --scan >= CORE_DBS;) { @@ -17604,18 +17602,16 @@ static int dbi_open(MDBX_txn *txn, const char *table_name, unsigned user_flags, txn->mt_dbistate[slot] = (uint8_t)dbiflags; txn->mt_dbxs[slot].md_name.iov_base = namedup; txn->mt_dbxs[slot].md_name.iov_len = len; - if ((txn->mt_flags & MDBX_TXN_RDONLY) == 0) - txn->tw.cursors[slot] = NULL; - txn->mt_numdbs += (slot == txn->mt_numdbs); - if ((dbiflags & DBI_CREAT) == 0) { + txn->mt_dbiseqs[slot] = ++env->me_dbiseqs[slot]; + if (!(dbiflags & DBI_CREAT)) env->me_dbflags[slot] = txn->mt_dbs[slot].md_flags | DB_VALID; + if (txn->mt_numdbs == slot) { mdbx_compiler_barrier(); - if (env->me_numdbs <= slot) - env->me_numdbs = slot + 1; - } else { - env->me_dbiseqs[slot]++; + txn->mt_numdbs = env->me_numdbs = slot + 1; + if (!(txn->mt_flags & MDBX_TXN_RDONLY)) + txn->tw.cursors[slot] = NULL; } - txn->mt_dbiseqs[slot] = env->me_dbiseqs[slot]; + mdbx_assert(env, env->me_numdbs > slot); *dbi = slot; } From 735da5fedde4a04d6a3765d923c250a2720d8d2d Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 16 Dec 2020 18:04:08 +0300 Subject: [PATCH 5/8] mdbx: auto-shrink env's DBI table. Change-Id: I9f423dab41863119a4491491e0ecd0a4aee42a82 --- src/core.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/core.c b/src/core.c index 14f55431..3f943a15 100644 --- a/src/core.c +++ b/src/core.c @@ -6859,7 +6859,7 @@ static void dbi_update(MDBX_txn *txn, int keep) { MDBX_dbi n = txn->mt_numdbs; if (n) { bool locked = false; - MDBX_env *env = txn->mt_env; + MDBX_env *const env = txn->mt_env; for (unsigned i = n; --i >= CORE_DBS;) { if (likely((txn->mt_dbistate[i] & DBI_CREAT) == 0)) @@ -6869,11 +6869,10 @@ static void dbi_update(MDBX_txn *txn, int keep) { mdbx_fastmutex_acquire(&env->me_dbi_lock) == MDBX_SUCCESS); locked = true; } + if (env->me_numdbs <= i || txn->mt_dbiseqs[i] != env->me_dbiseqs[i]) + continue /* dbi explicitly closed and/or then re-opened by other txn */; if (keep) { env->me_dbflags[i] = txn->mt_dbs[i].md_flags | DB_VALID; - mdbx_compiler_barrier(); - if (env->me_numdbs <= i) - env->me_numdbs = i + 1; } else { char *ptr = env->me_dbxs[i].md_name.iov_base; if (ptr) { @@ -6887,6 +6886,20 @@ static void dbi_update(MDBX_txn *txn, int keep) { } } + n = env->me_numdbs; + if (n > CORE_DBS && unlikely(!(env->me_dbflags[n - 1] & DB_VALID))) { + if (!locked) { + mdbx_ensure(env, + mdbx_fastmutex_acquire(&env->me_dbi_lock) == MDBX_SUCCESS); + locked = true; + } + + n = env->me_numdbs; + while (n > CORE_DBS && !(env->me_dbflags[n - 1] & DB_VALID)) + --n; + env->me_numdbs = n; + } + if (unlikely(locked)) mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); @@ -17669,10 +17682,15 @@ static int mdbx_dbi_close_locked(MDBX_env *env, MDBX_dbi dbi) { return MDBX_BAD_DBI; env->me_dbflags[dbi] = 0; + env->me_dbiseqs[dbi]++; env->me_dbxs[dbi].md_name.iov_len = 0; mdbx_compiler_barrier(); env->me_dbxs[dbi].md_name.iov_base = NULL; mdbx_free(ptr); + + if (env->me_numdbs == dbi + 1) + env->me_numdbs = dbi; + return MDBX_SUCCESS; } @@ -17686,7 +17704,9 @@ int mdbx_dbi_close(MDBX_env *env, MDBX_dbi dbi) { rc = mdbx_fastmutex_acquire(&env->me_dbi_lock); if (likely(rc == MDBX_SUCCESS)) { - rc = mdbx_dbi_close_locked(env, dbi); + rc = (dbi < env->me_maxdbs && (env->me_dbflags[dbi] & DB_VALID)) + ? mdbx_dbi_close_locked(env, dbi) + : MDBX_BAD_DBI; mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); } return rc; @@ -17852,7 +17872,6 @@ int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, bool del) { txn->mt_flags |= MDBX_TXN_ERROR; goto bailout; } - env->me_dbiseqs[dbi]++; mdbx_dbi_close_locked(env, dbi); mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); From d77af0bc1f24c2e32cdd43f4f44ff624c56e5f4e Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Thu, 17 Dec 2020 10:36:05 +0300 Subject: [PATCH 6/8] mdbx: more checks against the use renewed (re-created or re-opened) DBI-handles. More for https://github.com/erthink/libmdbx/issues/146 Change-Id: I09e40598aca18e7ebd9798dc3be8675de3f8d976 --- src/core.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core.c b/src/core.c index 3f943a15..2d2eb669 100644 --- a/src/core.c +++ b/src/core.c @@ -12688,6 +12688,9 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, if (unlikely(rc != MDBX_SUCCESS)) return rc; + if (unlikely(TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))) + return MDBX_BAD_DBI; + mdbx_cassert(mc, cursor_is_tracked(mc)); env = mc->mc_txn->mt_env; @@ -13487,6 +13490,9 @@ int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) { if (unlikely(rc != MDBX_SUCCESS)) return rc; + if (unlikely(TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))) + return MDBX_BAD_DBI; + if (unlikely(!(mc->mc_flags & C_INITIALIZED))) return MDBX_ENODATA; @@ -14123,6 +14129,9 @@ static __inline int mdbx_couple_init(MDBX_cursor_couple *couple, /* Initialize a cursor for a given transaction and database. */ static int mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi) { STATIC_ASSERT(offsetof(MDBX_cursor_couple, outer) == 0); + if (unlikely(TXN_DBI_CHANGED(txn, dbi))) + return MDBX_BAD_DBI; + return mdbx_couple_init(container_of(mc, MDBX_cursor_couple, outer), dbi, txn, &txn->mt_dbs[dbi], &txn->mt_dbxs[dbi], &txn->mt_dbistate[dbi]); From 760f1654c2371306d07fbeefdcfd18f87e80358c Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Thu, 17 Dec 2020 15:42:23 +0300 Subject: [PATCH 7/8] mdbx-test: minor extension for `jitter` test case. Trivial test for https://github.com/erthink/libmdbx/issues/146. Change-Id: If5b365ebedf8609c9ec12569d5e5173799379195 --- test/jitter.cc | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ test/test.h | 3 +++ 2 files changed, 52 insertions(+) diff --git a/test/jitter.cc b/test/jitter.cc index 71d58699..36066e76 100644 --- a/test/jitter.cc +++ b/test/jitter.cc @@ -14,6 +14,14 @@ #include "test.h" +void testcase_jitter::check_dbi_error(int expect, const char *stage) { + MDBX_stat stat; + int err = mdbx_dbi_stat(txn_guard.get(), dbi, &stat, sizeof(stat)); + if (err != expect) + failure("unexpected result for %s dbi-handle: expect %d, got %d", stage, + expect, err); +} + bool testcase_jitter::run() { int err; size_t upper_limit = config.params.size_upper; @@ -24,6 +32,47 @@ bool testcase_jitter::run() { jitter_delay(); db_open(); + if (!dbi && !mode_readonly()) { + // create table + txn_begin(false); + dbi = db_table_open(true); + check_dbi_error(MDBX_SUCCESS, "created-uncommitted"); + // note: here and below the 4-byte length keys and value are used + // to be compatible with any Db-flags given from command line. + MDBX_val key = {(void *)"k000", 4}, value = {(void *)"v001", 4}; + err = mdbx_put(txn_guard.get(), dbi, &key, &value, MDBX_UPSERT); + if (err != MDBX_SUCCESS) + failure_perror("jitter.put-1", err); + txn_end(false); + + // drop & re-create table, but abort txn + txn_begin(false); + check_dbi_error(MDBX_SUCCESS, "created-committed"); + err = mdbx_drop(txn_guard.get(), dbi, true); + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_drop(delete=true)", err); + check_dbi_error(MDBX_BAD_DBI, "dropped-uncommitted"); + dbi = db_table_open(true); + check_dbi_error(MDBX_SUCCESS, "recreated-uncommitted"); + txn_end(true); + + // check after aborted txn + txn_begin(false); + value = {(void *)"v002", 4}; + err = mdbx_put(txn_guard.get(), dbi, &key, &value, MDBX_UPSERT); + if (err != MDBX_BAD_DBI) + failure_perror("jitter.put-2", err); + check_dbi_error(MDBX_BAD_DBI, "dropped-recreated-aborted"); + // restore DBI + dbi = db_table_open(false); + check_dbi_error(MDBX_SUCCESS, "dropped-recreated-aborted+reopened"); + value = {(void *)"v003", 4}; + err = mdbx_put(txn_guard.get(), dbi, &key, &value, MDBX_UPSERT); + if (err != MDBX_SUCCESS) + failure_perror("jitter.put-3", err); + txn_end(false); + } + if (upper_limit < 1) { MDBX_envinfo info; err = mdbx_env_info_ex(db_guard.get(), txn_guard.get(), &info, diff --git a/test/test.h b/test/test.h index bcc33209..bd349178 100644 --- a/test/test.h +++ b/test/test.h @@ -287,6 +287,9 @@ public: }; class testcase_jitter : public testcase { +protected: + void check_dbi_error(int expect, const char *stage); + public: testcase_jitter(const actor_config &config, const mdbx_pid_t pid) : testcase(config, pid) {} From 1ebc1e7c4998361be9490d9ad17dfe1d691de012 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Thu, 17 Dec 2020 17:42:35 +0300 Subject: [PATCH 8/8] mdbx: update ChangeLog. Change-Id: Ib53b3180b2b8e5437eec649aa010bdd6779d41c9 --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index 66c5de37..813d8c9c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,6 +22,7 @@ Added features: Fixes: - Fixed missing cleanup (null assigned) in the C++ commit/abort (https://github.com/erthink/libmdbx/pull/143). - Fixed `mdbx_realloc()` for case of nullptr and `MDBX_AVOID_CRT=ON` for Windows. + - Fixed the possibility to use invalid and renewed (closed & re-opened, dropped & re-created) DBI-handles (https://github.com/erthink/libmdbx/issues/146). ## v0.9.2 scheduled at 2020-11-27