mirror of
https://gitflic.ru/project/erthink/libmdbx.git
synced 2025-05-14 14:58:29 +00:00
mdbx: merge branch devel
.
This commit is contained in:
commit
d1023dc6b5
@ -132,6 +132,8 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/preface.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/proto.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/refund.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/rkl.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/rkl.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/sort.h"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.c"
|
||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.h"
|
||||
@ -832,6 +834,8 @@ else()
|
||||
"${MDBX_SOURCE_DIR}/preface.h"
|
||||
"${MDBX_SOURCE_DIR}/proto.h"
|
||||
"${MDBX_SOURCE_DIR}/refund.c"
|
||||
"${MDBX_SOURCE_DIR}/rkl.c"
|
||||
"${MDBX_SOURCE_DIR}/rkl.h"
|
||||
"${MDBX_SOURCE_DIR}/sort.h"
|
||||
"${MDBX_SOURCE_DIR}/spill.c"
|
||||
"${MDBX_SOURCE_DIR}/spill.h"
|
||||
|
57
ChangeLog.md
57
ChangeLog.md
@ -4,7 +4,7 @@ ChangeLog
|
||||
English version [by liar Google](https://libmdbx-dqdkfa-ru.translate.goog/md__change_log.html?_x_tr_sl=ru&_x_tr_tl=en)
|
||||
and [by Yandex](https://translated.turbopages.org/proxy_u/ru-en.en/https/libmdbx.dqdkfa.ru/md__change_log.html).
|
||||
|
||||
## v0.14.1 в активной разработке без конкретизации даты выпуска
|
||||
## v0.14.1 выпуск запланирован в начале мая
|
||||
|
||||
Первый выпуск в новом кусте/линейке версий с добавлением функционала, расширением API и внутренними переработками.
|
||||
|
||||
@ -19,9 +19,62 @@ and [by Yandex](https://translated.turbopages.org/proxy_u/ru-en.en/https/libmdbx
|
||||
- [maxc0d3r](https://gitflic.ru/user/maxc0d3r) for bug reporting and testing.
|
||||
- [Алексею Костюку (aka Keller)](https://t.me/keller18306) за сообщения о проблеме копирования на NFS.
|
||||
|
||||
|
||||
Новое:
|
||||
|
||||
- Переработан код обновления GC и возврата страниц при фиксации транзакций.
|
||||
|
||||
Возникающая при этом задача алгоритмически сложна, так как список
|
||||
возвращаемых страниц находится в рекурсивной зависимости от самой
|
||||
процедуры возврата и связанных с этим операций, а прямые решения во
|
||||
многих случаях приводят к многократному росту накладных расходов.
|
||||
Поэтому исторически эта часть кода была запутанным наслоением «сдержек и
|
||||
противовесов», что создавало препятствие для развития. В ходе этой
|
||||
доработки, унаследованный из LMDB код связанный с обновлением GC, был
|
||||
полностью заменен вместе со всеми базирующимися на нём заплатками.
|
||||
|
||||
Новая реализация использует контейнеры идентификаторы (aka RKL),
|
||||
комбинирующие внутри списки элементов и непрерывные интервалы, что
|
||||
позволяет предельно сократить накладные расходы и упросить реализацию
|
||||
остальных алгоритмов. Основывается новая реализация на простом
|
||||
прагматичном подходе «резервирования со взвешенным запасом». Для
|
||||
подавляющего подмножества сценариев этого достаточно для однопроходного
|
||||
обновления GC, с общей сложностью от `O(1)` для мелких транзакций, до
|
||||
`O(log(N))` для огромных. При этом реализованный еще в 0.12.1 подход «Big
|
||||
Foot» (дробление больших списков retired-страниц) полностью избавляет GC
|
||||
от потребности в последовательностях смежных/соседствующих страниц и
|
||||
одновременно позволяет работать новому коду обновления GC только по
|
||||
самому простому и быстрому пути.
|
||||
|
||||
Тем не менее, при намеренном отключении «Big Foot», либо при работы с БД
|
||||
от старых версий движка без «Big Foot», возможны сложные ситуации, когда
|
||||
в GC могут огромные списки страниц, которые желательно дробить при
|
||||
возвращении неиспользованных переработанных остатков. В таких сценариях
|
||||
для возврата в GC требуется создавать больше записей чем было исходно
|
||||
переработано, что может приводить к нехватке имеющихся/переработанных
|
||||
идентификаторов. Тогда в игру вступает следующая часть нового кода,
|
||||
поиск в GC «дыр» (неиспользуемых промежутков/интервалов в пространстве
|
||||
ключей GC). Далее, если свободных идентификаторов (неиспользуемого
|
||||
пространства ключей GC) будет недостаточно, что весьма вероятно в
|
||||
некоторых сценариях, будет решаться задача родственная «укладке
|
||||
рюкзака». В конечном итоге, неиспользованные переработанные страницы
|
||||
будут возвращены в GC, с максимально равномерным
|
||||
распределением/дроблением и использованием имеющихся последовательностей
|
||||
смежных/соседствующих страниц, что гарантирует близость к теоретическому
|
||||
минимуму суммарной стоимости текущих действий и последующих операций.
|
||||
|
||||
На данный момент нет известных практических сценариев ведущих к
|
||||
отказу/неуспеху новой реализации обновления GC. Но гипотетически такие
|
||||
случаи возможны, как из-за ошибок/недочетов в реализации, так и из-за
|
||||
использования катастрофически неудачных режимов работы и значений опций
|
||||
(например `MDBX_opt_rp_augment_limit`). В текущем понимании, в том числе
|
||||
основываясь на объем тестирования, вероятность проявления
|
||||
ошибок/недочетов оценивается как крайне низкая, а устраняться замеченные
|
||||
проблемы будут по мере обнаружения. Однако, полностью автоматическое
|
||||
решение самых кошмарных и запутанных ситуаций с GC следует ожидать
|
||||
только при реализации дефрагментации — просто потому что нет иного
|
||||
рационального способа решения, за вычетом копирования БД с
|
||||
дефрагментацией.
|
||||
|
||||
- Добавлена опция сборки `MDBX_NOSUCCESS_PURE_COMMIT` предназначенная для отладки кода пользователя.
|
||||
По-умолчанию опция выключена и при фиксации пустых транзакции возвращается `MDBX_SUCCESS`.
|
||||
При включении опции, фиксация пишущих транзакций без каких-либо изменений считается нештатным поведением, с возвратом из `mdbx_txn_commit()` кода `MDBX_RESULT_TRUE` вместо `MDBX_SUCCESS`.
|
||||
|
@ -722,6 +722,7 @@ $(DIST_DIR)/@tmp-internals.inc: $(DIST_DIR)/@tmp-essentials.inc src/version.c $(
|
||||
-e '/#include "essentials.h"/d' \
|
||||
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
|
||||
-e '/#include "proto.h"/r src/proto.h' \
|
||||
-e '/#include "rkl.h"/r src/rkl.h' \
|
||||
-e '/#include "txl.h"/r src/txl.h' \
|
||||
-e '/#include "unaligned.h"/r src/unaligned.h' \
|
||||
-e '/#include "cogs.h"/r src/cogs.h' \
|
||||
|
6
mdbx.h
6
mdbx.h
@ -2775,10 +2775,10 @@ typedef struct MDBX_stat MDBX_stat;
|
||||
* Legacy mdbx_env_stat() correspond to calling \ref mdbx_env_stat_ex() with the
|
||||
* null `txn` argument.
|
||||
*
|
||||
* \param [in] env An environment handle returned by \ref mdbx_env_create()
|
||||
* \param [in] txn A transaction handle returned by \ref mdbx_txn_begin()
|
||||
* \param [in] env An environment handle returned by \ref mdbx_env_create().
|
||||
* \param [in] txn A transaction handle returned by \ref mdbx_txn_begin().
|
||||
* \param [out] stat The address of an \ref MDBX_stat structure where
|
||||
* the statistics will be copied
|
||||
* the statistics will be copied.
|
||||
* \param [in] bytes The size of \ref MDBX_stat.
|
||||
*
|
||||
* \returns A non-zero error value on failure and 0 on success. */
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "page-ops.c"
|
||||
#include "pnl.c"
|
||||
#include "refund.c"
|
||||
#include "rkl.c"
|
||||
#include "spill.c"
|
||||
#include "table.c"
|
||||
#include "tls.c"
|
||||
|
@ -955,7 +955,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
|
||||
env->basal_txn->wr.troika = meta_tap(env);
|
||||
eASSERT(env, !env->txn && !env->basal_txn->nested);
|
||||
env->basal_txn->txnid = env->basal_txn->wr.troika.txnid[env->basal_txn->wr.troika.recent];
|
||||
txn_snapshot_oldest(env->basal_txn);
|
||||
txn_gc_detent(env->basal_txn);
|
||||
}
|
||||
|
||||
/* get untouched params from current TXN or DB */
|
||||
|
@ -147,6 +147,9 @@ void env_options_adjust_dp_limit(MDBX_env *env) {
|
||||
if (env->options.dp_limit < CURSOR_STACK_SIZE * 4)
|
||||
env->options.dp_limit = CURSOR_STACK_SIZE * 4;
|
||||
}
|
||||
#ifdef MDBX_DEBUG_DPL_LIMIT
|
||||
env->options.dp_limit = MDBX_DEBUG_DPL_LIMIT;
|
||||
#endif /* MDBX_DEBUG_DPL_LIMIT */
|
||||
if (env->options.dp_initial > env->options.dp_limit && env->options.dp_initial > default_dp_initial(env))
|
||||
env->options.dp_initial = env->options.dp_limit;
|
||||
env->options.need_dp_limit_adjust = false;
|
||||
|
@ -514,23 +514,25 @@ int mdbx_txn_info(const MDBX_txn *txn, MDBX_txn_info *info, bool scan_rlt) {
|
||||
info->txn_reader_lag = INT64_MAX;
|
||||
lck_t *const lck = env->lck_mmap.lck;
|
||||
if (scan_rlt && lck) {
|
||||
txnid_t oldest_snapshot = txn->txnid;
|
||||
txnid_t oldest_reading = txn->txnid;
|
||||
const size_t snap_nreaders = atomic_load32(&lck->rdt_length, mo_AcquireRelease);
|
||||
if (snap_nreaders) {
|
||||
oldest_snapshot = txn_snapshot_oldest(txn);
|
||||
if (oldest_snapshot == txn->txnid - 1) {
|
||||
/* check if there is at least one reader */
|
||||
bool exists = false;
|
||||
txn_gc_detent(txn);
|
||||
oldest_reading = txn->env->gc.detent;
|
||||
if (oldest_reading == txn->wr.troika.txnid[txn->wr.troika.recent]) {
|
||||
/* Если самый старый используемый снимок является предыдущим, т. е. непосредственно предшествующим текущей
|
||||
* транзакции, то просматриваем таблицу читателей чтобы выяснить действительно ли снимок используется
|
||||
* читателями. */
|
||||
oldest_reading = txn->txnid;
|
||||
for (size_t i = 0; i < snap_nreaders; ++i) {
|
||||
if (atomic_load32(&lck->rdt[i].pid, mo_Relaxed) && txn->txnid > safe64_read(&lck->rdt[i].txnid)) {
|
||||
exists = true;
|
||||
if (atomic_load32(&lck->rdt[i].pid, mo_Relaxed) && txn->env->gc.detent == safe64_read(&lck->rdt[i].txnid)) {
|
||||
oldest_reading = txn->env->gc.detent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
oldest_snapshot += !exists;
|
||||
}
|
||||
}
|
||||
info->txn_reader_lag = txn->txnid - oldest_snapshot;
|
||||
info->txn_reader_lag = txn->txnid - oldest_reading;
|
||||
}
|
||||
}
|
||||
|
||||
|
28
src/audit.c
28
src/audit.c
@ -24,12 +24,11 @@ static size_t audit_db_used(const tree_t *db) {
|
||||
return db ? (size_t)db->branch_pages + (size_t)db->leaf_pages + (size_t)db->large_pages : 0;
|
||||
}
|
||||
|
||||
__cold static int audit_ex_locked(MDBX_txn *txn, size_t retired_stored, bool dont_filter_gc) {
|
||||
__cold static int audit_ex_locked(MDBX_txn *txn, const size_t retired_stored, const bool dont_filter_gc) {
|
||||
const MDBX_env *const env = txn->env;
|
||||
size_t pending = 0;
|
||||
if ((txn->flags & MDBX_TXN_RDONLY) == 0)
|
||||
pending = txn->wr.loose_count + MDBX_PNL_GETSIZE(txn->wr.repnl) +
|
||||
(MDBX_PNL_GETSIZE(txn->wr.retired_pages) - retired_stored);
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
const size_t pending = txn->wr.loose_count + MDBX_PNL_GETSIZE(txn->wr.repnl) +
|
||||
(MDBX_PNL_GETSIZE(txn->wr.retired_pages) - retired_stored);
|
||||
|
||||
cursor_couple_t cx;
|
||||
int rc = cursor_init(&cx.outer, txn, FREE_DBI);
|
||||
@ -40,17 +39,16 @@ __cold static int audit_ex_locked(MDBX_txn *txn, size_t retired_stored, bool don
|
||||
MDBX_val key, data;
|
||||
rc = outer_first(&cx.outer, &key, &data);
|
||||
while (rc == MDBX_SUCCESS) {
|
||||
if (!dont_filter_gc) {
|
||||
if (unlikely(key.iov_len != sizeof(txnid_t))) {
|
||||
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-key size", (unsigned)key.iov_len);
|
||||
return MDBX_CORRUPTED;
|
||||
}
|
||||
txnid_t id = unaligned_peek_u64(4, key.iov_base);
|
||||
if (txn->wr.gc.retxl ? txl_contain(txn->wr.gc.retxl, id) : (id <= txn->wr.gc.last_reclaimed))
|
||||
goto skip;
|
||||
if (unlikely(key.iov_len != sizeof(txnid_t))) {
|
||||
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-key size", (unsigned)key.iov_len);
|
||||
return MDBX_CORRUPTED;
|
||||
}
|
||||
gc += *(pgno_t *)data.iov_base;
|
||||
skip:
|
||||
const txnid_t id = unaligned_peek_u64(4, key.iov_base);
|
||||
const size_t len = *(pgno_t *)data.iov_base;
|
||||
const bool acc = dont_filter_gc || !gc_is_reclaimed(txn, id);
|
||||
TRACE("%s id %" PRIaTXN " len %zu", acc ? "acc" : "skip", id, len);
|
||||
if (acc)
|
||||
gc += len;
|
||||
rc = outer_next(&cx.outer, &key, &data, MDBX_NEXT);
|
||||
}
|
||||
tASSERT(txn, rc == MDBX_NOTFOUND);
|
||||
|
@ -1330,9 +1330,9 @@ __cold static int chk_handle_gc(MDBX_chk_scope_t *const scope, MDBX_chk_table_t
|
||||
(number + 1) * sizeof(pgno_t), data->iov_len);
|
||||
number = data->iov_len / sizeof(pgno_t) - 1;
|
||||
} else if (data->iov_len - (number + 1) * sizeof(pgno_t) >=
|
||||
/* LY: allow gap up to one page. it is ok
|
||||
/* LY: allow gap up to two page. it is ok
|
||||
* and better than shink-and-retry inside gc_update() */
|
||||
usr->env->ps)
|
||||
usr->env->ps * 2)
|
||||
chk_object_issue(scope, "entry", txnid, "extra idl space",
|
||||
"%" PRIuSIZE " < %" PRIuSIZE " (minor, not a trouble)", (number + 1) * sizeof(pgno_t),
|
||||
data->iov_len);
|
||||
|
10
src/cogs.h
10
src/cogs.h
@ -250,9 +250,15 @@ MDBX_NOTHROW_PURE_FUNCTION static inline const page_t *data_page(const void *dat
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline meta_t *page_meta(page_t *mp) { return (meta_t *)page_data(mp); }
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_numkeys(const page_t *mp) { return mp->lower >> 1; }
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_numkeys(const page_t *mp) {
|
||||
assert(mp->lower <= mp->upper);
|
||||
return mp->lower >> 1;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_room(const page_t *mp) { return mp->upper - mp->lower; }
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_room(const page_t *mp) {
|
||||
assert(mp->lower <= mp->upper);
|
||||
return mp->upper - mp->lower;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_space(const MDBX_env *env) {
|
||||
STATIC_ASSERT(PAGEHDRSZ % 2 == 0);
|
||||
|
@ -1781,8 +1781,7 @@ __hot csr_t cursor_seek(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, MDBX_cur
|
||||
}
|
||||
int cmp = mc->clc->k.cmp(&aligned_key, &nodekey);
|
||||
if (unlikely(cmp == 0)) {
|
||||
/* Probably happens rarely, but first node on the page
|
||||
* was the one we wanted. */
|
||||
/* Probably happens rarely, but first node on the page was the one we wanted. */
|
||||
mc->ki[mc->top] = 0;
|
||||
ret.exact = true;
|
||||
goto got_node;
|
||||
|
@ -53,7 +53,7 @@ static inline dpl_t *dpl_sort(const MDBX_txn *txn) {
|
||||
return likely(dl->sorted == dl->length) ? dl : dpl_sort_slowpath(txn);
|
||||
}
|
||||
|
||||
MDBX_INTERNAL __noinline size_t dpl_search(const MDBX_txn *txn, pgno_t pgno);
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL __noinline size_t dpl_search(const MDBX_txn *txn, pgno_t pgno);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL const page_t *debug_dpl_find(const MDBX_txn *txn, const pgno_t pgno);
|
||||
|
||||
@ -68,7 +68,7 @@ MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t dpl_endpgno(const dpl_t *dl, siz
|
||||
return dpl_npages(dl, i) + dl->items[i].pgno;
|
||||
}
|
||||
|
||||
static inline bool dpl_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npages) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline bool dpl_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npages) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
|
11
src/dxb.c
11
src/dxb.c
@ -1061,16 +1061,17 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika
|
||||
#endif /* MADV_DONTNEED || POSIX_MADV_DONTNEED */
|
||||
|
||||
/* LY: check conditions to shrink datafile */
|
||||
const pgno_t backlog_gap = 3 + pending->trees.gc.height * 3;
|
||||
const pgno_t stockpile_gap = 3 + pending->trees.gc.height * 3;
|
||||
pgno_t shrink_step = 0;
|
||||
if (pending->geometry.shrink_pv && pending->geometry.now - pending->geometry.first_unallocated >
|
||||
(shrink_step = pv2pages(pending->geometry.shrink_pv)) + backlog_gap) {
|
||||
if (pending->geometry.now > largest_pgno && pending->geometry.now - largest_pgno > shrink_step + backlog_gap) {
|
||||
(shrink_step = pv2pages(pending->geometry.shrink_pv)) + stockpile_gap) {
|
||||
if (pending->geometry.now > largest_pgno &&
|
||||
pending->geometry.now - largest_pgno > shrink_step + stockpile_gap) {
|
||||
const pgno_t aligner =
|
||||
pending->geometry.grow_pv ? /* grow_step */ pv2pages(pending->geometry.grow_pv) : shrink_step;
|
||||
const pgno_t with_backlog_gap = largest_pgno + backlog_gap;
|
||||
const pgno_t with_stockpile_gap = largest_pgno + stockpile_gap;
|
||||
const pgno_t aligned =
|
||||
pgno_align2os_pgno(env, (size_t)with_backlog_gap + aligner - with_backlog_gap % aligner);
|
||||
pgno_align2os_pgno(env, (size_t)with_stockpile_gap + aligner - with_stockpile_gap % aligner);
|
||||
const pgno_t bottom = (aligned > pending->geometry.lower) ? aligned : pending->geometry.lower;
|
||||
if (pending->geometry.now > bottom) {
|
||||
if (TROIKA_HAVE_STEADY(troika))
|
||||
|
@ -164,7 +164,7 @@ retry:;
|
||||
}
|
||||
eASSERT(env, head.txnid == recent_committed_txnid(env));
|
||||
env->basal_txn->txnid = head.txnid;
|
||||
txn_snapshot_oldest(env->basal_txn);
|
||||
txn_gc_detent(env->basal_txn);
|
||||
flags |= txn_shrink_allowed;
|
||||
}
|
||||
|
||||
@ -524,7 +524,7 @@ __cold int env_close(MDBX_env *env, bool resurrect_after_fork) {
|
||||
env->defer_free = nullptr;
|
||||
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
|
||||
|
||||
if (!(env->flags & MDBX_RDONLY))
|
||||
if ((env->flags & MDBX_RDONLY) == 0)
|
||||
osal_ioring_destroy(&env->ioring);
|
||||
|
||||
env->lck = nullptr;
|
||||
|
@ -30,8 +30,10 @@ typedef struct iov_ctx iov_ctx_t;
|
||||
|
||||
#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul || defined(_WIN64)
|
||||
#define MDBX_WORDBITS 64
|
||||
#define MDBX_WORDBITS_LN2 6
|
||||
#else
|
||||
#define MDBX_WORDBITS 32
|
||||
#define MDBX_WORDBITS_LN2 5
|
||||
#endif /* MDBX_WORDBITS */
|
||||
|
||||
#include "options.h"
|
||||
|
242
src/gc-get.c
242
src/gc-get.c
@ -570,14 +570,11 @@ static pgno_t *scan4seq_resolver(pgno_t *range, const size_t len, const size_t s
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#define ALLOC_COALESCE 4 /* внутреннее состояние */
|
||||
#define ALLOC_SHOULD_SCAN 8 /* внутреннее состояние */
|
||||
#define ALLOC_LIFO 16 /* внутреннее состояние */
|
||||
|
||||
static inline bool is_gc_usable(MDBX_txn *txn, const MDBX_cursor *mc, const uint8_t flags) {
|
||||
static inline bool is_reclaimable(MDBX_txn *txn, const MDBX_cursor *mc, const uint8_t flags) {
|
||||
/* If txn is updating the GC, then the retired-list cannot play catch-up with
|
||||
* itself by growing while trying to save it. */
|
||||
if (mc->tree == &txn->dbs[FREE_DBI] && !(flags & ALLOC_RESERVE) && !(mc->flags & z_gcu_preparation))
|
||||
STATIC_ASSERT(ALLOC_RESERVE == z_gcu_preparation);
|
||||
if (mc->tree == &txn->dbs[FREE_DBI] && !((flags | mc->flags) & z_gcu_preparation))
|
||||
return false;
|
||||
|
||||
/* avoid search inside empty tree and while tree is updating,
|
||||
@ -590,8 +587,6 @@ static inline bool is_gc_usable(MDBX_txn *txn, const MDBX_cursor *mc, const uint
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool is_already_reclaimed(const MDBX_txn *txn, txnid_t id) { return txl_contain(txn->wr.gc.retxl, id); }
|
||||
|
||||
__hot static pgno_t repnl_get_single(MDBX_txn *txn) {
|
||||
const size_t len = MDBX_PNL_GETSIZE(txn->wr.repnl);
|
||||
assert(len > 0);
|
||||
@ -721,6 +716,10 @@ __hot static pgno_t repnl_get_sequence(MDBX_txn *txn, const size_t num, uint8_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool gc_repnl_has_span(const MDBX_txn *txn, const size_t num) {
|
||||
return (num > 1) ? repnl_get_sequence((MDBX_txn *)txn, num, ALLOC_RESERVE) != 0 : !MDBX_PNL_IS_EMPTY(txn->wr.repnl);
|
||||
}
|
||||
|
||||
static inline pgr_t page_alloc_finalize(MDBX_env *const env, MDBX_txn *const txn, const MDBX_cursor *const mc,
|
||||
const pgno_t pgno, const size_t num) {
|
||||
#if MDBX_ENABLE_PROFGC
|
||||
@ -842,6 +841,13 @@ pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num, uint8_t flags)
|
||||
prof->spe_counter += 1;
|
||||
#endif /* MDBX_ENABLE_PROFGC */
|
||||
|
||||
/* Если взведен флажок ALLOC_RESERVE, то требуется только обеспечение соответствующего резерва в txn->wr.repnl
|
||||
* и/или txn->wr.gc.reclaimed, но без выделения и возврата страницы. При этом возможны три варианта вызова:
|
||||
* 1. num == 0 — требуется слот для возврата в GC остатков ранее переработанных/извлеченных страниц,
|
||||
* при этом нет смысла перерабатывать длинные записи, так как тогда дефицит свободных id/слотов не уменьшится;
|
||||
* 2. num == 1 — требуется увеличение резерва перед обновлением GC;
|
||||
* 3. num > 1 — требуется последовательность страниц для сохранения retired-страниц
|
||||
* при выключенном MDBX_ENABLE_BIGFOOT. */
|
||||
eASSERT(env, num > 0 || (flags & ALLOC_RESERVE));
|
||||
eASSERT(env, pnl_check_allocated(txn->wr.repnl, txn->geo.first_unallocated - MDBX_ENABLE_REFUND));
|
||||
|
||||
@ -866,13 +872,12 @@ pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num, uint8_t flags)
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
eASSERT(env, num == 0 || MDBX_PNL_GETSIZE(txn->wr.repnl) == 0);
|
||||
eASSERT(env, !(flags & ALLOC_RESERVE) || num == 0);
|
||||
eASSERT(env, num == 0 || MDBX_PNL_GETSIZE(txn->wr.repnl) == 0 || (flags & ALLOC_RESERVE));
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (unlikely(!is_gc_usable(txn, mc, flags))) {
|
||||
if (unlikely(!is_reclaimable(txn, mc, flags))) {
|
||||
eASSERT(env, (txn->flags & txn_gc_drained) || num > 1);
|
||||
goto no_gc;
|
||||
}
|
||||
@ -880,21 +885,18 @@ pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num, uint8_t flags)
|
||||
eASSERT(env, (flags & (ALLOC_COALESCE | ALLOC_LIFO | ALLOC_SHOULD_SCAN)) == 0);
|
||||
flags += (env->flags & MDBX_LIFORECLAIM) ? ALLOC_LIFO : 0;
|
||||
|
||||
if (/* Не коагулируем записи при подготовке резерва для обновления GC.
|
||||
* Иначе попытка увеличить резерв может приводить к необходимости ещё
|
||||
* большего резерва из-за увеличения списка переработанных страниц. */
|
||||
(flags & ALLOC_RESERVE) == 0) {
|
||||
if (txn->dbs[FREE_DBI].branch_pages && MDBX_PNL_GETSIZE(txn->wr.repnl) < env->maxgc_large1page / 2)
|
||||
flags += ALLOC_COALESCE;
|
||||
}
|
||||
/* Не коагулируем записи в случае запроса слота для возврата страниц в GC. Иначе попытка увеличить резерв
|
||||
* может приводить к необходимости ещё большего резерва из-за увеличения списка переработанных страниц. */
|
||||
if (num > 0 && txn->dbs[FREE_DBI].branch_pages && MDBX_PNL_GETSIZE(txn->wr.repnl) < env->maxgc_large1page / 2)
|
||||
flags += ALLOC_COALESCE;
|
||||
|
||||
MDBX_cursor *const gc = ptr_disp(env->basal_txn, sizeof(MDBX_txn));
|
||||
MDBX_cursor *const gc = txn_gc_cursor(txn);
|
||||
eASSERT(env, mc != gc && gc->next == gc);
|
||||
gc->txn = txn;
|
||||
gc->dbi_state = txn->dbi_state;
|
||||
gc->top_and_flags = z_fresh_mark;
|
||||
|
||||
txn->wr.prefault_write_activated = env->options.prefault_write;
|
||||
txn->wr.prefault_write_activated = !env->incore && env->options.prefault_write;
|
||||
if (txn->wr.prefault_write_activated) {
|
||||
/* Проверка посредством minicore() существенно снижает затраты, но в
|
||||
* простейших случаях (тривиальный бенчмарк) интегральная производительность
|
||||
@ -911,45 +913,38 @@ pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num, uint8_t flags)
|
||||
txn->wr.prefault_write_activated = false;
|
||||
}
|
||||
|
||||
retry_gc_refresh_oldest:;
|
||||
txnid_t oldest = txn_snapshot_oldest(txn);
|
||||
retry_gc_have_oldest:
|
||||
if (unlikely(oldest >= txn->txnid)) {
|
||||
ERROR("unexpected/invalid oldest-readed txnid %" PRIaTXN " for current-txnid %" PRIaTXN, oldest, txn->txnid);
|
||||
retry_gc_refresh_detent:
|
||||
txn_gc_detent(txn);
|
||||
retry_gc_have_detent:
|
||||
if (unlikely(txn->env->gc.detent >= txn->txnid)) {
|
||||
FATAL("unexpected/invalid gc-detent %" PRIaTXN " for current-txnid %" PRIaTXN, txn->env->gc.detent, txn->txnid);
|
||||
ret.err = MDBX_PROBLEM;
|
||||
goto fail;
|
||||
}
|
||||
const txnid_t detent = oldest + 1;
|
||||
|
||||
txnid_t id = 0;
|
||||
MDBX_cursor_op op = MDBX_FIRST;
|
||||
if (flags & ALLOC_LIFO) {
|
||||
if (!txn->wr.gc.retxl) {
|
||||
txn->wr.gc.retxl = txl_alloc();
|
||||
if (unlikely(!txn->wr.gc.retxl)) {
|
||||
ret.err = MDBX_ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
/* Begin lookup backward from oldest reader */
|
||||
id = detent - 1;
|
||||
id = txn->env->gc.detent;
|
||||
op = MDBX_SET_RANGE;
|
||||
} else if (txn->wr.gc.last_reclaimed) {
|
||||
} else {
|
||||
/* Continue lookup forward from last-reclaimed */
|
||||
id = txn->wr.gc.last_reclaimed + 1;
|
||||
if (id >= detent)
|
||||
goto depleted_gc;
|
||||
op = MDBX_SET_RANGE;
|
||||
id = rkl_highest(&txn->wr.gc.reclaimed);
|
||||
if (id) {
|
||||
id += 1;
|
||||
op = MDBX_SET_RANGE;
|
||||
if (id >= txn->env->gc.detent)
|
||||
goto depleted_gc;
|
||||
}
|
||||
}
|
||||
|
||||
next_gc:;
|
||||
MDBX_val key;
|
||||
key.iov_base = &id;
|
||||
key.iov_len = sizeof(id);
|
||||
|
||||
next_gc:
|
||||
#if MDBX_ENABLE_PROFGC
|
||||
prof->rsteps += 1;
|
||||
prof->rsteps += 1
|
||||
#endif /* MDBX_ENABLE_PROFGC */
|
||||
;
|
||||
MDBX_val key = {.iov_base = &id, .iov_len = sizeof(id)};
|
||||
|
||||
/* Seek first/next GC record */
|
||||
ret.err = cursor_ops(gc, &key, nullptr, op);
|
||||
@ -967,15 +962,18 @@ next_gc:;
|
||||
ret.err = MDBX_CORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
id = unaligned_peek_u64(4, key.iov_base);
|
||||
if (flags & ALLOC_LIFO) {
|
||||
op = MDBX_PREV;
|
||||
if (id >= detent || is_already_reclaimed(txn, id))
|
||||
if (id >= txn->env->gc.detent || gc_is_reclaimed(txn, id))
|
||||
goto next_gc;
|
||||
} else {
|
||||
op = MDBX_NEXT;
|
||||
if (unlikely(id >= detent))
|
||||
if (unlikely(id >= txn->env->gc.detent))
|
||||
goto depleted_gc;
|
||||
op = MDBX_NEXT;
|
||||
if (gc_is_reclaimed(txn, id))
|
||||
goto next_gc;
|
||||
}
|
||||
txn->flags &= ~txn_gc_drained;
|
||||
|
||||
@ -996,12 +994,23 @@ next_gc:;
|
||||
const size_t gc_len = MDBX_PNL_GETSIZE(gc_pnl);
|
||||
TRACE("gc-read: id #%" PRIaTXN " len %zu, re-list will %zu ", id, gc_len, gc_len + MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
|
||||
if (unlikely(gc_len + MDBX_PNL_GETSIZE(txn->wr.repnl) >= env->maxgc_large1page)) {
|
||||
/* Don't try to coalesce too much. */
|
||||
if (unlikely(!num)) {
|
||||
/* TODO: Проверка критериев пункта 2 сформулированного в gc_provide_slots().
|
||||
* Сейчас тут сильно упрощенная и не совсем верная проверка, так как пока недоступна информация о кол-ве имеющихся
|
||||
* слотов и их дефиците для возврата wr.repl. */
|
||||
if (gc_len > env->maxgc_large1page / 4 * 3
|
||||
/* если запись достаточно длинная, то переработка слота не особо увеличит место для возврата wr.repl, и т.п. */
|
||||
&& MDBX_PNL_GETSIZE(txn->wr.repnl) + gc_len > env->maxgc_large1page /* не помещается в хвост */) {
|
||||
DEBUG("avoid reclaiming %" PRIaTXN " slot, since it is too long (%zu)", id, gc_len);
|
||||
ret.err = MDBX_NOTFOUND;
|
||||
goto reserve_done;
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(gc_len + MDBX_PNL_GETSIZE(txn->wr.repnl) /* Don't try to coalesce too much. */ >=
|
||||
env->maxgc_large1page)) {
|
||||
if (flags & ALLOC_SHOULD_SCAN) {
|
||||
eASSERT(env, flags & ALLOC_COALESCE);
|
||||
eASSERT(env, !(flags & ALLOC_RESERVE));
|
||||
eASSERT(env, num > 0);
|
||||
eASSERT(env, (flags & ALLOC_COALESCE) /* && !(flags & ALLOC_RESERVE) */ && num > 0);
|
||||
#if MDBX_ENABLE_PROFGC
|
||||
env->lck->pgops.gc_prof.coalescences += 1;
|
||||
#endif /* MDBX_ENABLE_PROFGC */
|
||||
@ -1010,25 +1019,25 @@ next_gc:;
|
||||
eASSERT(env, MDBX_PNL_LAST(txn->wr.repnl) < txn->geo.first_unallocated &&
|
||||
MDBX_PNL_FIRST(txn->wr.repnl) < txn->geo.first_unallocated);
|
||||
if (likely(num == 1)) {
|
||||
pgno = repnl_get_single(txn);
|
||||
pgno = (flags & ALLOC_RESERVE) ? P_INVALID : repnl_get_single(txn);
|
||||
goto done;
|
||||
}
|
||||
pgno = repnl_get_sequence(txn, num, flags);
|
||||
if (likely(pgno))
|
||||
goto done;
|
||||
}
|
||||
flags -= ALLOC_COALESCE | ALLOC_SHOULD_SCAN;
|
||||
}
|
||||
flags &= ~(ALLOC_COALESCE | ALLOC_SHOULD_SCAN);
|
||||
if (unlikely(/* list is too long already */ MDBX_PNL_GETSIZE(txn->wr.repnl) >= env->options.rp_augment_limit) &&
|
||||
((/* not a slot-request from gc-update */ num &&
|
||||
/* have enough unallocated space */ txn->geo.upper >= txn->geo.first_unallocated + num &&
|
||||
monotime_since_cached(monotime_begin, &now_cache) + txn->wr.gc.time_acc >= env->options.gc_time_limit) ||
|
||||
monotime_since_cached(monotime_begin, &now_cache) + txn->wr.gc.spent >= env->options.gc_time_limit) ||
|
||||
gc_len + MDBX_PNL_GETSIZE(txn->wr.repnl) >= PAGELIST_LIMIT)) {
|
||||
/* Stop reclaiming to avoid large/overflow the page list. This is a rare
|
||||
* case while search for a continuously multi-page region in a
|
||||
* large database, see https://libmdbx.dqdkfa.ru/dead-github/issues/123 */
|
||||
* case while search for a continuously multi-page region in a large database,
|
||||
* see https://libmdbx.dqdkfa.ru/dead-github/issues/123 */
|
||||
NOTICE("stop reclaiming %s: %zu (current) + %zu "
|
||||
"(chunk) -> %zu, rp_augment_limit %u",
|
||||
"(chunk) >= %zu, rp_augment_limit %u",
|
||||
likely(gc_len + MDBX_PNL_GETSIZE(txn->wr.repnl) < PAGELIST_LIMIT) ? "since rp_augment_limit was reached"
|
||||
: "to avoid PNL overflow",
|
||||
MDBX_PNL_GETSIZE(txn->wr.repnl), gc_len, gc_len + MDBX_PNL_GETSIZE(txn->wr.repnl),
|
||||
@ -1038,12 +1047,17 @@ next_gc:;
|
||||
}
|
||||
|
||||
/* Remember ID of readed GC record */
|
||||
txn->wr.gc.last_reclaimed = id;
|
||||
if (flags & ALLOC_LIFO) {
|
||||
ret.err = txl_append(&txn->wr.gc.retxl, id);
|
||||
if (unlikely(ret.err != MDBX_SUCCESS))
|
||||
goto fail;
|
||||
}
|
||||
ret.err = rkl_push(&txn->wr.gc.reclaimed, id,
|
||||
false /* Вместо false, тут можно передавать/использовать (flags & ALLOC_LIFO) == 0, тогда
|
||||
* дыры/пропуски в идентификаторах GC будут образовывать непрерывные интервалы в wr.gc.reclaimed,
|
||||
* что обеспечит больше свободных идентификаторов/слотов для возврата страниц. Однако, это
|
||||
* также приведёт к пустым попыткам удаления отсутствующих записей в gc_clear_reclaimed(),
|
||||
* а далее к перекладыванию этих сплошных интервалов поэлементно в ready4reuse.
|
||||
* Поэтому смысла в этом решительно нет. Следует либо формировать сплошные интервалы при
|
||||
* работе gc_clear_reclaimed(), особенно в FIFO-режиме, либо искать их только в gc_provide_ids() */);
|
||||
TRACE("%" PRIaTXN " len %zu pushed to txn-rkl, err %d", id, gc_len, ret.err);
|
||||
if (unlikely(ret.err != MDBX_SUCCESS))
|
||||
goto fail;
|
||||
|
||||
/* Append PNL from GC record to wr.repnl */
|
||||
ret.err = pnl_need(&txn->wr.repnl, gc_len);
|
||||
@ -1087,22 +1101,25 @@ next_gc:;
|
||||
}
|
||||
eASSERT(env, pnl_check_allocated(txn->wr.repnl, txn->geo.first_unallocated - MDBX_ENABLE_REFUND));
|
||||
|
||||
/* Done for a kick-reclaim mode, actually no page needed */
|
||||
if (unlikely(num == 0)) {
|
||||
eASSERT(env, ret.err == MDBX_SUCCESS);
|
||||
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "early-exit for slot", id, MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
goto early_exit;
|
||||
}
|
||||
|
||||
/* TODO: delete reclaimed records */
|
||||
/* TODO: удаление загруженных из GC записей */
|
||||
|
||||
eASSERT(env, op == MDBX_PREV || op == MDBX_NEXT);
|
||||
if (flags & ALLOC_COALESCE) {
|
||||
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "coalesce-continue", id, MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
goto next_gc;
|
||||
if (MDBX_PNL_GETSIZE(txn->wr.repnl) < env->maxgc_large1page / 2) {
|
||||
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "coalesce-continue", id, MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
goto next_gc;
|
||||
}
|
||||
flags -= ALLOC_COALESCE;
|
||||
}
|
||||
|
||||
scan:
|
||||
if ((flags & ALLOC_RESERVE) && num < 2) {
|
||||
/* Если был нужен только slot/id для gc_reclaim_slot() или gc_reserve4stockpile() */
|
||||
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "reserve-done", id, MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
ret.err = MDBX_SUCCESS;
|
||||
goto reserve_done;
|
||||
}
|
||||
|
||||
eASSERT(env, flags & ALLOC_SHOULD_SCAN);
|
||||
eASSERT(env, num > 0);
|
||||
if (MDBX_PNL_GETSIZE(txn->wr.repnl) >= num) {
|
||||
@ -1118,17 +1135,16 @@ scan:
|
||||
goto done;
|
||||
}
|
||||
flags -= ALLOC_SHOULD_SCAN;
|
||||
if (ret.err == MDBX_SUCCESS) {
|
||||
if ((txn->flags & txn_gc_drained) == 0) {
|
||||
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "continue-search", id, MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
goto next_gc;
|
||||
}
|
||||
|
||||
depleted_gc:
|
||||
TRACE("%s: last id #%" PRIaTXN ", re-len %zu", "gc-depleted", id, MDBX_PNL_GETSIZE(txn->wr.repnl));
|
||||
ret.err = MDBX_NOTFOUND;
|
||||
txn->flags |= txn_gc_drained;
|
||||
if (flags & ALLOC_SHOULD_SCAN)
|
||||
goto scan;
|
||||
txn->flags |= txn_gc_drained;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
@ -1145,9 +1161,9 @@ depleted_gc:
|
||||
/* Does reclaiming stopped at the last steady point? */
|
||||
const meta_ptr_t recent = meta_recent(env, &txn->wr.troika);
|
||||
const meta_ptr_t prefer_steady = meta_prefer_steady(env, &txn->wr.troika);
|
||||
if (recent.ptr_c != prefer_steady.ptr_c && prefer_steady.is_steady && detent == prefer_steady.txnid + 1) {
|
||||
DEBUG("gc-kick-steady: recent %" PRIaTXN "-%s, steady %" PRIaTXN "-%s, detent %" PRIaTXN, recent.txnid,
|
||||
durable_caption(recent.ptr_c), prefer_steady.txnid, durable_caption(prefer_steady.ptr_c), detent);
|
||||
if (recent.ptr_c != prefer_steady.ptr_c && prefer_steady.is_steady && txn->env->gc.detent == prefer_steady.txnid) {
|
||||
DEBUG("gc-kick-steady: recent %" PRIaTXN "-%s, steady %" PRIaTXN "-%s", recent.txnid, durable_caption(recent.ptr_c),
|
||||
prefer_steady.txnid, durable_caption(prefer_steady.ptr_c));
|
||||
const pgno_t autosync_threshold = atomic_load32(&env->lck->autosync_threshold, mo_Relaxed);
|
||||
const uint64_t autosync_period = atomic_load64(&env->lck->autosync_period, mo_Relaxed);
|
||||
uint64_t eoos_timestamp;
|
||||
@ -1166,12 +1182,12 @@ depleted_gc:
|
||||
#if MDBX_ENABLE_PROFGC
|
||||
env->lck->pgops.gc_prof.wipes += 1;
|
||||
#endif /* MDBX_ENABLE_PROFGC */
|
||||
ret.err = meta_wipe_steady(env, detent);
|
||||
ret.err = meta_wipe_steady(env, txn->env->gc.detent);
|
||||
DEBUG("gc-wipe-steady, rc %d", ret.err);
|
||||
if (unlikely(ret.err != MDBX_SUCCESS))
|
||||
goto fail;
|
||||
eASSERT(env, prefer_steady.ptr_c != meta_prefer_steady(env, &txn->wr.troika).ptr_c);
|
||||
goto retry_gc_refresh_oldest;
|
||||
goto retry_gc_refresh_detent;
|
||||
}
|
||||
if ((autosync_threshold && atomic_load64(&env->lck->unsynced_pages, mo_Relaxed) >= autosync_threshold) ||
|
||||
(autosync_period && (eoos_timestamp = atomic_load64(&env->lck->eoos_timestamp, mo_Relaxed)) &&
|
||||
@ -1189,15 +1205,12 @@ depleted_gc:
|
||||
if (unlikely(ret.err != MDBX_SUCCESS))
|
||||
goto fail;
|
||||
eASSERT(env, prefer_steady.ptr_c != meta_prefer_steady(env, &txn->wr.troika).ptr_c);
|
||||
goto retry_gc_refresh_oldest;
|
||||
goto retry_gc_refresh_detent;
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(true == atomic_load32(&env->lck->rdt_refresh_flag, mo_AcquireRelease))) {
|
||||
oldest = txn_snapshot_oldest(txn);
|
||||
if (oldest >= detent)
|
||||
goto retry_gc_have_oldest;
|
||||
}
|
||||
if (unlikely(true == atomic_load32(&env->lck->rdt_refresh_flag, mo_AcquireRelease)) && txn_gc_detent(txn))
|
||||
goto retry_gc_have_detent;
|
||||
|
||||
/* Avoid kick lagging reader(s) if is enough unallocated space
|
||||
* at the end of database file. */
|
||||
@ -1206,11 +1219,8 @@ depleted_gc:
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (oldest < txn->txnid - xMDBX_TXNID_STEP) {
|
||||
oldest = mvcc_kick_laggards(env, oldest);
|
||||
if (oldest >= detent)
|
||||
goto retry_gc_have_oldest;
|
||||
}
|
||||
if (txn->txnid - txn->env->gc.detent > xMDBX_TXNID_STEP && mvcc_kick_laggards(env, txn->env->gc.detent))
|
||||
goto retry_gc_refresh_detent;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@ -1277,30 +1287,40 @@ done:
|
||||
eASSERT(env, ret.err != MDBX_SUCCESS);
|
||||
eASSERT(env, pnl_check_allocated(txn->wr.repnl, txn->geo.first_unallocated - MDBX_ENABLE_REFUND));
|
||||
int level;
|
||||
const char *what;
|
||||
if (flags & ALLOC_RESERVE) {
|
||||
level = (flags & ALLOC_UNIMPORTANT) ? MDBX_LOG_DEBUG : MDBX_LOG_NOTICE;
|
||||
what = num ? "reserve-pages" : "fetch-slot";
|
||||
} else {
|
||||
if (flags & ALLOC_UNIMPORTANT)
|
||||
level = MDBX_LOG_DEBUG;
|
||||
else if (flags & ALLOC_RESERVE)
|
||||
level = MDBX_LOG_NOTICE;
|
||||
else {
|
||||
txn->flags |= MDBX_TXN_ERROR;
|
||||
level = MDBX_LOG_ERROR;
|
||||
what = "pages";
|
||||
}
|
||||
if (LOG_ENABLED(level))
|
||||
debug_log(level, __func__, __LINE__,
|
||||
"unable alloc %zu %s, alloc-flags 0x%x, err %d, txn-flags "
|
||||
"0x%x, re-list-len %zu, loose-count %zu, gc: height %u, "
|
||||
"branch %zu, leaf %zu, large %zu, entries %zu\n",
|
||||
num, what, flags, ret.err, txn->flags, MDBX_PNL_GETSIZE(txn->wr.repnl), txn->wr.loose_count,
|
||||
txn->dbs[FREE_DBI].height, (size_t)txn->dbs[FREE_DBI].branch_pages,
|
||||
(size_t)txn->dbs[FREE_DBI].leaf_pages, (size_t)txn->dbs[FREE_DBI].large_pages,
|
||||
(size_t)txn->dbs[FREE_DBI].items);
|
||||
if (LOG_ENABLED(level)) {
|
||||
if (num)
|
||||
debug_log(level, __func__, __LINE__,
|
||||
"unable %s %zu, alloc-flags 0x%x, err %d, txn-flags "
|
||||
"0x%x, re-list-len %zu, loose-count %zu, gc: height %u, "
|
||||
"branch %zu, leaf %zu, large %zu, entries %zu\n",
|
||||
(flags & ALLOC_RESERVE) ? "reserve" : "alloc", num, flags, ret.err, txn->flags,
|
||||
MDBX_PNL_GETSIZE(txn->wr.repnl), txn->wr.loose_count, txn->dbs[FREE_DBI].height,
|
||||
(size_t)txn->dbs[FREE_DBI].branch_pages, (size_t)txn->dbs[FREE_DBI].leaf_pages,
|
||||
(size_t)txn->dbs[FREE_DBI].large_pages, (size_t)txn->dbs[FREE_DBI].items);
|
||||
else
|
||||
debug_log(level, __func__, __LINE__,
|
||||
"unable fetch-slot, alloc-flags 0x%x, err %d, txn-flags "
|
||||
"0x%x, re-list-len %zu, loose-count %zu, gc: height %u, "
|
||||
"branch %zu, leaf %zu, large %zu, entries %zu\n",
|
||||
flags, ret.err, txn->flags, MDBX_PNL_GETSIZE(txn->wr.repnl), txn->wr.loose_count,
|
||||
txn->dbs[FREE_DBI].height, (size_t)txn->dbs[FREE_DBI].branch_pages,
|
||||
(size_t)txn->dbs[FREE_DBI].leaf_pages, (size_t)txn->dbs[FREE_DBI].large_pages,
|
||||
(size_t)txn->dbs[FREE_DBI].items);
|
||||
}
|
||||
ret.page = nullptr;
|
||||
}
|
||||
if (num > 1)
|
||||
txn->wr.gc.time_acc += monotime_since_cached(monotime_begin, &now_cache);
|
||||
txn->wr.gc.spent += monotime_since_cached(monotime_begin, &now_cache);
|
||||
} else {
|
||||
early_exit:
|
||||
reserve_done:
|
||||
DEBUG("return nullptr for %zu pages for ALLOC_%s, rc %d", num, num ? "RESERVE" : "SLOT", ret.err);
|
||||
ret.page = nullptr;
|
||||
}
|
||||
|
1888
src/gc-put.c
1888
src/gc-put.c
File diff suppressed because it is too large
Load Diff
70
src/gc.h
70
src/gc.h
@ -5,14 +5,37 @@
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
/* Гистограмма решения нарезки фрагментов для ситуации нехватки идентификаторов/слотов. */
|
||||
typedef struct gc_dense_histogram {
|
||||
/* Размер массива одновременно задаёт максимальный размер последовательностей,
|
||||
* с которыми решается задача распределения.
|
||||
*
|
||||
* Использование длинных последовательностей контрпродуктивно, так как такие последовательности будут
|
||||
* создавать/воспроизводить/повторять аналогичные затруднения при последующей переработке. Однако,
|
||||
* в редких ситуациях это может быть единственным выходом. */
|
||||
unsigned end;
|
||||
pgno_t array[31];
|
||||
} gc_dense_histogram_t;
|
||||
|
||||
typedef struct gc_update_context {
|
||||
unsigned loop;
|
||||
pgno_t prev_first_unallocated;
|
||||
unsigned goodchunk;
|
||||
bool dense;
|
||||
size_t reserve_adj;
|
||||
pgno_t prev_first_unallocated;
|
||||
size_t retired_stored;
|
||||
size_t amount, reserved, cleaned_slot, reused_slot, fill_idx;
|
||||
txnid_t cleaned_id, rid;
|
||||
size_t return_reserved_lo, return_reserved_hi;
|
||||
txnid_t gc_first;
|
||||
intptr_t return_left;
|
||||
#ifndef MDBX_DEBUG_GCU
|
||||
#define MDBX_DEBUG_GCU 0
|
||||
#endif
|
||||
#if MDBX_DEBUG_GCU
|
||||
struct {
|
||||
txnid_t prev;
|
||||
unsigned n;
|
||||
} dbg;
|
||||
#endif /* MDBX_DEBUG_GCU */
|
||||
rkl_t ready4reuse, sequel;
|
||||
#if MDBX_ENABLE_BIGFOOT
|
||||
txnid_t bigfoot;
|
||||
#endif /* MDBX_ENABLE_BIGFOOT */
|
||||
@ -20,21 +43,38 @@ typedef struct gc_update_context {
|
||||
MDBX_cursor cursor;
|
||||
cursor_couple_t couple;
|
||||
};
|
||||
gc_dense_histogram_t dense_histogram;
|
||||
} gcu_t;
|
||||
|
||||
static inline int gc_update_init(MDBX_txn *txn, gcu_t *ctx) {
|
||||
memset(ctx, 0, offsetof(gcu_t, cursor));
|
||||
ctx->dense = txn->txnid <= MIN_TXNID;
|
||||
#if MDBX_ENABLE_BIGFOOT
|
||||
ctx->bigfoot = txn->txnid;
|
||||
#endif /* MDBX_ENABLE_BIGFOOT */
|
||||
return cursor_init(&ctx->cursor, txn, FREE_DBI);
|
||||
}
|
||||
MDBX_INTERNAL int gc_put_init(MDBX_txn *txn, gcu_t *ctx);
|
||||
MDBX_INTERNAL void gc_put_destroy(gcu_t *ctx);
|
||||
|
||||
#define ALLOC_DEFAULT 0 /* штатное/обычное выделение страниц */
|
||||
#define ALLOC_UNIMPORTANT 1 /* запрос неважен, невозможность выделения не приведет к ошибке транзакции */
|
||||
#define ALLOC_RESERVE 2 /* подготовка резерва для обновления GC, без аллокации */
|
||||
#define ALLOC_COALESCE 4 /* внутреннее состояние/флажок */
|
||||
#define ALLOC_SHOULD_SCAN 8 /* внутреннее состояние/флажок */
|
||||
#define ALLOC_LIFO 16 /* внутреннее состояние/флажок */
|
||||
|
||||
#define ALLOC_DEFAULT 0
|
||||
#define ALLOC_RESERVE 1
|
||||
#define ALLOC_UNIMPORTANT 2
|
||||
MDBX_INTERNAL pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num, uint8_t flags);
|
||||
|
||||
MDBX_INTERNAL pgr_t gc_alloc_single(const MDBX_cursor *const mc);
|
||||
MDBX_INTERNAL int gc_update(MDBX_txn *txn, gcu_t *ctx);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t gc_stockpile(const MDBX_txn *txn) {
|
||||
return MDBX_PNL_GETSIZE(txn->wr.repnl) + txn->wr.loose_count;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t gc_chunk_bytes(const size_t chunk) {
|
||||
return (chunk + 1) * sizeof(pgno_t);
|
||||
}
|
||||
|
||||
MDBX_INTERNAL bool gc_repnl_has_span(const MDBX_txn *txn, const size_t num);
|
||||
|
||||
static inline bool gc_is_reclaimed(const MDBX_txn *txn, const txnid_t id) {
|
||||
return rkl_contain(&txn->wr.gc.reclaimed, id) || rkl_contain(&txn->wr.gc.comeback, id);
|
||||
}
|
||||
|
||||
static inline txnid_t txnid_min(txnid_t a, txnid_t b) { return (a < b) ? a : b; }
|
||||
|
||||
static inline txnid_t txnid_max(txnid_t a, txnid_t b) { return (a > b) ? a : b; }
|
||||
|
@ -46,6 +46,7 @@ typedef struct bind_reader_slot_result {
|
||||
|
||||
#include "atomics-ops.h"
|
||||
#include "proto.h"
|
||||
#include "rkl.h"
|
||||
#include "txl.h"
|
||||
#include "unaligned.h"
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
@ -213,10 +214,9 @@ struct MDBX_txn {
|
||||
troika_t troika;
|
||||
pnl_t __restrict repnl; /* Reclaimed GC pages */
|
||||
struct {
|
||||
/* The list of reclaimed txn-ids from GC */
|
||||
txl_t __restrict retxl;
|
||||
txnid_t last_reclaimed; /* ID of last used record */
|
||||
uint64_t time_acc;
|
||||
rkl_t reclaimed; /* The list of reclaimed txn-ids from GC */
|
||||
uint64_t spent; /* Time spent reading and searching GC */
|
||||
rkl_t comeback; /* The list of ids of records returned into GC during commit, etc */
|
||||
} gc;
|
||||
bool prefault_write_activated;
|
||||
#if MDBX_ENABLE_REFUND
|
||||
@ -286,13 +286,14 @@ struct MDBX_cursor {
|
||||
};
|
||||
/* флаги проверки, в том числе биты для проверки типа листовых страниц. */
|
||||
uint8_t checking;
|
||||
uint8_t pad;
|
||||
|
||||
/* Указывает на txn->dbi_state[] для DBI этого курсора.
|
||||
* Модификатор __restrict тут полезен и безопасен в текущем понимании,
|
||||
* так как пересечение возможно только с dbi_state транзакции,
|
||||
* и происходит по-чтению до последующего изменения/записи. */
|
||||
uint8_t *__restrict dbi_state;
|
||||
/* Связь списка отслеживания курсоров в транзакции */
|
||||
/* Связь списка отслеживания курсоров в транзакции. */
|
||||
MDBX_txn *txn;
|
||||
/* Указывает на tree->dbs[] для DBI этого курсора. */
|
||||
tree_t *tree;
|
||||
@ -361,15 +362,14 @@ struct MDBX_env {
|
||||
uint16_t subpage_reserve_prereq;
|
||||
uint16_t subpage_reserve_limit;
|
||||
atomic_pgno_t mlocked_pgno;
|
||||
uint8_t ps2ln; /* log2 of DB page size */
|
||||
int8_t stuck_meta; /* recovery-only: target meta page or less that zero */
|
||||
uint16_t merge_threshold, merge_threshold_gc; /* pages emptier than this are
|
||||
candidates for merging */
|
||||
unsigned max_readers; /* size of the reader table */
|
||||
MDBX_dbi max_dbi; /* size of the DB table */
|
||||
uint32_t pid; /* process ID of this env */
|
||||
osal_thread_key_t me_txkey; /* thread-key for readers */
|
||||
struct { /* path to the DB files */
|
||||
uint8_t ps2ln; /* log2 of DB page size */
|
||||
int8_t stuck_meta; /* recovery-only: target meta page or less that zero */
|
||||
uint16_t merge_threshold; /* pages emptier than this are candidates for merging */
|
||||
unsigned max_readers; /* size of the reader table */
|
||||
MDBX_dbi max_dbi; /* size of the DB table */
|
||||
uint32_t pid; /* process ID of this env */
|
||||
osal_thread_key_t me_txkey; /* thread-key for readers */
|
||||
struct { /* path to the DB files */
|
||||
pathchar_t *lck, *dxb, *specified;
|
||||
void *buffer;
|
||||
} pathname;
|
||||
@ -466,6 +466,9 @@ struct MDBX_env {
|
||||
/* --------------------------------------------------- mostly volatile part */
|
||||
|
||||
MDBX_txn *txn; /* current write transaction */
|
||||
struct {
|
||||
txnid_t detent;
|
||||
} gc;
|
||||
osal_fastmutex_t dbi_lock;
|
||||
unsigned n_dbi; /* number of DBs opened */
|
||||
|
||||
@ -537,7 +540,9 @@ MDBX_MAYBE_UNUSED static void static_checks(void) {
|
||||
STATIC_ASSERT(offsetof(lck_t, cached_oldest) % MDBX_CACHELINE_SIZE == 0);
|
||||
STATIC_ASSERT(offsetof(lck_t, rdt_length) % MDBX_CACHELINE_SIZE == 0);
|
||||
#endif /* MDBX_LOCKING */
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
STATIC_ASSERT(offsetof(lck_t, rdt) % MDBX_CACHELINE_SIZE == 0);
|
||||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||||
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
STATIC_ASSERT(NODESIZE == offsetof(node_t, payload));
|
||||
@ -546,11 +551,7 @@ MDBX_MAYBE_UNUSED static void static_checks(void) {
|
||||
STATIC_ASSERT(sizeof(clc_t) == 3 * sizeof(void *));
|
||||
STATIC_ASSERT(sizeof(kvx_t) == 8 * sizeof(void *));
|
||||
|
||||
#if MDBX_WORDBITS == 64
|
||||
#define KVX_SIZE_LN2 6
|
||||
#else
|
||||
#define KVX_SIZE_LN2 5
|
||||
#endif
|
||||
#define KVX_SIZE_LN2 MDBX_WORDBITS_LN2
|
||||
STATIC_ASSERT(sizeof(kvx_t) == (1u << KVX_SIZE_LN2));
|
||||
}
|
||||
#endif /* Disabled for MSVC 19.0 (VisualStudio 2015) */
|
||||
|
@ -300,7 +300,7 @@ __cold MDBX_INTERNAL int mvcc_cleanup_dead(MDBX_env *env, int rdt_locked, int *d
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold txnid_t mvcc_kick_laggards(MDBX_env *env, const txnid_t straggler) {
|
||||
__cold bool mvcc_kick_laggards(MDBX_env *env, const txnid_t straggler) {
|
||||
DEBUG("DB size maxed out by reading #%" PRIaTXN, straggler);
|
||||
osal_memory_fence(mo_AcquireRelease, false);
|
||||
MDBX_hsr_func *const callback = env->hsr_callback;
|
||||
@ -410,5 +410,5 @@ __cold txnid_t mvcc_kick_laggards(MDBX_env *env, const txnid_t straggler) {
|
||||
NOTICE("hsr-kick: done turn %" PRIaTXN " -> %" PRIaTXN " +%" PRIaTXN, straggler, oldest, turn);
|
||||
callback(env, env->txn, 0, 0, straggler, (turn < UINT_MAX) ? (unsigned)turn : UINT_MAX, 0, -retry);
|
||||
}
|
||||
return oldest;
|
||||
return oldest > straggler;
|
||||
}
|
||||
|
13
src/node.c
13
src/node.c
@ -53,12 +53,6 @@ int __must_check_result node_add_branch(MDBX_cursor *mc, size_t indx, const MDBX
|
||||
cASSERT(mc, mp->txnid >= mc->txn->front_txnid);
|
||||
STATIC_ASSERT(NODESIZE % 2 == 0);
|
||||
|
||||
/* Move higher pointers up one slot. */
|
||||
const size_t nkeys = page_numkeys(mp);
|
||||
cASSERT(mc, nkeys >= indx);
|
||||
for (size_t i = nkeys; i > indx; --i)
|
||||
mp->entries[i] = mp->entries[i - 1];
|
||||
|
||||
/* Adjust free space offsets. */
|
||||
const size_t branch_bytes = branch_size(mc->txn->env, key);
|
||||
const intptr_t lower = mp->lower + sizeof(indx_t);
|
||||
@ -67,6 +61,13 @@ int __must_check_result node_add_branch(MDBX_cursor *mc, size_t indx, const MDBX
|
||||
mc->txn->flags |= MDBX_TXN_ERROR;
|
||||
return MDBX_PAGE_FULL;
|
||||
}
|
||||
|
||||
/* Move higher pointers up one slot. */
|
||||
const size_t nkeys = page_numkeys(mp);
|
||||
cASSERT(mc, nkeys >= indx);
|
||||
for (size_t i = nkeys; i > indx; --i)
|
||||
mp->entries[i] = mp->entries[i - 1];
|
||||
|
||||
mp->lower = (indx_t)lower;
|
||||
mp->entries[indx] = mp->upper = (indx_t)upper;
|
||||
|
||||
|
22
src/pnl.c
22
src/pnl.c
@ -23,6 +23,13 @@ void pnl_free(pnl_t pnl) {
|
||||
osal_free(pnl - 1);
|
||||
}
|
||||
|
||||
pnl_t pnl_clone(const pnl_t src) {
|
||||
pnl_t pl = pnl_alloc(MDBX_PNL_ALLOCLEN(src));
|
||||
if (likely(pl))
|
||||
memcpy(pl, src, MDBX_PNL_SIZEOF(src));
|
||||
return pl;
|
||||
}
|
||||
|
||||
void pnl_shrink(pnl_t __restrict *__restrict ppnl) {
|
||||
assert(pnl_bytes2size(pnl_size2bytes(MDBX_PNL_INITIAL)) >= MDBX_PNL_INITIAL &&
|
||||
pnl_bytes2size(pnl_size2bytes(MDBX_PNL_INITIAL)) < MDBX_PNL_INITIAL * 3 / 2);
|
||||
@ -234,3 +241,18 @@ __hot __noinline size_t pnl_search_nochk(const pnl_t pnl, pgno_t pgno) {
|
||||
assert(!MDBX_PNL_ORDERED(it[0], pgno));
|
||||
return it - begin + 1;
|
||||
}
|
||||
|
||||
size_t pnl_maxspan(const pnl_t pnl) {
|
||||
size_t len = MDBX_PNL_GETSIZE(pnl);
|
||||
if (len > 1) {
|
||||
size_t span = 1, left = len - span;
|
||||
const pgno_t *scan = MDBX_PNL_BEGIN(pnl);
|
||||
do {
|
||||
const bool contiguous = MDBX_PNL_CONTIGUOUS(*scan, scan[span], span);
|
||||
span += contiguous;
|
||||
scan += 1 - contiguous;
|
||||
} while (--left);
|
||||
len = span;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
15
src/pnl.h
15
src/pnl.h
@ -45,16 +45,18 @@ typedef const pgno_t *const_pnl_t;
|
||||
#define MDBX_PNL_EDGE(pl) ((pl) + 1)
|
||||
#define MDBX_PNL_LEAST(pl) MDBX_PNL_FIRST(pl)
|
||||
#define MDBX_PNL_MOST(pl) MDBX_PNL_LAST(pl)
|
||||
#define MDBX_PNL_CONTIGUOUS(prev, next, span) ((next) - (prev)) == (span))
|
||||
#else
|
||||
#define MDBX_PNL_EDGE(pl) ((pl) + MDBX_PNL_GETSIZE(pl))
|
||||
#define MDBX_PNL_LEAST(pl) MDBX_PNL_LAST(pl)
|
||||
#define MDBX_PNL_MOST(pl) MDBX_PNL_FIRST(pl)
|
||||
#define MDBX_PNL_CONTIGUOUS(prev, next, span) (((prev) - (next)) == (span))
|
||||
#endif
|
||||
|
||||
#define MDBX_PNL_SIZEOF(pl) ((MDBX_PNL_GETSIZE(pl) + 1) * sizeof(pgno_t))
|
||||
#define MDBX_PNL_IS_EMPTY(pl) (MDBX_PNL_GETSIZE(pl) == 0)
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline size_t pnl_size2bytes(size_t size) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline size_t pnl_size2bytes(size_t size) {
|
||||
assert(size > 0 && size <= PAGELIST_LIMIT);
|
||||
#if MDBX_PNL_PREALLOC_FOR_RADIXSORT
|
||||
|
||||
@ -69,7 +71,7 @@ MDBX_MAYBE_UNUSED static inline size_t pnl_size2bytes(size_t size) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline pgno_t pnl_bytes2size(const size_t bytes) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t pnl_bytes2size(const size_t bytes) {
|
||||
size_t size = bytes / sizeof(pgno_t);
|
||||
assert(size > 3 && size <= PAGELIST_LIMIT + /* alignment gap */ 65536);
|
||||
size -= 3;
|
||||
@ -83,6 +85,8 @@ MDBX_INTERNAL pnl_t pnl_alloc(size_t size);
|
||||
|
||||
MDBX_INTERNAL void pnl_free(pnl_t pnl);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL pnl_t pnl_clone(const pnl_t src);
|
||||
|
||||
MDBX_INTERNAL int pnl_reserve(pnl_t __restrict *__restrict ppnl, const size_t wanna);
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline int __must_check_result pnl_need(pnl_t __restrict *__restrict ppnl, size_t num) {
|
||||
@ -110,7 +114,7 @@ MDBX_INTERNAL int __must_check_result pnl_append_span(__restrict pnl_t *ppnl, pg
|
||||
|
||||
MDBX_INTERNAL int __must_check_result pnl_insert_span(__restrict pnl_t *ppnl, pgno_t pgno, size_t n);
|
||||
|
||||
MDBX_INTERNAL size_t pnl_search_nochk(const pnl_t pnl, pgno_t pgno);
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t pnl_search_nochk(const pnl_t pnl, pgno_t pgno);
|
||||
|
||||
MDBX_INTERNAL void pnl_sort_nochk(pnl_t pnl);
|
||||
|
||||
@ -126,7 +130,8 @@ MDBX_MAYBE_UNUSED static inline void pnl_sort(pnl_t pnl, size_t limit4check) {
|
||||
(void)limit4check;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline size_t pnl_search(const pnl_t pnl, pgno_t pgno, size_t limit) {
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline size_t pnl_search(const pnl_t pnl, pgno_t pgno,
|
||||
size_t limit) {
|
||||
assert(pnl_check_allocated(pnl, limit));
|
||||
if (MDBX_HAVE_CMOV) {
|
||||
/* cmov-ускоренный бинарный поиск может читать (но не использовать) один
|
||||
@ -144,3 +149,5 @@ MDBX_MAYBE_UNUSED static inline size_t pnl_search(const pnl_t pnl, pgno_t pgno,
|
||||
}
|
||||
|
||||
MDBX_INTERNAL size_t pnl_merge(pnl_t dst, const pnl_t src);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t pnl_maxspan(const pnl_t pnl);
|
||||
|
@ -15,9 +15,8 @@ MDBX_INTERNAL bsr_t mvcc_bind_slot(MDBX_env *env);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL pgno_t mvcc_largest_this(MDBX_env *env, pgno_t largest);
|
||||
MDBX_INTERNAL txnid_t mvcc_shapshot_oldest(MDBX_env *const env, const txnid_t steady);
|
||||
MDBX_INTERNAL pgno_t mvcc_snapshot_largest(const MDBX_env *env, pgno_t last_used_page);
|
||||
MDBX_INTERNAL txnid_t mvcc_kick_laggards(MDBX_env *env, const txnid_t straggler);
|
||||
MDBX_INTERNAL int mvcc_cleanup_dead(MDBX_env *env, int rlocked, int *dead);
|
||||
MDBX_INTERNAL txnid_t mvcc_kick_laggards(MDBX_env *env, const txnid_t laggard);
|
||||
MDBX_INTERNAL bool mvcc_kick_laggards(MDBX_env *env, const txnid_t laggard);
|
||||
|
||||
/* dxb.c */
|
||||
MDBX_INTERNAL int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bits);
|
||||
@ -62,10 +61,11 @@ struct commit_timestamp {
|
||||
};
|
||||
|
||||
MDBX_INTERNAL bool txn_refund(MDBX_txn *txn);
|
||||
MDBX_INTERNAL txnid_t txn_snapshot_oldest(const MDBX_txn *const txn);
|
||||
MDBX_INTERNAL bool txn_gc_detent(const MDBX_txn *const txn);
|
||||
MDBX_INTERNAL int txn_check_badbits_parked(const MDBX_txn *txn, int bad_bits);
|
||||
MDBX_INTERNAL void txn_done_cursors(MDBX_txn *txn);
|
||||
MDBX_INTERNAL int txn_shadow_cursors(const MDBX_txn *parent, const size_t dbi);
|
||||
MDBX_INTERNAL MDBX_cursor *txn_gc_cursor(MDBX_txn *txn);
|
||||
|
||||
MDBX_INTERNAL MDBX_txn *txn_alloc(const MDBX_txn_flags_t flags, MDBX_env *env);
|
||||
MDBX_INTERNAL int txn_abort(MDBX_txn *txn);
|
||||
|
639
src/rkl.c
Normal file
639
src/rkl.c
Normal file
@ -0,0 +1,639 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static inline size_t rkl_size2bytes(const size_t size) {
|
||||
assert(size > 0 && size <= txl_max * 2);
|
||||
size_t bytes = ceil_powerof2(MDBX_ASSUME_MALLOC_OVERHEAD + sizeof(txnid_t) * size, txl_granulate * sizeof(txnid_t)) -
|
||||
MDBX_ASSUME_MALLOC_OVERHEAD;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static inline size_t rkl_bytes2size(const size_t bytes) {
|
||||
size_t size = bytes / sizeof(txnid_t);
|
||||
assert(size > 0 && size <= txl_max * 2);
|
||||
return size;
|
||||
}
|
||||
|
||||
void rkl_init(rkl_t *rkl) {
|
||||
rkl->list_limit = ARRAY_LENGTH(rkl->inplace);
|
||||
rkl->list = rkl->inplace;
|
||||
rkl_clear(rkl);
|
||||
}
|
||||
|
||||
void rkl_clear(rkl_t *rkl) {
|
||||
rkl->solid_begin = UINT64_MAX;
|
||||
rkl->solid_end = 0;
|
||||
rkl->list_length = 0;
|
||||
}
|
||||
|
||||
void rkl_destroy(rkl_t *rkl) {
|
||||
void *ptr = rkl->list;
|
||||
rkl->list = nullptr;
|
||||
if (ptr != rkl->inplace)
|
||||
osal_free(ptr);
|
||||
}
|
||||
|
||||
static inline bool solid_empty(const rkl_t *rkl) { return !(rkl->solid_begin < rkl->solid_end); }
|
||||
|
||||
#define RKL_ORDERED(first, last) ((first) < (last))
|
||||
|
||||
SEARCH_IMPL(rkl_bsearch, txnid_t, txnid_t, RKL_ORDERED)
|
||||
|
||||
void rkl_destructive_move(rkl_t *src, rkl_t *dst) {
|
||||
assert(rkl_check(src));
|
||||
dst->solid_begin = src->solid_begin;
|
||||
dst->solid_end = src->solid_end;
|
||||
dst->list_length = src->list_length;
|
||||
if (dst->list != dst->inplace)
|
||||
osal_free(dst->list);
|
||||
if (src->list != src->inplace) {
|
||||
dst->list = src->list;
|
||||
dst->list_limit = src->list_limit;
|
||||
} else {
|
||||
dst->list = dst->inplace;
|
||||
dst->list_limit = ARRAY_LENGTH(src->inplace);
|
||||
memcpy(dst->inplace, src->list, sizeof(dst->inplace));
|
||||
}
|
||||
rkl_init(src);
|
||||
}
|
||||
|
||||
static int rkl_resize(rkl_t *rkl, size_t wanna_size) {
|
||||
assert(wanna_size > rkl->list_length);
|
||||
assert(rkl_check(rkl));
|
||||
STATIC_ASSERT(txl_max < INT_MAX / sizeof(txnid_t));
|
||||
if (unlikely(wanna_size > txl_max)) {
|
||||
ERROR("rkl too long (%zu >= %zu)", wanna_size, (size_t)txl_max);
|
||||
return MDBX_TXN_FULL;
|
||||
}
|
||||
if (unlikely(wanna_size < rkl->list_length)) {
|
||||
ERROR("unable shrink rkl to %zu since length is %u", wanna_size, rkl->list_length);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
|
||||
if (unlikely(wanna_size <= ARRAY_LENGTH(rkl->inplace))) {
|
||||
if (rkl->list != rkl->inplace) {
|
||||
assert(rkl->list_limit > ARRAY_LENGTH(rkl->inplace) && rkl->list_length <= ARRAY_LENGTH(rkl->inplace));
|
||||
memcpy(rkl->inplace, rkl->list, sizeof(rkl->inplace));
|
||||
rkl->list_limit = ARRAY_LENGTH(rkl->inplace);
|
||||
osal_free(rkl->list);
|
||||
rkl->list = rkl->inplace;
|
||||
} else {
|
||||
assert(rkl->list_limit == ARRAY_LENGTH(rkl->inplace));
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
if (wanna_size != rkl->list_limit) {
|
||||
size_t bytes = rkl_size2bytes(wanna_size);
|
||||
void *ptr = (rkl->list == rkl->inplace) ? osal_malloc(bytes) : osal_realloc(rkl->list, bytes);
|
||||
if (unlikely(!ptr))
|
||||
return MDBX_ENOMEM;
|
||||
#ifdef osal_malloc_usable_size
|
||||
bytes = osal_malloc_usable_size(ptr);
|
||||
#endif /* osal_malloc_usable_size */
|
||||
rkl->list_limit = rkl_bytes2size(bytes);
|
||||
if (rkl->list == rkl->inplace)
|
||||
memcpy(ptr, rkl->inplace, sizeof(rkl->inplace));
|
||||
rkl->list = ptr;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int rkl_copy(const rkl_t *src, rkl_t *dst) {
|
||||
assert(rkl_check(src));
|
||||
rkl_init(dst);
|
||||
if (!rkl_empty(src)) {
|
||||
if (dst->list_limit < src->list_length) {
|
||||
int err = rkl_resize(dst, src->list_limit);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
}
|
||||
memcpy(dst->list, src->list, sizeof(txnid_t) * src->list_length);
|
||||
dst->list_length = src->list_length;
|
||||
dst->solid_begin = src->solid_begin;
|
||||
dst->solid_end = src->solid_end;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
size_t rkl_len(const rkl_t *rkl) { return rkl_empty(rkl) ? 0 : rkl->solid_end - rkl->solid_begin + rkl->list_length; }
|
||||
|
||||
__hot bool rkl_contain(const rkl_t *rkl, txnid_t id) {
|
||||
assert(rkl_check(rkl));
|
||||
if (id >= rkl->solid_begin && id < rkl->solid_end)
|
||||
return true;
|
||||
if (rkl->list_length) {
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, id);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
assert(it >= rkl->list && it <= end);
|
||||
if (it != rkl->list)
|
||||
assert(RKL_ORDERED(it[-1], id));
|
||||
if (it != end) {
|
||||
assert(!RKL_ORDERED(it[0], id));
|
||||
return *it == id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
__hot bool rkl_find(const rkl_t *rkl, txnid_t id, rkl_iter_t *iter) {
|
||||
assert(rkl_check(rkl));
|
||||
*iter = rkl_iterator(rkl, false);
|
||||
if (id >= rkl->solid_begin) {
|
||||
if (id < rkl->solid_end) {
|
||||
iter->pos = iter->solid_offset + (unsigned)(id - rkl->solid_begin);
|
||||
return true;
|
||||
}
|
||||
iter->pos = (unsigned)(rkl->solid_end - rkl->solid_begin);
|
||||
}
|
||||
if (rkl->list_length) {
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, id);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
assert(it >= rkl->list && it <= end);
|
||||
if (it != rkl->list)
|
||||
assert(RKL_ORDERED(it[-1], id));
|
||||
iter->pos += (unsigned)(it - rkl->list);
|
||||
if (it != end) {
|
||||
assert(!RKL_ORDERED(it[0], id));
|
||||
return *it == id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline txnid_t list_remove_first(rkl_t *rkl) {
|
||||
assert(rkl->list_length > 0);
|
||||
const txnid_t first = rkl->list[0];
|
||||
if (--rkl->list_length) {
|
||||
/* TODO: Можно подумать о том, чтобы для избавления от memove() добавить headroom или вместо длины и
|
||||
* указателя на список использовать три поля: list_begin, list_end и list_buffer. */
|
||||
size_t i = 0;
|
||||
do
|
||||
rkl->list[i] = rkl->list[i + 1];
|
||||
while (++i <= rkl->list_length);
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
static inline txnid_t after_cut(rkl_t *rkl, const txnid_t out) {
|
||||
if (rkl->list_length == 0 && rkl->solid_begin == rkl->solid_end) {
|
||||
rkl->solid_end = 0;
|
||||
rkl->solid_begin = UINT64_MAX;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static int extend_solid(rkl_t *rkl, txnid_t solid_begin, txnid_t solid_end, const txnid_t id) {
|
||||
if (rkl->list_length) {
|
||||
const txnid_t *i = rkl_bsearch(rkl->list, rkl->list_length, id);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
/* если начало или конец списка примыкает к непрерывному интервалу,
|
||||
* то переносим эти элементы из списка в непрерывный интервал */
|
||||
txnid_t *f = (txnid_t *)i;
|
||||
while (f > rkl->list && f[-1] >= solid_begin - 1) {
|
||||
f -= 1;
|
||||
solid_begin -= 1;
|
||||
if (unlikely(*f != solid_begin))
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
txnid_t *t = (txnid_t *)i;
|
||||
while (t < end && *t <= solid_end) {
|
||||
if (unlikely(*t != solid_end))
|
||||
return MDBX_RESULT_TRUE;
|
||||
solid_end += 1;
|
||||
t += 1;
|
||||
}
|
||||
if (f < t) {
|
||||
rkl->list_length -= t - f;
|
||||
while (t < end)
|
||||
*f++ = *t++;
|
||||
}
|
||||
}
|
||||
|
||||
rkl->solid_begin = solid_begin;
|
||||
rkl->solid_end = solid_end;
|
||||
assert(rkl_check(rkl));
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int rkl_push(rkl_t *rkl, const txnid_t id, const bool known_continuous) {
|
||||
assert(id >= MIN_TXNID && id < INVALID_TXNID);
|
||||
assert(rkl_check(rkl));
|
||||
|
||||
if (rkl->solid_begin >= rkl->solid_end) {
|
||||
/* непрерывный интервал пуст */
|
||||
return extend_solid(rkl, id, id + 1, id);
|
||||
} else if (id < rkl->solid_begin) {
|
||||
if (known_continuous || id + 1 == rkl->solid_begin)
|
||||
/* id примыкает к solid_begin */
|
||||
return extend_solid(rkl, id, rkl->solid_end, id);
|
||||
} else if (id >= rkl->solid_end) {
|
||||
if (known_continuous || id == rkl->solid_end)
|
||||
/* id примыкает к solid_end */
|
||||
return extend_solid(rkl, rkl->solid_begin, id + 1, id);
|
||||
} else {
|
||||
/* id входит в интервал между solid_begin и solid_end, т.е. подан дубликат */
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
if (rkl->list_length == 1 && rkl->solid_end == rkl->solid_begin + 1 &&
|
||||
(rkl->list[0] == id + 1 || rkl->list[0] == id - 1)) {
|
||||
/* В списке один элемент и добавляемый id примыкает к нему, при этом в непрерывном интервале тоже один элемент.
|
||||
* Лучше поменять элементы списка и непрерывного интервала. */
|
||||
const txnid_t couple = (rkl->list[0] == id - 1) ? id - 1 : id;
|
||||
rkl->list[0] = rkl->solid_begin;
|
||||
rkl->solid_begin = couple;
|
||||
rkl->solid_end = couple + 2;
|
||||
assert(rkl_check(rkl));
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
if (unlikely(rkl->list_length == rkl->list_limit)) {
|
||||
/* удваиваем размер буфера если закончилось место */
|
||||
size_t x2 = (rkl->list_limit + 1) << 1;
|
||||
x2 = (x2 > 62) ? x2 : 62;
|
||||
x2 = (x2 < txl_max) ? x2 : txl_max;
|
||||
x2 = (x2 > rkl->list_length) ? x2 : rkl->list_length + 42;
|
||||
int err = rkl_resize(rkl, x2);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
assert(rkl->list_limit > rkl->list_length);
|
||||
}
|
||||
|
||||
size_t i = rkl->list_length;
|
||||
/* ищем место для вставки двигаясь от конца к началу списка, сразу переставляя/раздвигая элементы */
|
||||
while (i > 0) {
|
||||
if (RKL_ORDERED(id, rkl->list[i - 1])) {
|
||||
rkl->list[i] = rkl->list[i - 1];
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
if (unlikely(id == rkl->list[i - 1])) {
|
||||
while (++i < rkl->list_length)
|
||||
rkl->list[i - 1] = rkl->list[i];
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
rkl->list[i] = id;
|
||||
rkl->list_length++;
|
||||
assert(rkl_check(rkl));
|
||||
|
||||
/* После добавления id в списке могла образоваться длинная последовательность,
|
||||
* которую (возможно) стоит обменять с непрерывным интервалом. */
|
||||
if (rkl->list_length > (MDBX_DEBUG ? 2 : 16) &&
|
||||
((i > 0 && rkl->list[i - 1] == id - 1) || (i + 1 < rkl->list_length && rkl->list[i + 1] == id + 1))) {
|
||||
txnid_t new_solid_begin = id;
|
||||
size_t from = i;
|
||||
while (from > 0 && rkl->list[from - 1] == new_solid_begin - 1) {
|
||||
from -= 1;
|
||||
new_solid_begin -= 1;
|
||||
}
|
||||
txnid_t new_solid_end = id + 1;
|
||||
size_t to = i + 1;
|
||||
while (to < rkl->list_length && rkl->list[to] == new_solid_end) {
|
||||
to += 1;
|
||||
new_solid_end += 1;
|
||||
}
|
||||
|
||||
const size_t new_solid_len = to - from;
|
||||
if (new_solid_len > 3) {
|
||||
const size_t old_solid_len = rkl->solid_end - rkl->solid_begin;
|
||||
if (new_solid_len > old_solid_len) {
|
||||
/* Новая непрерывная последовательность длиннее текущей.
|
||||
* Считаем обмен выгодным, если он дешевле пути развития событий с добавлением следующего элемента в список. */
|
||||
const size_t old_solid_pos = rkl_bsearch(rkl->list, rkl->list_length, rkl->solid_begin) - rkl->list;
|
||||
const size_t swap_cost =
|
||||
/* количество элементов списка после изымаемой из списка последовательности,
|
||||
* которые нужно переместить */
|
||||
rkl->list_length - to +
|
||||
/* количество элементов списка после позиции добавляемой в список последовательности,
|
||||
* которые нужно переместить */
|
||||
((from > old_solid_pos) ? from - old_solid_pos : 0)
|
||||
/* количество элементов списка добавляемой последовательности, которые нужно добавить */
|
||||
+ old_solid_len;
|
||||
/* количество элементов списка, которые нужно переместить для вставки еще-одного/следующего элемента */
|
||||
const size_t new_insert_cost = rkl->list_length - i;
|
||||
/* coverity[logical_vs_bitwise] */
|
||||
if (unlikely(swap_cost < new_insert_cost) || MDBX_DEBUG) {
|
||||
/* Изымаемая последовательность длиннее добавляемой, поэтому:
|
||||
* - список станет короче;
|
||||
* - перемещать хвост нужно всегда к началу;
|
||||
* - если начальные элементы потребуется раздвигать,
|
||||
* то места хватит и остающиеся элементы в конце не будут перезаписаны. */
|
||||
size_t moved = 0;
|
||||
if (from > old_solid_pos) {
|
||||
/* добавляемая последовательность ближе к началу, нужно раздвинуть элементы в голове для вставки. */
|
||||
moved = from - old_solid_pos;
|
||||
do {
|
||||
from -= 1;
|
||||
rkl->list[from + old_solid_len] = rkl->list[from];
|
||||
} while (from > old_solid_pos);
|
||||
} else if (from + new_solid_len < old_solid_pos) {
|
||||
/* добавляемая последовательность дальше от начала,
|
||||
* перемещаем часть элементов из хвоста после изымаемой последовательности */
|
||||
do
|
||||
rkl->list[from++] = rkl->list[to++];
|
||||
while (from < old_solid_pos - new_solid_len);
|
||||
}
|
||||
|
||||
/* вставляем последовательноть */
|
||||
i = 0;
|
||||
do
|
||||
rkl->list[from++] = rkl->solid_begin + i++;
|
||||
while (i != old_solid_len);
|
||||
|
||||
/* сдвигаем оставшийся хвост */
|
||||
while (to < rkl->list_length)
|
||||
rkl->list[moved + from++] = rkl->list[to++];
|
||||
|
||||
rkl->list_length = rkl->list_length - new_solid_len + old_solid_len;
|
||||
rkl->solid_begin = new_solid_begin;
|
||||
rkl->solid_end = new_solid_end;
|
||||
assert(rkl_check(rkl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
txnid_t rkl_pop(rkl_t *rkl, const bool highest_not_lowest) {
|
||||
assert(rkl_check(rkl));
|
||||
|
||||
if (rkl->list_length) {
|
||||
assert(rkl->solid_begin <= rkl->solid_end);
|
||||
if (highest_not_lowest && (solid_empty(rkl) || rkl->solid_end < rkl->list[rkl->list_length - 1]))
|
||||
return after_cut(rkl, rkl->list[rkl->list_length -= 1]);
|
||||
if (!highest_not_lowest && (solid_empty(rkl) || rkl->solid_begin > rkl->list[0]))
|
||||
return after_cut(rkl, list_remove_first(rkl));
|
||||
}
|
||||
|
||||
if (!solid_empty(rkl))
|
||||
return after_cut(rkl, highest_not_lowest ? --rkl->solid_end : rkl->solid_begin++);
|
||||
|
||||
assert(rkl_empty(rkl));
|
||||
return 0;
|
||||
}
|
||||
|
||||
txnid_t rkl_lowest(const rkl_t *rkl) {
|
||||
if (rkl->list_length)
|
||||
return (solid_empty(rkl) || rkl->list[0] < rkl->solid_begin) ? rkl->list[0] : rkl->solid_begin;
|
||||
return !solid_empty(rkl) ? rkl->solid_begin : INVALID_TXNID;
|
||||
}
|
||||
|
||||
txnid_t rkl_highest(const rkl_t *rkl) {
|
||||
if (rkl->list_length)
|
||||
return (solid_empty(rkl) || rkl->list[rkl->list_length - 1] >= rkl->solid_end) ? rkl->list[rkl->list_length - 1]
|
||||
: rkl->solid_end - 1;
|
||||
return !solid_empty(rkl) ? rkl->solid_end - 1 : 0;
|
||||
}
|
||||
|
||||
int rkl_merge(rkl_t *dst, const rkl_t *src, bool ignore_duplicates) {
|
||||
if (src->list_length) {
|
||||
size_t i = src->list_length;
|
||||
do {
|
||||
int err = rkl_push(dst, src->list[i - 1], false);
|
||||
if (unlikely(err != MDBX_SUCCESS) && (!ignore_duplicates || err != MDBX_RESULT_TRUE))
|
||||
return err;
|
||||
} while (--i);
|
||||
}
|
||||
|
||||
txnid_t id = src->solid_begin;
|
||||
while (id < src->solid_end) {
|
||||
int err = rkl_push(dst, id, false);
|
||||
if (unlikely(err != MDBX_SUCCESS) && (!ignore_duplicates || err != MDBX_RESULT_TRUE))
|
||||
return err;
|
||||
++id;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
rkl_iter_t rkl_iterator(const rkl_t *rkl, const bool reverse) {
|
||||
rkl_iter_t iter = {.rkl = rkl, .pos = reverse ? rkl_len(rkl) : 0, .solid_offset = 0};
|
||||
if (!solid_empty(rkl) && rkl->list_length) {
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, rkl->solid_begin);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
assert(it >= rkl->list && it <= end && (it == end || *it > rkl->solid_begin));
|
||||
iter.solid_offset = it - rkl->list;
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
|
||||
txnid_t rkl_turn(rkl_iter_t *iter, const bool reverse) {
|
||||
assert((unsigned)reverse == (unsigned)!!reverse);
|
||||
size_t pos = iter->pos - reverse;
|
||||
if (unlikely(pos >= rkl_len(iter->rkl)))
|
||||
return 0;
|
||||
|
||||
iter->pos = pos + !reverse;
|
||||
assert(iter->pos <= rkl_len(iter->rkl));
|
||||
|
||||
const size_t solid_len = iter->rkl->solid_end - iter->rkl->solid_begin;
|
||||
if (iter->rkl->list_length) {
|
||||
if (pos < iter->solid_offset)
|
||||
return iter->rkl->list[pos];
|
||||
else if (pos < iter->solid_offset + solid_len)
|
||||
return iter->rkl->solid_begin + pos - iter->solid_offset;
|
||||
else
|
||||
return iter->rkl->list[pos - solid_len];
|
||||
}
|
||||
|
||||
assert(pos < solid_len);
|
||||
return iter->rkl->solid_begin + pos;
|
||||
}
|
||||
|
||||
size_t rkl_left(rkl_iter_t *iter, const bool reverse) {
|
||||
assert(iter->pos <= rkl_len(iter->rkl));
|
||||
return reverse ? iter->pos : rkl_len(iter->rkl) - iter->pos;
|
||||
}
|
||||
|
||||
#if 1
|
||||
#define DEBUG_HOLE(hole) \
|
||||
do { \
|
||||
} while (0)
|
||||
#else
|
||||
#define DEBUG_HOLE(hole) \
|
||||
do { \
|
||||
printf(" return-%sward: %d, ", reverse ? "back" : "for", __LINE__); \
|
||||
if (hole.begin == hole.end) \
|
||||
printf("empty-hole\n"); \
|
||||
else if (hole.end - hole.begin == 1) \
|
||||
printf("hole %" PRIaTXN "\n", hole.begin); \
|
||||
else \
|
||||
printf("hole %" PRIaTXN "-%" PRIaTXN "\n", hole.begin, hole.end - 1); \
|
||||
fflush(nullptr); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
rkl_hole_t rkl_hole(rkl_iter_t *iter, const bool reverse) {
|
||||
assert((unsigned)reverse == (unsigned)!!reverse);
|
||||
rkl_hole_t hole;
|
||||
const size_t len = rkl_len(iter->rkl);
|
||||
size_t pos = iter->pos;
|
||||
if (unlikely(pos >= len)) {
|
||||
if (len == 0) {
|
||||
hole.begin = 1;
|
||||
hole.end = MAX_TXNID;
|
||||
iter->pos = 0;
|
||||
DEBUG_HOLE(hole);
|
||||
return hole;
|
||||
} else if (pos == len && reverse) {
|
||||
/* шаг назад из позиции на конце rkl */
|
||||
} else if (reverse) {
|
||||
hole.begin = 1;
|
||||
hole.end = 1 /* rkl_lowest(iter->rkl); */;
|
||||
iter->pos = 0;
|
||||
DEBUG_HOLE(hole);
|
||||
return hole;
|
||||
} else {
|
||||
hole.begin = MAX_TXNID /* rkl_highest(iter->rkl) + 1 */;
|
||||
hole.end = MAX_TXNID;
|
||||
iter->pos = len;
|
||||
DEBUG_HOLE(hole);
|
||||
return hole;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t solid_len = iter->rkl->solid_end - iter->rkl->solid_begin;
|
||||
if (iter->rkl->list_length) {
|
||||
/* список элементов не пуст */
|
||||
txnid_t here, there;
|
||||
for (size_t next;; pos = next) {
|
||||
next = reverse ? pos - 1 : pos + 1;
|
||||
if (pos < iter->solid_offset) {
|
||||
/* текущая позиция перед непрерывным интервалом */
|
||||
here = iter->rkl->list[pos];
|
||||
if (next == iter->solid_offset) {
|
||||
/* в следующей позиции начинается непрерывный интерал (при поиске вперед) */
|
||||
assert(!reverse);
|
||||
hole.begin = here + 1;
|
||||
hole.end = iter->rkl->solid_begin;
|
||||
next += solid_len;
|
||||
assert(hole.begin < hole.end /* зазор обязан быть, иначе это ошибка не-слияния */);
|
||||
/* зазор между элементом списка перед сплошным интервалом и началом интервала */
|
||||
iter->pos = next - 1;
|
||||
DEBUG_HOLE(hole);
|
||||
return hole;
|
||||
}
|
||||
if (next >= len)
|
||||
/* уперлись в конец или начало rkl */
|
||||
break;
|
||||
/* следующая позиция также перед непрерывным интервалом */
|
||||
there = iter->rkl->list[next];
|
||||
} else if (pos >= iter->solid_offset + solid_len) {
|
||||
/* текущая позиция после непрерывного интервала */
|
||||
here = (pos < len) ? iter->rkl->list[pos - solid_len] : MAX_TXNID;
|
||||
if (next >= len)
|
||||
/* уперлись в конец или начало rkl */
|
||||
break;
|
||||
if (next == iter->solid_offset + solid_len - 1) {
|
||||
/* в следующей позиции конец непрерывного интервала (при поиске назад) */
|
||||
assert(reverse);
|
||||
hole.begin = iter->rkl->solid_end;
|
||||
hole.end = here;
|
||||
pos = iter->solid_offset;
|
||||
assert(hole.begin < hole.end /* зазор обязан быть, иначе это ошибка не-слияния */);
|
||||
/* зазор между элементом списка после сплошного интервала и концом интервала */
|
||||
iter->pos = pos;
|
||||
DEBUG_HOLE(hole);
|
||||
return hole;
|
||||
}
|
||||
/* следующая позиция также после непрерывного интервала */
|
||||
there = iter->rkl->list[next - solid_len];
|
||||
} else if (reverse) {
|
||||
/* текущая позиция внутри непрерывного интервала и поиск назад */
|
||||
next = iter->solid_offset - 1;
|
||||
here = iter->rkl->solid_begin;
|
||||
if (next >= len)
|
||||
/* нет элементов списка перед непрерывным интервалом */
|
||||
break;
|
||||
/* предыдущая позиция перед непрерывным интервалом */
|
||||
there = iter->rkl->list[next];
|
||||
} else {
|
||||
/* текущая позиция внутри непрерывного интервала и поиск вперед */
|
||||
next = iter->solid_offset + solid_len;
|
||||
here = iter->rkl->solid_end - 1;
|
||||
if (next >= len)
|
||||
/* нет элементов списка после непрерывного интервала */
|
||||
break;
|
||||
/* следующая позиция после непрерывного интервала */
|
||||
there = iter->rkl->list[next - solid_len];
|
||||
}
|
||||
|
||||
hole.begin = (reverse ? there : here) + 1;
|
||||
hole.end = reverse ? here : there;
|
||||
if (hole.begin < hole.end) {
|
||||
/* есть зазор между текущей и следующей позицией */
|
||||
iter->pos = next;
|
||||
DEBUG_HOLE(hole);
|
||||
return hole;
|
||||
}
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
/* уперлись в начало rkl, возвращаем зазор перед началом rkl */
|
||||
hole.begin = 1;
|
||||
hole.end = here;
|
||||
iter->pos = 0;
|
||||
DEBUG_HOLE(hole);
|
||||
} else {
|
||||
/* уперлись в конец rkl, возвращаем зазор после конца rkl */
|
||||
hole.begin = here + 1;
|
||||
hole.end = MAX_TXNID;
|
||||
iter->pos = len;
|
||||
DEBUG_HOLE(hole);
|
||||
}
|
||||
return hole;
|
||||
}
|
||||
|
||||
/* список элементов пуст, но есть непрерывный интервал */
|
||||
iter->pos = reverse ? 0 : len;
|
||||
if (reverse && pos < len) {
|
||||
/* возвращаем зазор перед непрерывным интервалом */
|
||||
hole.begin = 1;
|
||||
hole.end = iter->rkl->solid_begin;
|
||||
DEBUG_HOLE(hole);
|
||||
} else {
|
||||
/* возвращаем зазор после непрерывного интервала */
|
||||
hole.begin = iter->rkl->solid_end;
|
||||
hole.end = MAX_TXNID;
|
||||
DEBUG_HOLE(hole);
|
||||
}
|
||||
return hole;
|
||||
}
|
||||
|
||||
bool rkl_check(const rkl_t *rkl) {
|
||||
if (!rkl)
|
||||
return false;
|
||||
if (rkl->list == rkl->inplace && unlikely(rkl->list_limit != ARRAY_LENGTH(rkl->inplace)))
|
||||
return false;
|
||||
if (unlikely(rkl->list_limit < ARRAY_LENGTH(rkl->inplace)))
|
||||
return false;
|
||||
|
||||
if (rkl_empty(rkl))
|
||||
return rkl->list_length == 0 && solid_empty(rkl);
|
||||
|
||||
if (rkl->list_length) {
|
||||
for (size_t i = 1; i < rkl->list_length; ++i)
|
||||
if (unlikely(!RKL_ORDERED(rkl->list[i - 1], rkl->list[i])))
|
||||
return false;
|
||||
if (!solid_empty(rkl) && rkl->solid_begin - 1 <= rkl->list[rkl->list_length - 1] &&
|
||||
rkl->solid_end >= rkl->list[0]) {
|
||||
/* непрерывный интервал "плавает" внутри списка, т.е. находится между какими-то соседними значениями */
|
||||
const txnid_t *it = rkl_bsearch(rkl->list, rkl->list_length, rkl->solid_begin);
|
||||
const txnid_t *const end = rkl->list + rkl->list_length;
|
||||
if (it < rkl->list || it > end)
|
||||
return false;
|
||||
if (it > rkl->list && it[-1] >= rkl->solid_begin)
|
||||
return false;
|
||||
if (it < end && it[0] <= rkl->solid_end)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
76
src/rkl.h
Normal file
76
src/rkl.h
Normal file
@ -0,0 +1,76 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
/* Сортированный набор txnid, использующий внутри комбинацию непрерывного интервала и списка.
|
||||
* Обеспечивает хранение id записей при переработке, очистку и обновлении GC, включая возврат остатков переработанных
|
||||
* страниц.
|
||||
*
|
||||
* При переработке GC записи преимущественно выбираются последовательно, но это не гарантируется. В LIFO-режиме
|
||||
* переработка и добавление записей в rkl происходит преимущественно в обратном порядке, но из-за завершения читающих
|
||||
* транзакций могут быть «скачки» в прямом направлении. В FIFO-режиме записи GC перерабатываются в прямом порядке и при
|
||||
* этом линейно, но не обязательно строго последовательно, при этом гарантируется что между добавляемыми в rkl
|
||||
* идентификаторами в GC нет записей, т.е. между первой (минимальный id) и последней (максимальный id) в GC нет записей
|
||||
* и весь интервал может быть использован для возврата остатков страниц в GC.
|
||||
*
|
||||
* Таким образом, комбинация линейного интервала и списка (отсортированного в порядке возрастания элементов) является
|
||||
* рациональным решением, близким к теоретически оптимальному пределу.
|
||||
*
|
||||
* Реализация rkl достаточно проста/прозрачная, если не считать неочевидную «магию» обмена непрерывного интервала и
|
||||
* образующихся в списке последовательностей. Однако, именно этот автоматически выполняемый без лишних операций обмен
|
||||
* оправдывает все накладные расходы. */
|
||||
typedef struct MDBX_rkl {
|
||||
txnid_t solid_begin, solid_end; /* начало и конец непрерывной последовательности solid_begin ... solid_end-1. */
|
||||
unsigned list_length; /* текущая длина списка. */
|
||||
unsigned list_limit; /* размер буфера выделенного под список, равен ARRAY_LENGTH(inplace) когда list == inplace. */
|
||||
txnid_t *list; /* список отдельных элементов в порядке возрастания (наименьший в начале). */
|
||||
txnid_t inplace[4 + 8]; /* статический массив для коротких списков, чтобы избавиться от выделения/освобождения памяти
|
||||
* в большинстве случаев. */
|
||||
} rkl_t;
|
||||
|
||||
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 MDBX_INTERNAL void rkl_destroy(rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void rkl_destructive_move(rkl_t *dst, rkl_t *src);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result int rkl_copy(const rkl_t *src, rkl_t *dst);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool rkl_empty(const rkl_t *rkl) {
|
||||
return rkl->solid_begin > rkl->solid_end;
|
||||
}
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL bool rkl_check(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t rkl_len(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL txnid_t rkl_lowest(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL txnid_t rkl_highest(const rkl_t *rkl);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline txnid_t rkl_edge(const rkl_t *rkl,
|
||||
const bool highest_not_lowest) {
|
||||
return highest_not_lowest ? rkl_highest(rkl) : rkl_lowest(rkl);
|
||||
}
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result int rkl_push(rkl_t *rkl, const txnid_t id,
|
||||
const bool known_continuous);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL txnid_t rkl_pop(rkl_t *rkl, const bool highest_not_lowest);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result int rkl_merge(rkl_t *dst, const rkl_t *src, bool ignore_duplicates);
|
||||
|
||||
/* Итератор для rkl.
|
||||
* Обеспечивает изоляцию внутреннего устройства rkl от остального кода, чем существенно его упрощает.
|
||||
* Фактически именно использованием rkl с итераторами ликвидируется "ребус" исторически образовавшийся в gc-update. */
|
||||
typedef struct MDBX_rkl_iter {
|
||||
const rkl_t *rkl;
|
||||
unsigned pos;
|
||||
unsigned solid_offset;
|
||||
} rkl_iter_t;
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result rkl_iter_t rkl_iterator(const rkl_t *rkl, const bool reverse);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result txnid_t rkl_turn(rkl_iter_t *iter, const bool reverse);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t rkl_left(rkl_iter_t *iter, const bool reverse);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool rkl_find(const rkl_t *rkl, const txnid_t id, rkl_iter_t *iter);
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION __must_check_result MDBX_INTERNAL bool rkl_contain(const rkl_t *rkl,
|
||||
txnid_t id);
|
||||
|
||||
typedef struct MDBX_rkl_hole {
|
||||
txnid_t begin;
|
||||
txnid_t end;
|
||||
} rkl_hole_t;
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL __must_check_result rkl_hole_t rkl_hole(rkl_iter_t *iter, const bool reverse);
|
@ -38,11 +38,10 @@ static MDBX_cursor *cursor_clone(const MDBX_cursor *csrc, cursor_couple_t *coupl
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
void recalculate_merge_thresholds(MDBX_env *env) {
|
||||
const size_t bytes = page_space(env);
|
||||
env->merge_threshold = (uint16_t)(bytes - (bytes * env->options.merge_threshold_16dot16_percent >> 16));
|
||||
env->merge_threshold_gc =
|
||||
(uint16_t)(bytes - ((env->options.merge_threshold_16dot16_percent > 19005) ? bytes / 3 /* 33 % */
|
||||
: bytes / 4 /* 25 % */));
|
||||
const size_t whole_page_space = page_space(env);
|
||||
env->merge_threshold =
|
||||
(uint16_t)(whole_page_space - (whole_page_space * env->options.merge_threshold_16dot16_percent >> 16));
|
||||
eASSERT(env, env->merge_threshold >= whole_page_space / 2 && env->merge_threshold <= whole_page_space / 64 * 63);
|
||||
}
|
||||
|
||||
int tree_drop(MDBX_cursor *mc, const bool may_have_tables) {
|
||||
@ -446,8 +445,8 @@ static int page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) {
|
||||
cASSERT(cdst, cdst->top > 0);
|
||||
cASSERT(cdst, cdst->top + 1 < cdst->tree->height || is_leaf(cdst->pg[cdst->tree->height - 1]));
|
||||
cASSERT(csrc, csrc->top + 1 < csrc->tree->height || is_leaf(csrc->pg[csrc->tree->height - 1]));
|
||||
cASSERT(cdst,
|
||||
csrc->txn->env->options.prefer_waf_insteadof_balance || page_room(pdst) >= page_used(cdst->txn->env, psrc));
|
||||
cASSERT(cdst, cursor_dbi(csrc) == FREE_DBI || csrc->txn->env->options.prefer_waf_insteadof_balance ||
|
||||
page_room(pdst) >= page_used(cdst->txn->env, psrc));
|
||||
const int pagetype = page_type(psrc);
|
||||
|
||||
/* Move all nodes from src to dst */
|
||||
@ -680,8 +679,18 @@ int tree_rebalance(MDBX_cursor *mc) {
|
||||
const size_t minkeys = (pagetype & P_BRANCH) + (size_t)1;
|
||||
|
||||
/* Pages emptier than this are candidates for merging. */
|
||||
size_t room_threshold =
|
||||
likely(mc->tree != &mc->txn->dbs[FREE_DBI]) ? mc->txn->env->merge_threshold : mc->txn->env->merge_threshold_gc;
|
||||
size_t room_threshold = mc->txn->env->merge_threshold;
|
||||
bool minimize_waf = mc->txn->env->options.prefer_waf_insteadof_balance;
|
||||
if (unlikely(mc->tree == &mc->txn->dbs[FREE_DBI])) {
|
||||
/* В случае GC всегда минимизируем WAF, а рыхлые страницы объединяем только при наличии запаса в gc_stockpile().
|
||||
* Это позволяет уменьшить WAF и избавиться от лишних действий/циклов как при переработке GC,
|
||||
* так и при возврате неиспользованных страниц. Сбалансированность b-tree при этом почти не деградирует,
|
||||
* ибо добавление/удаление/обновление запиcей происходит почти всегда только по краям. */
|
||||
minimize_waf = true;
|
||||
room_threshold = page_space(mc->txn->env);
|
||||
if (gc_stockpile(mc->txn) > mc->tree->height + mc->tree->height)
|
||||
room_threshold >>= 1;
|
||||
}
|
||||
|
||||
const size_t numkeys = page_numkeys(tp);
|
||||
const size_t room = page_room(tp);
|
||||
@ -802,10 +811,26 @@ int tree_rebalance(MDBX_cursor *mc) {
|
||||
const size_t right_room = right ? page_room(right) : 0;
|
||||
const size_t left_nkeys = left ? page_numkeys(left) : 0;
|
||||
const size_t right_nkeys = right ? page_numkeys(right) : 0;
|
||||
|
||||
/* Нужно выбрать между правой и левой страницами для слияния текущей или перемещения узла в текущую.
|
||||
* Таким образом, нужно выбрать один из четырёх вариантов согласно критериям.
|
||||
*
|
||||
* Если включен minimize_waf, то стараемся не вовлекать чистые страницы,
|
||||
* пренебрегая идеальностью баланса ради уменьшения WAF.
|
||||
*
|
||||
* При этом отдельные варианты могут быть не доступны, либо "не сработать" из-за того что:
|
||||
* - в какой-то branch-странице не хватит места из-за распространения/обновления первых ключей,
|
||||
* которые хранятся в родительских страницах;
|
||||
* - при включенном minimize_waf распространение/обновление первых ключей
|
||||
* потребуется разделение какой-либо странице, что увеличит WAF и поэтому обесценивает дальнейшее
|
||||
* следование minimize_waf. */
|
||||
|
||||
bool involve = !(left && right);
|
||||
retry:
|
||||
cASSERT(mc, mc->top > 0);
|
||||
if (left_room > room_threshold && left_room >= right_room && (is_modifable(mc->txn, left) || involve)) {
|
||||
const bool consider_left = left && (involve || is_modifable(mc->txn, left));
|
||||
const bool consider_right = right && (involve || is_modifable(mc->txn, right));
|
||||
if (consider_left && left_room > room_threshold && left_room >= right_room) {
|
||||
/* try merge with left */
|
||||
cASSERT(mc, left_nkeys >= minkeys);
|
||||
mn->pg[mn->top] = left;
|
||||
@ -825,7 +850,7 @@ retry:
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (right_room > room_threshold && (is_modifable(mc->txn, right) || involve)) {
|
||||
if (consider_right && right_room > room_threshold) {
|
||||
/* try merge with right */
|
||||
cASSERT(mc, right_nkeys >= minkeys);
|
||||
mn->pg[mn->top] = right;
|
||||
@ -843,8 +868,7 @@ retry:
|
||||
}
|
||||
}
|
||||
|
||||
if (left_nkeys > minkeys && (right_nkeys <= left_nkeys || right_room >= left_room) &&
|
||||
(is_modifable(mc->txn, left) || involve)) {
|
||||
if (consider_left && left_nkeys > minkeys && (right_nkeys <= left_nkeys || right_room >= left_room)) {
|
||||
/* try move from left */
|
||||
mn->pg[mn->top] = left;
|
||||
mn->ki[mn->top - 1] = (indx_t)(ki_pre_top - 1);
|
||||
@ -860,7 +884,7 @@ retry:
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (right_nkeys > minkeys && (is_modifable(mc->txn, right) || involve)) {
|
||||
if (consider_right && right_nkeys > minkeys) {
|
||||
/* try move from right */
|
||||
mn->pg[mn->top] = right;
|
||||
mn->ki[mn->top - 1] = (indx_t)(ki_pre_top + 1);
|
||||
@ -884,17 +908,20 @@ retry:
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
if (mc->txn->env->options.prefer_waf_insteadof_balance && likely(room_threshold > 0)) {
|
||||
if (minimize_waf && room_threshold > 0) {
|
||||
/* Если включен minimize_waf, то переходим к попыткам слияния с сильно
|
||||
* заполненными страницами до вовлечения чистых страниц (не измененных в этой транзакции) */
|
||||
room_threshold = 0;
|
||||
goto retry;
|
||||
}
|
||||
if (likely(!involve) &&
|
||||
(likely(mc->tree != &mc->txn->dbs[FREE_DBI]) || mc->txn->wr.loose_pages || MDBX_PNL_GETSIZE(mc->txn->wr.repnl) ||
|
||||
(mc->flags & z_gcu_preparation) || (mc->txn->flags & txn_gc_drained) || room_threshold)) {
|
||||
if (!involve) {
|
||||
/* Теперь допускаем вовлечение чистых страниц (не измененных в этой транзакции),
|
||||
* что улучшает баланс в дереве, но увеличивает WAF. */
|
||||
involve = true;
|
||||
goto retry;
|
||||
}
|
||||
if (likely(room_threshold > 0)) {
|
||||
if (room_threshold > 0) {
|
||||
/* Если не нашли подходящей соседней, то допускаем слияние с сильно заполненными страницами */
|
||||
room_threshold = 0;
|
||||
goto retry;
|
||||
}
|
||||
@ -1228,6 +1255,7 @@ int page_split(MDBX_cursor *mc, const MDBX_val *const newkey, MDBX_val *const ne
|
||||
|
||||
/* root split? */
|
||||
prev_top += mc->top - top;
|
||||
cASSERT(mn, prev_top <= mn->top && prev_top <= mc->top);
|
||||
|
||||
/* Right page might now have changed parent.
|
||||
* Check if left page also changed parent. */
|
||||
|
@ -63,14 +63,14 @@ static int txl_reserve(txl_t __restrict *__restrict ptxl, const size_t wanna) {
|
||||
return MDBX_ENOMEM;
|
||||
}
|
||||
|
||||
static __always_inline int __must_check_result txl_need(txl_t __restrict *__restrict ptxl, size_t num) {
|
||||
static inline int __must_check_result txl_need(txl_t __restrict *__restrict ptxl, size_t num) {
|
||||
assert(MDBX_PNL_GETSIZE(*ptxl) <= txl_max && MDBX_PNL_ALLOCLEN(*ptxl) >= MDBX_PNL_GETSIZE(*ptxl));
|
||||
assert(num <= PAGELIST_LIMIT);
|
||||
const size_t wanna = (size_t)MDBX_PNL_GETSIZE(*ptxl) + num;
|
||||
return likely(MDBX_PNL_ALLOCLEN(*ptxl) >= wanna) ? MDBX_SUCCESS : txl_reserve(ptxl, wanna);
|
||||
}
|
||||
|
||||
static __always_inline void txl_xappend(txl_t __restrict txl, txnid_t id) {
|
||||
static inline void txl_xappend(txl_t __restrict txl, txnid_t id) {
|
||||
assert(MDBX_PNL_GETSIZE(txl) < MDBX_PNL_ALLOCLEN(txl));
|
||||
txl[0] += 1;
|
||||
MDBX_PNL_LAST(txl) = id;
|
||||
|
10
src/txl.h
10
src/txl.h
@ -15,12 +15,12 @@ enum txl_rules {
|
||||
txl_max = (1u << 26) - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t)
|
||||
};
|
||||
|
||||
MDBX_INTERNAL txl_t txl_alloc(void);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL txl_t txl_alloc(void);
|
||||
|
||||
MDBX_INTERNAL void txl_free(txl_t txl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void txl_free(txl_t txl);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result txl_append(txl_t __restrict *ptxl, txnid_t id);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL int __must_check_result txl_append(txl_t __restrict *ptxl, txnid_t id);
|
||||
|
||||
MDBX_INTERNAL void txl_sort(txl_t txl);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void txl_sort(txl_t txl);
|
||||
|
||||
MDBX_INTERNAL bool txl_contain(const txl_t txl, txnid_t id);
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool txl_contain(const txl_t txl, txnid_t id);
|
||||
|
@ -62,6 +62,8 @@ __cold MDBX_txn *txn_basal_create(const size_t max_dbi) {
|
||||
if (unlikely(!txn))
|
||||
return txn;
|
||||
|
||||
rkl_init(&txn->wr.gc.reclaimed);
|
||||
rkl_init(&txn->wr.gc.comeback);
|
||||
txn->dbs = ptr_disp(txn, base);
|
||||
txn->cursors = ptr_disp(txn->dbs, max_dbi * sizeof(txn->dbs[0]));
|
||||
txn->dbi_seqs = ptr_disp(txn->cursors, max_dbi * sizeof(txn->cursors[0]));
|
||||
@ -82,7 +84,8 @@ __cold MDBX_txn *txn_basal_create(const size_t max_dbi) {
|
||||
|
||||
__cold void txn_basal_destroy(MDBX_txn *txn) {
|
||||
dpl_free(txn);
|
||||
txl_free(txn->wr.gc.retxl);
|
||||
rkl_destroy(&txn->wr.gc.reclaimed);
|
||||
rkl_destroy(&txn->wr.gc.comeback);
|
||||
pnl_free(txn->wr.retired_pages);
|
||||
pnl_free(txn->wr.spilled.list);
|
||||
pnl_free(txn->wr.repnl);
|
||||
@ -122,10 +125,9 @@ int txn_basal_start(MDBX_txn *txn, unsigned flags) {
|
||||
MDBX_PNL_SETSIZE(txn->wr.retired_pages, 0);
|
||||
txn->wr.spilled.list = nullptr;
|
||||
txn->wr.spilled.least_removed = 0;
|
||||
txn->wr.gc.time_acc = 0;
|
||||
txn->wr.gc.last_reclaimed = 0;
|
||||
if (txn->wr.gc.retxl)
|
||||
MDBX_PNL_SETSIZE(txn->wr.gc.retxl, 0);
|
||||
txn->wr.gc.spent = 0;
|
||||
tASSERT(txn, rkl_empty(&txn->wr.gc.reclaimed));
|
||||
txn->env->gc.detent = 0;
|
||||
env->txn = txn;
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
@ -141,6 +143,8 @@ int txn_basal_end(MDBX_txn *txn, unsigned mode) {
|
||||
env->txn = nullptr;
|
||||
pnl_free(txn->wr.spilled.list);
|
||||
txn->wr.spilled.list = nullptr;
|
||||
rkl_clear_and_shrink(&txn->wr.gc.reclaimed);
|
||||
rkl_clear_and_shrink(&txn->wr.gc.comeback);
|
||||
|
||||
eASSERT(env, txn->parent == nullptr);
|
||||
pnl_shrink(&txn->wr.retired_pages);
|
||||
@ -259,9 +263,19 @@ int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts) {
|
||||
}
|
||||
|
||||
gcu_t gcu_ctx;
|
||||
int rc = gc_update_init(txn, &gcu_ctx);
|
||||
int rc = gc_put_init(txn, &gcu_ctx);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = gc_update(txn, &gcu_ctx);
|
||||
|
||||
#if MDBX_ENABLE_BIGFOOT
|
||||
const txnid_t commit_txnid = gcu_ctx.bigfoot;
|
||||
if (commit_txnid > txn->txnid)
|
||||
TRACE("use @%" PRIaTXN " (+%zu) for commit bigfoot-txn", commit_txnid, (size_t)(commit_txnid - txn->txnid));
|
||||
#else
|
||||
const txnid_t commit_txnid = txn->txnid;
|
||||
#endif
|
||||
gc_put_destroy(&gcu_ctx);
|
||||
|
||||
if (ts)
|
||||
ts->gc_cpu = osal_cputime(nullptr) - ts->gc_cpu;
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
@ -335,13 +349,6 @@ int txn_basal_commit(MDBX_txn *txn, struct commit_timestamp *ts) {
|
||||
meta.canary = txn->canary;
|
||||
memcpy(&meta.dxbid, &head.ptr_c->dxbid, sizeof(meta.dxbid));
|
||||
|
||||
txnid_t commit_txnid = txn->txnid;
|
||||
#if MDBX_ENABLE_BIGFOOT
|
||||
if (gcu_ctx.bigfoot > txn->txnid) {
|
||||
commit_txnid = gcu_ctx.bigfoot;
|
||||
TRACE("use @%" PRIaTXN " (+%zu) for commit bigfoot-txn", commit_txnid, (size_t)(commit_txnid - txn->txnid));
|
||||
}
|
||||
#endif
|
||||
meta.unsafe_sign = DATASIGN_NONE;
|
||||
meta_set_txnid(env, &meta, commit_txnid);
|
||||
|
||||
|
@ -357,6 +357,7 @@ int txn_nested_create(MDBX_txn *parent, const MDBX_txn_flags_t flags) {
|
||||
txn->env->txn = txn;
|
||||
txn->owner = parent->owner;
|
||||
txn->wr.troika = parent->wr.troika;
|
||||
rkl_init(&txn->wr.gc.reclaimed);
|
||||
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
txn->dbi_sparse = parent->dbi_sparse;
|
||||
@ -411,12 +412,11 @@ int txn_nested_create(MDBX_txn *parent, const MDBX_txn_flags_t flags) {
|
||||
= parent->geo.first_unallocated) -
|
||||
MDBX_ENABLE_REFUND));
|
||||
|
||||
txn->wr.gc.time_acc = parent->wr.gc.time_acc;
|
||||
txn->wr.gc.last_reclaimed = parent->wr.gc.last_reclaimed;
|
||||
if (parent->wr.gc.retxl) {
|
||||
txn->wr.gc.retxl = parent->wr.gc.retxl;
|
||||
parent->wr.gc.retxl = (void *)(intptr_t)MDBX_PNL_GETSIZE(parent->wr.gc.retxl);
|
||||
}
|
||||
txn->wr.gc.spent = parent->wr.gc.spent;
|
||||
rkl_init(&txn->wr.gc.comeback);
|
||||
err = rkl_copy(&parent->wr.gc.reclaimed, &txn->wr.gc.reclaimed);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
|
||||
txn->wr.retired_pages = parent->wr.retired_pages;
|
||||
parent->wr.retired_pages = (void *)(intptr_t)MDBX_PNL_GETSIZE(parent->wr.retired_pages);
|
||||
@ -433,6 +433,7 @@ int txn_nested_create(MDBX_txn *parent, const MDBX_txn_flags_t flags) {
|
||||
tASSERT(txn, txn->wr.dirtyroom + txn->wr.dirtylist->length ==
|
||||
(txn->parent ? txn->parent->wr.dirtyroom : txn->env->options.dp_limit));
|
||||
tASSERT(parent, parent->cursors[FREE_DBI] == nullptr);
|
||||
// TODO: shadow GC' cursor
|
||||
return txn_shadow_cursors(parent, MAIN_DBI);
|
||||
}
|
||||
|
||||
@ -442,11 +443,7 @@ void txn_nested_abort(MDBX_txn *nested) {
|
||||
nested->signature = 0;
|
||||
nested->owner = 0;
|
||||
|
||||
if (nested->wr.gc.retxl) {
|
||||
tASSERT(parent, MDBX_PNL_GETSIZE(nested->wr.gc.retxl) >= (uintptr_t)parent->wr.gc.retxl);
|
||||
MDBX_PNL_SETSIZE(nested->wr.gc.retxl, (uintptr_t)parent->wr.gc.retxl);
|
||||
parent->wr.gc.retxl = nested->wr.gc.retxl;
|
||||
}
|
||||
rkl_destroy(&nested->wr.gc.reclaimed);
|
||||
|
||||
if (nested->wr.retired_pages) {
|
||||
tASSERT(parent, MDBX_PNL_GETSIZE(nested->wr.retired_pages) >= (uintptr_t)parent->wr.retired_pages);
|
||||
@ -522,17 +519,14 @@ int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts) {
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
parent->wr.gc.retxl = txn->wr.gc.retxl;
|
||||
txn->wr.gc.retxl = nullptr;
|
||||
|
||||
parent->wr.retired_pages = txn->wr.retired_pages;
|
||||
txn->wr.retired_pages = nullptr;
|
||||
|
||||
pnl_free(parent->wr.repnl);
|
||||
parent->wr.repnl = txn->wr.repnl;
|
||||
txn->wr.repnl = nullptr;
|
||||
parent->wr.gc.time_acc = txn->wr.gc.time_acc;
|
||||
parent->wr.gc.last_reclaimed = txn->wr.gc.last_reclaimed;
|
||||
parent->wr.gc.spent = txn->wr.gc.spent;
|
||||
rkl_destructive_move(&txn->wr.gc.reclaimed, &parent->wr.gc.reclaimed);
|
||||
|
||||
parent->geo = txn->geo;
|
||||
parent->canary = txn->canary;
|
||||
|
19
src/txn.c
19
src/txn.c
@ -3,8 +3,18 @@
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
__hot txnid_t txn_snapshot_oldest(const MDBX_txn *const txn) {
|
||||
return mvcc_shapshot_oldest(txn->env, txn->wr.troika.txnid[txn->wr.troika.prefer_steady]);
|
||||
MDBX_cursor *txn_gc_cursor(MDBX_txn *txn) {
|
||||
tASSERT(txn, (txn->flags & (MDBX_TXN_BLOCKED | MDBX_TXN_RDONLY)) == 0);
|
||||
return ptr_disp(txn->env->basal_txn, sizeof(MDBX_txn));
|
||||
}
|
||||
|
||||
__hot bool txn_gc_detent(const MDBX_txn *const txn) {
|
||||
const txnid_t detent = mvcc_shapshot_oldest(txn->env, txn->wr.troika.txnid[txn->wr.troika.prefer_steady]);
|
||||
if (likely(detent == txn->env->gc.detent))
|
||||
return false;
|
||||
|
||||
txn->env->gc.detent = detent;
|
||||
return true;
|
||||
}
|
||||
|
||||
void txn_done_cursors(MDBX_txn *txn) {
|
||||
@ -420,12 +430,9 @@ MDBX_txn *txn_alloc(const MDBX_txn_flags_t flags, MDBX_env *env) {
|
||||
txn = osal_malloc(size);
|
||||
if (unlikely(!txn))
|
||||
return txn;
|
||||
#if MDBX_DEBUG
|
||||
memset(txn, 0xCD, size);
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(txn, size);
|
||||
#endif /* MDBX_DEBUG */
|
||||
MDBX_ANALYSIS_ASSUME(size > base);
|
||||
memset(txn, 0, (MDBX_GOOFY_MSVC_STATIC_ANALYZER && base > size) ? size : base);
|
||||
|
||||
txn->dbs = ptr_disp(txn, base);
|
||||
txn->cursors = ptr_disp(txn->dbs, env->max_dbi * sizeof(txn->dbs[0]));
|
||||
#if MDBX_DEBUG
|
||||
|
11
src/utils.c
11
src/utils.c
@ -3,6 +3,17 @@
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED MDBX_INTERNAL unsigned ceil_log2n(size_t value_uintptr) {
|
||||
assert(value_uintptr > 0 && value_uintptr < INT32_MAX);
|
||||
value_uintptr -= 1;
|
||||
value_uintptr |= value_uintptr >> 1;
|
||||
value_uintptr |= value_uintptr >> 2;
|
||||
value_uintptr |= value_uintptr >> 4;
|
||||
value_uintptr |= value_uintptr >> 8;
|
||||
value_uintptr |= value_uintptr >> 16;
|
||||
return log2n_powerof2(value_uintptr + 1);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL unsigned log2n_powerof2(size_t value_uintptr) {
|
||||
assert(value_uintptr > 0 && value_uintptr < INT32_MAX && is_powerof2(value_uintptr));
|
||||
assert((value_uintptr & -(intptr_t)value_uintptr) == value_uintptr);
|
||||
|
@ -58,6 +58,8 @@ MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline size_t ceil_powerof2
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED MDBX_INTERNAL unsigned log2n_powerof2(size_t value_uintptr);
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED MDBX_INTERNAL unsigned ceil_log2n(size_t value_uintptr);
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL uint64_t rrxmrrxmsx_0(uint64_t v);
|
||||
|
||||
struct monotime_cache {
|
||||
|
@ -298,6 +298,7 @@ else()
|
||||
add_extra_test(upsert_alldups SOURCE extra/upsert_alldups.c)
|
||||
add_extra_test(dupfix_addodd SOURCE extra/dupfix_addodd.c)
|
||||
endif()
|
||||
add_extra_test(details_rkl SOURCE extra/details_rkl.c)
|
||||
if(MDBX_BUILD_CXX)
|
||||
if(NOT WIN32 OR NOT MDBX_CXX_STANDARD LESS 17)
|
||||
add_extra_test(cursor_closing TIMEOUT 10800)
|
||||
|
488
test/extra/details_rkl.c
Normal file
488
test/extra/details_rkl.c
Normal file
@ -0,0 +1,488 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2025
|
||||
|
||||
#define debug_log debug_log_sub
|
||||
|
||||
#include "../../src/rkl.c"
|
||||
#include "../../src/txl.c"
|
||||
|
||||
MDBX_MAYBE_UNUSED __cold void debug_log_sub(int level, const char *function, int line, const char *fmt, ...) {
|
||||
(void)level;
|
||||
(void)function;
|
||||
(void)line;
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
|
||||
static size_t tst_failed, tst_ok, tst_iterations, tst_cases, tst_cases_hole;
|
||||
#ifndef NDEBUG
|
||||
static size_t tst_target;
|
||||
#endif
|
||||
|
||||
static bool check_bool(bool v, bool expect, const char *fn, unsigned line) {
|
||||
if (unlikely(v != expect)) {
|
||||
++tst_failed;
|
||||
fflush(nullptr);
|
||||
fprintf(stderr, "iteration %zi: got %s, expected %s, at %s:%u\n", tst_iterations, v ? "true" : "false",
|
||||
expect ? "true" : "false", fn, line);
|
||||
fflush(nullptr);
|
||||
return false;
|
||||
}
|
||||
++tst_ok;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_eq(uint64_t v, uint64_t expect, const char *fn, unsigned line) {
|
||||
if (unlikely(v != expect)) {
|
||||
++tst_failed;
|
||||
fflush(nullptr);
|
||||
fprintf(stderr, "iteration %zi: %" PRIu64 " (got) != %" PRIu64 " (expected), at %s:%u\n", tst_iterations, v, expect,
|
||||
fn, line);
|
||||
fflush(nullptr);
|
||||
return false;
|
||||
}
|
||||
++tst_ok;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define CHECK_BOOL(T, EXPECT) check_bool((T), (EXPECT), __func__, __LINE__)
|
||||
#define CHECK_TRUE(T) CHECK_BOOL(T, true)
|
||||
#define CHECK_FALSE(T) CHECK_BOOL(T, false)
|
||||
#define CHECK_EQ(T, EXPECT) check_eq((T), (EXPECT), __func__, __LINE__)
|
||||
|
||||
void trivia(void) {
|
||||
rkl_t x, y;
|
||||
|
||||
rkl_init(&x);
|
||||
rkl_init(&y);
|
||||
CHECK_TRUE(rkl_check(&x));
|
||||
CHECK_TRUE(rkl_empty(&x));
|
||||
CHECK_EQ(rkl_len(&x), 0);
|
||||
|
||||
rkl_iter_t f = rkl_iterator(&x, false);
|
||||
rkl_iter_t r = rkl_iterator(&x, true);
|
||||
CHECK_EQ(rkl_left(&f, false), 0);
|
||||
CHECK_EQ(rkl_left(&f, true), 0);
|
||||
CHECK_EQ(rkl_left(&r, false), 0);
|
||||
CHECK_EQ(rkl_left(&r, true), 0);
|
||||
CHECK_EQ(rkl_turn(&f, false), 0);
|
||||
CHECK_EQ(rkl_turn(&f, true), 0);
|
||||
CHECK_EQ(rkl_turn(&r, false), 0);
|
||||
CHECK_EQ(rkl_turn(&r, true), 0);
|
||||
CHECK_TRUE(rkl_check(&x));
|
||||
|
||||
rkl_hole_t hole;
|
||||
hole = rkl_hole(&f, true);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&f, false);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&r, true);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&r, false);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
|
||||
CHECK_EQ(rkl_push(&x, 42, false), MDBX_SUCCESS);
|
||||
CHECK_TRUE(rkl_check(&x));
|
||||
CHECK_FALSE(rkl_empty(&x));
|
||||
CHECK_EQ(rkl_len(&x), 1);
|
||||
CHECK_EQ(rkl_push(&x, 42, true), MDBX_RESULT_TRUE);
|
||||
CHECK_TRUE(rkl_check(&x));
|
||||
|
||||
f = rkl_iterator(&x, false);
|
||||
r = rkl_iterator(&x, true);
|
||||
CHECK_EQ(rkl_left(&f, false), 1);
|
||||
CHECK_EQ(rkl_left(&f, true), 0);
|
||||
CHECK_EQ(rkl_left(&r, false), 0);
|
||||
CHECK_EQ(rkl_left(&r, true), 1);
|
||||
|
||||
CHECK_EQ(rkl_turn(&f, true), 0);
|
||||
CHECK_EQ(rkl_turn(&f, false), 42);
|
||||
CHECK_EQ(rkl_turn(&f, false), 0);
|
||||
CHECK_EQ(rkl_turn(&f, true), 42);
|
||||
CHECK_EQ(rkl_turn(&f, true), 0);
|
||||
|
||||
CHECK_EQ(rkl_turn(&r, false), 0);
|
||||
CHECK_EQ(rkl_turn(&r, true), 42);
|
||||
CHECK_EQ(rkl_turn(&r, true), 0);
|
||||
CHECK_EQ(rkl_turn(&r, false), 42);
|
||||
CHECK_EQ(rkl_turn(&r, false), 0);
|
||||
|
||||
f = rkl_iterator(&x, false);
|
||||
hole = rkl_hole(&f, false);
|
||||
CHECK_EQ(hole.begin, 43);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&f, false);
|
||||
CHECK_EQ(hole.begin, MAX_TXNID);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&f, true);
|
||||
CHECK_EQ(hole.begin, 43);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&f, true);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, 42);
|
||||
hole = rkl_hole(&f, true);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, 42);
|
||||
|
||||
r = rkl_iterator(&x, true);
|
||||
hole = rkl_hole(&r, false);
|
||||
CHECK_EQ(hole.begin, MAX_TXNID);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&r, true);
|
||||
CHECK_EQ(hole.begin, 43);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&r, true);
|
||||
CHECK_EQ(hole.begin, 1);
|
||||
CHECK_EQ(hole.end, 42);
|
||||
hole = rkl_hole(&r, false);
|
||||
CHECK_EQ(hole.begin, 43);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
hole = rkl_hole(&r, false);
|
||||
CHECK_EQ(hole.begin, MAX_TXNID);
|
||||
CHECK_EQ(hole.end, MAX_TXNID);
|
||||
|
||||
rkl_resize(&x, 222);
|
||||
CHECK_FALSE(rkl_empty(&x));
|
||||
CHECK_TRUE(rkl_check(&x));
|
||||
|
||||
rkl_destructive_move(&x, &y);
|
||||
CHECK_TRUE(rkl_check(&x));
|
||||
CHECK_TRUE(rkl_check(&y));
|
||||
rkl_destroy(&x);
|
||||
rkl_destroy(&y);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
|
||||
uint64_t prng_state;
|
||||
|
||||
static uint64_t prng(void) {
|
||||
prng_state = prng_state * UINT64_C(6364136223846793005) + 1;
|
||||
return prng_state;
|
||||
}
|
||||
|
||||
static bool flipcoin(void) { return (bool)prng() & 1; }
|
||||
|
||||
static bool stochastic_pass(const unsigned start, const unsigned width, const unsigned n) {
|
||||
rkl_t k, c;
|
||||
txl_t l = txl_alloc();
|
||||
if (!CHECK_TRUE(l))
|
||||
return false;
|
||||
|
||||
rkl_init(&k);
|
||||
rkl_init(&c);
|
||||
const size_t errors = tst_failed;
|
||||
|
||||
rkl_iter_t f = rkl_iterator(&k, false);
|
||||
rkl_iter_t r = rkl_iterator(&k, true);
|
||||
|
||||
txnid_t lowest = UINT_MAX;
|
||||
txnid_t highest = 0;
|
||||
while (MDBX_PNL_GETSIZE(l) < n) {
|
||||
txnid_t id = (txnid_t)(prng() % width + start);
|
||||
if (id < MIN_TXNID || id >= INVALID_TXNID)
|
||||
continue;
|
||||
if (txl_contain(l, id)) {
|
||||
if (CHECK_TRUE(rkl_contain(&k, id)) && CHECK_EQ(rkl_push(&k, id, false), MDBX_RESULT_TRUE))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (!CHECK_FALSE(rkl_contain(&k, id)))
|
||||
break;
|
||||
|
||||
if (tst_iterations % (1u << 24) == 0 && tst_iterations) {
|
||||
printf("done %.3fM iteration, %zu cases\n", tst_iterations / 1000000.0, tst_cases);
|
||||
fflush(nullptr);
|
||||
}
|
||||
tst_iterations += 1;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (tst_iterations == tst_target) {
|
||||
printf("reach %zu iteration\n", tst_iterations);
|
||||
fflush(nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!CHECK_EQ(rkl_push(&k, id, false), MDBX_SUCCESS))
|
||||
break;
|
||||
if (!CHECK_TRUE(rkl_check(&k)))
|
||||
break;
|
||||
if (!CHECK_EQ(txl_append(&l, id), MDBX_SUCCESS))
|
||||
break;
|
||||
if (!CHECK_TRUE(rkl_contain(&k, id)))
|
||||
break;
|
||||
|
||||
lowest = (lowest < id) ? lowest : id;
|
||||
highest = (highest > id) ? highest : id;
|
||||
if (!CHECK_EQ(rkl_lowest(&k), lowest))
|
||||
break;
|
||||
if (!CHECK_EQ(rkl_highest(&k), highest))
|
||||
break;
|
||||
}
|
||||
|
||||
txl_sort(l);
|
||||
CHECK_EQ(rkl_len(&k), n);
|
||||
CHECK_EQ(MDBX_PNL_GETSIZE(l), n);
|
||||
|
||||
f = rkl_iterator(&k, false);
|
||||
r = rkl_iterator(&k, true);
|
||||
CHECK_EQ(rkl_left(&f, false), n);
|
||||
CHECK_EQ(rkl_left(&f, true), 0);
|
||||
CHECK_EQ(rkl_left(&r, false), 0);
|
||||
CHECK_EQ(rkl_left(&r, true), n);
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
CHECK_EQ(rkl_turn(&f, false), l[n - i]);
|
||||
CHECK_EQ(rkl_left(&f, false), n - i - 1);
|
||||
CHECK_EQ(rkl_left(&f, true), i + 1);
|
||||
|
||||
CHECK_EQ(rkl_turn(&r, true), l[i + 1]);
|
||||
r.pos += 1;
|
||||
CHECK_EQ(rkl_turn(&r, true), l[i + 1]);
|
||||
CHECK_EQ(rkl_left(&r, true), n - i - 1);
|
||||
CHECK_EQ(rkl_left(&r, false), i + 1);
|
||||
}
|
||||
|
||||
if (CHECK_EQ(rkl_copy(&k, &c), MDBX_SUCCESS)) {
|
||||
for (size_t i = 1; i <= n; ++i) {
|
||||
if (!CHECK_FALSE(rkl_empty(&k)))
|
||||
break;
|
||||
if (!CHECK_FALSE(rkl_empty(&c)))
|
||||
break;
|
||||
CHECK_EQ(rkl_pop(&k, true), l[i]);
|
||||
CHECK_EQ(rkl_pop(&c, false), l[1 + n - i]);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_TRUE(rkl_empty(&k));
|
||||
CHECK_TRUE(rkl_empty(&c));
|
||||
|
||||
rkl_destroy(&k);
|
||||
rkl_destroy(&c);
|
||||
txl_free(l);
|
||||
|
||||
++tst_cases;
|
||||
return errors == tst_failed;
|
||||
}
|
||||
|
||||
static bool stochastic(const size_t limit_cases, const size_t limit_loops) {
|
||||
for (unsigned loop = 0; tst_cases < limit_cases || loop < limit_loops; ++loop)
|
||||
for (unsigned width = 2; width < 10; ++width)
|
||||
for (unsigned n = 1; n < width; ++n)
|
||||
for (unsigned prev = 1, start = 0, t; start < 4242; t = start + prev, prev = start, start = t)
|
||||
if (!stochastic_pass(start, 1u << width, 1u << n) || tst_failed > 42) {
|
||||
puts("bailout\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
|
||||
static bool bit(size_t set, size_t n) {
|
||||
assert(n < CHAR_BIT * sizeof(set));
|
||||
return (set >> n) & 1;
|
||||
}
|
||||
|
||||
static size_t hamming_weight(size_t v) {
|
||||
const size_t m1 = (size_t)UINT64_C(0x5555555555555555);
|
||||
const size_t m2 = (size_t)UINT64_C(0x3333333333333333);
|
||||
const size_t m4 = (size_t)UINT64_C(0x0f0f0f0f0f0f0f0f);
|
||||
const size_t h01 = (size_t)UINT64_C(0x0101010101010101);
|
||||
v -= (v >> 1) & m1;
|
||||
v = (v & m2) + ((v >> 2) & m2);
|
||||
v = (v + (v >> 4)) & m4;
|
||||
return (v * h01) >> (sizeof(v) * 8 - 8);
|
||||
}
|
||||
|
||||
static bool check_hole(const size_t set, const rkl_hole_t hole, size_t *acc) {
|
||||
const size_t errors = tst_failed;
|
||||
++tst_iterations;
|
||||
|
||||
if (hole.begin > 1)
|
||||
CHECK_EQ(bit(set, hole.begin - 1), 1);
|
||||
if (hole.end < CHAR_BIT * sizeof(set))
|
||||
CHECK_EQ(bit(set, hole.end), 1);
|
||||
|
||||
for (size_t n = hole.begin; n < hole.end && n < CHAR_BIT * sizeof(set); n++) {
|
||||
CHECK_EQ(bit(set, n), 0);
|
||||
*acc += 1;
|
||||
}
|
||||
|
||||
return errors == tst_failed;
|
||||
}
|
||||
|
||||
static void debug_set(const size_t set, const char *str, int iter_offset) {
|
||||
#if 1
|
||||
(void)set;
|
||||
(void)str;
|
||||
(void)iter_offset;
|
||||
#else
|
||||
printf("\ncase %s+%d: count %zu, holes", str, iter_offset, hamming_weight(~set) - 1);
|
||||
for (size_t k, i = 1; i < CHAR_BIT * sizeof(set); ++i) {
|
||||
if (!bit(set, i)) {
|
||||
printf(" %zu", i);
|
||||
for (k = i; k < CHAR_BIT * sizeof(set) - 1 && !bit(set, k + 1); ++k)
|
||||
;
|
||||
if (k > i) {
|
||||
printf("-%zu", k);
|
||||
i = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
fflush(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool check_holes_bothsides(const size_t set, rkl_iter_t const *i) {
|
||||
const size_t number_of_holes = hamming_weight(~set) - 1;
|
||||
size_t acc = 0;
|
||||
|
||||
rkl_iter_t f = *i;
|
||||
for (;;) {
|
||||
rkl_hole_t hole = rkl_hole(&f, false);
|
||||
if (hole.begin == hole.end)
|
||||
break;
|
||||
if (!check_hole(set, hole, &acc))
|
||||
return false;
|
||||
if (hole.end >= CHAR_BIT * sizeof(set))
|
||||
break;
|
||||
}
|
||||
|
||||
rkl_iter_t b = *i;
|
||||
for (;;) {
|
||||
rkl_hole_t hole = rkl_hole(&b, true);
|
||||
if (hole.begin == hole.end)
|
||||
break;
|
||||
if (!check_hole(set, hole, &acc))
|
||||
return false;
|
||||
if (hole.begin == 1)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!CHECK_EQ(acc, number_of_holes))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_holes_fourways(const size_t set, const rkl_t *rkl) {
|
||||
rkl_iter_t i = rkl_iterator(rkl, false);
|
||||
int o = 0;
|
||||
do {
|
||||
debug_set(set, "initial-forward", o++);
|
||||
if (!check_holes_bothsides(set, &i))
|
||||
return false;
|
||||
} while (rkl_turn(&i, false));
|
||||
|
||||
do {
|
||||
debug_set(set, "recoil-reverse", --o);
|
||||
if (!check_holes_bothsides(set, &i))
|
||||
return false;
|
||||
} while (rkl_turn(&i, true));
|
||||
|
||||
i = rkl_iterator(rkl, true);
|
||||
o = 0;
|
||||
do {
|
||||
debug_set(set, "initial-reverse", --o);
|
||||
if (!check_holes_bothsides(set, &i))
|
||||
return false;
|
||||
} while (rkl_turn(&i, false));
|
||||
|
||||
do {
|
||||
debug_set(set, "recoil-forward", o++);
|
||||
if (!check_holes_bothsides(set, &i))
|
||||
return false;
|
||||
} while (rkl_turn(&i, true));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool stochastic_pass_hole(size_t set, size_t trims) {
|
||||
const size_t one = 1;
|
||||
set &= ~one;
|
||||
if (!set)
|
||||
return true;
|
||||
|
||||
++tst_cases_hole;
|
||||
|
||||
rkl_t rkl;
|
||||
rkl_init(&rkl);
|
||||
for (size_t n = 1; n < CHAR_BIT * sizeof(set); ++n)
|
||||
if (bit(set, n))
|
||||
CHECK_EQ(rkl_push(&rkl, n, false), MDBX_SUCCESS);
|
||||
|
||||
if (!check_holes_fourways(set, &rkl))
|
||||
return false;
|
||||
|
||||
while (rkl_len(&rkl) > 1 && trims-- > 0) {
|
||||
if (flipcoin()) {
|
||||
const size_t l = (size_t)rkl_pop(&rkl, false);
|
||||
if (l == 0)
|
||||
break;
|
||||
assert(bit(set, l));
|
||||
set -= one << l;
|
||||
if (!check_holes_fourways(set, &rkl))
|
||||
return false;
|
||||
} else {
|
||||
|
||||
const size_t h = (size_t)rkl_pop(&rkl, true);
|
||||
if (h == 0)
|
||||
break;
|
||||
assert(bit(set, h));
|
||||
set -= one << h;
|
||||
if (!check_holes_fourways(set, &rkl))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t prng_word(void) {
|
||||
size_t word = (size_t)(prng() >> 32);
|
||||
if (sizeof(word) > 4)
|
||||
word = (uint64_t)word << 32 | (size_t)(prng() >> 32);
|
||||
return word;
|
||||
}
|
||||
|
||||
static bool stochastic_hole(size_t probes) {
|
||||
for (size_t n = 0; n < probes; ++n) {
|
||||
size_t set = prng_word();
|
||||
if (!stochastic_pass_hole(set, prng() % 11))
|
||||
return false;
|
||||
if (!stochastic_pass_hole(set & prng_word(), prng() % 11))
|
||||
return false;
|
||||
if (!stochastic_pass_hole(set | prng_word(), prng() % 11))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------*/
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
#ifndef NDEBUG
|
||||
// tst_target = 281870;
|
||||
#endif
|
||||
prng_state = (uint64_t)time(nullptr);
|
||||
printf("prng-seed %" PRIu64 "\n", prng_state);
|
||||
fflush(nullptr);
|
||||
|
||||
trivia();
|
||||
stochastic(42 * 42 * 42, 42);
|
||||
stochastic_hole(24 * 24 * 24);
|
||||
printf("done: %zu+%zu cases, %zu iterations, %zu checks ok, %zu checks failed\n", tst_cases, tst_cases_hole,
|
||||
tst_iterations, tst_ok, tst_failed);
|
||||
fflush(nullptr);
|
||||
return tst_failed ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
@ -460,9 +460,9 @@ int main(int argc, char *const argv[]) {
|
||||
params.datalen_max = params.datalen_min;
|
||||
continue;
|
||||
}
|
||||
if (config::parse_option(argc, argv, narg, "batch.read", params.batch_read, config::no_scale, 1))
|
||||
if (config::parse_option(argc, argv, narg, "batch.read", params.batch_read, config::decimal, 1))
|
||||
continue;
|
||||
if (config::parse_option(argc, argv, narg, "batch.write", params.batch_write, config::no_scale, 1))
|
||||
if (config::parse_option(argc, argv, narg, "batch.write", params.batch_write, config::decimal, 1))
|
||||
continue;
|
||||
if (config::parse_option(argc, argv, narg, "delay", params.delaystart, config::duration))
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user