mirror of
https://github.com/isar/libmdbx.git
synced 2025-12-16 05:02:21 +08:00
Compare commits
7 Commits
get-cached
...
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64904f0cab | ||
|
|
47a80b23f3 | ||
|
|
aee4971a2f | ||
|
|
77ac97879a | ||
|
|
0ec2758784 | ||
|
|
5598ec9a98 | ||
|
|
73215bdcc7 |
@@ -67,7 +67,6 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-dbi.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-env.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-extra.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-get-cached.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-key-transform.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-misc.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/api-opts.c"
|
||||
@@ -770,7 +769,6 @@ else()
|
||||
"${MDBX_SOURCE_DIR}/api-dbi.c"
|
||||
"${MDBX_SOURCE_DIR}/api-env.c"
|
||||
"${MDBX_SOURCE_DIR}/api-extra.c"
|
||||
"${MDBX_SOURCE_DIR}/api-get-cached.c"
|
||||
"${MDBX_SOURCE_DIR}/api-key-transform.c"
|
||||
"${MDBX_SOURCE_DIR}/api-misc.c"
|
||||
"${MDBX_SOURCE_DIR}/api-opts.c"
|
||||
|
||||
@@ -767,9 +767,9 @@ $(DIST_DIR)/mdbx.c: $(DIST_DIR)/@tmp-internals.inc $(lastword $(MAKEFILE_LIST))
|
||||
) | $(SED) -e '/#include "/d;/#pragma once/d' -e 's|@INCLUDE|#include|' \
|
||||
-e '/ clang-format o/d;/ \*INDENT-O/d' -e '3i /* clang-format off */' | cat -s >$@
|
||||
|
||||
$(DIST_DIR)/mdbx.c++: $(DIST_DIR)/@tmp-essentials.inc src/mdbx.c++ $(lastword $(MAKEFILE_LIST))
|
||||
$(DIST_DIR)/mdbx.c++: $(DIST_DIR)/@tmp-internals.inc src/mdbx.c++ $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)cat $(DIST_DIR)/@tmp-essentials.inc src/mdbx.c++ | $(SED) \
|
||||
$(QUIET)cat $(DIST_DIR)/@tmp-internals.inc src/mdbx.c++ | $(SED) \
|
||||
-e '/#define xMDBX_ALLOY/d' \
|
||||
-e '/#include "/d;/#pragma once/d' \
|
||||
-e 's|@INCLUDE|#include|;s|"mdbx.h"|"mdbx.h++"|' \
|
||||
|
||||
123
mdbx.h
123
mdbx.h
@@ -4939,129 +4939,6 @@ LIBMDBX_API int mdbx_get_ex(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MD
|
||||
* \retval MDBX_EINVAL An invalid parameter was specified. */
|
||||
LIBMDBX_API int mdbx_get_equal_or_great(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data);
|
||||
|
||||
/** \brief Lightweight transparent cache entry structure used by \ref mdbx_cache_get().
|
||||
* \ingroup c_crud
|
||||
*
|
||||
* The approach of these caching is to preserve address of a value retrieved from the database with an extremely fast
|
||||
* check of relevance it based on a transaction ID within an internal b-tree structures. Event a b-tree was modified
|
||||
* then the search for the corresponding key from the root of the b-tree to leaf pages stops as soon as reaches a page
|
||||
* that has not been modified after the last check of given cache entry. This way, the minimum actions is performed,
|
||||
* which is no slower than a usual key search in the worst case, and at best, only a few lightweight checks will be do.
|
||||
*
|
||||
* \note The cache structure allows it to be placed in shared memory and used by multiple processes.
|
||||
* However, such interaction and management are not provided by libmdbx in any way yet now.
|
||||
*
|
||||
* \note An each cache entry must be initialized by \ref mdbx_cache_init() before first use. */
|
||||
typedef struct MDBX_cache_entry {
|
||||
uint64_t trunk_txnid; /**< The transaction/MVCC-snapshot ID of a page or other internal DB structure
|
||||
* that hold the cached data or reflect it state. */
|
||||
uint64_t last_confirmed_txnid; /**< The recent transaction/MVCC-snapshot ID wherein the cache entry
|
||||
* was checked and confirmed. */
|
||||
size_t offset; /**< The offset of cached data value for a corresponding key.
|
||||
* The zero value means \ref MDBX_NOTFOUND. */
|
||||
uint32_t length; /**< The length of cached data value for a corresponding key. */
|
||||
} MDBX_cache_entry_t;
|
||||
|
||||
/** \brief Initializes the cache entry before the first use.
|
||||
* \ingroup c_crud
|
||||
* \see MDBX_cache_entry
|
||||
* \see mdbx_cache_get() */
|
||||
LIBMDBX_INLINE_API(void, mdbx_cache_init, (MDBX_cache_entry_t * entry)) {
|
||||
entry->offset = 0;
|
||||
entry->length = 0;
|
||||
entry->trunk_txnid = 0;
|
||||
entry->last_confirmed_txnid = 0;
|
||||
}
|
||||
|
||||
/** \brief Cache entry status returned by \ref mdbx_cache_get().
|
||||
* \ingroup c_crud
|
||||
* \see MDBX_cache_entry
|
||||
* \see mdbx_cache_init() */
|
||||
typedef enum MDBX_cache_status {
|
||||
/** \brief The error other than \ref MDBX_NOTFOUND has occurred.
|
||||
* \details There is no correct result since an error has occurred that is not related
|
||||
* to the absence of the desired key-value pair.
|
||||
* The given cache entry has not been changed. */
|
||||
MDBX_CACHE_ERROR = -2,
|
||||
|
||||
/** \brief The result was obtained by bypassing the cache, because
|
||||
* the transaction is too old to using the cache entry.
|
||||
* \details The cache entry reflects a newer version of the data that is unavailable within
|
||||
* an MVCC-snapshot used by current transaction.
|
||||
* The given cache entry has not been changed.
|
||||
* The result of getting a value is correct until the transaction end. */
|
||||
MDBX_CACHE_BEHIND = -1,
|
||||
|
||||
/** \brief The result was obtained by bypassing the cache, because
|
||||
* the given cache entry being updated by another thread.
|
||||
* \details When accessing the cache entry, a race condition was detected with its update by another thread.
|
||||
* Therefore, the result was obtained without using the cache entry and without affecting an operation of other
|
||||
* threads using it, including the ones performing an update. For a read transaction, the result is correct until
|
||||
* the transaction end. For a write transactions, the result is correct until the value is explicitly changed or
|
||||
* the transaction is completed. */
|
||||
MDBX_CACHE_RACE = 0,
|
||||
|
||||
/** \brief The result of getting a value is correct, but it cannot be cached since
|
||||
* the changes have not been committed.
|
||||
* \details The requested value of a pair is in a dirty state itself or on a dirty page with other updated items.
|
||||
* This cache entry has not been changed because the corresponding data changes have not yet been committed
|
||||
* and could be aborted.
|
||||
* The result of the get operation and data value are valid within the current write transaction
|
||||
* until any next modification. */
|
||||
MDBX_CACHE_DIRTY = 1,
|
||||
|
||||
/** \brief The result of getting a value is correct and was retrieved from the cache entry which is untouched.
|
||||
* \details There were no changes in the cached data after the last check.
|
||||
* The given cache entry was not altered as it is complete up-to-date.
|
||||
* For a read transaction, the result is correct until the transaction end.
|
||||
* For a write transactions, the result is correct until the value is explicitly changed
|
||||
* or the transaction is completed. */
|
||||
MDBX_CACHE_HIT = 2,
|
||||
|
||||
/** \brief The result of getting a value is correct and has been retrieved from the cache, which has been
|
||||
* altered to reflect recently committed transactions.
|
||||
* \details There were no changes in the cached data after the last check.
|
||||
* The given cache entry has been slightly updated to reflect the relevance of the data for recent committed
|
||||
* transaction(s). For a read transaction, the result is correct until the transaction end. For a write transactions,
|
||||
* the result is correct until the value is explicitly changed or the transaction is completed. */
|
||||
MDBX_CACHE_CONFIRMED = 3,
|
||||
|
||||
/** \brief The result of getting a value is correct and corresponds to the fresh data readed from the database,
|
||||
* which also putted into the cache entry.
|
||||
* \details After the last check, either the value of the requested pair itself changed,
|
||||
* or it was moved to a new page due to the updating of neighboring items.
|
||||
* The given cache entry has been completely updated to reflect the actual data.
|
||||
* For a read transaction, the result is correct until the transaction end.
|
||||
* For a write transactions, the result is correct until the value is explicitly changed
|
||||
* or the transaction is completed. */
|
||||
MDBX_CACHE_REFRESHED = 4
|
||||
} MDBX_cache_status_t;
|
||||
|
||||
/** \brief Pair of error code and cache status as a result of \ref mdbx_cache_get().
|
||||
* \ingroup c_crud */
|
||||
typedef struct MDBX_cache_result {
|
||||
MDBX_error_t errcode;
|
||||
MDBX_cache_status_t status;
|
||||
} MDBX_cache_result_t;
|
||||
|
||||
/** \brief Get items from a table using cache.
|
||||
* \ingroup c_crud
|
||||
* \see mdbx_cache_get_SingleThreaded()
|
||||
* \see MDBX_cache_entry
|
||||
* \see mdbx_cache_init()
|
||||
*/
|
||||
LIBMDBX_API MDBX_cache_result_t mdbx_cache_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data,
|
||||
volatile MDBX_cache_entry_t *entry);
|
||||
|
||||
/** \brief FIXME
|
||||
* \ingroup c_crud
|
||||
* \see mdbx_cache_get()
|
||||
* \see MDBX_cache_entry
|
||||
* \see mdbx_cache_init()
|
||||
*/
|
||||
LIBMDBX_API MDBX_cache_result_t mdbx_cache_get_SingleThreaded(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key,
|
||||
MDBX_val *data, MDBX_cache_entry_t *entry);
|
||||
|
||||
/** \brief Store items into a table.
|
||||
* \ingroup c_crud
|
||||
*
|
||||
|
||||
26
mdbx.h++
26
mdbx.h++
@@ -370,6 +370,9 @@ class txn_managed;
|
||||
class cursor;
|
||||
class cursor_managed;
|
||||
|
||||
/// \brief Transaction ID and MVCC-snapshot number.
|
||||
using txnid = uint64_t;
|
||||
|
||||
/// \brief Default buffer.
|
||||
using default_buffer = buffer<default_allocator, default_capacity_policy>;
|
||||
|
||||
@@ -997,6 +1000,9 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val {
|
||||
friend MDBX_CXX14_CONSTEXPR bool operator<=(const slice &a, const slice &b) noexcept;
|
||||
friend MDBX_CXX14_CONSTEXPR bool operator>=(const slice &a, const slice &b) noexcept;
|
||||
friend MDBX_CXX14_CONSTEXPR bool operator!=(const slice &a, const slice &b) noexcept;
|
||||
#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L
|
||||
friend MDBX_CXX14_CONSTEXPR auto operator<=>(const slice &a, const slice &b) noexcept;
|
||||
#endif /* __cpp_impl_three_way_comparison */
|
||||
|
||||
/// \brief Checks the slice is not refers to null address or has zero length.
|
||||
MDBX_CXX11_CONSTEXPR bool is_valid() const noexcept { return !(iov_base == nullptr && iov_len != 0); }
|
||||
@@ -2753,6 +2759,9 @@ struct pair {
|
||||
friend MDBX_CXX14_CONSTEXPR bool operator<=(const pair &a, const pair &b) noexcept;
|
||||
friend MDBX_CXX14_CONSTEXPR bool operator>=(const pair &a, const pair &b) noexcept;
|
||||
friend MDBX_CXX14_CONSTEXPR bool operator!=(const pair &a, const pair &b) noexcept;
|
||||
#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L
|
||||
friend MDBX_CXX14_CONSTEXPR auto operator<=>(const pair &a, const pair &b) noexcept;
|
||||
#endif /* __cpp_impl_three_way_comparison */
|
||||
};
|
||||
|
||||
/// \brief Combines pair of slices for key and value with boolean flag to
|
||||
@@ -4478,6 +4487,11 @@ public:
|
||||
inline void upsert(const slice &key, const slice &value);
|
||||
inline slice upsert_reserve(const slice &key, size_t value_length);
|
||||
|
||||
/// \brief Updates value associated with a key at the current cursor position.
|
||||
void update_current(const slice &value);
|
||||
/// \brief Reserves and returns the space to storing a value associated with a key at the current cursor position.
|
||||
slice reverse_current(size_t value_length);
|
||||
|
||||
inline void update(const slice &key, const slice &value);
|
||||
inline bool try_update(const slice &key, const slice &value);
|
||||
inline slice update_reserve(const slice &key, size_t value_length);
|
||||
@@ -5011,6 +5025,12 @@ MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX14_CONSTEXPR bool operator!=(const slice &a,
|
||||
return slice::compare_fast(a, b) != 0;
|
||||
}
|
||||
|
||||
#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX14_CONSTEXPR auto operator<=>(const slice &a, const slice &b) noexcept {
|
||||
return slice::compare_lexicographically(a, b);
|
||||
}
|
||||
#endif /* __cpp_impl_three_way_comparison */
|
||||
|
||||
template <class ALLOCATOR>
|
||||
inline string<ALLOCATOR> slice::as_hex_string(bool uppercase, unsigned wrap_width, const ALLOCATOR &allocator) const {
|
||||
return to_hex(*this, uppercase, wrap_width).as_string<ALLOCATOR>(allocator);
|
||||
@@ -5109,6 +5129,12 @@ MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX14_CONSTEXPR bool operator!=(const pair &a, c
|
||||
memcmp(a.value.data(), b.value.data(), a.value.length()) != 0;
|
||||
}
|
||||
|
||||
#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX14_CONSTEXPR auto operator<=>(const pair &a, const pair &b) noexcept {
|
||||
return pair::compare_lexicographically(a, b);
|
||||
}
|
||||
#endif /* __cpp_impl_three_way_comparison */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class ALLOCATOR, typename CAPACITY_POLICY>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "api-dbi.c"
|
||||
#include "api-env.c"
|
||||
#include "api-extra.c"
|
||||
#include "api-get-cached.c"
|
||||
#include "api-key-transform.c"
|
||||
#include "api-misc.c"
|
||||
#include "api-opts.c"
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static MDBX_cache_result_t cache_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data,
|
||||
MDBX_cache_entry_t *entry);
|
||||
|
||||
static inline bool is_outside_dxb(const MDBX_txn *txn, const void *ptr) {
|
||||
const MDBX_env *env = txn->env;
|
||||
const ptrdiff_t offset = ptr_dist(ptr, env->dxb_mmap.base);
|
||||
return offset < 0 || (size_t)offset >= pgno2bytes(env, txn->geo.first_unallocated);
|
||||
}
|
||||
|
||||
static inline bool is_not_commited(const MDBX_txn *txn, const page_t *mp) {
|
||||
tASSERT(txn, mp >= (const page_t *)txn->env->dxb_mmap.base &&
|
||||
mp < (const page_t *)(ptr_disp(txn->env->dxb_mmap.base,
|
||||
pgno2bytes(txn->env, txn->geo.first_unallocated))));
|
||||
return mp->txnid > txn_basis_snapshot(txn);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline bool is_inside_dxb_and_commited(const MDBX_txn *txn, const void *ptr) {
|
||||
return !is_outside_dxb(txn, ptr) && !is_not_commited(txn, ptr2page(txn->env, ptr));
|
||||
}
|
||||
|
||||
static inline MDBX_cache_result_t cache_result(int err, MDBX_cache_status_t status) {
|
||||
MDBX_cache_result_t result = {.errcode = err, .status = status};
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline MDBX_cache_result_t cache_error(int err) {
|
||||
assert(err != MDBX_SUCCESS && err != MDBX_RESULT_TRUE);
|
||||
return cache_result(err, MDBX_CACHE_ERROR);
|
||||
}
|
||||
|
||||
static inline MDBX_cache_result_t cache_fallback(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data,
|
||||
MDBX_cache_status_t status) {
|
||||
MDBX_cache_entry_t stub = {.last_confirmed_txnid = 0, .trunk_txnid = 0};
|
||||
MDBX_cache_result_t result = cache_get(txn, dbi, key, data, &stub);
|
||||
if (result.status > MDBX_CACHE_DIRTY)
|
||||
result.status = status;
|
||||
return result;
|
||||
}
|
||||
|
||||
__hot static MDBX_cache_result_t cache_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data,
|
||||
MDBX_cache_entry_t *entry) {
|
||||
DKBUF_DEBUG;
|
||||
DEBUG("===> cached-get dbi %u, key [%s], entry %p (trunk %" PRIaTXN ", last_confirmed %" PRIaTXN
|
||||
", data offset %zu, len %u)",
|
||||
dbi, DKEY_DEBUG(key), __Wpedantic_format_voidptr(entry), entry->trunk_txnid, entry->last_confirmed_txnid,
|
||||
entry->offset, entry->length);
|
||||
|
||||
if (unlikely(entry->trunk_txnid > entry->last_confirmed_txnid))
|
||||
return cache_error(LOG_IFERR(MDBX_INVALID));
|
||||
|
||||
int err = check_txn(txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
tASSERT(txn, entry->offset || entry->length == 0);
|
||||
tASSERT(txn, entry->last_confirmed_txnid <= MAX_TXNID);
|
||||
if (unlikely(txn->txnid < entry->trunk_txnid))
|
||||
/* the used/read MVCC-snapshot is behind the accessible MVCC-range */
|
||||
return cache_fallback(txn, dbi, key, data, MDBX_CACHE_BEHIND);
|
||||
|
||||
if (likely(txn->txnid <= entry->last_confirmed_txnid)) {
|
||||
/* cache hit fast-path */
|
||||
data->iov_base = entry->offset ? ptr_disp(txn->env->dxb_mmap.base, entry->offset) : 0;
|
||||
data->iov_len = entry->length;
|
||||
tASSERT(txn, (!entry->offset && !entry->length) || is_inside_dxb_and_commited(txn, data->iov_base));
|
||||
return cache_result(data->iov_base ? MDBX_SUCCESS : MDBX_NOTFOUND, MDBX_CACHE_HIT);
|
||||
}
|
||||
|
||||
err = dbi_check(txn, dbi);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
const uint64_t committed_snapshot_txnid = txn_basis_snapshot(txn);
|
||||
txnid_t trunk_txnid = txn->front_txnid;
|
||||
if (unlikely(txn->dbi_state[dbi] & DBI_STALE)) {
|
||||
err = tbl_refresh((MDBX_txn *)txn, dbi);
|
||||
if (unlikely(err != MDBX_SUCCESS)) {
|
||||
if (err == MDBX_NOTFOUND) {
|
||||
/* the corresponding table has been deleted */
|
||||
not_found:
|
||||
data->iov_base = nullptr;
|
||||
data->iov_len = 0;
|
||||
MDBX_cache_status_t status = MDBX_CACHE_DIRTY;
|
||||
if (trunk_txnid <= committed_snapshot_txnid) {
|
||||
status = MDBX_CACHE_CONFIRMED;
|
||||
if (entry->offset || !entry->trunk_txnid) {
|
||||
status = MDBX_CACHE_REFRESHED;
|
||||
tASSERT(txn, (!entry->offset && !entry->trunk_txnid) || trunk_txnid > entry->trunk_txnid);
|
||||
entry->offset = 0;
|
||||
entry->length = 0;
|
||||
entry->trunk_txnid = trunk_txnid;
|
||||
}
|
||||
entry->last_confirmed_txnid = committed_snapshot_txnid;
|
||||
}
|
||||
return cache_result(MDBX_NOTFOUND, status);
|
||||
}
|
||||
return cache_error(LOG_IFERR(err));
|
||||
}
|
||||
}
|
||||
|
||||
if (txn->dbs[dbi].mod_txnid /* tree->mod_txnid maybe zero in a legacy DB */)
|
||||
trunk_txnid = txn->dbs[dbi].mod_txnid;
|
||||
if ((txn->flags & MDBX_TXN_RDONLY) == 0) {
|
||||
const MDBX_txn *scan = txn;
|
||||
do
|
||||
if ((scan->flags & MDBX_TXN_DIRTY) && (dbi == MAIN_DBI || (scan->dbi_state[dbi] & DBI_DIRTY))) {
|
||||
/* После коммита вложенных тразакций может быть mod_txnid > front */
|
||||
trunk_txnid = scan->front_txnid;
|
||||
break;
|
||||
}
|
||||
while (unlikely((scan = scan->parent) != nullptr));
|
||||
}
|
||||
|
||||
if (trunk_txnid <= entry->last_confirmed_txnid) {
|
||||
tASSERT(txn, (txn->dbi_state[dbi] & DBI_DIRTY) == 0);
|
||||
cache_confirmed:
|
||||
tASSERT(txn, trunk_txnid <= committed_snapshot_txnid && trunk_txnid <= entry->last_confirmed_txnid);
|
||||
tASSERT(txn, trunk_txnid == entry->trunk_txnid);
|
||||
data->iov_base = entry->offset ? ptr_disp(txn->env->dxb_mmap.base, entry->offset) : 0;
|
||||
data->iov_len = entry->length;
|
||||
tASSERT(txn, (!entry->offset && !entry->length) || is_inside_dxb_and_commited(txn, data->iov_base));
|
||||
if (entry->last_confirmed_txnid == committed_snapshot_txnid)
|
||||
return cache_result(data->iov_base ? MDBX_SUCCESS : MDBX_NOTFOUND, MDBX_CACHE_HIT);
|
||||
entry->last_confirmed_txnid = committed_snapshot_txnid;
|
||||
return cache_result(data->iov_base ? MDBX_SUCCESS : MDBX_NOTFOUND, MDBX_CACHE_CONFIRMED);
|
||||
}
|
||||
|
||||
if (unlikely(txn->dbs[dbi].root == P_INVALID)) {
|
||||
/* the corresponding table is empty now */
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
cursor_couple_t cx;
|
||||
err = cursor_init(&cx.outer, txn, dbi);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
alignkey_t aligned;
|
||||
err = check_key(&cx.outer, key, &aligned);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
cx.outer.top = 0;
|
||||
cx.outer.ki[0] = 0;
|
||||
err = page_get(&cx.outer, txn->dbs[dbi].root, &cx.outer.pg[0], trunk_txnid);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
page_t *mp = cx.outer.pg[0];
|
||||
if ((trunk_txnid = mp->txnid) <= entry->last_confirmed_txnid)
|
||||
goto cache_confirmed;
|
||||
|
||||
intptr_t ki = page_numkeys(mp) - 1;
|
||||
while (is_branch(mp)) {
|
||||
const struct node_search_result nsr = node_search(&cx.outer, key);
|
||||
if (likely(nsr.node))
|
||||
ki = cx.outer.ki[cx.outer.top] + (intptr_t)nsr.exact - 1;
|
||||
err = page_get(&cx.outer, node_pgno(page_node(mp, ki)), &mp, trunk_txnid);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
if ((trunk_txnid = mp->txnid) <= entry->last_confirmed_txnid)
|
||||
goto cache_confirmed;
|
||||
|
||||
ki = page_numkeys(mp) - 1;
|
||||
err = cursor_push(&cx.outer, mp, ki);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
}
|
||||
|
||||
if (!MDBX_DISABLE_VALIDATION && unlikely(!check_leaf_type(&cx.outer, mp))) {
|
||||
ERROR("unexpected leaf-page #%" PRIaPGNO " type 0x%x seen by cursor", mp->pgno, mp->flags);
|
||||
err = MDBX_CORRUPTED;
|
||||
return cache_error(LOG_IFERR(err));
|
||||
}
|
||||
|
||||
struct node_search_result nsr = node_search(&cx.outer, &aligned.key);
|
||||
if (!nsr.exact)
|
||||
goto not_found;
|
||||
|
||||
if (unlikely(node_flags(nsr.node) & N_DUP)) {
|
||||
/* TODO: It is possible to implement support, but need to think through the usage scenarios */
|
||||
err = MDBX_EMULTIVAL;
|
||||
return cache_error(LOG_IFERR(err));
|
||||
}
|
||||
|
||||
err = node_read(&cx.outer, nsr.node, data, mp);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return cache_error(LOG_IFERR(err));
|
||||
|
||||
if (trunk_txnid > committed_snapshot_txnid) {
|
||||
tASSERT(txn, trunk_txnid > entry->last_confirmed_txnid && trunk_txnid > entry->trunk_txnid);
|
||||
return cache_result(MDBX_SUCCESS, MDBX_CACHE_DIRTY);
|
||||
}
|
||||
|
||||
tASSERT(txn, is_inside_dxb_and_commited(txn, data->iov_base));
|
||||
tASSERT(txn, trunk_txnid <= committed_snapshot_txnid && trunk_txnid > entry->last_confirmed_txnid &&
|
||||
trunk_txnid > entry->trunk_txnid);
|
||||
entry->offset = ptr_dist(data->iov_base, txn->env->dxb_mmap.base);
|
||||
entry->length = (uint32_t)data->iov_len;
|
||||
entry->trunk_txnid = trunk_txnid;
|
||||
entry->last_confirmed_txnid = committed_snapshot_txnid;
|
||||
return cache_result(MDBX_SUCCESS, MDBX_CACHE_REFRESHED);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
__hot MDBX_cache_result_t mdbx_cache_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data,
|
||||
volatile MDBX_cache_entry_t *entry) {
|
||||
|
||||
if (unlikely(!key || !data || !entry))
|
||||
return cache_error(LOG_IFERR(MDBX_EINVAL));
|
||||
|
||||
MDBX_cache_entry_t local = *entry;
|
||||
while (true) {
|
||||
MDBX_cache_entry_t again;
|
||||
again.last_confirmed_txnid = safe64_read((mdbx_atomic_uint64_t *)&entry->last_confirmed_txnid);
|
||||
if (unlikely(again.last_confirmed_txnid > MAX_TXNID)) {
|
||||
atomic_yield();
|
||||
again.last_confirmed_txnid = safe64_read((mdbx_atomic_uint64_t *)&entry->last_confirmed_txnid);
|
||||
if (unlikely(again.last_confirmed_txnid > MAX_TXNID)) {
|
||||
atomic_yield();
|
||||
again.last_confirmed_txnid = safe64_read((mdbx_atomic_uint64_t *)&entry->last_confirmed_txnid);
|
||||
if (unlikely(again.last_confirmed_txnid > MAX_TXNID))
|
||||
return cache_fallback(txn, dbi, key, data, MDBX_CACHE_RACE);
|
||||
}
|
||||
}
|
||||
|
||||
again.trunk_txnid = entry->trunk_txnid;
|
||||
again.offset = entry->offset;
|
||||
again.length = entry->length;
|
||||
if (local.last_confirmed_txnid == again.last_confirmed_txnid && local.trunk_txnid == again.trunk_txnid &&
|
||||
local.offset == again.offset && local.length == again.length)
|
||||
break;
|
||||
|
||||
local = again;
|
||||
atomic_yield();
|
||||
}
|
||||
|
||||
MDBX_cache_result_t result = cache_get(txn, dbi, key, data, &local);
|
||||
if (result.status > MDBX_CACHE_HIT) {
|
||||
tASSERT(txn, local.last_confirmed_txnid < MAX_TXNID && local.trunk_txnid <= local.last_confirmed_txnid &&
|
||||
local.trunk_txnid > 0);
|
||||
while (true) {
|
||||
const txnid_t snap = safe64_read((mdbx_atomic_uint64_t *)&entry->last_confirmed_txnid);
|
||||
if (snap >= local.last_confirmed_txnid) {
|
||||
result.status = MDBX_CACHE_RACE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (likely(safe64_reset_compare((mdbx_atomic_uint64_t *)&entry->last_confirmed_txnid, snap))) {
|
||||
entry->trunk_txnid = 0;
|
||||
osal_compiler_barrier();
|
||||
entry->offset = local.offset;
|
||||
entry->length = local.length;
|
||||
entry->trunk_txnid = local.trunk_txnid;
|
||||
safe64_write((mdbx_atomic_uint64_t *)&entry->last_confirmed_txnid, local.last_confirmed_txnid);
|
||||
break;
|
||||
}
|
||||
|
||||
atomic_yield();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
__hot MDBX_cache_result_t mdbx_cache_get_SingleThreaded(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key,
|
||||
MDBX_val *data, MDBX_cache_entry_t *entry) {
|
||||
if (unlikely(!key || !data || !entry))
|
||||
return cache_error(LOG_IFERR(MDBX_EINVAL));
|
||||
|
||||
return cache_get(txn, dbi, key, data, entry);
|
||||
}
|
||||
|
||||
LIBMDBX_API void mdbx_cache_init(MDBX_cache_entry_t *entry) { __inline_mdbx_cache_init(entry); }
|
||||
@@ -249,6 +249,8 @@ __dll_export
|
||||
#else
|
||||
#if defined(__ANDROID_API__)
|
||||
"Android" MDBX_STRINGIFY(__ANDROID_API__)
|
||||
#elif defined(__OHOS__)
|
||||
"Harmony OS"
|
||||
#elif defined(__linux__) || defined(__gnu_linux__)
|
||||
"Linux"
|
||||
#elif defined(EMSCRIPTEN) || defined(__EMSCRIPTEN__)
|
||||
@@ -296,7 +298,9 @@ __dll_export
|
||||
|
||||
"-"
|
||||
|
||||
#if defined(__amd64__)
|
||||
#if defined(__e2k__) || defined(__elbrus__)
|
||||
"Elbrus"
|
||||
#elif defined(__amd64__)
|
||||
"AMD64"
|
||||
#elif defined(__ia32__)
|
||||
"IA32"
|
||||
@@ -333,6 +337,8 @@ __dll_export
|
||||
"SPARC"
|
||||
#elif defined(__s390__) || defined(__s390) || defined(__zarch__) || defined(__zarch)
|
||||
"S390"
|
||||
#elif defined(__riscv) || defined(__riscv__) || defined(__RISCV) || defined(__RISCV__)
|
||||
"RISC-V (стеклянные бусы)"
|
||||
#else
|
||||
"UnknownARCH"
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push, 1)
|
||||
#if _MSC_VER > 1913
|
||||
#pragma warning(disable : 5054) /* deprecated between enumerations of different types */
|
||||
#endif
|
||||
#endif /* MSVC */
|
||||
|
||||
typedef struct dp dp_t;
|
||||
typedef struct dpl dpl_t;
|
||||
typedef struct kvx kvx_t;
|
||||
@@ -561,6 +568,8 @@ MDBX_MAYBE_UNUSED static void static_checks(void) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#ifndef __cplusplus
|
||||
|
||||
#include "node.h"
|
||||
|
||||
#include "dbi.h"
|
||||
@@ -588,3 +597,9 @@ MDBX_MAYBE_UNUSED static void static_checks(void) {
|
||||
#include "walk.h"
|
||||
|
||||
#include "sort.h"
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif /* MSVC */
|
||||
|
||||
24
src/mdbx.c++
24
src/mdbx.c++
@@ -27,6 +27,8 @@
|
||||
#include <cctype> // for isxdigit(), etc
|
||||
#include <system_error>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#if 0 /* Unused for now */
|
||||
@@ -1492,7 +1494,7 @@ void txn_managed::commit(commit_latency *latency) {
|
||||
}
|
||||
|
||||
void txn_managed::commit_embark_read() {
|
||||
auto env = this->env();
|
||||
auto env = handle_->env;
|
||||
commit();
|
||||
error::success_or_throw(::mdbx_txn_begin(env, nullptr, MDBX_TXN_RDONLY, &handle_));
|
||||
}
|
||||
@@ -1607,6 +1609,26 @@ __cold bool txn::rename_map(const ::std::string &old_name, const ::std::string &
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void cursor::update_current(const slice &value) {
|
||||
default_buffer holder;
|
||||
auto key = current().key;
|
||||
if (error::boolean_or_throw(mdbx_is_dirty(handle_->txn, key.iov_base)))
|
||||
key = holder.assign(key);
|
||||
|
||||
update(key, value);
|
||||
}
|
||||
|
||||
slice cursor::reverse_current(size_t value_length) {
|
||||
default_buffer holder;
|
||||
auto key = current().key;
|
||||
if (error::boolean_or_throw(mdbx_is_dirty(handle_->txn, key.iov_base)))
|
||||
key = holder.assign(key);
|
||||
|
||||
return update_reserve(key, value_length);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
__cold ::std::ostream &operator<<(::std::ostream &out, const slice &it) {
|
||||
out << "{";
|
||||
if (!it.is_valid())
|
||||
|
||||
17
src/osal.c
17
src/osal.c
@@ -110,23 +110,6 @@ typedef struct _FILE_PROVIDER_EXTERNAL_INFO_V1 {
|
||||
#define ERROR_NOT_CAPABLE 775L
|
||||
#endif
|
||||
|
||||
#ifndef _CRT_ASSERT
|
||||
#define _CRT_ASSERT 2
|
||||
#endif
|
||||
|
||||
#ifndef _ACRTIMP
|
||||
#if defined _CRTIMP && !defined _VCRT_DEFINED_CRTIMP
|
||||
#define _ACRTIMP _CRTIMP
|
||||
#elif !defined _CORECRT_BUILD && defined _DLL
|
||||
#define _ACRTIMP __declspec(dllimport)
|
||||
#else
|
||||
#define _ACRTIMP
|
||||
#endif
|
||||
#endif
|
||||
|
||||
_ACRTIMP int __cdecl _CrtDbgReport(_In_ int _ReportType, _In_opt_z_ char const *_FileName, _In_ int _Linenumber,
|
||||
_In_opt_z_ char const *_ModuleName, _In_opt_z_ char const *_Format, ...);
|
||||
|
||||
#endif /* _WIN32 || _WIN64 */
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
@@ -31,7 +31,7 @@ MDBX_INTERNAL int __must_check_result dxb_sync_locked(MDBX_env *env, unsigned fl
|
||||
#if defined(ENABLE_MEMCHECK) || defined(__SANITIZE_ADDRESS__)
|
||||
MDBX_INTERNAL void dxb_sanitize_tail(MDBX_env *env, MDBX_txn *txn);
|
||||
#else
|
||||
static inline void dxb_sanitize_tail(MDBX_env *env, MDBX_txn *txn) {
|
||||
MDBX_MAYBE_UNUSED static inline void dxb_sanitize_tail(MDBX_env *env, MDBX_txn *txn) {
|
||||
(void)env;
|
||||
(void)txn;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ typedef struct MDBX_rkl {
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_init(rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_clear(rkl_t *rkl);
|
||||
static inline void rkl_clear_and_shrink(rkl_t *rkl) { rkl_clear(rkl); /* TODO */ }
|
||||
MDBX_MAYBE_UNUSED static inline void rkl_clear_and_shrink(rkl_t *rkl) { rkl_clear(rkl); /* TODO */ }
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_destroy(rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_destructive_move(rkl_t *src, rkl_t *dst);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result int rkl_copy(const rkl_t *src, rkl_t *dst);
|
||||
|
||||
@@ -25,6 +25,6 @@ MDBX_MAYBE_UNUSED MDBX_INTERNAL void txl_sort(txl_t txl);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool txl_contain(const txl_t txl, txnid_t id);
|
||||
|
||||
static inline size_t txl_alloclen(const_txl_t txl) { return txl[-1]; }
|
||||
MDBX_MAYBE_UNUSED static inline size_t txl_alloclen(const_txl_t txl) { return txl[-1]; }
|
||||
|
||||
static inline size_t txl_size(const_txl_t txl) { return txl[0]; }
|
||||
MDBX_MAYBE_UNUSED static inline size_t txl_size(const_txl_t txl) { return txl[0]; }
|
||||
|
||||
@@ -13,12 +13,14 @@ MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline size_t field_alignme
|
||||
}
|
||||
|
||||
/* read-thunk for UB-sanitizer */
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline uint8_t peek_u8(const uint8_t *__restrict ptr) { return *ptr; }
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint8_t peek_u8(const uint8_t *__restrict ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/* write-thunk for UB-sanitizer */
|
||||
static inline void poke_u8(uint8_t *__restrict ptr, const uint8_t v) { *ptr = v; }
|
||||
MDBX_MAYBE_UNUSED static inline void poke_u8(uint8_t *__restrict ptr, const uint8_t v) { *ptr = v; }
|
||||
|
||||
static inline void *bcopy_2(void *__restrict dst, const void *__restrict src) {
|
||||
MDBX_MAYBE_UNUSED static inline void *bcopy_2(void *__restrict dst, const void *__restrict src) {
|
||||
uint8_t *__restrict d = (uint8_t *)dst;
|
||||
const uint8_t *__restrict s = (uint8_t *)src;
|
||||
d[0] = s[0];
|
||||
@@ -26,7 +28,7 @@ static inline void *bcopy_2(void *__restrict dst, const void *__restrict src) {
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline void *bcopy_4(void *const __restrict dst, const void *const __restrict src) {
|
||||
MDBX_MAYBE_UNUSED static inline void *bcopy_4(void *const __restrict dst, const void *const __restrict src) {
|
||||
uint8_t *__restrict d = (uint8_t *)dst;
|
||||
const uint8_t *__restrict s = (uint8_t *)src;
|
||||
d[0] = s[0];
|
||||
@@ -36,7 +38,7 @@ static inline void *bcopy_4(void *const __restrict dst, const void *const __rest
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline void *bcopy_8(void *const __restrict dst, const void *const __restrict src) {
|
||||
MDBX_MAYBE_UNUSED static inline void *bcopy_8(void *const __restrict dst, const void *const __restrict src) {
|
||||
uint8_t *__restrict d = (uint8_t *)dst;
|
||||
const uint8_t *__restrict s = (uint8_t *)src;
|
||||
d[0] = s[0];
|
||||
@@ -50,8 +52,8 @@ static inline void *bcopy_8(void *const __restrict dst, const void *const __rest
|
||||
return d;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline uint16_t unaligned_peek_u16(const size_t expected_alignment,
|
||||
const void *const ptr) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint16_t unaligned_peek_u16(const size_t expected_alignment,
|
||||
const void *const ptr) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 2 || (expected_alignment % sizeof(uint16_t)) == 0)
|
||||
return *(const uint16_t *)ptr;
|
||||
@@ -66,7 +68,8 @@ MDBX_NOTHROW_PURE_FUNCTION static inline uint16_t unaligned_peek_u16(const size_
|
||||
}
|
||||
}
|
||||
|
||||
static inline void unaligned_poke_u16(const size_t expected_alignment, void *const __restrict ptr, const uint16_t v) {
|
||||
MDBX_MAYBE_UNUSED static inline void unaligned_poke_u16(const size_t expected_alignment, void *const __restrict ptr,
|
||||
const uint16_t v) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 2 || (expected_alignment % sizeof(v)) == 0)
|
||||
*(uint16_t *)ptr = v;
|
||||
@@ -79,8 +82,8 @@ static inline void unaligned_poke_u16(const size_t expected_alignment, void *con
|
||||
}
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t unaligned_peek_u32(const size_t expected_alignment,
|
||||
const void *const __restrict ptr) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint32_t
|
||||
unaligned_peek_u32(const size_t expected_alignment, const void *const __restrict ptr) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 4 || (expected_alignment % sizeof(uint32_t)) == 0)
|
||||
return *(const uint32_t *)ptr;
|
||||
@@ -99,7 +102,8 @@ MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t unaligned_peek_u32(const size_
|
||||
}
|
||||
}
|
||||
|
||||
static inline void unaligned_poke_u32(const size_t expected_alignment, void *const __restrict ptr, const uint32_t v) {
|
||||
MDBX_MAYBE_UNUSED static inline void unaligned_poke_u32(const size_t expected_alignment, void *const __restrict ptr,
|
||||
const uint32_t v) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 4 || (expected_alignment % sizeof(v)) == 0)
|
||||
*(uint32_t *)ptr = v;
|
||||
@@ -115,8 +119,8 @@ static inline void unaligned_poke_u32(const size_t expected_alignment, void *con
|
||||
}
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline uint64_t unaligned_peek_u64(const size_t expected_alignment,
|
||||
const void *const __restrict ptr) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint64_t
|
||||
unaligned_peek_u64(const size_t expected_alignment, const void *const __restrict ptr) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 8 || (expected_alignment % sizeof(uint64_t)) == 0)
|
||||
return *(const uint64_t *)ptr;
|
||||
@@ -135,8 +139,8 @@ MDBX_NOTHROW_PURE_FUNCTION static inline uint64_t unaligned_peek_u64(const size_
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint64_t unaligned_peek_u64_volatile(const size_t expected_alignment,
|
||||
const volatile void *const __restrict ptr) {
|
||||
MDBX_MAYBE_UNUSED static inline uint64_t unaligned_peek_u64_volatile(const size_t expected_alignment,
|
||||
const volatile void *const __restrict ptr) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
assert(expected_alignment % sizeof(uint32_t) == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 8 || (expected_alignment % sizeof(uint64_t)) == 0)
|
||||
@@ -152,7 +156,8 @@ static inline uint64_t unaligned_peek_u64_volatile(const size_t expected_alignme
|
||||
}
|
||||
}
|
||||
|
||||
static inline void unaligned_poke_u64(const size_t expected_alignment, void *const __restrict ptr, const uint64_t v) {
|
||||
MDBX_MAYBE_UNUSED static inline void unaligned_poke_u64(const size_t expected_alignment, void *const __restrict ptr,
|
||||
const uint64_t v) {
|
||||
assert((uintptr_t)ptr % expected_alignment == 0);
|
||||
if (MDBX_UNALIGNED_OK >= 8 || (expected_alignment % sizeof(v)) == 0)
|
||||
*(uint64_t *)ptr = v;
|
||||
@@ -183,7 +188,7 @@ static inline void unaligned_poke_u64(const size_t expected_alignment, void *con
|
||||
#define UNALIGNED_POKE_64(ptr, struct, field, value) \
|
||||
unaligned_poke_u64(1, ptr_disp(ptr, offsetof(struct, field)), value)
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t peek_pgno(const void *const __restrict ptr) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t peek_pgno(const void *const __restrict ptr) {
|
||||
if (sizeof(pgno_t) == sizeof(uint32_t))
|
||||
return (pgno_t)unaligned_peek_u32(1, ptr);
|
||||
else if (sizeof(pgno_t) == sizeof(uint64_t))
|
||||
@@ -195,7 +200,7 @@ MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t peek_pgno(const void *const __re
|
||||
}
|
||||
}
|
||||
|
||||
static inline void poke_pgno(void *const __restrict ptr, const pgno_t pgno) {
|
||||
MDBX_MAYBE_UNUSED static inline void poke_pgno(void *const __restrict ptr, const pgno_t pgno) {
|
||||
if (sizeof(pgno) == sizeof(uint32_t))
|
||||
unaligned_poke_u32(1, ptr, pgno);
|
||||
else if (sizeof(pgno) == sizeof(uint64_t))
|
||||
|
||||
@@ -292,7 +292,6 @@ else()
|
||||
add_extra_test(dbi)
|
||||
add_extra_test(open)
|
||||
add_extra_test(txn)
|
||||
add_extra_test(get_cached)
|
||||
endif()
|
||||
add_extra_test(hex_base64_base58)
|
||||
endif()
|
||||
|
||||
@@ -42,7 +42,7 @@ static char log_buffer[1024];
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
bool case0_trivia(mdbx::env env) {
|
||||
bool case0(mdbx::env env) {
|
||||
auto txn = env.start_write();
|
||||
auto table = txn.create_map("case0", mdbx::key_mode::usual, mdbx::value_mode::single);
|
||||
auto cursor_1 = txn.open_cursor(table);
|
||||
@@ -395,7 +395,7 @@ int doit() {
|
||||
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(),
|
||||
mdbx::env::operate_parameters(N + 2, 0, mdbx::env::nested_transactions));
|
||||
|
||||
bool ok = case0_trivia(env);
|
||||
bool ok = case0(env);
|
||||
ok = case1(env) && ok;
|
||||
ok = case2(env) && ok;
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
#include "mdbx.h++"
|
||||
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#if defined(__cpp_lib_latch) && __cpp_lib_latch >= 201907L
|
||||
#include <latch>
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_MEMCHECK) || defined(MDBX_CI)
|
||||
#if MDBX_DEBUG || !defined(NDEBUG)
|
||||
#define RELIEF_FACTOR 16
|
||||
#else
|
||||
#define RELIEF_FACTOR 8
|
||||
#endif
|
||||
#elif MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
|
||||
#define RELIEF_FACTOR 4
|
||||
#elif UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
|
||||
#define RELIEF_FACTOR 2
|
||||
#else
|
||||
#define RELIEF_FACTOR 1
|
||||
#endif
|
||||
|
||||
// static const auto NN = 1000u / RELIEF_FACTOR;
|
||||
|
||||
#if defined(__cpp_lib_latch) && __cpp_lib_latch >= 201907L
|
||||
static const auto N = std::min(17u, std::thread::hardware_concurrency());
|
||||
#else
|
||||
static const auto N = 3u;
|
||||
#endif
|
||||
|
||||
static void logger_nofmt(MDBX_log_level_t loglevel, const char *function, int line, const char *msg,
|
||||
unsigned length) noexcept {
|
||||
(void)length;
|
||||
(void)loglevel;
|
||||
fflush(nullptr);
|
||||
std::cout << function << ":" << line << " " << msg;
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
static char log_buffer[1024];
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
typedef MDBX_cache_result_t (*get_cached_t)(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data,
|
||||
MDBX_cache_entry_t *entry);
|
||||
|
||||
static bool check_state(const MDBX_cache_result_t &r, const MDBX_error_t wanna_errcode,
|
||||
const MDBX_cache_status_t wanna_status, unsigned line) {
|
||||
if (r.errcode == wanna_errcode && r.status == wanna_status)
|
||||
return true;
|
||||
std::cerr << "unecpected (at " << line
|
||||
<< "): "
|
||||
"err "
|
||||
<< r.errcode << " (wanna " << wanna_errcode
|
||||
<< "), "
|
||||
"status "
|
||||
<< r.status << " (wanna " << wanna_status << ")" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_state_and_value(const MDBX_cache_result_t &r, const mdbx::slice &value,
|
||||
const MDBX_error_t wanna_errcode, const MDBX_cache_status_t wanna_status,
|
||||
const mdbx::slice &wanna_value, unsigned line) {
|
||||
|
||||
bool ok = check_state(r, wanna_errcode, wanna_status, line);
|
||||
if (value != wanna_value) {
|
||||
std::cerr << "mismatch value (at " << line << "): " << value << " (wanna " << wanna_value << ")" << std::endl;
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool case0_trivia(mdbx::env env) {
|
||||
get_cached_t get_cached = mdbx_cache_get_SingleThreaded;
|
||||
auto txn = env.start_write();
|
||||
auto table = txn.create_map("case0", mdbx::key_mode::usual, mdbx::value_mode::single);
|
||||
|
||||
MDBX_cache_entry_t entry;
|
||||
mdbx_cache_init(&entry);
|
||||
MDBX_val data;
|
||||
MDBX_cache_result_t r;
|
||||
|
||||
bool ok = true;
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state(r, MDBX_NOTFOUND, MDBX_CACHE_DIRTY, __LINE__) && ok;
|
||||
|
||||
txn.commit_embark_read();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state(r, MDBX_NOTFOUND, MDBX_CACHE_REFRESHED, __LINE__) && ok;
|
||||
|
||||
// drops the table as if it were done by another process
|
||||
{
|
||||
auto params = mdbx::env::operate_parameters(42);
|
||||
params.options.no_sticky_threads = true;
|
||||
mdbx::env_managed env2(env.get_path(), params);
|
||||
auto txn2 = env2.start_write();
|
||||
txn2.drop_map("case0");
|
||||
txn2.commit();
|
||||
}
|
||||
txn.renew_reading();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state(r, MDBX_NOTFOUND, MDBX_CACHE_CONFIRMED, __LINE__) && ok;
|
||||
|
||||
txn.abort();
|
||||
txn = env.start_write();
|
||||
table = txn.create_map("case0", mdbx::key_mode::usual, mdbx::value_mode::single);
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state(r, MDBX_NOTFOUND, MDBX_CACHE_DIRTY, __LINE__) && ok;
|
||||
|
||||
txn.commit_embark_read();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state(r, MDBX_NOTFOUND, MDBX_CACHE_CONFIRMED, __LINE__) && ok;
|
||||
|
||||
txn.abort();
|
||||
txn = env.start_write();
|
||||
txn.insert(table, "key", "value");
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_DIRTY, "value", __LINE__) && ok;
|
||||
|
||||
txn.commit_embark_read();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_REFRESHED, "value", __LINE__) && ok;
|
||||
|
||||
txn.abort();
|
||||
txn = env.start_write();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_HIT, "value", __LINE__) && ok;
|
||||
txn.update(table, "key", "42");
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_DIRTY, "42", __LINE__) && ok;
|
||||
|
||||
txn.commit_embark_read();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_REFRESHED, "42", __LINE__) && ok;
|
||||
|
||||
MDBX_cache_entry_t entry2;
|
||||
mdbx_cache_init(&entry2);
|
||||
txn.abort();
|
||||
txn = env.start_write();
|
||||
txn.insert(table, "key2", "value2");
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_DIRTY, "42", __LINE__) && ok;
|
||||
r = get_cached(txn, table, mdbx::slice("key2"), &data, &entry2);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_DIRTY, "value2", __LINE__) && ok;
|
||||
|
||||
txn.commit_embark_read();
|
||||
r = get_cached(txn, table, mdbx::slice("key"), &data, &entry);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_REFRESHED, "42", __LINE__) && ok;
|
||||
r = get_cached(txn, table, mdbx::slice("key2"), &data, &entry2);
|
||||
ok = check_state_and_value(r, data, MDBX_SUCCESS, MDBX_CACHE_REFRESHED, "value2", __LINE__) && ok;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
int doit() {
|
||||
mdbx::path db_filename = "test-get-cached";
|
||||
mdbx::env::remove(db_filename);
|
||||
|
||||
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(),
|
||||
mdbx::env::operate_parameters(N + 2, 0, mdbx::env::nested_transactions));
|
||||
|
||||
bool ok = case0_trivia(env);
|
||||
// ok = case1(env) && ok;
|
||||
// ok = case2(env) && ok;
|
||||
|
||||
if (ok) {
|
||||
std::cout << "OK\n";
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
std::cout << "FAIL!\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT | MDBX_DBG_LEGACY_MULTIOPEN, logger_nofmt, log_buffer,
|
||||
sizeof(log_buffer));
|
||||
try {
|
||||
return doit();
|
||||
} catch (const std::exception &ex) {
|
||||
std::cerr << "Exception: " << ex.what() << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user