mdbx: merge branch master into devel.

This commit is contained in:
Леонид Юрьев (Leonid Yuriev) 2025-03-31 00:52:52 +03:00
commit 650569cc6a
59 changed files with 4000 additions and 2660 deletions

1067
ChangeLog-01.md Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -164,6 +164,10 @@ else
$(info $(TIP) Use `make V=1` for verbose.)
endif
ifeq ($(UNAME),Darwin)
$(info $(TIP) Use `brew install gnu-sed gnu-tar` and add ones to the beginning of the PATH.)
endif
all: show-options $(LIBRARIES) $(MDBX_TOOLS)
help:
@ -295,6 +299,10 @@ lib-shared libmdbx.$(SO_SUFFIX): mdbx-dylib.o $(call select_by,MDBX_BUILD_CXX,md
@echo ' LD $@'
$(QUIET)$(call select_by,MDBX_BUILD_CXX,$(CXX) $(CXXFLAGS),$(CC) $(CFLAGS)) $^ -pthread -shared $(LDFLAGS) $(call select_by,MDBX_BUILD_CXX,$(LIB_STDCXXFS)) $(LIBS) -o $@
ninja-assertions: CMAKE_OPT += -DMDBX_FORCE_ASSERTIONS=ON
ninja-assertions: cmake-build
ninja-debug: CMAKE_OPT += -DCMAKE_BUILD_TYPE=Debug
ninja-debug: cmake-build
ninja: cmake-build
cmake-build:
@echo " RUN: cmake -G Ninja && cmake --build"
@ -392,6 +400,9 @@ TEST_ITER := $(shell $(uname2titer))
TEST_SRC := test/osal-$(TEST_OSAL).c++ $(filter-out $(wildcard test/osal-*.c++),$(wildcard test/*.c++)) $(call select_by,MDBX_BUILD_CXX,,src/mdbx.c++)
TEST_INC := $(wildcard test/*.h++)
TEST_OBJ := $(patsubst %.c++,%.o,$(TEST_SRC))
ifndef SED
SED := $(shell which gnu-sed 2>&- || echo sed)
endif
TAR ?= $(shell which gnu-tar 2>&- || echo tar)
ZIP ?= $(shell which zip || echo "echo 'Please install zip'")
CLANG_FORMAT ?= $(shell (which clang-format-19 || which clang-format) 2>/dev/null)
@ -408,11 +419,11 @@ MAN_SRCDIR := src/man1/
ALLOY_DEPS := $(shell git ls-files src/ | grep -e /tools -e /man -v)
MDBX_GIT_DIR := $(shell if [ -d .git ]; then echo .git; elif [ -s .git -a -f .git ]; then grep '^gitdir: ' .git | cut -d ':' -f 2; else echo git_directory_is_absent; fi)
MDBX_GIT_LASTVTAG := $(shell git describe --tags --dirty=-DIRTY --abbrev=0 '--match=v[0-9]*' 2>&- || echo 'Please fetch tags and/or install non-obsolete git version')
MDBX_GIT_3DOT := $(shell set -o pipefail; echo "$(MDBX_GIT_LASTVTAG)" | sed -n 's|^v*\([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\)\(.*\)|\1|p' || echo 'Please fetch tags and/or use non-obsolete git version')
MDBX_GIT_3DOT := $(shell set -o pipefail; echo "$(MDBX_GIT_LASTVTAG)" | $(SED) -n 's|^v*\([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\)\(.*\)|\1|p' || echo 'Please fetch tags and/or use non-obsolete git version')
MDBX_GIT_TWEAK := $(shell set -o pipefail; git rev-list $(shell git describe --tags --abbrev=0 '--match=v[0-9]*')..HEAD --count 2>&- || echo 'Please fetch tags and/or use non-obsolete git version')
MDBX_GIT_TIMESTAMP := $(shell git show --no-patch --format=%cI HEAD 2>&- || echo 'Please install latest get version')
MDBX_GIT_DESCRIBE := $(shell git describe --tags --long --dirty '--match=v[0-9]*' 2>&- || echo 'Please fetch tags and/or install non-obsolete git version')
MDBX_GIT_PRERELEASE := $(shell echo "$(MDBX_GIT_LASTVTAG)" | sed -n 's|^v*\([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\)\(.*\)-\([-.0-1a-zA-Z]\+\)|\3|p')
MDBX_GIT_PRERELEASE := $(shell echo "$(MDBX_GIT_LASTVTAG)" | $(SED) -n 's|^v*\([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\)\(.*\)-\([-.0-1a-zA-Z]\+\)|\3|p')
MDBX_VERSION_PURE = $(MDBX_GIT_3DOT)$(if $(filter-out 0,$(MDBX_GIT_TWEAK)),.$(MDBX_GIT_TWEAK),)$(if $(MDBX_GIT_PRERELEASE),-$(MDBX_GIT_PRERELEASE),)
MDBX_VERSION_IDENT = $(shell set -o pipefail; echo -n '$(MDBX_GIT_DESCRIBE)' | tr -c -s '[a-zA-Z0-9.]' _)
MDBX_VERSION_NODOT = $(subst .,_,$(MDBX_VERSION_IDENT))
@ -424,7 +435,7 @@ MDBX_SMOKE_EXTRA ?=
check: DESTDIR = $(shell pwd)/@check-install
check: CMAKE_OPT = -Werror=dev
check: smoke-assertion ninja dist install test ctest
check: smoke-assertion ninja-assertions dist install test ctest
smoke-assertion: MDBX_BUILD_OPTIONS:=$(strip $(MDBX_BUILD_OPTIONS) -DMDBX_FORCE_ASSERTIONS=1 -UNDEBUG -DMDBX_DEBUG=0)
smoke-assertion: smoke
@ -552,7 +563,7 @@ $(MDBX_GIT_DIR)/HEAD $(MDBX_GIT_DIR)/index $(MDBX_GIT_DIR)/refs/tags:
src/version.c: src/version.c.in $(lastword $(MAKEFILE_LIST)) $(MDBX_GIT_DIR)/HEAD $(MDBX_GIT_DIR)/index $(MDBX_GIT_DIR)/refs/tags LICENSE NOTICE
@echo ' MAKE $@'
$(QUIET)sed \
$(QUIET)$(SED) \
-e "s|@MDBX_GIT_TIMESTAMP@|$(MDBX_GIT_TIMESTAMP)|" \
-e "s|@MDBX_GIT_TREE@|$(shell git show --no-patch --format=%T HEAD || echo 'Please install latest get version')|" \
-e "s|@MDBX_GIT_COMMIT@|$(shell git show --no-patch --format=%H HEAD || echo 'Please install latest get version')|" \
@ -586,7 +597,7 @@ mdbx-static.o: src/config.h src/version.c src/alloy.c $(ALLOY_DEPS) $(lastword $
docs/Doxyfile: docs/Doxyfile.in src/version.c $(lastword $(MAKEFILE_LIST))
@echo ' MAKE $@'
$(QUIET)sed \
$(QUIET)$(SED) \
-e "s|@MDBX_GIT_TIMESTAMP@|$(MDBX_GIT_TIMESTAMP)|" \
-e "s|@MDBX_GIT_TREE@|$(shell git show --no-patch --format=%T HEAD || echo 'Please install latest get version')|" \
-e "s|@MDBX_GIT_COMMIT@|$(shell git show --no-patch --format=%H HEAD || echo 'Please install latest get version')|" \
@ -602,7 +613,7 @@ docs/Doxyfile: docs/Doxyfile.in src/version.c $(lastword $(MAKEFILE_LIST))
define md-extract-section
docs/__$(1).md: $(2) $(lastword $(MAKEFILE_LIST))
@echo ' EXTRACT $1'
$(QUIET)sed -n '/<!-- section-begin $(1) -->/,/<!-- section-end -->/p' $(2) >$$@ && test -s $$@
$(QUIET)$(SED) -n '/<!-- section-begin $(1) -->/,/<!-- section-end -->/p' $(2) >$$@ && test -s $$@
endef
$(foreach section,overview mithril characteristics improvements history usage performance bindings,$(eval $(call md-extract-section,$(section),README.md)))
@ -617,17 +628,18 @@ docs/overall.md: docs/__overview.md docs/_toc.md docs/__mithril.md docs/__histor
docs/intro.md: docs/_preface.md docs/__characteristics.md docs/__improvements.md docs/_restrictions.md docs/__performance.md
@echo ' MAKE $@'
$(QUIET)cat $^ | sed 's/^Performance comparison$$/Performance comparison {#performance}/;s/^Improvements beyond LMDB$$/Improvements beyond LMDB {#improvements}/' >$@
$(QUIET)cat $^ | $(SED) 's/^Performance comparison$$/Performance comparison {#performance}/;s/^Improvements beyond LMDB$$/Improvements beyond LMDB {#improvements}/' >$@
docs/usage.md: docs/__usage.md docs/_starting.md docs/__bindings.md
@echo ' MAKE $@'
$(QUIET)echo -e "\\page usage Usage\n\\section getting Building & Embedding" | cat - $^ | sed 's/^Bindings$$/Bindings {#bindings}/' >$@
$(QUIET)echo -e "\\page usage Usage\n\\section getting Building & Embedding" | cat - $^ | $(SED) 's/^Bindings$$/Bindings {#bindings}/' >$@
doxygen: docs/Doxyfile docs/overall.md docs/intro.md docs/usage.md mdbx.h mdbx.h++ src/options.h ChangeLog.md COPYRIGHT LICENSE NOTICE docs/favicon.ico docs/manifest.webmanifest $(lastword $(MAKEFILE_LIST))
doxygen: docs/Doxyfile docs/overall.md docs/intro.md docs/usage.md mdbx.h mdbx.h++ src/options.h ChangeLog.md COPYRIGHT LICENSE NOTICE docs/favicon.ico docs/manifest.webmanifest docs/ld+json $(lastword $(MAKEFILE_LIST))
@echo ' RUNNING doxygen...'
$(QUIET)rm -rf docs/html && \
cat mdbx.h | tr '\n' '\r' | sed -e 's/LIBMDBX_INLINE_API\s*(\s*\([^,]\+\),\s*\([^,]\+\),\s*(\s*\([^)]\+\)\s*)\s*)\s*{/inline \1 \2(\3) {/g' | tr '\r' '\n' >docs/mdbx.h && \
cp mdbx.h++ src/options.h ChangeLog.md docs/ && (cd docs && doxygen Doxyfile $(HUSH)) && cp COPYRIGHT LICENSE NOTICE docs/favicon.ico docs/manifest.webmanifest docs/html/
cat mdbx.h | tr '\n' '\r' | $(SED) -e 's/LIBMDBX_INLINE_API\s*(\s*\([^,]\+\),\s*\([^,]\+\),\s*(\s*\([^)]\+\)\s*)\s*)\s*{/inline \1 \2(\3) {/g' | tr '\r' '\n' >docs/mdbx.h && \
cp mdbx.h++ src/options.h ChangeLog.md docs/ && (cd docs && doxygen Doxyfile $(HUSH)) && cp COPYRIGHT LICENSE NOTICE docs/favicon.ico docs/manifest.webmanifest docs/html/ && \
$(SED) -i docs/html/index.html -e '/\/MathJax.js"><\/script>/r docs/ld+json' -e 's/<title>libmdbx: Overall<\/title>//;T;r docs/title'
mdbx++-dylib.o: src/config.h src/mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST))
@echo ' CC $@'
@ -650,7 +662,7 @@ release-assets: libmdbx-amalgamated-$(MDBX_GIT_3DOT).zpaq \
libmdbx-amalgamated-$(MDBX_GIT_3DOT).tar.gz \
libmdbx-amalgamated-$(subst .,_,$(MDBX_GIT_3DOT)).zip
$(QUIET)([ \
"$$(set -o pipefail; git describe | sed -n '/^v[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}$$/p' || echo fail-left)" \
"$$(set -o pipefail; git describe | $(SED) -n '/^v[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}$$/p' || echo fail-left)" \
== \
"$$(git describe --tags --dirty=-dirty || echo fail-right)" ] \
|| (echo 'ERROR: Is not a valid release because not in the clean state with a suitable annotated tag!!!' >&2 && false)) \
@ -660,7 +672,7 @@ release-assets: libmdbx-amalgamated-$(MDBX_GIT_3DOT).zpaq \
@echo -n ' VERIFY amalgamated sources...'
$(QUIET)rm -rf $@ $(DIST_DIR)/@tmp-essentials.inc $(DIST_DIR)/@tmp-internals.inc \
&& if grep -R "define xMDBX_ALLOY" dist | grep -q MDBX_BUILD_SOURCERY; then echo "sed output is WRONG!" >&2; exit 2; fi \
&& rm -rf @dist-check && cp -r -p $(DIST_DIR) @dist-check && ($(MAKE) -j IOARENA=false CXXSTD=$(CXXSTD) -C @dist-check all ninja >@dist-check.log 2>@dist-check.err || (cat @dist-check.err && exit 1)) \
&& rm -rf @dist-check && cp -r -p $(DIST_DIR) @dist-check && ($(MAKE) -j IOARENA=false CXXSTD=$(CXXSTD) -C @dist-check all ninja-assertions >@dist-check.log 2>@dist-check.err || (cat @dist-check.err && exit 1)) \
&& touch $@ || (echo " FAILED! See @dist-check.log and @dist-check.err" >&2; exit 2) && echo " Ok"
%.tar.gz: @dist-checked.tag
@ -687,7 +699,7 @@ $(DIST_DIR)/@tmp-essentials.inc: src/version.c $(ALLOY_DEPS) $(lastword $(MAKEFI
@echo ' ALLOYING...'
$(QUIET)mkdir -p dist \
&& (grep -v '#include ' src/alloy.c && echo '#define MDBX_BUILD_SOURCERY $(MDBX_BUILD_SOURCERY)' \
&& sed \
&& $(SED) \
-e 's|#include "../mdbx.h"|@INCLUDE "mdbx.h"|' \
-e '/#include "preface.h"/r src/preface.h' \
-e '/#include "osal.h"/r src/osal.h' \
@ -699,14 +711,14 @@ $(DIST_DIR)/@tmp-essentials.inc: src/version.c $(ALLOY_DEPS) $(lastword $(MAKEFI
-e '/#include "utils.h"/r src/utils.h' \
-e '/#include "pnl.h"/r src/pnl.h' \
src/essentials.h \
| sed \
| $(SED) \
-e '/#pragma once/d' -e '/#include "/d' \
-e '/ clang-format o/d' -e '/ \*INDENT-O/d' \
| grep -v '^/// ') >$@
$(DIST_DIR)/@tmp-internals.inc: $(DIST_DIR)/@tmp-essentials.inc src/version.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST))
$(QUIET)(cat $(DIST_DIR)/@tmp-essentials.inc \
&& sed \
&& $(SED) \
-e '/#include "essentials.h"/d' \
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
-e '/#include "proto.h"/r src/proto.h' \
@ -729,22 +741,22 @@ $(DIST_DIR)/@tmp-internals.inc: $(DIST_DIR)/@tmp-essentials.inc src/version.c $(
-e '/#include "walk.h"/r src/walk.h' \
-e '/#include "windows-import.h"/r src/windows-import.h' \
src/internals.h \
| sed \
| $(SED) \
-e '/#pragma once/d' -e '/#include "/d' \
-e '/ clang-format o/d' -e '/ \*INDENT-O/d' \
| grep -v '^/// ') >$@
$(DIST_DIR)/mdbx.c: $(DIST_DIR)/@tmp-internals.inc $(lastword $(MAKEFILE_LIST))
@echo ' MAKE $@'
$(QUIET)(cat $(DIST_DIR)/@tmp-internals.inc $(shell git ls-files src/*.c | grep -v alloy) src/version.c | sed \
$(QUIET)(cat $(DIST_DIR)/@tmp-internals.inc $(shell git ls-files src/*.c | grep -v alloy) src/version.c | $(SED) \
-e '/#include "debug_begin.h"/r src/debug_begin.h' \
-e '/#include "debug_end.h"/r src/debug_end.h' \
) | sed -e '/#include "/d;/#pragma once/d' -e 's|@INCLUDE|#include|' \
) | $(SED) -e '/#include "/d;/#pragma once/d' -e 's|@INCLUDE|#include|' \
-e '/ clang-format o/d;/ \*INDENT-O/d' -e '3i /* clang-format off */' | cat -s >$@
$(DIST_DIR)/mdbx.c++: $(DIST_DIR)/@tmp-essentials.inc src/mdbx.c++ $(lastword $(MAKEFILE_LIST))
@echo ' MAKE $@'
$(QUIET)cat $(DIST_DIR)/@tmp-essentials.inc src/mdbx.c++ | sed \
$(QUIET)cat $(DIST_DIR)/@tmp-essentials.inc src/mdbx.c++ | $(SED) \
-e '/#define xMDBX_ALLOY/d' \
-e '/#include "/d;/#pragma once/d' \
-e 's|@INCLUDE|#include|;s|"mdbx.h"|"mdbx.h++"|' \
@ -754,12 +766,12 @@ define dist-tool-rule
$(DIST_DIR)/mdbx_$(1).c: src/tools/$(1).c src/tools/wingetopt.h src/tools/wingetopt.c \
$(DIST_DIR)/@tmp-internals.inc $(lastword $(MAKEFILE_LIST))
@echo ' MAKE $$@'
$(QUIET)mkdir -p dist && sed \
$(QUIET)mkdir -p dist && $(SED) \
-e '/#include "essentials.h"/r $(DIST_DIR)/@tmp-essentials.inc' \
-e '/#include "wingetopt.h"/r src/tools/wingetopt.c' \
-e '/ clang-format o/d' -e '/ \*INDENT-O/d' \
src/tools/$(1).c \
| sed -e '/#include "/d;/#pragma once/d;/#define xMDBX_ALLOY/d' -e 's|@INCLUDE|#include|' \
| $(SED) -e '/#include "/d;/#pragma once/d;/#define xMDBX_ALLOY/d' -e 's|@INCLUDE|#include|' \
-e '/ clang-format o/d;/ \*INDENT-O/d' -e '9i /* clang-format off */' | cat -s >$$@
endef
@ -768,7 +780,7 @@ $(foreach file,$(TOOLS),$(eval $(call dist-tool-rule,$(file))))
define dist-extra-rule
$(DIST_DIR)/$(1): $(1) src/version.c $(lastword $(MAKEFILE_LIST))
@echo ' REFINE $$@'
$(QUIET)mkdir -p $$(dir $$@) && sed -e '/^#> dist-cutoff-begin/,/^#< dist-cutoff-end/d' $$< | cat -s >$$@
$(QUIET)mkdir -p $$(dir $$@) && $(SED) -e '/^#> dist-cutoff-begin/,/^#< dist-cutoff-end/d' $$< | cat -s >$$@
endef
$(foreach file,mdbx.h mdbx.h++ $(filter-out man1/% VERSION.json .clang-format-ignore %.in ntdll.def,$(DIST_EXTRA)),$(eval $(call dist-extra-rule,$(file))))
@ -817,7 +829,7 @@ cross-gcc:
@echo "FOR INSTANCE: sudo apt install \$$(apt list 'g++-*' | grep 'g++-[a-z0-9]\+-linux-gnu/' | cut -f 1 -d / | sort -u)"
$(QUIET)for CC in $(CROSS_LIST_NOQEMU) $(CROSS_LIST); do \
echo "===================== $$CC"; \
$(MAKE) IOARENA=false CXXSTD= clean && CC=$$CC CXX=$$(echo $$CC | sed 's/-gcc/-g++/') EXE_LDFLAGS=-static $(MAKE) IOARENA=false all || exit $$?; \
$(MAKE) IOARENA=false CXXSTD= clean && CC=$$CC CXX=$$(echo $$CC | $(SED) 's/-gcc/-g++/') EXE_LDFLAGS=-static $(MAKE) IOARENA=false all || exit $$?; \
done
# Unfortunately qemu don't provide robust support for futexes.
@ -831,7 +843,7 @@ cross-qemu:
$(QUIET)for CC in $(CROSS_LIST); do \
echo "===================== $$CC + qemu"; \
$(MAKE) IOARENA=false CXXSTD= clean && \
CC=$$CC CXX=$$(echo $$CC | sed 's/-gcc/-g++/') EXE_LDFLAGS=-static MDBX_BUILD_OPTIONS="-DMDBX_SAFE4QEMU $(MDBX_BUILD_OPTIONS)" \
CC=$$CC CXX=$$(echo $$CC | $(SED) 's/-gcc/-g++/') EXE_LDFLAGS=-static MDBX_BUILD_OPTIONS="-DMDBX_SAFE4QEMU $(MDBX_BUILD_OPTIONS)" \
$(MAKE) IOARENA=false smoke-singleprocess test-singleprocess || exit $$?; \
done
@ -898,13 +910,13 @@ bench-$(1)_$(2).txt: $(3) $(IOARENA) $(lastword $(MAKEFILE_LIST))
$(QUIET)(export LD_LIBRARY_PATH="./:$$$${LD_LIBRARY_PATH}"; \
ldd $(IOARENA) | grep -i $(1) && \
$(IOARENA) -D $(1) -B batch -m $(BENCH_CRUD_MODE) -n $(2) \
| tee $$@ | grep throughput | sed 's/throughput/batch×N/' && \
| tee $$@ | grep throughput | $(SED) 's/throughput/batch×N/' && \
$(IOARENA) -D $(1) -B crud -m $(BENCH_CRUD_MODE) -n $(2) \
| tee -a $$@ | grep throughput | sed 's/throughput/ crud/' && \
| tee -a $$@ | grep throughput | $(SED) 's/throughput/ crud/' && \
$(IOARENA) -D $(1) -B iterate,get,iterate,get,iterate -m $(BENCH_CRUD_MODE) -r 4 -n $(2) \
| tee -a $$@ | grep throughput | sed '0,/throughput/{s/throughput/iterate/};s/throughput/ get/' && \
| tee -a $$@ | grep throughput | $(SED) '0,/throughput/{s/throughput/iterate/};s/throughput/ get/' && \
$(IOARENA) -D $(1) -B delete -m $(BENCH_CRUD_MODE) -n $(2) \
| tee -a $$@ | grep throughput | sed 's/throughput/ delete/' && \
| tee -a $$@ | grep throughput | $(SED) 's/throughput/ delete/' && \
true) || mv -f $$@ $$@.error
endef

30
NOTICE
View File

@ -8,16 +8,32 @@ documentation, C++ API description and links to the original git repo
with the source code. Questions, feedback and suggestions are welcome
to the Telegram' group https://t.me/libmdbx.
Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`.
Donations are welcome to the Ethereum/ERC-20 `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`.
Всё будет хорошо!
Copyright 2015-2025 Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru>
SPDX-License-Identifier: Apache-2.0
For notes about the license change, credits and acknowledgments,
please refer to the COPYRIGHT file within original libmdbx source code
repository https://gitflic.ru/project/erthink/libmdbx
please refer to the COPYRIGHT file within libmdbx source.
On 2022-04-15 the Github administration, without any warning nor
explanation, deleted _libmdbx_ along with a lot of other projects,
simultaneously blocking access for many developers.
For the same reason ~~Github~~ is blacklisted forever.
---
On 2022-04-15, without any warnings or following explanations, the
Github administration deleted _libmdbx_, my account and all other
projects (status 404). A few months later, without any involvement or
notification from/to me, the projects were restored/opened in the "public
read-only archive" status from some kind of incomplete backup. I regard
these actions of Github as malicious sabotage, and I consider the Github
service itself to have lost trust forever.
As a result of what has happened, I will never, under any circumstances,
post the primary sources (aka origins) of my projects on Github, or rely
in any way on the Github infrastructure.
Nevertheless, realizing that it is more convenient for users of
_libmdbx_ and other my projects to access ones on Github, I do not want
to restrict their freedom or create inconvenience, and therefore I place
mirrors (aka mirrors) of such repositories on Github since 2025. At the
same time, I would like to emphasize once again that these are only
mirrors that can be frozen, blocked or deleted at any time, as was the
case in 2022.

View File

@ -9,7 +9,7 @@
> [5](https://libmdbx.dqdkfa.ru/tg-archive/messages5.html), [6](https://libmdbx.dqdkfa.ru/tg-archive/messages6.html), [7](https://libmdbx.dqdkfa.ru/tg-archive/messages7.html)).
> See the [ChangeLog](https://gitflic.ru/project/erthink/libmdbx/blob?file=ChangeLog.md) for `NEWS` and latest updates.
> Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`.
> Donations are welcome to the Ethereum/ERC-20 `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`.
> Всё будет хорошо!
@ -65,7 +65,51 @@ Historically, _libmdbx_ is a deeply revised and extended descendant of the amazi
[Lightning Memory-Mapped Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
_libmdbx_ inherits all benefits from _LMDB_, but resolves some issues and adds [a set of improvements](#improvements-beyond-lmdb).
### MithrilDB and Future
## Github
### на Русском (мой родной язык)
Весной 2022, без каких-либо предупреждений или пояснений, администрация
Github удалила мой аккаунт и все проекты. Через несколько месяцев, без
какого-либо моего участия или уведомления, проекты были
восстановлены/открыты в статусе "public read-only archive" из какой-то
неполноценной резервной копии. Эти действия Github я расцениваю как
злонамеренный саботаж, а сам сервис Github считаю навсегда утратившим
какое-либо доверие.
Вследствие произошедшего, никогда и ни при каких условиях, я не буду
размещать на Github первоисточники (aka origins) моих проектов, либо
как-либо полагаться на инфраструктуру Github.
Тем не менее, понимая что пользователям моих проектов удобнее получать к
ним доступ именно на Github, я не хочу ограничивать их свободу или
создавать неудобство, и поэтому размещаю на Github зеркала (aka mirrors)
репозиториев моих проектов. При этом ещё раз акцентирую внимание, что
это только зеркала, которые могут быть заморожены, заблокированы или
удалены в любой момент, как это уже было в 2022.
### in English
In the spring of 2022, without any warnings or explanations, the Github
administration deleted my account and all projects. A few months later,
without any involvement or notification from me, the projects were
restored/opened in the "public read-only archive" status from some kind
of incomplete backup. I regard these actions of Github as malicious
sabotage, and I consider the Github service itself to have lost any
trust forever.
As a result of what has happened, I will never, under any circumstances,
post the primary sources (aka origins) of my projects on Github, or rely
in any way on the Github infrastructure.
Nevertheless, realizing that it is more convenient for users of my
projects to access them on Github, I do not want to restrict their
freedom or create inconvenience, and therefore I place mirrors of my
project repositories on Github. At the same time, I would like to
emphasize once again that these are only mirrors that can be frozen,
blocked or deleted at any time, as was the case in 2022.
## MithrilDB and Future
<!-- section-begin mithril -->
@ -235,7 +279,7 @@ which is also (mostly) applicable to _libmdbx_ with minor clarification:
- a database could shared by multiple processes, i.e. no multi-process issues;
- no issues with moving a cursor(s) after the deletion;
- _libmdbx_ provides zero-overhead database compactification, so a database file could be shrinked/truncated in particular cases;
- excluding dist I/O time _libmdbx_ could be -3 times faster than BoltDB and up to 10-100K times faster than both BoltDB and LMDB in particular extreme cases;
- excluding disk I/O time _libmdbx_ could be ≈3 times faster than BoltDB and up to 10-100K times faster than both BoltDB and LMDB in particular extreme cases;
- _libmdbx_ provides more features compared to BoltDB and/or LMDB.
<!-- section-end -->
@ -607,16 +651,19 @@ error when opening the database in a _WSL1_ environment.
### MacOS
Current [native build tools](https://en.wikipedia.org/wiki/Xcode) for
MacOS include GNU Make, CLANG and an outdated version of Bash.
Therefore, to build the library, it is enough to run `make all` in the
However, the build script uses GNU-kind of `sed` and `tar`.
So the easiest way to install all prerequirements is to use [Homebrew](https://brew.sh/),
just by `brew install bash make cmake ninja gnu-sed gnu-tar --with-default-names`.
Next, to build the library, it is enough to run `make all` in the
directory with source code, and run `make check` to execute the base
tests. If something goes wrong, it is recommended to install
[Homebrew](https://brew.sh/) and try again.
To run the [long stochastic test scenario](test/stochastic.sh), you
will need to install the current (not outdated) version of
[Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)). To do this, I
recommend that you install [Homebrew](https://brew.sh/) and then execute
`brew install bash`.
[Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)).
Just install it as noted above.
### Android
I recommend using CMake to build _libmdbx_ for Android.
@ -646,6 +693,7 @@ Bindings
| Java | [mdbxjni](https://github.com/castortech/mdbxjni) | [Castor Technologies](https://castortech.com/) |
| Go | [mdbx-go](https://github.com/torquem-ch/mdbx-go) | [Alex Sharov](https://github.com/AskAlexSharov) |
| Ruby | [ruby-mdbx](https://rubygems.org/gems/mdbx/) | [Mahlon E. Smith](https://github.com/mahlonsmith) |
| Zig | [mdbx-zig](https://github.com/theseyan/lmdbx-zig) | [Sayan J. Das](https://github.com/theseyan) |
##### Obsolete/Outdated/Unsupported:

View File

@ -668,7 +668,9 @@ if(CMAKE_COMPILER_IS_CLANG)
if(CMAKE_CLANG_AR
AND CMAKE_CLANG_NM
AND CMAKE_CLANG_RANLIB
AND ((CLANG_LTO_PLUGIN AND CMAKE_LD_GOLD) OR CMAKE_CLANG_LD OR APPLE))
AND ((CLANG_LTO_PLUGIN AND CMAKE_LD_GOLD)
OR CMAKE_CLANG_LD
OR APPLE))
if(ANDROID AND CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 12)
set(CLANG_LTO_AVAILABLE FALSE)
message(

View File

@ -228,7 +228,7 @@ class libmdbx(ConanFile):
if os.path.exists(version_json_pathname):
self.version = json.load(
open(version_json_pathname, encoding='utf-8'))['semver']
version_from = "'" + version_jsonpath_name + "'"
version_from = "'" + version_json_pathname + "'"
else:
self.version = self.fetch_versioninfo_from_git()['semver']
version_from = 'Git'

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,10 @@
<link rel="icon" href="favicon.ico">
<link rel="icon" href="img/bear.png" type="image/png">
<link rel="apple-touch-icon" href="img/bear.png">
<meta property="og:type" content="article"/>
<meta property="og:url" content="https://libmdbx.dqdkfa.ru/"/>
<meta name="twitter:title" content="One of the fastest embeddable key-value engine"/>
<meta name="twitter:description" content="MDBX surpasses the legendary LMDB in terms of reliability, features and performance. For now libmdbx is chosen by all modern Ethereum frontiers as a storage engine."/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<!--BEGIN PROJECT_ICON-->

27
docs/ld+json Normal file
View File

@ -0,0 +1,27 @@
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "Группа в Telegram",
"url": "https://t.me/libmdbx"
},{
"@type": "ListItem",
"position": 2,
"name": "Исходный код",
"url": "https://gitflic.ru/project/erthink/libmdbx"
},{
"@type": "ListItem",
"position": 3,
"name": "C++ API",
"url": "https://libmdbx.dqdkfa.ru/group__cxx__api.html"
},{
"@type": "ListItem",
"position": 4,
"name": "Mirror on Github",
"url": "https://github.com/erthink/libmdbx"
}]
}
</script>

2
docs/title Normal file
View File

@ -0,0 +1,2 @@
<title>libmdbx: One of the fastest embeddable key-value engine</title>
<meta name="description" content="libmdbx surpasses the legendary LMDB in terms of reliability, features and performance. For now libmdbx is chosen by all modern Ethereum frontiers as a storage engine.">

141
mdbx.h
View File

@ -204,7 +204,7 @@ typedef mode_t mdbx_mode_t;
#ifndef __has_cpp_attribute
#define __has_cpp_attribute(x) 0
#define __has_cpp_attribute_qualified(x) 0
#elif defined(_MSC_VER)
#elif defined(_MSC_VER) || (__clang__ && __clang__ < 14)
/* MSVC don't support `namespace::attr` syntax */
#define __has_cpp_attribute_qualified(x) 0
#else
@ -318,7 +318,7 @@ typedef mode_t mdbx_mode_t;
#ifndef MDBX_DEPRECATED
#ifdef __deprecated
#define MDBX_DEPRECATED __deprecated
#elif defined(DOXYGEN) || ((!defined(__GNUC__) || defined(__clang__) || __GNUC__ > 5) && \
#elif defined(DOXYGEN) || ((!defined(__GNUC__) || (defined(__clang__) && __clang__ > 19) || __GNUC__ > 5) && \
((defined(__cplusplus) && __cplusplus >= 201403L && __has_cpp_attribute(deprecated) && \
__has_cpp_attribute(deprecated) >= 201309L) || \
(!defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202304L)))
@ -504,7 +504,7 @@ typedef mode_t mdbx_mode_t;
#if defined(DOXYGEN) || \
(defined(__cplusplus) && __cplusplus >= 201603L && __has_cpp_attribute(maybe_unused) && \
__has_cpp_attribute(maybe_unused) >= 201603L) || \
__has_cpp_attribute(maybe_unused) >= 201603L && (!defined(__clang__) || __clang__ > 19)) || \
(!defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ > 202005L)
#define MDBX_MAYBE_UNUSED [[maybe_unused]]
#elif defined(__GNUC__) || __has_attribute(__unused__)
@ -581,7 +581,8 @@ typedef mode_t mdbx_mode_t;
extern "C" {
#endif
/* MDBX version 0.14.x */
/* MDBX version 0.14.x, but it is unstable/under-development yet. */
#define MDBX_VERSION_UNSTABLE
#define MDBX_VERSION_MAJOR 0
#define MDBX_VERSION_MINOR 14
@ -1718,7 +1719,7 @@ typedef enum MDBX_cursor_op {
/** \ref MDBX_DUPFIXED -only: Return up to a page of duplicate data items
* from current cursor position. Move cursor to prepare
* for \ref MDBX_NEXT_MULTIPLE. */
* for \ref MDBX_NEXT_MULTIPLE. \see MDBX_SEEK_AND_GET_MULTIPLE */
MDBX_GET_MULTIPLE,
/** Position at last key/data item */
@ -1734,8 +1735,8 @@ typedef enum MDBX_cursor_op {
MDBX_NEXT_DUP,
/** \ref MDBX_DUPFIXED -only: Return up to a page of duplicate data items
* from next cursor position. Move cursor to prepare
* for `MDBX_NEXT_MULTIPLE`. */
* from next cursor position. Move cursor to prepare for `MDBX_NEXT_MULTIPLE`.
* \see MDBX_SEEK_AND_GET_MULTIPLE \see MDBX_GET_MULTIPLE */
MDBX_NEXT_MULTIPLE,
/** Position at first data item of next key */
@ -1760,7 +1761,8 @@ typedef enum MDBX_cursor_op {
MDBX_SET_RANGE,
/** \ref MDBX_DUPFIXED -only: Position at previous page and return up to
* a page of duplicate data items. */
* a page of duplicate data items.
* \see MDBX_SEEK_AND_GET_MULTIPLE \see MDBX_GET_MULTIPLE */
MDBX_PREV_MULTIPLE,
/** Positions cursor at first key-value pair greater than or equal to
@ -1791,26 +1793,33 @@ typedef enum MDBX_cursor_op {
* \ref MDBX_NOTFOUND otherwise. */
MDBX_SET_UPPERBOUND,
/* Doubtless cursor positioning at a specified key. */
/** Doubtless cursor positioning at a specified key. */
MDBX_TO_KEY_LESSER_THAN,
MDBX_TO_KEY_LESSER_OR_EQUAL,
MDBX_TO_KEY_EQUAL,
MDBX_TO_KEY_GREATER_OR_EQUAL,
MDBX_TO_KEY_GREATER_THAN,
MDBX_TO_KEY_LESSER_OR_EQUAL /** \copydoc MDBX_TO_KEY_LESSER_THAN */,
MDBX_TO_KEY_EQUAL /** \copydoc MDBX_TO_KEY_LESSER_THAN */,
MDBX_TO_KEY_GREATER_OR_EQUAL /** \copydoc MDBX_TO_KEY_LESSER_THAN */,
MDBX_TO_KEY_GREATER_THAN /** \copydoc MDBX_TO_KEY_LESSER_THAN */,
/* Doubtless cursor positioning at a specified key-value pair
/** Doubtless cursor positioning at a specified key-value pair
* for dupsort/multi-value hives. */
MDBX_TO_EXACT_KEY_VALUE_LESSER_THAN,
MDBX_TO_EXACT_KEY_VALUE_LESSER_OR_EQUAL,
MDBX_TO_EXACT_KEY_VALUE_EQUAL,
MDBX_TO_EXACT_KEY_VALUE_GREATER_OR_EQUAL,
MDBX_TO_EXACT_KEY_VALUE_GREATER_THAN,
MDBX_TO_EXACT_KEY_VALUE_LESSER_OR_EQUAL /** \copydoc MDBX_TO_EXACT_KEY_VALUE_LESSER_THAN */,
MDBX_TO_EXACT_KEY_VALUE_EQUAL /** \copydoc MDBX_TO_EXACT_KEY_VALUE_LESSER_THAN */,
MDBX_TO_EXACT_KEY_VALUE_GREATER_OR_EQUAL /** \copydoc MDBX_TO_EXACT_KEY_VALUE_LESSER_THAN */,
MDBX_TO_EXACT_KEY_VALUE_GREATER_THAN /** \copydoc MDBX_TO_EXACT_KEY_VALUE_LESSER_THAN */,
/** Doubtless cursor positioning at a specified key-value pair
* for dupsort/multi-value hives. */
MDBX_TO_PAIR_LESSER_THAN,
MDBX_TO_PAIR_LESSER_OR_EQUAL,
MDBX_TO_PAIR_EQUAL,
MDBX_TO_PAIR_GREATER_OR_EQUAL,
MDBX_TO_PAIR_GREATER_THAN
MDBX_TO_PAIR_LESSER_OR_EQUAL /** \copydoc MDBX_TO_PAIR_LESSER_THAN */,
MDBX_TO_PAIR_EQUAL /** \copydoc MDBX_TO_PAIR_LESSER_THAN */,
MDBX_TO_PAIR_GREATER_OR_EQUAL /** \copydoc MDBX_TO_PAIR_LESSER_THAN */,
MDBX_TO_PAIR_GREATER_THAN /** \copydoc MDBX_TO_PAIR_LESSER_THAN */,
/** \ref MDBX_DUPFIXED -only: Seek to given key and return up to a page of
* duplicate data items from current cursor position. Move cursor to prepare
* for \ref MDBX_NEXT_MULTIPLE. \see MDBX_GET_MULTIPLE */
MDBX_SEEK_AND_GET_MULTIPLE
} MDBX_cursor_op;
/** \brief Errors and return codes
@ -1964,8 +1973,7 @@ typedef enum MDBX_error {
* recycling old MVCC snapshots. */
MDBX_OUSTED = -30411,
/** MVCC snapshot used by read transaction is outdated and could not be
* copied since corresponding meta-pages was overwritten. */
/** MVCC snapshot used by parked transaction was bygone. */
MDBX_MVCC_RETARDED = -30410,
/* The last of MDBX-added error codes */
@ -5124,6 +5132,10 @@ MDBX_NOTHROW_PURE_FUNCTION LIBMDBX_API void *mdbx_cursor_get_userctx(const MDBX_
* the same table handle as it was created with. This may be done whether the
* previous transaction is live or dead.
*
* If the transaction is nested, then the cursor should not be used in its parent transaction.
* Otherwise it is no way to restore state if this nested transaction will be aborted,
* nor impossible to define the expected behavior.
*
* \note In contrast to LMDB, the MDBX required that any opened cursors can be
* reused and must be freed explicitly, regardless ones was opened in a
* read-only or write transaction. The REASON for this is eliminates ambiguity
@ -5139,7 +5151,7 @@ MDBX_NOTHROW_PURE_FUNCTION LIBMDBX_API void *mdbx_cursor_get_userctx(const MDBX_
* \retval MDBX_THREAD_MISMATCH Given transaction is not owned
* by current thread.
* \retval MDBX_EINVAL An invalid parameter was specified. */
LIBMDBX_API int mdbx_cursor_bind(const MDBX_txn *txn, MDBX_cursor *cursor, MDBX_dbi dbi);
LIBMDBX_API int mdbx_cursor_bind(MDBX_txn *txn, MDBX_cursor *cursor, MDBX_dbi dbi);
/** \brief Unbind cursor from a transaction.
* \ingroup c_cursors
@ -5148,6 +5160,10 @@ LIBMDBX_API int mdbx_cursor_bind(const MDBX_txn *txn, MDBX_cursor *cursor, MDBX_
* the original DBI-handle internally. Thus it could be renewed with any running
* transaction or closed.
*
* If the transaction is nested, then the cursor should not be used in its parent transaction.
* Otherwise it is no way to restore state if this nested transaction will be aborted,
* nor impossible to define the expected behavior.
*
* \see mdbx_cursor_renew()
* \see mdbx_cursor_bind()
* \see mdbx_cursor_close()
@ -5208,14 +5224,19 @@ LIBMDBX_API int mdbx_cursor_reset(MDBX_cursor *cursor);
* \retval MDBX_THREAD_MISMATCH Given transaction is not owned
* by current thread.
* \retval MDBX_EINVAL An invalid parameter was specified. */
LIBMDBX_API int mdbx_cursor_open(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **cursor);
LIBMDBX_API int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **cursor);
/** \brief Close a cursor handle.
/** \brief Closes a cursor handle without returning error code.
* \ingroup c_cursors
*
* The cursor handle will be freed and must not be used again after this call,
* but its transaction may still be live.
*
* This function returns `void` but panic in case of error. Use \ref mdbx_cursor_close2()
* if you need to receive an error code instead of an app crash.
*
* \see mdbx_cursor_close2
*
* \note In contrast to LMDB, the MDBX required that any opened cursors can be
* reused and must be freed explicitly, regardless ones was opened in a
* read-only or write transaction. The REASON for this is eliminates ambiguity
@ -5226,11 +5247,59 @@ LIBMDBX_API int mdbx_cursor_open(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor
* or \ref mdbx_cursor_create(). */
LIBMDBX_API void mdbx_cursor_close(MDBX_cursor *cursor);
/** \brief Unbind or closes all cursors of a given transaction.
/** \brief Closes a cursor handle with returning error code.
* \ingroup c_cursors
*
* Unbinds either closes all cursors associated (opened or renewed) with
* a given transaction in a bulk with minimal overhead.
* The cursor handle will be freed and must not be used again after this call,
* but its transaction may still be live.
*
* \see mdbx_cursor_close
*
* \note In contrast to LMDB, the MDBX required that any opened cursors can be
* reused and must be freed explicitly, regardless ones was opened in a
* read-only or write transaction. The REASON for this is eliminates ambiguity
* which helps to avoid errors such as: use-after-free, double-free, i.e.
* memory corruption and segfaults.
*
* \param [in] cursor A cursor handle returned by \ref mdbx_cursor_open()
* or \ref mdbx_cursor_create().
* \returns A non-zero error value on failure and 0 on success,
* some possible errors are:
* \retval MDBX_THREAD_MISMATCH Given transaction is not owned
* by current thread.
* \retval MDBX_EINVAL An invalid parameter was specified. */
LIBMDBX_API int mdbx_cursor_close2(MDBX_cursor *cursor);
/** \brief Unbind or closes all cursors of a given transaction and of all
* its parent transactions if ones are.
* \ingroup c_cursors
*
* Unbinds either closes all cursors associated (opened, renewed or binded) with
* the given transaction in a bulk with minimal overhead.
*
* \see mdbx_cursor_unbind()
* \see mdbx_cursor_close()
*
* \param [in] txn A transaction handle returned by \ref mdbx_txn_begin().
* \param [in] unbind If non-zero, unbinds cursors and leaves ones reusable.
* Otherwise close and dispose cursors.
* \param [in,out] count An optional pointer to return the number of cursors
* processed by the requested operation.
*
* \returns A non-zero error value on failure and 0 on success,
* some possible errors are:
* \retval MDBX_THREAD_MISMATCH Given transaction is not owned
* by current thread.
* \retval MDBX_BAD_TXN Given transaction is invalid or has
* a child/nested transaction transaction. */
LIBMDBX_API int mdbx_txn_release_all_cursors_ex(const MDBX_txn *txn, bool unbind, size_t *count);
/** \brief Unbind or closes all cursors of a given transaction and of all
* its parent transactions if ones are.
* \ingroup c_cursors
*
* Unbinds either closes all cursors associated (opened, renewed or binded) with
* the given transaction in a bulk with minimal overhead.
*
* \see mdbx_cursor_unbind()
* \see mdbx_cursor_close()
@ -5239,13 +5308,15 @@ LIBMDBX_API void mdbx_cursor_close(MDBX_cursor *cursor);
* \param [in] unbind If non-zero, unbinds cursors and leaves ones reusable.
* Otherwise close and dispose cursors.
*
* \returns A negative error value on failure or the number of closed cursors
* on success, some possible errors are:
* \returns A non-zero error value on failure and 0 on success,
* some possible errors are:
* \retval MDBX_THREAD_MISMATCH Given transaction is not owned
* by current thread.
* \retval MDBX_BAD_TXN Given transaction is invalid or has
* a child/nested transaction transaction. */
LIBMDBX_API int mdbx_txn_release_all_cursors(const MDBX_txn *txn, bool unbind);
LIBMDBX_INLINE_API(int, mdbx_txn_release_all_cursors, (const MDBX_txn *txn, bool unbind)) {
return mdbx_txn_release_all_cursors_ex(txn, unbind, NULL);
}
/** \brief Renew a cursor handle for use within the given transaction.
* \ingroup c_cursors
@ -5271,7 +5342,7 @@ LIBMDBX_API int mdbx_txn_release_all_cursors(const MDBX_txn *txn, bool unbind);
* \retval MDBX_EINVAL An invalid parameter was specified.
* \retval MDBX_BAD_DBI The cursor was not bound to a DBI-handle
* or such a handle became invalid. */
LIBMDBX_API int mdbx_cursor_renew(const MDBX_txn *txn, MDBX_cursor *cursor);
LIBMDBX_API int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *cursor);
/** \brief Return the cursor's transaction handle.
* \ingroup c_cursors
@ -6471,6 +6542,8 @@ typedef struct MDBX_chk_table {
struct MDBX_chk_histogram key_len;
/// Values length histogram
struct MDBX_chk_histogram val_len;
/// Number of multi-values (aka duplicates) histogram
struct MDBX_chk_histogram multival;
} histogram;
} MDBX_chk_table_t;

419
mdbx.h++
View File

@ -107,6 +107,16 @@
#include <span>
#endif
#if !defined(_MSC_VER) || defined(__clang__)
/* adequate compilers */
#define MDBX_EXTERN_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) extern template class API_ATTRIBUTES API_TYPENAME
#define MDBX_INSTALL_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) template class API_TYPENAME
#else
/* stupid microsoft showing off */
#define MDBX_EXTERN_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) extern template class API_TYPENAME
#define MDBX_INSTALL_API_TEMPLATE(API_ATTRIBUTES, API_TYPENAME) template class API_ATTRIBUTES API_TYPENAME
#endif
#if __cplusplus >= 201103L
#include <chrono>
#include <ratio>
@ -148,8 +158,7 @@
#endif
#endif /* Byte Order */
/** Workaround for old compilers without properly support for `C++17 constexpr`.
*/
/** Workaround for old compilers without properly support for `C++17 constexpr` */
#if defined(DOXYGEN)
#define MDBX_CXX17_CONSTEXPR constexpr
#elif defined(__cpp_constexpr) && __cpp_constexpr >= 201603L && \
@ -160,8 +169,7 @@
#define MDBX_CXX17_CONSTEXPR inline
#endif /* MDBX_CXX17_CONSTEXPR */
/** Workaround for old compilers without properly support for C++20 `constexpr`.
*/
/** Workaround for old compilers without properly support for C++20 `constexpr`. */
#if defined(DOXYGEN)
#define MDBX_CXX20_CONSTEXPR constexpr
#elif defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L && \
@ -185,8 +193,7 @@
#define MDBX_CXX20_CONSTEXPR_ENUM inline
#endif /* CONSTEXPR_ENUM_FLAGS_OPERATIONS */
/** Workaround for old compilers without support assertion inside `constexpr`
* functions. */
/** Workaround for old compilers without support assertion inside `constexpr` functions. */
#if defined(CONSTEXPR_ASSERT)
#define MDBX_CONSTEXPR_ASSERT(expr) CONSTEXPR_ASSERT(expr)
#elif defined NDEBUG
@ -211,8 +218,7 @@
#endif
#endif /* MDBX_UNLIKELY */
/** Workaround for old compilers without properly support for C++20 `if
* constexpr`. */
/** Workaround for old compilers without properly support for C++20 `if constexpr`. */
#if defined(DOXYGEN)
#define MDBX_IF_CONSTEXPR constexpr
#elif defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L
@ -243,7 +249,7 @@
#endif /* MDBX_CXX20_UNLIKELY */
#ifndef MDBX_HAVE_CXX20_CONCEPTS
#if defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 202002L
#if defined(__cpp_concepts) && __cpp_concepts >= 202002L && defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 202002L
#include <concepts>
#define MDBX_HAVE_CXX20_CONCEPTS 1
#elif defined(DOXYGEN)
@ -388,8 +394,7 @@ namespace filesystem = ::std::experimental::filesystem;
namespace filesystem = ::std::filesystem;
/// \brief Defined if `mdbx::filesystem::path` is available.
/// \details If defined, it is always `mdbx::filesystem::path`,
/// which in turn can be refs to `std::filesystem::path`
/// or `std::experimental::filesystem::path`.
/// which in turn can be refs to `std::filesystem::path` or `std::experimental::filesystem::path`.
/// Nonetheless `MDBX_STD_FILESYSTEM_PATH` not defined if the `::mdbx::path`
/// is fallbacked to c `std::string` or `std::wstring`.
#define MDBX_STD_FILESYSTEM_PATH ::mdbx::filesystem::path
@ -489,9 +494,7 @@ public:
static inline void success_or_panic(int error_code, const char *context_where, const char *func_who) noexcept;
};
/// \brief Base class for all libmdbx's exceptions that are corresponds
/// to libmdbx errors.
///
/// \brief Base class for all libmdbx's exceptions that are corresponds to libmdbx errors.
/// \see MDBX_error_t
class LIBMDBX_API_TYPE exception : public ::std::runtime_error {
using base = ::std::runtime_error;
@ -507,8 +510,7 @@ public:
const ::mdbx::error error() const noexcept { return error_; }
};
/// \brief Fatal exception that lead termination anyway
/// in dangerous unrecoverable cases.
/// \brief Fatal exception that lead termination anyway in dangerous unrecoverable cases.
class LIBMDBX_API_TYPE fatal : public exception {
using base = exception;
@ -644,8 +646,7 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val {
/// \brief Create an empty slice.
MDBX_CXX11_CONSTEXPR slice() noexcept;
/// \brief Create a slice that refers to [0,bytes-1] of memory bytes pointed
/// by ptr.
/// \brief Create a slice that refers to [0,bytes-1] of memory bytes pointed by ptr.
MDBX_CXX14_CONSTEXPR slice(const void *ptr, size_t bytes);
/// \brief Create a slice that refers to [begin,end] of memory bytes.
@ -1000,8 +1001,7 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val {
/// \brief Checks the slice is not refers to null address or has zero length.
MDBX_CXX11_CONSTEXPR bool is_valid() const noexcept { return !(iov_base == nullptr && iov_len != 0); }
/// \brief Build an invalid slice which non-zero length and refers to null
/// address.
/// \brief Build an invalid slice which non-zero length and refers to null address.
MDBX_CXX14_CONSTEXPR static slice invalid() noexcept { return slice(size_t(-1)); }
template <typename POD> MDBX_CXX14_CONSTEXPR POD as_pod() const {
@ -1162,7 +1162,12 @@ template <typename T, typename A> struct swap_alloc<T, A, true> {
} // namespace allocation_aware_details
struct default_capacity_policy {
enum : size_t { extra_inplace_storage = 0, pettiness_threshold = 64, max_reserve = 65536 };
enum : size_t {
extra_inplace_storage = 0,
inplace_storage_size_rounding = 16,
pettiness_threshold = 64,
max_reserve = 65536
};
static MDBX_CXX11_CONSTEXPR size_t round(const size_t value) {
static_assert((pettiness_threshold & (pettiness_threshold - 1)) == 0, "pettiness_threshold must be a power of 2");
@ -1224,8 +1229,7 @@ struct LIBMDBX_API to_hex {
char *write_bytes(char *dest, size_t dest_size) const;
/// \brief Output hexadecimal dump of passed slice to the std::ostream.
/// \throws std::ios_base::failure corresponding to std::ostream::write()
/// behaviour.
/// \throws std::ios_base::failure corresponding to std::ostream::write() behaviour.
::std::ostream &output(::std::ostream &out) const;
/// \brief Checks whether a passed slice is empty,
@ -1268,23 +1272,18 @@ struct LIBMDBX_API to_base58 {
return wrap_width ? bytes + bytes / wrap_width : bytes;
}
/// \brief Fills the buffer by [Base58](https://en.wikipedia.org/wiki/Base58)
/// dump of passed slice.
/// \brief Fills the buffer by [Base58](https://en.wikipedia.org/wiki/Base58) dump of passed slice.
/// \throws std::length_error if given buffer is too small.
char *write_bytes(char *dest, size_t dest_size) const;
/// \brief Output [Base58](https://en.wikipedia.org/wiki/Base58)
/// dump of passed slice to the std::ostream.
/// \throws std::ios_base::failure corresponding to std::ostream::write()
/// behaviour.
/// \brief Output [Base58](https://en.wikipedia.org/wiki/Base58) dump of passed slice to the std::ostream.
/// \throws std::ios_base::failure corresponding to std::ostream::write() behaviour.
::std::ostream &output(::std::ostream &out) const;
/// \brief Checks whether a passed slice is empty,
/// and therefore there will be no output bytes.
/// \brief Checks whether a passed slice is empty, and therefore there will be no output bytes.
bool is_empty() const noexcept { return source.empty(); }
/// \brief Checks whether the content of a passed slice is a valid data
/// and could be encoded or unexpectedly not.
/// \brief Checks whether the content of a passed slice is a valid data and could be encoded or unexpectedly not.
bool is_erroneous() const noexcept { return false; }
};
@ -1326,8 +1325,7 @@ struct LIBMDBX_API to_base64 {
/// \brief Output [Base64](https://en.wikipedia.org/wiki/Base64)
/// dump of passed slice to the std::ostream.
/// \throws std::ios_base::failure corresponding to std::ostream::write()
/// behaviour.
/// \throws std::ios_base::failure corresponding to std::ostream::write() behaviour.
::std::ostream &output(::std::ostream &out) const;
/// \brief Checks whether a passed slice is empty,
@ -1368,13 +1366,11 @@ struct LIBMDBX_API from_hex {
/// hexadecimal dump from a passed slice to decoded data.
MDBX_CXX11_CONSTEXPR size_t envisage_result_length() const noexcept { return source.length() >> 1; }
/// \brief Fills the destination with data decoded from hexadecimal dump
/// from a passed slice.
/// \brief Fills the destination with data decoded from hexadecimal dump from a passed slice.
/// \throws std::length_error if given buffer is too small.
char *write_bytes(char *dest, size_t dest_size) const;
/// \brief Checks whether a passed slice is empty,
/// and therefore there will be no output bytes.
/// \brief Checks whether a passed slice is empty, and therefore there will be no output bytes.
bool is_empty() const noexcept { return source.empty(); }
/// \brief Checks whether the content of a passed slice is a valid hexadecimal
@ -1407,8 +1403,7 @@ struct LIBMDBX_API from_base58 {
}
/// \brief Returns the number of bytes needed for conversion
/// [Base58](https://en.wikipedia.org/wiki/Base58) dump from a passed slice to
/// decoded data.
/// [Base58](https://en.wikipedia.org/wiki/Base58) dump from a passed slice to decoded data.
MDBX_CXX11_CONSTEXPR size_t envisage_result_length() const noexcept {
return source.length() /* могут быть все нули кодируемые один-к-одному */;
}
@ -1418,13 +1413,11 @@ struct LIBMDBX_API from_base58 {
/// \throws std::length_error if given buffer is too small.
char *write_bytes(char *dest, size_t dest_size) const;
/// \brief Checks whether a passed slice is empty,
/// and therefore there will be no output bytes.
/// \brief Checks whether a passed slice is empty, and therefore there will be no output bytes.
bool is_empty() const noexcept { return source.empty(); }
/// \brief Checks whether the content of a passed slice is a valid
/// [Base58](https://en.wikipedia.org/wiki/Base58) dump, and therefore there
/// could be decoded or not.
/// [Base58](https://en.wikipedia.org/wiki/Base58) dump, and therefore there could be decoded or not.
bool is_erroneous() const noexcept;
};
@ -1453,8 +1446,7 @@ struct LIBMDBX_API from_base64 {
}
/// \brief Returns the number of bytes needed for conversion
/// [Base64](https://en.wikipedia.org/wiki/Base64) dump from a passed slice to
/// decoded data.
/// [Base64](https://en.wikipedia.org/wiki/Base64) dump from a passed slice to decoded data.
MDBX_CXX11_CONSTEXPR size_t envisage_result_length() const noexcept { return (source.length() + 3) / 4 * 3; }
/// \brief Fills the destination with data decoded from
@ -1486,13 +1478,16 @@ public:
max_length = MDBX_MAXDATASIZE,
max_capacity = (max_length / 3u * 4u + 1023u) & ~size_t(1023),
extra_inplace_storage = reservation_policy::extra_inplace_storage,
inplace_storage_size_rounding =
(alignof(max_align_t) * 2 > size_t(reservation_policy::inplace_storage_size_rounding))
? alignof(max_align_t) * 2
: size_t(reservation_policy::inplace_storage_size_rounding),
pettiness_threshold = reservation_policy::pettiness_threshold
};
private:
friend class txn;
struct silo;
using swap_alloc = allocation_aware_details::swap_alloc<silo, allocator_type>;
using swap_alloc = allocation_aware_details::swap_alloc<struct silo, allocator_type>;
struct silo /* Empty Base Class Optimization */ : public allocator_type {
MDBX_CXX20_CONSTEXPR const allocator_type &get_allocator() const noexcept { return *this; }
MDBX_CXX20_CONSTEXPR allocator_type &get_allocator() noexcept { return *this; }
@ -1529,41 +1524,51 @@ private:
#endif /* __cpp_lib_to_address */
}
union bin {
struct allocated {
union alignas(max_align_t) bin {
struct stub_allocated_holder /* используется только для вычисления (минимального необходимого) размера,
с учетом выравнивания */
{
allocator_pointer ptr_;
size_t capacity_bytes_;
constexpr allocated(allocator_pointer ptr, size_t bytes) noexcept : ptr_(ptr), capacity_bytes_(bytes) {}
constexpr allocated(const allocated &) noexcept = default;
constexpr allocated(allocated &&) noexcept = default;
MDBX_CXX17_CONSTEXPR allocated &operator=(const allocated &) noexcept = default;
MDBX_CXX17_CONSTEXPR allocated &operator=(allocated &&) noexcept = default;
size_t stub_capacity_bytes_;
};
allocated allocated_;
uint64_t align_hint_;
byte inplace_[(sizeof(allocated) + extra_inplace_storage + 7u) & ~size_t(7)];
static constexpr bool is_suitable_for_inplace(size_t capacity_bytes) noexcept {
static_assert(sizeof(bin) == sizeof(inplace_), "WTF?");
return capacity_bytes < sizeof(bin);
}
enum : byte { lastbyte_inplace_signature = byte(~byte(0)) };
enum : byte { lastbyte_poison = 0, lastbyte_inplace_signature = byte(~byte(lastbyte_poison)) };
enum : size_t {
inplace_signature_limit = size_t(lastbyte_inplace_signature)
<< (sizeof(size_t /* allocated::capacity_bytes_ */) - 1) * CHAR_BIT
<< (sizeof(size_t /* allocated::capacity_bytes_ */) - 1) * CHAR_BIT,
inplace_size_rounding = size_t(inplace_storage_size_rounding) - 1,
inplace_size =
(sizeof(stub_allocated_holder) + extra_inplace_storage + inplace_size_rounding) & ~inplace_size_rounding
};
constexpr byte inplace_lastbyte() const noexcept { return inplace_[sizeof(bin) - 1]; }
MDBX_CXX17_CONSTEXPR byte &inplace_lastbyte() noexcept { return inplace_[sizeof(bin) - 1]; }
struct capacity_holder {
byte pad_[inplace_size - sizeof(allocator_pointer)];
size_t bytes_;
};
struct inplace_flag_holder {
byte buffer_[inplace_size - sizeof(byte)];
byte lastbyte_;
};
allocator_pointer allocated_ptr_;
capacity_holder capacity_;
inplace_flag_holder inplace_;
static constexpr bool is_suitable_for_inplace(size_t capacity_bytes) noexcept {
static_assert((size_t(reservation_policy::inplace_storage_size_rounding) &
(size_t(reservation_policy::inplace_storage_size_rounding) - 1)) == 0,
"CAPACITY_POLICY::inplace_storage_size_rounding must be power of 2");
static_assert(sizeof(bin) == sizeof(inplace_) && sizeof(bin) == sizeof(capacity_), "WTF?");
return capacity_bytes < sizeof(bin);
}
constexpr bool is_inplace() const noexcept {
static_assert(size_t(inplace_signature_limit) > size_t(max_capacity), "WTF?");
static_assert(std::numeric_limits<size_t>::max() - (std::numeric_limits<size_t>::max() >> CHAR_BIT) ==
inplace_signature_limit,
"WTF?");
return inplace_lastbyte() == lastbyte_inplace_signature;
return inplace_.lastbyte_ == lastbyte_inplace_signature;
}
constexpr bool is_allocated() const noexcept { return !is_inplace(); }
@ -1571,26 +1576,27 @@ private:
if (destroy_ptr) {
MDBX_CONSTEXPR_ASSERT(is_allocated());
/* properly destroy allocator::pointer */
allocated_.~allocated();
allocated_ptr_.~allocator_pointer();
}
if (::std::is_trivial<allocator_pointer>::value)
/* workaround for "uninitialized" warning from some compilers */
memset(&allocated_.ptr_, 0, sizeof(allocated_.ptr_));
inplace_lastbyte() = lastbyte_inplace_signature;
MDBX_CONSTEXPR_ASSERT(is_inplace() && address() == inplace_ && is_suitable_for_inplace(capacity()));
memset(&allocated_ptr_, 0, sizeof(allocated_ptr_));
inplace_.lastbyte_ = lastbyte_inplace_signature;
MDBX_CONSTEXPR_ASSERT(is_inplace() && address() == inplace_.buffer_ && is_suitable_for_inplace(capacity()));
return address();
}
template <bool construct_ptr>
MDBX_CXX17_CONSTEXPR byte *make_allocated(allocator_pointer ptr, size_t capacity_bytes) noexcept {
MDBX_CONSTEXPR_ASSERT(inplace_signature_limit > capacity_bytes);
if (construct_ptr)
if (construct_ptr) {
/* properly construct allocator::pointer */
new (&allocated_) allocated(ptr, capacity_bytes);
else {
new (&allocated_ptr_) allocator_pointer(ptr);
capacity_.bytes_ = capacity_bytes;
} else {
MDBX_CONSTEXPR_ASSERT(is_allocated());
allocated_.ptr_ = ptr;
allocated_.capacity_bytes_ = capacity_bytes;
allocated_ptr_ = ptr;
capacity_.bytes_ = capacity_bytes;
}
MDBX_CONSTEXPR_ASSERT(is_allocated() && address() == to_address(ptr) && capacity() == capacity_bytes);
return address();
@ -1608,16 +1614,17 @@ private:
MDBX_CXX20_CONSTEXPR ~bin() {
if (is_allocated())
/* properly destroy allocator::pointer */
allocated_.~allocated();
allocated_ptr_.~allocator_pointer();
}
MDBX_CXX20_CONSTEXPR bin(bin &&ditto) noexcept {
if (ditto.is_inplace()) {
// micro-optimization: don't use make_inplace<> here
// since memcpy() will copy the flag.
memcpy(inplace_, ditto.inplace_, sizeof(inplace_));
memcpy(&inplace_, &ditto.inplace_, sizeof(inplace_));
MDBX_CONSTEXPR_ASSERT(is_inplace());
} else {
new (&allocated_) allocated(::std::move(ditto.allocated_));
new (&allocated_ptr_) allocator_pointer(::std::move(ditto.allocated_ptr_));
capacity_.bytes_ = ditto.capacity_.bytes_;
ditto.make_inplace<true>();
MDBX_CONSTEXPR_ASSERT(is_allocated());
}
@ -1629,13 +1636,13 @@ private:
// since memcpy() will copy the flag.
if (is_allocated())
/* properly destroy allocator::pointer */
allocated_.~allocated();
memcpy(inplace_, ditto.inplace_, sizeof(inplace_));
allocated_ptr_.~allocator_pointer();
memcpy(&inplace_, &ditto.inplace_, sizeof(inplace_));
MDBX_CONSTEXPR_ASSERT(is_inplace());
} else if (is_inplace())
make_allocated<true>(ditto.allocated_.ptr_, ditto.allocated_.capacity_bytes_);
make_allocated<true>(ditto.allocated_ptr_, ditto.capacity_.bytes_);
else
make_allocated<false>(ditto.allocated_.ptr_, ditto.allocated_.capacity_bytes_);
make_allocated<false>(ditto.allocated_ptr_, ditto.capacity_.bytes_);
return *this;
}
@ -1656,12 +1663,12 @@ private:
}
constexpr const byte *address() const noexcept {
return is_inplace() ? inplace_ : static_cast<const byte *>(to_address(allocated_.ptr_));
return is_inplace() ? inplace_.buffer_ : static_cast<const byte *>(to_address(allocated_ptr_));
}
MDBX_CXX17_CONSTEXPR byte *address() noexcept {
return is_inplace() ? inplace_ : static_cast<byte *>(to_address(allocated_.ptr_));
return is_inplace() ? inplace_.buffer_ : static_cast<byte *>(to_address(allocated_ptr_));
}
constexpr size_t capacity() const noexcept { return is_inplace() ? sizeof(bin) - 1 : allocated_.capacity_bytes_; }
constexpr size_t capacity() const noexcept { return is_inplace() ? sizeof(bin) - 1 : capacity_.bytes_; }
} bin_;
MDBX_CXX20_CONSTEXPR void *init(size_t capacity) {
@ -1678,7 +1685,7 @@ private:
MDBX_CXX20_CONSTEXPR void release() noexcept {
if (bin_.is_allocated()) {
deallocate_storage(bin_.allocated_.ptr_, bin_.allocated_.capacity_bytes_);
deallocate_storage(bin_.allocated_ptr_, bin_.capacity_.bytes_);
bin_.template make_inplace<true>();
}
}
@ -1709,7 +1716,7 @@ private:
if (bin::is_suitable_for_inplace(new_capacity)) {
assert(bin_.is_allocated());
const auto old_allocated = ::std::move(bin_.allocated_.ptr_);
const auto old_allocated = ::std::move(bin_.allocated_ptr_);
byte *const new_place = bin_.template make_inplace<true>() + wanna_headroom;
if (MDBX_LIKELY(length))
MDBX_CXX20_LIKELY memcpy(new_place, content, length);
@ -1727,7 +1734,7 @@ private:
return new_place;
}
const auto old_allocated = ::std::move(bin_.allocated_.ptr_);
const auto old_allocated = ::std::move(bin_.allocated_ptr_);
if (external_content)
deallocate_storage(old_allocated, old_capacity);
const auto pair = allocate_storage(new_capacity);
@ -1906,8 +1913,7 @@ public:
/// the buffer, rather than stores it.
MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX20_CONSTEXPR bool is_reference() const noexcept { return !is_freestanding(); }
/// \brief Returns the number of bytes that can be held in currently allocated
/// storage.
/// \brief Returns the number of bytes that can be held in currently allocated storage.
MDBX_NOTHROW_PURE_FUNCTION MDBX_CXX20_CONSTEXPR size_t capacity() const noexcept {
return is_freestanding() ? silo_.capacity() : 0;
}
@ -1931,16 +1937,14 @@ public:
MDBX_CXX11_CONSTEXPR const byte *end_byte_ptr() const noexcept { return slice_.end_byte_ptr(); }
/// \brief Returns casted to pointer to byte an address of data.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to
/// an external one.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to an external one.
MDBX_CXX11_CONSTEXPR byte *byte_ptr() noexcept {
MDBX_CONSTEXPR_ASSERT(is_freestanding());
return const_cast<byte *>(slice_.byte_ptr());
}
/// \brief Returns casted to pointer to byte an end of data.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to
/// an external one.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to an external one.
MDBX_CXX11_CONSTEXPR byte *end_byte_ptr() noexcept {
MDBX_CONSTEXPR_ASSERT(is_freestanding());
return const_cast<byte *>(slice_.end_byte_ptr());
@ -1953,16 +1957,14 @@ public:
MDBX_CXX11_CONSTEXPR const char *end_char_ptr() const noexcept { return slice_.end_char_ptr(); }
/// \brief Returns casted to pointer to char an address of data.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to
/// an external one.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to an external one.
MDBX_CXX11_CONSTEXPR char *char_ptr() noexcept {
MDBX_CONSTEXPR_ASSERT(is_freestanding());
return const_cast<char *>(slice_.char_ptr());
}
/// \brief Returns casted to pointer to char an end of data.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to
/// an external one.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to an external one.
MDBX_CXX11_CONSTEXPR char *end_char_ptr() noexcept {
MDBX_CONSTEXPR_ASSERT(is_freestanding());
return const_cast<char *>(slice_.end_char_ptr());
@ -1975,16 +1977,14 @@ public:
MDBX_CXX11_CONSTEXPR const void *end() const noexcept { return slice_.end(); }
/// \brief Return a pointer to the beginning of the referenced data.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to
/// an external one.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to an external one.
MDBX_CXX11_CONSTEXPR void *data() noexcept {
MDBX_CONSTEXPR_ASSERT(is_freestanding());
return const_cast<void *>(slice_.data());
}
/// \brief Return a pointer to the end of the referenced data.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to
/// an external one.
/// \pre REQUIRES: The buffer should store data chunk, but not referenced to an external one.
MDBX_CXX11_CONSTEXPR void *end() noexcept {
MDBX_CONSTEXPR_ASSERT(is_freestanding());
return const_cast<void *>(slice_.end());
@ -2708,8 +2708,13 @@ inline string<ALLOCATOR> make_string(const PRODUCER &producer, const ALLOCATOR &
return result;
}
/// \brief Combines data slice with boolean flag to represent result of certain
/// operations.
MDBX_EXTERN_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<legacy_allocator>);
#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI
MDBX_EXTERN_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<polymorphic_allocator>);
#endif /* __cpp_lib_memory_resource >= 201603L */
/// \brief Combines data slice with boolean flag to represent result of certain operations.
struct value_result {
slice value;
bool done;
@ -2722,8 +2727,7 @@ struct value_result {
}
};
/// \brief Combines pair of slices for key and value to represent result of
/// certain operations.
/// \brief Combines pair of slices for key and value to represent result of certain operations.
struct pair {
using stl_pair = std::pair<slice, slice>;
slice key, value;
@ -2833,9 +2837,13 @@ template <typename ALLOCATOR, typename CAPACITY_POLICY> struct buffer_pair_spec
operator pair() const noexcept { return pair(key, value); }
};
/// \brief Combines pair of buffers for key and value to hold an operands for certain operations.
template <typename BUFFER>
using buffer_pair = buffer_pair_spec<typename BUFFER::allocator_type, typename BUFFER::reservation_policy>;
/// \brief Default pair of buffers.
using default_buffer_pair = buffer_pair<default_buffer>;
/// end of cxx_data @}
//------------------------------------------------------------------------------
@ -2879,8 +2887,7 @@ MDBX_CXX01_CONSTEXPR_ENUM bool is_reverse(key_mode mode) noexcept {
MDBX_CXX01_CONSTEXPR_ENUM bool is_msgpack(key_mode mode) noexcept { return mode == key_mode::msgpack; }
/// \brief Kind of the values and sorted multi-values with corresponding
/// comparison.
/// \brief Kind of the values and sorted multi-values with corresponding comparison.
enum class value_mode {
single = MDBX_DB_DEFAULTS, ///< Usual single value for each key. In terms of
///< keys, they are unique.
@ -2960,8 +2967,7 @@ MDBX_CXX01_CONSTEXPR_ENUM bool is_reverse(value_mode mode) noexcept {
MDBX_CXX01_CONSTEXPR_ENUM bool is_msgpack(value_mode mode) noexcept { return mode == value_mode::msgpack; }
/// \brief A handle for an individual table (aka key-value space, maps or
/// sub-database) in the environment.
/// \brief A handle for an individual table (aka key-value space, maps or sub-database) in the environment.
/// \see txn::open_map() \see txn::create_map()
/// \see txn::clear_map() \see txn::drop_map()
/// \see txn::get_handle_info() \see txn::get_map_stat()
@ -3090,12 +3096,10 @@ public:
/// environment).
intptr_t size_upper{default_value};
/// \brief The growth step in bytes, must be greater than zero to allow the
/// database to grow.
/// \brief The growth step in bytes, must be greater than zero to allow the database to grow.
intptr_t growth_step{default_value};
/// \brief The shrink threshold in bytes, must be greater than zero to allow
/// the database to shrink.
/// \brief The shrink threshold in bytes, must be greater than zero to allow the database to shrink.
intptr_t shrink_threshold{default_value};
/// \brief The database page size for new database creation
@ -3230,47 +3234,34 @@ public:
static inline size_t pagesize_min() noexcept;
/// \brief Returns the maximal database page size in bytes.
static inline size_t pagesize_max() noexcept;
/// \brief Returns the minimal database size in bytes for specified page
/// size.
/// \brief Returns the minimal database size in bytes for specified page size.
static inline size_t dbsize_min(intptr_t pagesize);
/// \brief Returns the maximal database size in bytes for specified page
/// size.
/// \brief Returns the maximal database size in bytes for specified page size.
static inline size_t dbsize_max(intptr_t pagesize);
/// \brief Returns the minimal key size in bytes for specified table
/// flags.
/// \brief Returns the minimal key size in bytes for specified table flags.
static inline size_t key_min(MDBX_db_flags_t flags) noexcept;
/// \brief Returns the minimal key size in bytes for specified keys mode.
static inline size_t key_min(key_mode mode) noexcept;
/// \brief Returns the maximal key size in bytes for specified page size and
/// table flags.
/// \brief Returns the maximal key size in bytes for specified page size and table flags.
static inline size_t key_max(intptr_t pagesize, MDBX_db_flags_t flags);
/// \brief Returns the maximal key size in bytes for specified page size and
/// keys mode.
/// \brief Returns the maximal key size in bytes for specified page size and keys mode.
static inline size_t key_max(intptr_t pagesize, key_mode mode);
/// \brief Returns the maximal key size in bytes for given environment and
/// table flags.
/// \brief Returns the maximal key size in bytes for given environment and table flags.
static inline size_t key_max(const env &, MDBX_db_flags_t flags);
/// \brief Returns the maximal key size in bytes for given environment and
/// keys mode.
/// \brief Returns the maximal key size in bytes for given environment and keys mode.
static inline size_t key_max(const env &, key_mode mode);
/// \brief Returns the minimal values size in bytes for specified table
/// flags.
/// \brief Returns the minimal values size in bytes for specified table flags.
static inline size_t value_min(MDBX_db_flags_t flags) noexcept;
/// \brief Returns the minimal values size in bytes for specified values
/// mode.
/// \brief Returns the minimal values size in bytes for specified values mode.
static inline size_t value_min(value_mode) noexcept;
/// \brief Returns the maximal value size in bytes for specified page size
/// and table flags.
/// \brief Returns the maximal value size in bytes for specified page size and table flags.
static inline size_t value_max(intptr_t pagesize, MDBX_db_flags_t flags);
/// \brief Returns the maximal value size in bytes for specified page size
/// and values mode.
/// \brief Returns the maximal value size in bytes for specified page size and values mode.
static inline size_t value_max(intptr_t pagesize, value_mode);
/// \brief Returns the maximal value size in bytes for given environment and
/// table flags.
/// \brief Returns the maximal value size in bytes for given environment and table flags.
static inline size_t value_max(const env &, MDBX_db_flags_t flags);
/// \brief Returns the maximal value size in bytes for specified page size
/// and values mode.
/// \brief Returns the maximal value size in bytes for specified page size and values mode.
static inline size_t value_max(const env &, value_mode);
/// \brief Returns maximal size of key-value pair to fit in a single page
@ -3350,13 +3341,11 @@ public:
/// \brief Make sure that the environment is not being used by other
/// processes, or return an error otherwise.
ensure_unused = MDBX_ENV_ENSURE_UNUSED,
/// \brief Wait until other processes closes the environment before
/// deletion.
/// \brief Wait until other processes closes the environment before deletion.
wait_for_unused = MDBX_ENV_WAIT_FOR_UNUSED
};
/// \brief Removes the environment's files in a proper and multiprocess-safe
/// way.
/// \brief Removes the environment's files in a proper and multiprocess-safe way.
#ifdef MDBX_STD_FILESYSTEM_PATH
static bool remove(const MDBX_STD_FILESYSTEM_PATH &pathname, const remove_mode mode = just_remove);
#endif /* MDBX_STD_FILESYSTEM_PATH */
@ -3382,12 +3371,10 @@ public:
/// \brief Return snapshot information about the MDBX environment.
inline info get_info() const;
/// \brief Return statistics about the MDBX environment accordingly to the
/// specified transaction.
/// \brief Return statistics about the MDBX environment accordingly to the specified transaction.
inline stat get_stat(const txn &) const;
/// \brief Return information about the MDBX environment accordingly to the
/// specified transaction.
/// \brief Return information about the MDBX environment accordingly to the specified transaction.
inline info get_info(const txn &) const;
/// \brief Returns the file descriptor for the DXB file of MDBX environment.
@ -3399,8 +3386,7 @@ public:
/// Returns environment flags.
inline MDBX_env_flags_t get_flags() const;
/// \brief Returns the maximum number of threads/reader slots for the
/// environment.
/// \brief Returns the maximum number of threads/reader slots for the environment.
/// \see extra_runtime_option::max_readers
inline unsigned max_readers() const;
@ -3784,8 +3770,7 @@ public:
/// volume of dirty pages) in bytes.
size_t size_max() const { return env().transaction_size_max(); }
/// \brief Returns current write transaction size (i.e.summary volume of dirty
/// pages) in bytes.
/// \brief Returns current write transaction size (i.e.summary volume of dirty pages) in bytes.
size_t size_current() const {
assert(is_readwrite());
return size_t(get_info().txn_space_dirty);
@ -3944,42 +3929,32 @@ public:
inline map_handle::info get_handle_info(map_handle map) const;
using canary = ::MDBX_canary;
/// \brief Set integers markers (aka "canary") associated with the
/// environment.
/// \brief Set integers markers (aka "canary") associated with the environment.
inline txn &put_canary(const canary &);
/// \brief Returns fours integers markers (aka "canary") associated with the
/// environment.
/// \brief Returns fours integers markers (aka "canary") associated with the environment.
inline canary get_canary() const;
/// Reads sequence generator associated with a key-value map (aka
/// table).
/// Reads sequence generator associated with a key-value map (aka table).
inline uint64_t sequence(map_handle map) const;
/// \brief Reads and increment sequence generator associated with a key-value
/// map (aka table).
/// \brief Reads and increment sequence generator associated with a key-value map (aka table).
inline uint64_t sequence(map_handle map, uint64_t increment);
/// \brief Compare two keys according to a particular key-value map (aka
/// table).
/// \brief Compare two keys according to a particular key-value map (aka table).
inline int compare_keys(map_handle map, const slice &a, const slice &b) const noexcept;
/// \brief Compare two values according to a particular key-value map (aka
/// table).
/// \brief Compare two values according to a particular key-value map (aka table).
inline int compare_values(map_handle map, const slice &a, const slice &b) const noexcept;
/// \brief Compare keys of two pairs according to a particular key-value map
/// (aka table).
/// \brief Compare keys of two pairs according to a particular key-value map (aka table).
inline int compare_keys(map_handle map, const pair &a, const pair &b) const noexcept;
/// \brief Compare values of two pairs according to a particular key-value map
/// (aka table).
/// \brief Compare values of two pairs according to a particular key-value map(aka table).
inline int compare_values(map_handle map, const pair &a, const pair &b) const noexcept;
/// \brief Get value by key from a key-value map (aka table).
inline slice get(map_handle map, const slice &key) const;
/// \brief Get first of multi-value and values count by key from a key-value
/// multimap (aka table).
/// \brief Get first of multi-value and values count by key from a key-value multimap (aka table).
inline slice get(map_handle map, slice key, size_t &values_count) const;
/// \brief Get value by key from a key-value map (aka table).
inline slice get(map_handle map, const slice &key, const slice &value_at_absence) const;
/// \brief Get first of multi-value and values count by key from a key-value
/// multimap (aka table).
/// \brief Get first of multi-value and values count by key from a key-value multimap (aka table).
inline slice get(map_handle map, slice key, size_t &values_count, const slice &value_at_absence) const;
/// \brief Get value for equal or great key from a table.
/// \return Bundle of key-value pair and boolean flag,
@ -4060,8 +4035,9 @@ public:
return append(map, kv.key, kv.value, multivalue_order_preserved);
}
size_t put_multiple_samelength(map_handle map, const slice &key, const size_t value_length, const void *values_array,
size_t values_count, put_mode mode, bool allow_partial = false);
inline size_t put_multiple_samelength(map_handle map, const slice &key, const size_t value_length,
const void *values_array, size_t values_count, put_mode mode,
bool allow_partial = false);
template <typename VALUE>
size_t put_multiple_samelength(map_handle map, const slice &key, const VALUE *values_array, size_t values_count,
put_mode mode, bool allow_partial = false) {
@ -4155,9 +4131,9 @@ public:
class LIBMDBX_API_TYPE cursor {
protected:
MDBX_cursor *handle_{nullptr};
MDBX_CXX11_CONSTEXPR cursor(MDBX_cursor *ptr) noexcept;
public:
MDBX_CXX11_CONSTEXPR cursor(MDBX_cursor *ptr) noexcept;
MDBX_CXX11_CONSTEXPR cursor() noexcept = default;
cursor(const cursor &) noexcept = default;
inline cursor &operator=(cursor &&other) noexcept;
@ -4244,9 +4220,14 @@ public:
batch_samelength = MDBX_GET_MULTIPLE,
batch_samelength_next = MDBX_NEXT_MULTIPLE,
batch_samelength_previous = MDBX_PREV_MULTIPLE
batch_samelength_previous = MDBX_PREV_MULTIPLE,
seek_and_batch_samelength = MDBX_SEEK_AND_GET_MULTIPLE
};
// TODO: добавить легковесный proxy-класс для замещения параметра throw_notfound более сложным набором опций,
// в том числе с explicit-конструктором из bool, чтобы защититься от неявной конвертации ключей поиска
// и других параметров в bool-throw_notfound.
struct move_result : public pair_result {
inline move_result(const cursor &cursor, bool throw_notfound);
move_result(cursor &cursor, move_operation operation, bool throw_notfound)
@ -4444,8 +4425,8 @@ public:
inline move_result lower_bound_multivalue(const slice &key, const slice &value, bool throw_notfound = false);
inline move_result upper_bound_multivalue(const slice &key, const slice &value, bool throw_notfound = false);
inline move_result get_multiple_samelength(const slice &key, bool throw_notfound = true) {
return move(batch_samelength, key, throw_notfound);
inline move_result seek_multiple_samelength(const slice &key, bool throw_notfound = true) {
return move(seek_and_batch_samelength, key, throw_notfound);
}
inline move_result get_multiple_samelength(bool throw_notfound = false) {
@ -4472,13 +4453,11 @@ public:
//----------------------------------------------------------------------------
/// \brief Renew/bind a cursor with a new transaction and previously used
/// key-value map handle.
inline void renew(const ::mdbx::txn &txn);
/// \brief Renew/bind a cursor with a new transaction and previously used key-value map handle.
inline void renew(::mdbx::txn &txn);
/// \brief Bind/renew a cursor with a new transaction and specified key-value
/// map handle.
inline void bind(const ::mdbx::txn &txn, ::mdbx::map_handle map_handle);
/// \brief Bind/renew a cursor with a new transaction and specified key-value map handle.
inline void bind(::mdbx::txn &txn, ::mdbx::map_handle map_handle);
/// \brief Unbind cursor from a transaction.
inline void unbind();
@ -4510,18 +4489,31 @@ public:
value_result try_insert(const pair &kv) { return try_insert(kv.key, kv.value); }
void upsert(const pair &kv) { return upsert(kv.key, kv.value); }
/// \brief Removes single key-value pair or all multi-values at the current
/// cursor position.
/// \brief Removes single key-value pair or all multi-values at the current cursor position.
inline bool erase(bool whole_multivalue = false);
/// \brief Seeks and removes first value or whole multi-value of the given
/// key.
/// \brief Seeks and removes first value or whole multi-value of the given key.
/// \return `True` if the key is found and a value(s) is removed.
inline bool erase(const slice &key, bool whole_multivalue = true);
/// \brief Seeks and removes the particular multi-value entry of the key.
/// \return `True` if the given key-value pair is found and removed.
inline bool erase(const slice &key, const slice &value);
inline size_t put_multiple_samelength(const slice &key, const size_t value_length, const void *values_array,
size_t values_count, put_mode mode, bool allow_partial = false);
template <typename VALUE>
size_t put_multiple_samelength(const slice &key, const VALUE *values_array, size_t values_count, put_mode mode,
bool allow_partial = false) {
static_assert(::std::is_standard_layout<VALUE>::value && !::std::is_pointer<VALUE>::value &&
!::std::is_array<VALUE>::value,
"Must be a standard layout type!");
return put_multiple_samelength(key, sizeof(VALUE), values_array, values_count, mode, allow_partial);
}
template <typename VALUE>
void put_multiple_samelength(const slice &key, const ::std::vector<VALUE> &vector, put_mode mode) {
put_multiple_samelength(key, vector.data(), vector.size(), mode);
}
};
/// \brief Managed cursor.
@ -4545,7 +4537,10 @@ public:
}
/// \brief Explicitly closes the cursor.
void close();
inline void close() {
error::success_or_throw(::mdbx_cursor_close2(handle_));
handle_ = nullptr;
}
cursor_managed(cursor_managed &&) = default;
cursor_managed &operator=(cursor_managed &&other) noexcept {
@ -4558,6 +4553,12 @@ public:
return *this;
}
inline MDBX_cursor *withdraw_handle() noexcept {
MDBX_cursor *handle = handle_;
handle_ = nullptr;
return handle;
}
cursor_managed(const cursor_managed &) = delete;
cursor_managed &operator=(const cursor_managed &) = delete;
~cursor_managed() noexcept { ::mdbx_cursor_close(handle_); }
@ -5604,10 +5605,9 @@ inline cursor_managed txn::open_cursor(map_handle map) const {
}
inline size_t txn::release_all_cursors(bool unbind) const {
int err = ::mdbx_txn_release_all_cursors(handle_, unbind);
if (MDBX_UNLIKELY(err < 0))
MDBX_CXX20_UNLIKELY error::throw_exception(err);
return size_t(err);
size_t count;
error::success_or_throw(::mdbx_txn_release_all_cursors_ex(handle_, unbind, &count));
return count;
}
inline ::mdbx::map_handle txn::open_map(const ::mdbx::slice &name, const ::mdbx::key_mode key_mode,
@ -6167,9 +6167,9 @@ inline cursor::estimate_result cursor::estimate(move_operation operation) const
return estimate_result(*this, operation);
}
inline void cursor::renew(const ::mdbx::txn &txn) { error::success_or_throw(::mdbx_cursor_renew(txn, handle_)); }
inline void cursor::renew(::mdbx::txn &txn) { error::success_or_throw(::mdbx_cursor_renew(txn, handle_)); }
inline void cursor::bind(const ::mdbx::txn &txn, ::mdbx::map_handle map_handle) {
inline void cursor::bind(::mdbx::txn &txn, ::mdbx::map_handle map_handle) {
error::success_or_throw(::mdbx_cursor_bind(txn, handle_, map_handle.dbi));
}
@ -6177,7 +6177,6 @@ inline void cursor::unbind() { error::success_or_throw(::mdbx_cursor_unbind(hand
inline txn cursor::txn() const {
MDBX_txn *txn = ::mdbx_cursor_txn(handle_);
error::throw_on_nullptr(txn, MDBX_EINVAL);
return ::mdbx::txn(txn);
}
@ -6302,6 +6301,24 @@ inline bool cursor::erase(const slice &key, const slice &value) {
return data.done && erase();
}
inline size_t cursor::put_multiple_samelength(const slice &key, const size_t value_length, const void *values_array,
size_t values_count, put_mode mode, bool allow_partial) {
MDBX_val args[2] = {{const_cast<void *>(values_array), value_length}, {nullptr, values_count}};
const int err = ::mdbx_cursor_put(handle_, const_cast<slice *>(&key), args, MDBX_put_flags_t(mode) | MDBX_MULTIPLE);
switch (err) {
case MDBX_SUCCESS:
MDBX_CXX20_LIKELY break;
case MDBX_KEYEXIST:
if (allow_partial)
break;
mdbx_txn_break(txn());
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
default:
MDBX_CXX20_UNLIKELY error::throw_exception(err);
}
return args[1].iov_len /* done item count */;
}
/// end cxx_api @}
} // namespace mdbx

View File

@ -1,7 +1,7 @@
From 0ba6ba5e6d6311213a21f033729e18826729230a Mon Sep 17 00:00:00 2001
From 49256dcd050fd0ee67860b7bc544dabe088d08e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=D0=AE=D1=80=D1=8C?=
=?UTF-8?q?=D0=B5=D0=B2=20=28Leonid=20Yuriev=29?= <leo@yuriev.ru>
Date: Tue, 14 Jan 2025 12:57:03 +0300
Date: Fri, 14 Feb 2025 21:34:25 +0300
Subject: [PATCH] package/libmdbx: new package (library/database).
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
@ -13,11 +13,9 @@ This patch adds libmdbx:
focused on creating unique lightweight solutions.
- libmdbx surpasses the legendary LMDB (Lightning Memory-Mapped Database)
in terms of reliability, features and performance.
- more information at https://gitflic.ru/project/erthink/libmdbx
- more information at https://libmdbx.dqdkfa.ru
The 0.13.13 "Korolev" is stable release of _libmdbx_ branch with new superior features
on the birthday and in memory of Sergei Korolev who was the lead Soviet rocket
engineer and spacecraft designer.
The 0.13.4 "Sigma Boy" is stable release of _libmdbx_ branch with new superior features.
The complete ChangeLog: https://gitflic.ru/project/erthink/libmdbx/blob?file=ChangeLog.md
@ -26,9 +24,9 @@ Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
DEVELOPERS | 3 +++
package/Config.in | 1 +
package/libmdbx/Config.in | 45 ++++++++++++++++++++++++++++++++++++
package/libmdbx/libmdbx.hash | 5 ++++
package/libmdbx/libmdbx.hash | 6 +++++
package/libmdbx/libmdbx.mk | 42 +++++++++++++++++++++++++++++++++
5 files changed, 96 insertions(+)
5 files changed, 97 insertions(+)
create mode 100644 package/libmdbx/Config.in
create mode 100644 package/libmdbx/libmdbx.hash
create mode 100644 package/libmdbx/libmdbx.mk
@ -112,18 +110,19 @@ index 0000000000..a9a4ac45c5
+ !BR2_TOOLCHAIN_GCC_AT_LEAST_4_4
diff --git a/package/libmdbx/libmdbx.hash b/package/libmdbx/libmdbx.hash
new file mode 100644
index 0000000000..1c91aa2f90
index 0000000000..202937e7be
--- /dev/null
+++ b/package/libmdbx/libmdbx.hash
@@ -0,0 +1,5 @@
@@ -0,0 +1,6 @@
+# Hashes from: https://libmdbx.dqdkfa.ru/release/SHA256SUMS
+sha256 2e42505f1ceb57945569db3c1a5db9b5216d8f72da7c75c240ff81196f8e9a0b libmdbx-amalgamated-0.13.3.tar.xz
+sha256 86df30ca2231c9b3ad71424bb829dca9041947f5539d4295030c653d4982c1be libmdbx-amalgamated-0.13.4.tar.xz
+
+# Locally calculated
+sha256 0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594 LICENSE
+sha256 699a62986b6c8d31124646dffd4b15872c7d3bc5eecea5994edb1f5195df49d1 NOTICE
diff --git a/package/libmdbx/libmdbx.mk b/package/libmdbx/libmdbx.mk
new file mode 100644
index 0000000000..b42ab629fe
index 0000000000..a8a6f3dbdf
--- /dev/null
+++ b/package/libmdbx/libmdbx.mk
@@ -0,0 +1,42 @@
@ -133,12 +132,12 @@ index 0000000000..b42ab629fe
+#
+################################################################################
+
+LIBMDBX_VERSION = 0.13.3
+LIBMDBX_VERSION = 0.13.4
+LIBMDBX_SOURCE = libmdbx-amalgamated-$(LIBMDBX_VERSION).tar.xz
+LIBMDBX_SITE = https://libmdbx.dqdkfa.ru/release
+LIBMDBX_SUPPORTS_IN_SOURCE_BUILD = NO
+LIBMDBX_LICENSE = OLDAP-2.8
+LIBMDBX_LICENSE_FILES = LICENSE
+LIBMDBX_LICENSE = Apache-2.0
+LIBMDBX_LICENSE_FILES = LICENSE NOTICE
+LIBMDBX_REDISTRIBUTE = YES
+LIBMDBX_STRIP_COMPONENTS = 0
+LIBMDBX_INSTALL_STAGING = YES
@ -170,5 +169,5 @@ index 0000000000..b42ab629fe
+
+$(eval $(cmake-package))
--
2.48.0
2.48.1

View File

@ -141,7 +141,7 @@ __cold int mdbx_env_warmup(const MDBX_env *env, const MDBX_txn *txn, MDBX_warmup
return LOG_IFERR(MDBX_EINVAL);
if (txn) {
int err = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_ERROR);
int err = check_txn(txn, MDBX_TXN_FINISHED | MDBX_TXN_ERROR);
if (unlikely(err != MDBX_SUCCESS))
return LOG_IFERR(err);
}
@ -342,7 +342,7 @@ __cold int mdbx_env_set_flags(MDBX_env *env, MDBX_env_flags_t flags, bool onoff)
if (unlikely(env->flags & MDBX_RDONLY))
return LOG_IFERR(MDBX_EACCESS);
const bool lock_needed = (env->flags & ENV_ACTIVE) && !env_txn0_owned(env);
const bool lock_needed = (env->flags & ENV_ACTIVE) && !env_owned_wrtxn(env);
bool should_unlock = false;
if (lock_needed) {
rc = lck_txn_lock(env, false);

View File

@ -12,8 +12,7 @@ MDBX_cursor *mdbx_cursor_create(void *context) {
couple->outer.signature = cur_signature_ready4dispose;
couple->outer.next = &couple->outer;
couple->userctx = context;
couple->outer.top_and_flags = z_poor_mark;
couple->inner.cursor.top_and_flags = z_poor_mark | z_inner;
cursor_reset(couple);
VALGRIND_MAKE_MEM_DEFINED(&couple->outer.backup, sizeof(couple->outer.backup));
VALGRIND_MAKE_MEM_DEFINED(&couple->outer.tree, sizeof(couple->outer.tree));
VALGRIND_MAKE_MEM_DEFINED(&couple->outer.clc, sizeof(couple->outer.clc));
@ -23,59 +22,45 @@ MDBX_cursor *mdbx_cursor_create(void *context) {
return &couple->outer;
}
int mdbx_cursor_renew(const MDBX_txn *txn, MDBX_cursor *mc) {
int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *mc) {
return likely(mc) ? mdbx_cursor_bind(txn, mc, (kvx_t *)mc->clc - txn->env->kvs) : LOG_IFERR(MDBX_EINVAL);
}
int mdbx_cursor_reset(MDBX_cursor *mc) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_ready4dispose && mc->signature != cur_signature_live))
return LOG_IFERR(MDBX_EBADSIGN);
cursor_couple_t *couple = (cursor_couple_t *)mc;
couple->outer.top_and_flags = z_poor_mark;
couple->inner.cursor.top_and_flags = z_poor_mark | z_inner;
return MDBX_SUCCESS;
}
int mdbx_cursor_bind(const MDBX_txn *txn, MDBX_cursor *mc, MDBX_dbi dbi) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_ready4dispose && mc->signature != cur_signature_live))
return LOG_IFERR(MDBX_EBADSIGN);
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
int rc = cursor_check(mc, MDBX_TXN_FINISHED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
rc = dbi_check(txn, dbi);
cursor_reset((cursor_couple_t *)mc);
return MDBX_SUCCESS;
}
int mdbx_cursor_bind(MDBX_txn *txn, MDBX_cursor *mc, MDBX_dbi dbi) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_ready4dispose && mc->signature != cur_signature_live)) {
int rc = (mc->signature == cur_signature_wait4eot) ? MDBX_EINVAL : MDBX_EBADSIGN;
return LOG_IFERR(rc);
}
int rc = check_txn(txn, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(dbi == FREE_DBI && !(txn->flags & MDBX_TXN_RDONLY)))
return LOG_IFERR(MDBX_EACCESS);
if (unlikely(mc->backup)) /* Cursor from parent transaction */ {
cASSERT(mc, mc->signature == cur_signature_live);
if (unlikely(cursor_dbi(mc) != dbi ||
/* paranoia */ mc->signature != cur_signature_live || mc->txn != txn))
return LOG_IFERR(MDBX_EINVAL);
rc = dbi_check(txn, dbi);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cASSERT(mc, mc->tree == &txn->dbs[dbi]);
cASSERT(mc, mc->clc == &txn->env->kvs[dbi].clc);
cASSERT(mc, cursor_dbi(mc) == dbi);
return likely(cursor_dbi(mc) == dbi &&
/* paranoia */ mc->signature == cur_signature_live && mc->txn == txn)
? MDBX_SUCCESS
: LOG_IFERR(MDBX_EINVAL) /* Disallow change DBI in nested
transactions */
;
}
if (unlikely(mc->backup)) /* Cursor from parent transaction */
LOG_IFERR(MDBX_EINVAL);
if (mc->signature == cur_signature_live) {
if (mc->txn == txn && cursor_dbi(mc) == dbi)
return MDBX_SUCCESS;
rc = mdbx_cursor_unbind(mc);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
@ -88,7 +73,7 @@ int mdbx_cursor_bind(const MDBX_txn *txn, MDBX_cursor *mc, MDBX_dbi dbi) {
mc->next = txn->cursors[dbi];
txn->cursors[dbi] = mc;
((MDBX_txn *)txn)->flags |= txn_may_have_cursors;
txn->flags |= txn_may_have_cursors;
return MDBX_SUCCESS;
}
@ -100,34 +85,39 @@ int mdbx_cursor_unbind(MDBX_cursor *mc) {
return (mc->signature == cur_signature_ready4dispose) ? MDBX_SUCCESS : LOG_IFERR(MDBX_EBADSIGN);
if (unlikely(mc->backup)) /* Cursor from parent transaction */
/* TODO: реализовать при переходе на двусвязный список курсоров */
return LOG_IFERR(MDBX_EINVAL);
eASSERT(nullptr, mc->txn && mc->txn->signature == txn_signature);
cASSERT(mc, mc->signature == cur_signature_live);
cASSERT(mc, !mc->backup);
int rc = check_txn(mc->txn, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!mc->txn || mc->txn->signature != txn_signature)) {
ERROR("Wrong cursor's transaction %p 0x%x", __Wpedantic_format_voidptr(mc->txn), mc->txn ? mc->txn->signature : 0);
return LOG_IFERR(MDBX_PROBLEM);
}
if (mc->next != mc) {
const size_t dbi = (kvx_t *)mc->clc - mc->txn->env->kvs;
cASSERT(mc, cursor_dbi(mc) == dbi);
const size_t dbi = cursor_dbi(mc);
cASSERT(mc, dbi < mc->txn->n_dbi);
cASSERT(mc, &mc->txn->env->kvs[dbi].clc == mc->clc);
if (dbi < mc->txn->n_dbi) {
MDBX_cursor **prev = &mc->txn->cursors[dbi];
while (*prev && *prev != mc)
while (/* *prev && */ *prev != mc) {
ENSURE(mc->txn->env, (*prev)->signature == cur_signature_live || (*prev)->signature == cur_signature_wait4eot);
prev = &(*prev)->next;
}
cASSERT(mc, *prev == mc);
*prev = mc->next;
}
mc->next = mc;
}
cursor_drown((cursor_couple_t *)mc);
mc->signature = cur_signature_ready4dispose;
mc->flags = 0;
return MDBX_SUCCESS;
}
int mdbx_cursor_open(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
if (unlikely(!ret))
return LOG_IFERR(MDBX_EINVAL);
*ret = nullptr;
@ -146,44 +136,69 @@ int mdbx_cursor_open(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
return MDBX_SUCCESS;
}
void mdbx_cursor_close(MDBX_cursor *mc) {
if (likely(mc)) {
ENSURE(nullptr, mc->signature == cur_signature_live || mc->signature == cur_signature_ready4dispose);
void mdbx_cursor_close(MDBX_cursor *cursor) {
if (likely(cursor)) {
int err = mdbx_cursor_close2(cursor);
if (unlikely(err != MDBX_SUCCESS))
mdbx_panic("%s:%d error %d (%s) while closing cursor", __func__, __LINE__, err, mdbx_liberr2str(err));
}
}
int mdbx_cursor_close2(MDBX_cursor *mc) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (mc->signature == cur_signature_ready4dispose) {
if (unlikely(mc->txn || mc->backup))
return LOG_IFERR(MDBX_PANIC);
cursor_drown((cursor_couple_t *)mc);
mc->signature = 0;
osal_free(mc);
return MDBX_SUCCESS;
}
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR(MDBX_EBADSIGN);
MDBX_txn *const txn = mc->txn;
if (!mc->backup) {
mc->txn = nullptr;
/* Unlink from txn, if tracked. */
int rc = check_txn(txn, MDBX_TXN_FINISHED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (mc->backup) {
/* Cursor closed before nested txn ends */
cursor_reset((cursor_couple_t *)mc);
mc->signature = cur_signature_wait4eot;
return MDBX_SUCCESS;
}
if (mc->next != mc) {
ENSURE(txn->env, check_txn(txn, 0) == MDBX_SUCCESS);
const size_t dbi = (kvx_t *)mc->clc - txn->env->kvs;
tASSERT(txn, dbi < txn->n_dbi);
if (dbi < txn->n_dbi) {
const size_t dbi = cursor_dbi(mc);
cASSERT(mc, dbi < mc->txn->n_dbi);
cASSERT(mc, &mc->txn->env->kvs[dbi].clc == mc->clc);
if (likely(dbi < txn->n_dbi)) {
MDBX_cursor **prev = &txn->cursors[dbi];
while (*prev && *prev != mc)
while (/* *prev && */ *prev != mc) {
ENSURE(txn->env, (*prev)->signature == cur_signature_live || (*prev)->signature == cur_signature_wait4eot);
prev = &(*prev)->next;
}
tASSERT(txn, *prev == mc);
*prev = mc->next;
}
mc->next = mc;
}
cursor_drown((cursor_couple_t *)mc);
mc->signature = 0;
osal_free(mc);
} else {
/* Cursor closed before nested txn ends */
tASSERT(txn, mc->signature == cur_signature_live);
ENSURE(txn->env, check_txn_rw(txn, 0) == MDBX_SUCCESS);
mc->signature = cur_signature_wait4eot;
}
}
return MDBX_SUCCESS;
}
int mdbx_cursor_copy(const MDBX_cursor *src, MDBX_cursor *dest) {
if (unlikely(!src))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(src->signature != cur_signature_live))
return LOG_IFERR((src->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check(src, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
int rc = mdbx_cursor_bind(src->txn, dest, cursor_dbi(src));
rc = mdbx_cursor_bind(src->txn, dest, cursor_dbi(src));
if (unlikely(rc != MDBX_SUCCESS))
return rc;
@ -208,43 +223,64 @@ again:
return MDBX_SUCCESS;
}
int mdbx_txn_release_all_cursors(const MDBX_txn *txn, bool unbind) {
int mdbx_txn_release_all_cursors_ex(const MDBX_txn *txn, bool unbind, size_t *count) {
int rc = check_txn(txn, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
if (likely(rc == MDBX_SUCCESS)) {
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
size_t n = 0;
do {
TXN_FOREACH_DBI_FROM(txn, i, MAIN_DBI) {
while (txn->cursors[i]) {
MDBX_cursor *mc = txn->cursors[i];
ENSURE(nullptr, mc->signature == cur_signature_live && (mc->next != mc) && !mc->backup);
rc = likely(rc < INT_MAX) ? rc + 1 : rc;
txn->cursors[i] = mc->next;
mc->next = mc;
if (unbind) {
mc->signature = cur_signature_ready4dispose;
mc->flags = 0;
MDBX_cursor *mc = txn->cursors[i], *next = nullptr;
if (mc) {
txn->cursors[i] = nullptr;
do {
next = mc->next;
if (mc->signature == cur_signature_live) {
mc->signature = cur_signature_wait4eot;
cursor_drown((cursor_couple_t *)mc);
} else
ENSURE(nullptr, mc->signature == cur_signature_wait4eot);
if (mc->backup) {
MDBX_cursor *bk = mc->backup;
mc->next = bk->next;
mc->backup = bk->backup;
mc->backup = nullptr;
bk->signature = 0;
bk = bk->next;
osal_free(bk);
} else {
mc->signature = cur_signature_ready4dispose;
mc->next = mc;
++n;
if (!unbind) {
mc->signature = 0;
osal_free(mc);
}
}
} while ((mc = next) != nullptr);
}
} else {
eASSERT(nullptr, rc < 0);
LOG_IFERR(rc);
}
return rc;
txn = txn->parent;
} while (txn);
if (count)
*count = n;
return MDBX_SUCCESS;
}
int mdbx_cursor_compare(const MDBX_cursor *l, const MDBX_cursor *r, bool ignore_multival) {
const int incomparable = INT16_MAX + 1;
if (unlikely(!l))
return r ? -incomparable * 9 : 0;
else if (unlikely(!r))
return incomparable * 9;
if (unlikely(l->signature != cur_signature_live))
return (r->signature == cur_signature_live) ? -incomparable * 8 : 0;
if (unlikely(r->signature != cur_signature_live))
return (l->signature == cur_signature_live) ? incomparable * 8 : 0;
if (unlikely(cursor_check_pure(l) != MDBX_SUCCESS))
return (cursor_check_pure(r) == MDBX_SUCCESS) ? -incomparable * 8 : 0;
if (unlikely(cursor_check_pure(r) != MDBX_SUCCESS))
return (cursor_check_pure(l) == MDBX_SUCCESS) ? incomparable * 8 : 0;
if (unlikely(l->clc != r->clc)) {
if (l->txn->env != r->txn->env)
@ -310,13 +346,7 @@ int mdbx_cursor_compare(const MDBX_cursor *l, const MDBX_cursor *r, bool ignore_
}
int mdbx_cursor_count_ex(const MDBX_cursor *mc, size_t *count, MDBX_stat *ns, size_t bytes) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = check_txn(mc->txn, MDBX_TXN_BLOCKED);
int rc = cursor_check_ro(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
@ -366,11 +396,9 @@ int mdbx_cursor_count(const MDBX_cursor *mc, size_t *count) {
}
int mdbx_cursor_on_first(const MDBX_cursor *mc) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check_pure(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
for (intptr_t i = 0; i <= mc->top; ++i) {
if (mc->ki[i])
@ -381,11 +409,9 @@ int mdbx_cursor_on_first(const MDBX_cursor *mc) {
}
int mdbx_cursor_on_first_dup(const MDBX_cursor *mc) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check_pure(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (is_filled(mc) && mc->subcur) {
mc = &mc->subcur->cursor;
@ -399,11 +425,9 @@ int mdbx_cursor_on_first_dup(const MDBX_cursor *mc) {
}
int mdbx_cursor_on_last(const MDBX_cursor *mc) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check_pure(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
for (intptr_t i = 0; i <= mc->top; ++i) {
size_t nkeys = page_numkeys(mc->pg[i]);
@ -415,11 +439,9 @@ int mdbx_cursor_on_last(const MDBX_cursor *mc) {
}
int mdbx_cursor_on_last_dup(const MDBX_cursor *mc) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check_pure(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (is_filled(mc) && mc->subcur) {
mc = &mc->subcur->cursor;
@ -434,29 +456,18 @@ int mdbx_cursor_on_last_dup(const MDBX_cursor *mc) {
}
int mdbx_cursor_eof(const MDBX_cursor *mc) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check_pure(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
return is_eof(mc) ? MDBX_RESULT_TRUE : MDBX_RESULT_FALSE;
}
int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, MDBX_cursor_op op) {
if (unlikely(mc == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = check_txn(mc->txn, MDBX_TXN_BLOCKED);
int rc = cursor_check_ro(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(cursor_dbi_changed(mc)))
return LOG_IFERR(MDBX_BAD_DBI);
return LOG_IFERR(cursor_ops(mc, key, data, op));
}
@ -581,19 +592,13 @@ int mdbx_cursor_get_batch(MDBX_cursor *mc, size_t *count, MDBX_val *pairs, size_
return LOG_IFERR(MDBX_EINVAL);
*count = 0;
if (unlikely(mc == nullptr || limit < 4 || limit > INTPTR_MAX - 2))
if (unlikely(limit < 4 || limit > INTPTR_MAX - 2))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = check_txn(mc->txn, MDBX_TXN_BLOCKED);
int rc = cursor_check_ro(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(cursor_dbi_changed(mc)))
return LOG_IFERR(MDBX_BAD_DBI);
if (unlikely(mc->subcur))
return LOG_IFERR(MDBX_INCOMPATIBLE) /* must be a non-dupsort table */;
@ -662,11 +667,9 @@ bailout:
/*----------------------------------------------------------------------------*/
int mdbx_cursor_set_userctx(MDBX_cursor *mc, void *ctx) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_ready4dispose && mc->signature != cur_signature_live))
return LOG_IFERR(MDBX_EBADSIGN);
int rc = cursor_check(mc, 0);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cursor_couple_t *couple = container_of(mc, cursor_couple_t, outer);
couple->userctx = ctx;
@ -688,11 +691,9 @@ MDBX_txn *mdbx_cursor_txn(const MDBX_cursor *mc) {
if (unlikely(!mc || mc->signature != cur_signature_live))
return nullptr;
MDBX_txn *txn = mc->txn;
if (unlikely(!txn || txn->signature != txn_signature))
if (unlikely(!txn || txn->signature != txn_signature || (txn->flags & MDBX_TXN_FINISHED)))
return nullptr;
if (unlikely(txn->flags & MDBX_TXN_FINISHED))
return nullptr;
return txn;
return (txn->flags & MDBX_TXN_HAS_CHILD) ? txn->env->txn : txn;
}
MDBX_dbi mdbx_cursor_dbi(const MDBX_cursor *mc) {
@ -704,37 +705,17 @@ MDBX_dbi mdbx_cursor_dbi(const MDBX_cursor *mc) {
/*----------------------------------------------------------------------------*/
int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags) {
if (unlikely(mc == nullptr || key == nullptr || data == nullptr))
if (unlikely(key == nullptr || data == nullptr))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = check_txn_rw(mc->txn, MDBX_TXN_BLOCKED);
int rc = cursor_check_rw(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(cursor_dbi_changed(mc)))
return LOG_IFERR(MDBX_BAD_DBI);
cASSERT(mc, cursor_is_tracked(mc));
/* Check this first so counter will always be zero on any early failures. */
if (unlikely(flags & MDBX_MULTIPLE)) {
if (unlikely(flags & MDBX_RESERVE))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(!(mc->tree->flags & MDBX_DUPFIXED)))
return LOG_IFERR(MDBX_INCOMPATIBLE);
const size_t dcount = data[1].iov_len;
if (unlikely(dcount < 2 || data->iov_len == 0))
return LOG_IFERR(MDBX_BAD_VALSIZE);
if (unlikely(mc->tree->dupfix_size != data->iov_len) && mc->tree->dupfix_size)
return LOG_IFERR(MDBX_BAD_VALSIZE);
if (unlikely(dcount > MAX_MAPSIZE / 2 / (BRANCH_NODE_MAX(MDBX_MAX_PAGESIZE) - NODESIZE))) {
/* checking for multiplication overflow */
if (unlikely(dcount > MAX_MAPSIZE / 2 / data->iov_len))
return LOG_IFERR(MDBX_TOO_LARGE);
}
rc = cursor_check_multiple(mc, key, data, flags);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
}
if (flags & MDBX_RESERVE) {
@ -743,35 +724,21 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, MDBX_p
data->iov_base = nullptr;
}
if (unlikely(mc->txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
return LOG_IFERR((mc->txn->flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN);
return LOG_IFERR(cursor_put_checklen(mc, key, data, flags));
}
int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = check_txn_rw(mc->txn, MDBX_TXN_BLOCKED);
int rc = cursor_check_rw(mc);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(cursor_dbi_changed(mc)))
return LOG_IFERR(MDBX_BAD_DBI);
return LOG_IFERR(cursor_del(mc, flags));
}
__cold int mdbx_cursor_ignord(MDBX_cursor *mc) {
if (unlikely(!mc))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(mc->signature != cur_signature_live))
return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = cursor_check(mc, 0);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
mc->checking |= z_ignord;
if (mc->subcur)

View File

@ -11,6 +11,12 @@ __cold static intptr_t reasonable_db_maxsize(void) {
/* the 32-bit limit is good enough for fallback */
return cached_result = MAX_MAPSIZE32;
#if defined(__SANITIZE_ADDRESS__)
total_ram_pages >>= 4;
#endif /* __SANITIZE_ADDRESS__ */
if (RUNNING_ON_VALGRIND)
total_ram_pages >>= 4;
if (unlikely((size_t)total_ram_pages * 2 > MAX_MAPSIZE / (size_t)pagesize))
return cached_result = MAX_MAPSIZE;
assert(MAX_MAPSIZE >= (size_t)(total_ram_pages * pagesize * 2));
@ -615,7 +621,7 @@ __cold int mdbx_env_close_ex(MDBX_env *env, bool dont_sync) {
#endif /* Windows */
}
if (env->basal_txn && env->basal_txn->owner == osal_thread_self())
if (env->basal_txn && (MDBX_TXN_CHECKOWNER ? env->basal_txn->owner == osal_thread_self() : !!env->basal_txn->owner))
lck_txn_unlock(env);
eASSERT(env, env->signature.weak == 0);
@ -925,8 +931,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
const bool txn0_owned = env->basal_txn && env_txn0_owned(env);
const bool inside_txn = txn0_owned && env->txn;
MDBX_txn *const txn_owned = env_owned_wrtxn(env);
bool should_unlock = false;
#if MDBX_DEBUG && 0 /* минимальные шаги для проверки/отладки уже не нужны */
@ -942,7 +947,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
if (unlikely(env->flags & MDBX_RDONLY))
return LOG_IFERR(MDBX_EACCESS);
if (!txn0_owned) {
if (!txn_owned) {
int err = lck_txn_lock(env, false);
if (unlikely(err != MDBX_SUCCESS))
return LOG_IFERR(err);
@ -956,8 +961,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
/* get untouched params from current TXN or DB */
if (pagesize <= 0 || pagesize >= INT_MAX)
pagesize = env->ps;
const geo_t *const geo =
inside_txn ? &env->txn->geo : &meta_recent(env, &env->basal_txn->wr.troika).ptr_c->geometry;
const geo_t *const geo = env->txn ? &env->txn->geo : &meta_recent(env, &env->basal_txn->wr.troika).ptr_c->geometry;
if (size_lower < 0)
size_lower = pgno2bytes(env, geo->lower);
if (size_now < 0)
@ -982,7 +986,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
size_now = usedbytes;
} else {
/* env NOT yet mapped */
if (unlikely(inside_txn))
if (unlikely(env->txn))
return LOG_IFERR(MDBX_PANIC);
/* is requested some auto-value for pagesize ? */
@ -1171,8 +1175,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
ENSURE(env, pagesize == (intptr_t)env->ps);
meta_t meta;
memset(&meta, 0, sizeof(meta));
if (!inside_txn) {
eASSERT(env, should_unlock);
if (!env->txn) {
const meta_ptr_t head = meta_recent(env, &env->basal_txn->wr.troika);
uint64_t timestamp = 0;
@ -1262,7 +1265,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
if (unlikely(rc != MDBX_SUCCESS))
goto bailout;
}
if (inside_txn) {
if (env->txn) {
env->txn->geo = new_geo;
env->txn->flags |= MDBX_TXN_DIRTY;
} else {
@ -1387,17 +1390,17 @@ __cold int mdbx_env_stat_ex(const MDBX_env *env, const MDBX_txn *txn, MDBX_stat
if (unlikely(err != MDBX_SUCCESS))
return LOG_IFERR(err);
if (env->txn && env_txn0_owned(env))
MDBX_txn *txn_owned = env_owned_wrtxn(env);
if (txn_owned)
/* inside write-txn */
return LOG_IFERR(stat_acc(env->txn, dest, bytes));
return LOG_IFERR(stat_acc(txn_owned, dest, bytes));
MDBX_txn *tmp_txn;
err = mdbx_txn_begin((MDBX_env *)env, nullptr, MDBX_TXN_RDONLY, &tmp_txn);
err = mdbx_txn_begin((MDBX_env *)env, nullptr, MDBX_TXN_RDONLY, &txn_owned);
if (unlikely(err != MDBX_SUCCESS))
return LOG_IFERR(err);
const int rc = stat_acc(tmp_txn, dest, bytes);
err = mdbx_txn_abort(tmp_txn);
const int rc = stat_acc(txn_owned, dest, bytes);
err = mdbx_txn_abort(txn_owned);
if (unlikely(err != MDBX_SUCCESS))
return LOG_IFERR(err);
return LOG_IFERR(rc);

View File

@ -117,7 +117,6 @@ __cold int mdbx_thread_unregister(const MDBX_env *env) {
return MDBX_RESULT_TRUE /* not registered */;
eASSERT(env, r->pid.weak == env->pid);
eASSERT(env, r->tid.weak == osal_thread_self());
if (unlikely(r->pid.weak != env->pid || r->tid.weak != osal_thread_self()))
return LOG_IFERR(MDBX_BAD_RSLOT);
@ -154,8 +153,10 @@ int mdbx_txn_unlock(MDBX_env *env) {
if (unlikely(env->flags & MDBX_RDONLY))
return LOG_IFERR(MDBX_EACCESS);
#if MDBX_TXN_CHECKOWNER
if (unlikely(env->basal_txn->owner != osal_thread_self()))
return LOG_IFERR(MDBX_THREAD_MISMATCH);
#endif /* MDBX_TXN_CHECKOWNER */
if (unlikely((env->basal_txn->flags & MDBX_TXN_FINISHED) == 0))
return LOG_IFERR(MDBX_BUSY);

View File

@ -199,9 +199,7 @@ __cold const char *mdbx_liberr2str(int errnum) {
return "MDBX_OUSTED: The parked read transaction was outed for the sake"
" of recycling old MVCC snapshots";
case MDBX_MVCC_RETARDED:
return "MDBX_MVCC_RETARDED: MVCC snapshot used by read transaction"
" is outdated and could not be copied"
" since corresponding meta-pages was overwritten";
return "MDBX_MVCC_RETARDED: MVCC snapshot used by parked transaction was bygone";
default:
return nullptr;
}

View File

@ -180,7 +180,7 @@ __cold int mdbx_env_set_option(MDBX_env *env, const MDBX_option_t option, uint64
if (unlikely(err != MDBX_SUCCESS))
return LOG_IFERR(err);
const bool lock_needed = ((env->flags & ENV_ACTIVE) && env->basal_txn && !env_txn0_owned(env));
const bool lock_needed = ((env->flags & ENV_ACTIVE) && env->basal_txn && !env_owned_wrtxn(env));
bool should_unlock = false;
switch (option) {
case MDBX_opt_sync_bytes:

View File

@ -16,12 +16,6 @@ __hot static int cursor_diff(const MDBX_cursor *const __restrict x, const MDBX_c
r->level = 0;
r->root_nkeys = 0;
if (unlikely(x->signature != cur_signature_live))
return (x->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN;
if (unlikely(y->signature != cur_signature_live))
return (y->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN;
int rc = check_txn(x->txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
@ -146,12 +140,20 @@ __hot static ptrdiff_t estimate(const tree_t *tree, diff_t *const __restrict dr)
* Range-Estimation API */
__hot int mdbx_estimate_distance(const MDBX_cursor *first, const MDBX_cursor *last, ptrdiff_t *distance_items) {
if (unlikely(first == nullptr || last == nullptr || distance_items == nullptr))
if (unlikely(!distance_items))
return LOG_IFERR(MDBX_EINVAL);
int rc = cursor_check_pure(first);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
rc = cursor_check_pure(last);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
*distance_items = 0;
diff_t dr;
int rc = cursor_diff(last, first, &dr);
rc = cursor_diff(last, first, &dr);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
@ -172,14 +174,10 @@ __hot int mdbx_estimate_distance(const MDBX_cursor *first, const MDBX_cursor *la
__hot int mdbx_estimate_move(const MDBX_cursor *cursor, MDBX_val *key, MDBX_val *data, MDBX_cursor_op move_op,
ptrdiff_t *distance_items) {
if (unlikely(cursor == nullptr || distance_items == nullptr || move_op == MDBX_GET_CURRENT ||
move_op == MDBX_GET_MULTIPLE))
if (unlikely(!distance_items || move_op == MDBX_GET_CURRENT || move_op == MDBX_GET_MULTIPLE))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(cursor->signature != cur_signature_live))
return LOG_IFERR((cursor->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
int rc = check_txn(cursor->txn, MDBX_TXN_BLOCKED);
int rc = cursor_check_ro(cursor);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
@ -232,10 +230,6 @@ __hot int mdbx_estimate_move(const MDBX_cursor *cursor, MDBX_val *key, MDBX_val
__hot int mdbx_estimate_range(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *begin_key, const MDBX_val *begin_data,
const MDBX_val *end_key, const MDBX_val *end_data, ptrdiff_t *size_items) {
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!size_items))
return LOG_IFERR(MDBX_EINVAL);
@ -248,6 +242,10 @@ __hot int mdbx_estimate_range(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val
if (unlikely(begin_key == MDBX_EPSILON && end_key == MDBX_EPSILON))
return LOG_IFERR(MDBX_EINVAL);
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cursor_couple_t begin;
/* LY: first, initialize cursor to refresh a DB in case it have DB_STALE */
rc = cursor_init(&begin.outer, txn, dbi);

View File

@ -51,15 +51,15 @@ __cold int mdbx_dbi_dupsort_depthmask(const MDBX_txn *txn, MDBX_dbi dbi, uint32_
}
int mdbx_canary_get(const MDBX_txn *txn, MDBX_canary *canary) {
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(canary == nullptr))
return LOG_IFERR(MDBX_EINVAL);
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
if (unlikely(rc != MDBX_SUCCESS)) {
memset(canary, 0, sizeof(*canary));
return LOG_IFERR(rc);
}
if (unlikely(canary == nullptr))
return LOG_IFERR(MDBX_EINVAL);
*canary = txn->canary;
return MDBX_SUCCESS;
}
@ -68,13 +68,13 @@ int mdbx_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *d
DKBUF_DEBUG;
DEBUG("===> get db %u key [%s]", dbi, DKEY_DEBUG(key));
if (unlikely(!key || !data))
return LOG_IFERR(MDBX_EINVAL);
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!key || !data))
return LOG_IFERR(MDBX_EINVAL);
cursor_couple_t cx;
rc = cursor_init(&cx.outer, txn, dbi);
if (unlikely(rc != MDBX_SUCCESS))
@ -84,15 +84,12 @@ int mdbx_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *d
}
int mdbx_get_equal_or_great(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data) {
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!key || !data))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(txn->flags & MDBX_TXN_BLOCKED))
return LOG_IFERR(MDBX_BAD_TXN);
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cursor_couple_t cx;
rc = cursor_init(&cx.outer, txn, dbi);
@ -106,13 +103,13 @@ int mdbx_get_ex(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data
DKBUF_DEBUG;
DEBUG("===> get db %u key [%s]", dbi, DKEY_DEBUG(key));
if (unlikely(!key || !data))
return LOG_IFERR(MDBX_EINVAL);
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!key || !data))
return LOG_IFERR(MDBX_EINVAL);
cursor_couple_t cx;
rc = cursor_init(&cx.outer, txn, dbi);
if (unlikely(rc != MDBX_SUCCESS))
@ -179,7 +176,7 @@ int mdbx_canary_put(MDBX_txn *txn, const MDBX_canary *canary) {
* расположен в той-же странице памяти, в том числе для многостраничных
* P_LARGE страниц с длинными данными. */
int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) {
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
@ -215,18 +212,15 @@ int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) {
}
int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, const MDBX_val *data) {
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!key))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(dbi <= FREE_DBI))
return LOG_IFERR(MDBX_BAD_DBI);
if (unlikely(txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
return LOG_IFERR((txn->flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN);
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cursor_couple_t cx;
rc = cursor_init(&cx.outer, txn, dbi);
@ -254,10 +248,6 @@ int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, const MDBX_val *d
}
int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags) {
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!key || !data))
return LOG_IFERR(MDBX_EINVAL);
@ -268,13 +258,27 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, M
MDBX_APPENDDUP | MDBX_CURRENT | MDBX_MULTIPLE)))
return LOG_IFERR(MDBX_EINVAL);
if (unlikely(txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
return LOG_IFERR((txn->flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN);
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cursor_couple_t cx;
rc = cursor_init(&cx.outer, txn, dbi);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(flags & MDBX_MULTIPLE)) {
rc = cursor_check_multiple(&cx.outer, key, data, flags);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
}
if (flags & MDBX_RESERVE) {
if (unlikely(cx.outer.tree->flags & (MDBX_DUPSORT | MDBX_REVERSEDUP | MDBX_INTEGERDUP | MDBX_DUPFIXED)))
return LOG_IFERR(MDBX_INCOMPATIBLE);
data->iov_base = nullptr;
}
cx.outer.next = txn->cursors[dbi];
txn->cursors[dbi] = &cx.outer;
@ -330,10 +334,6 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, M
int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new_data, MDBX_val *old_data,
MDBX_put_flags_t flags, MDBX_preserve_func preserver, void *preserver_context) {
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(!key || !old_data || old_data == new_data))
return LOG_IFERR(MDBX_EINVAL);
@ -350,6 +350,10 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *
MDBX_APPENDDUP | MDBX_CURRENT)))
return LOG_IFERR(MDBX_EINVAL);
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
cursor_couple_t cx;
rc = cursor_init(&cx.outer, txn, dbi);
if (unlikely(rc != MDBX_SUCCESS))
@ -407,7 +411,7 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *
}
if (is_modifable(txn, page)) {
if (new_data && cmp_lenfast(&present_data, new_data) == 0) {
if (new_data && eq_fast(&present_data, new_data)) {
/* если данные совпадают, то ничего делать не надо */
*old_data = *new_data;
goto bailout;

View File

@ -9,7 +9,7 @@ __attribute__((__no_sanitize_thread__, __noinline__))
#endif
int mdbx_txn_straggler(const MDBX_txn *txn, int *percent)
{
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
if (likely(rc == MDBX_SUCCESS))
rc = check_env(txn->env, true);
if (unlikely(rc != MDBX_SUCCESS))
@ -104,11 +104,13 @@ int mdbx_txn_abort(MDBX_txn *txn) {
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
#if MDBX_TXN_CHECKOWNER
if ((txn->flags & (MDBX_TXN_RDONLY | MDBX_NOSTICKYTHREADS)) == MDBX_NOSTICKYTHREADS &&
unlikely(txn->owner != osal_thread_self())) {
mdbx_txn_break(txn);
return LOG_IFERR(MDBX_THREAD_MISMATCH);
}
#endif /* MDBX_TXN_CHECKOWNER */
return LOG_IFERR(txn_abort(txn));
}
@ -215,9 +217,13 @@ int mdbx_txn_begin_ex(MDBX_env *env, MDBX_txn *parent, MDBX_txn_flags_t flags, M
MDBX_txn *txn = nullptr;
if (parent) {
/* Nested transactions: Max 1 child, write txns only, no writemap */
rc = check_txn_rw(parent, MDBX_TXN_RDONLY | MDBX_WRITEMAP | MDBX_TXN_BLOCKED);
if (unlikely(rc != MDBX_SUCCESS)) {
if (rc == MDBX_BAD_TXN && (parent->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)) == 0) {
rc = check_txn(parent, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
if (unlikely(rc != MDBX_SUCCESS))
return LOG_IFERR(rc);
if (unlikely(parent->flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP))) {
rc = MDBX_BAD_TXN;
if ((parent->flags & MDBX_TXN_RDONLY) == 0) {
ERROR("%s mode is incompatible with nested transactions", "MDBX_WRITEMAP");
rc = MDBX_INCOMPATIBLE;
}
@ -363,11 +369,13 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
goto done;
}
#if MDBX_TXN_CHECKOWNER
if ((txn->flags & MDBX_NOSTICKYTHREADS) && txn == env->basal_txn && unlikely(txn->owner != osal_thread_self())) {
txn->flags |= MDBX_TXN_ERROR;
rc = MDBX_THREAD_MISMATCH;
return LOG_IFERR(rc);
}
#endif /* MDBX_TXN_CHECKOWNER */
if (unlikely(txn->flags & MDBX_TXN_ERROR)) {
rc = MDBX_RESULT_TRUE;

View File

@ -78,7 +78,7 @@ __cold static int audit_ex_locked(MDBX_txn *txn, size_t retired_stored, bool don
if (db)
ctx.used += audit_db_used(db);
else if (dbi_state(txn, dbi))
WARNING("audit %s@%" PRIaTXN ": unable account dbi %zd / \"%*s\", state 0x%02x", txn->parent ? "nested-" : "",
WARNING("audit %s@%" PRIaTXN ": unable account dbi %zd / \"%.*s\", state 0x%02x", txn->parent ? "nested-" : "",
txn->txnid, dbi, (int)env->kvs[dbi].name.iov_len, (const char *)env->kvs[dbi].name.iov_base,
dbi_state(txn, dbi));
}

View File

@ -159,6 +159,19 @@ __cold static MDBX_chk_line_t *MDBX_PRINTF_ARGS(2, 3) chk_print(MDBX_chk_line_t
return line;
}
__cold MDBX_MAYBE_UNUSED static void chk_println_va(MDBX_chk_scope_t *const scope, enum MDBX_chk_severity severity,
const char *fmt, va_list args) {
chk_line_end(chk_print_va(chk_line_begin(scope, severity), fmt, args));
}
__cold MDBX_MAYBE_UNUSED static void chk_println(MDBX_chk_scope_t *const scope, enum MDBX_chk_severity severity,
const char *fmt, ...) {
va_list args;
va_start(args, fmt);
chk_println_va(scope, severity, fmt, args);
va_end(args);
}
__cold static MDBX_chk_line_t *chk_print_size(MDBX_chk_line_t *line, const char *prefix, const uint64_t value,
const char *suffix) {
static const char sf[] = "KMGTPEZY"; /* LY: Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta! */
@ -455,11 +468,10 @@ __cold static void chk_dispose(MDBX_chk_internal_t *chk) {
chk->cb->table_dispose(chk->usr, tbl);
tbl->cookie = nullptr;
}
if (tbl != &chk->table_gc && tbl != &chk->table_main) {
if (tbl != &chk->table_gc && tbl != &chk->table_main)
osal_free(tbl);
}
}
}
osal_free(chk->v2a_buf.iov_base);
osal_free(chk->pagemap);
chk->usr->internal = nullptr;
@ -1127,6 +1139,7 @@ __cold static int chk_db(MDBX_chk_scope_t *const scope, MDBX_dbi dbi, MDBX_chk_t
const size_t maxkeysize = mdbx_env_get_maxkeysize_ex(env, tbl->flags);
MDBX_val prev_key = {nullptr, 0}, prev_data = {nullptr, 0};
MDBX_val key, data;
size_t dups_count = 0;
err = mdbx_cursor_get(cursor, &key, &data, MDBX_FIRST);
while (err == MDBX_SUCCESS) {
err = chk_check_break(scope);
@ -1150,6 +1163,12 @@ __cold static int chk_db(MDBX_chk_scope_t *const scope, MDBX_dbi dbi, MDBX_chk_t
}
if (prev_key.iov_base) {
if (key.iov_base == prev_key.iov_base)
dups_count += 1;
else {
histogram_acc(dups_count, &tbl->histogram.multival);
dups_count = 0;
}
if (prev_data.iov_base && !bad_data && (tbl->flags & MDBX_DUPFIXED) && prev_data.iov_len != data.iov_len) {
chk_object_issue(scope, "entry", record_count, "different data length", "%" PRIuPTR " != %" PRIuPTR,
prev_data.iov_len, data.iov_len);
@ -1236,17 +1255,27 @@ __cold static int chk_db(MDBX_chk_scope_t *const scope, MDBX_dbi dbi, MDBX_chk_t
err = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT);
}
if (prev_key.iov_base)
histogram_acc(dups_count, &tbl->histogram.multival);
err = (err != MDBX_NOTFOUND) ? chk_error_rc(scope, err, "mdbx_cursor_get") : MDBX_SUCCESS;
if (err == MDBX_SUCCESS && record_count != db->items)
chk_scope_issue(scope, "different number of entries %" PRIuSIZE " != %" PRIu64, record_count, db->items);
bailout:
if (cursor) {
if (handler) {
if (tbl->histogram.key_len.count) {
if (record_count) {
MDBX_chk_line_t *line = chk_line_begin(scope, MDBX_chk_info);
line = histogram_dist(line, &tbl->histogram.key_len, "key length density", "0/1", false);
chk_line_feed(line);
line = histogram_dist(line, &tbl->histogram.val_len, "value length density", "0/1", false);
if (tbl->histogram.multival.amount) {
chk_line_feed(line);
line = histogram_dist(line, &tbl->histogram.multival, "number of multi-values density", "single", false);
chk_line_feed(line);
line = chk_print(line, "number of keys %" PRIuSIZE ", average values per key %.1f",
tbl->histogram.multival.count, record_count / (double)tbl->histogram.multival.count);
}
chk_line_end(line);
}
if (scope->stage == MDBX_chk_maindb)

View File

@ -200,6 +200,10 @@ static inline bool check_table_flags(unsigned flags) {
}
}
static inline int tbl_setup_ifneed(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db) {
return likely(kvx->clc.v.lmax) ? MDBX_SUCCESS : tbl_setup(env, kvx, db);
}
/*----------------------------------------------------------------------------*/
MDBX_NOTHROW_PURE_FUNCTION static inline size_t pgno2bytes(const MDBX_env *env, size_t pgno) {
@ -418,9 +422,10 @@ static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
return MDBX_EPERM;
if (unlikely(txn->flags & bad_bits)) {
if ((bad_bits & MDBX_TXN_RDONLY) && unlikely(txn->flags & MDBX_TXN_RDONLY))
return MDBX_EACCESS;
if ((bad_bits & MDBX_TXN_PARKED) == 0)
return MDBX_BAD_TXN;
else
return txn_check_badbits_parked(txn, bad_bits);
}
}
@ -439,14 +444,7 @@ static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
}
static inline int check_txn_rw(const MDBX_txn *txn, int bad_bits) {
int err = check_txn(txn, bad_bits & ~MDBX_TXN_PARKED);
if (unlikely(err))
return err;
if (unlikely(txn->flags & MDBX_TXN_RDONLY))
return MDBX_EACCESS;
return MDBX_SUCCESS;
return check_txn(txn, (bad_bits | MDBX_TXN_RDONLY) & ~MDBX_TXN_PARKED);
}
/*----------------------------------------------------------------------------*/

View File

@ -5,7 +5,7 @@
#include "internals.h"
__cold int cursor_check(const MDBX_cursor *mc) {
__cold int cursor_validate(const MDBX_cursor *mc) {
if (!mc->txn->wr.dirtylist) {
cASSERT(mc, (mc->txn->flags & MDBX_WRITEMAP) != 0 && !MDBX_AVOID_MSYNC);
} else {
@ -81,10 +81,10 @@ __cold int cursor_check(const MDBX_cursor *mc) {
return MDBX_SUCCESS;
}
__cold int cursor_check_updating(MDBX_cursor *mc) {
__cold int cursor_validate_updating(MDBX_cursor *mc) {
const uint8_t checking = mc->checking;
mc->checking |= z_updating;
const int rc = cursor_check(mc);
const int rc = cursor_validate(mc);
mc->checking = checking;
return rc;
}
@ -184,12 +184,12 @@ __hot int cursor_touch(MDBX_cursor *const mc, const MDBX_val *key, const MDBX_va
/*----------------------------------------------------------------------------*/
int cursor_shadow(MDBX_cursor *cursor, MDBX_txn *nested_txn, const size_t dbi) {
tASSERT(nested_txn, cursor->signature == cur_signature_live);
tASSERT(nested_txn, cursor->txn != nested_txn);
int cursor_shadow(MDBX_cursor *cursor, MDBX_txn *nested, const size_t dbi) {
tASSERT(nested, cursor->signature == cur_signature_live);
tASSERT(nested, cursor->txn != nested);
cASSERT(cursor, cursor->txn->flags & txn_may_have_cursors);
cASSERT(cursor, dbi == cursor_dbi(cursor));
tASSERT(nested_txn, dbi > FREE_DBI && dbi < nested_txn->n_dbi);
tASSERT(nested, dbi > FREE_DBI && dbi < nested->n_dbi);
const size_t size = cursor->subcur ? sizeof(MDBX_cursor) + sizeof(subcur_t) : sizeof(MDBX_cursor);
MDBX_cursor *const shadow = osal_malloc(size);
@ -202,30 +202,32 @@ int cursor_shadow(MDBX_cursor *cursor, MDBX_txn *nested_txn, const size_t dbi) {
#endif /* MDBX_DEBUG */
*shadow = *cursor;
cursor->backup = shadow;
cursor->txn = nested_txn;
cursor->tree = &nested_txn->dbs[dbi];
cursor->dbi_state = &nested_txn->dbi_state[dbi];
cursor->txn = nested;
cursor->tree = &nested->dbs[dbi];
cursor->dbi_state = &nested->dbi_state[dbi];
subcur_t *subcur = cursor->subcur;
if (subcur) {
*(subcur_t *)(shadow + 1) = *subcur;
subcur->cursor.txn = nested_txn;
subcur->cursor.dbi_state = cursor->dbi_state;
subcur->cursor.txn = nested;
subcur->cursor.dbi_state = &nested->dbi_state[dbi];
}
return MDBX_SUCCESS;
}
void cursor_eot(MDBX_cursor *cursor) {
MDBX_cursor *cursor_eot(MDBX_cursor *cursor, MDBX_txn *txn) {
MDBX_cursor *const next = cursor->next;
const unsigned stage = cursor->signature;
MDBX_cursor *const shadow = cursor->backup;
ENSURE(cursor->txn->env, stage == cur_signature_live || (stage == cur_signature_wait4eot && shadow));
ENSURE(txn->env, stage == cur_signature_live || (stage == cur_signature_wait4eot && shadow));
tASSERT(txn, cursor->txn == txn);
if (shadow) {
subcur_t *subcur = cursor->subcur;
cASSERT(cursor, cursor->txn->parent != nullptr);
/* Zap: Using uninitialized memory '*cursor->backup'. */
tASSERT(txn, txn->parent != nullptr && shadow->txn == txn->parent);
/* Zap: Using uninitialized memory '*subcur->backup'. */
MDBX_SUPPRESS_GOOFY_MSVC_ANALYZER(6001);
ENSURE(cursor->txn->env, shadow->signature == cur_signature_live);
cASSERT(cursor, subcur == shadow->subcur);
if (((cursor->txn->flags | cursor->txn->parent->flags) & MDBX_TXN_ERROR) == 0) {
ENSURE(txn->env, shadow->signature == cur_signature_live);
tASSERT(txn, subcur == shadow->subcur);
if ((txn->flags & MDBX_TXN_ERROR) == 0) {
/* Update pointers to parent txn */
cursor->next = shadow->next;
cursor->backup = shadow->backup;
@ -233,25 +235,25 @@ void cursor_eot(MDBX_cursor *cursor) {
cursor->tree = shadow->tree;
cursor->dbi_state = shadow->dbi_state;
if (subcur) {
subcur->cursor.txn = cursor->txn;
subcur->cursor.dbi_state = cursor->dbi_state;
subcur->cursor.txn = shadow->txn;
subcur->cursor.dbi_state = shadow->dbi_state;
}
} else {
/* Restore from backup, i.e. rollback/abort nested txn */
*cursor = *shadow;
cursor->signature = stage /* Promote (cur_signature_wait4eot) state to parent txn */;
if (subcur)
*subcur = *(subcur_t *)(shadow + 1);
}
if (stage == cur_signature_wait4eot /* Cursor was closed by user */)
cursor->signature = stage /* Promote closed state to parent txn */;
shadow->signature = 0;
osal_free(shadow);
} else {
ENSURE(cursor->txn->env, stage == cur_signature_live);
be_poor(cursor);
cursor->signature = cur_signature_ready4dispose /* Cursor may be reused */;
cursor->next = cursor;
cursor_drown((cursor_couple_t *)cursor);
}
return next;
}
/*----------------------------------------------------------------------------*/
@ -293,10 +295,7 @@ static __always_inline int couple_init(cursor_couple_t *couple, const MDBX_txn *
if (unlikely(*dbi_state & DBI_STALE))
return tbl_fetch(couple->outer.txn, cursor_dbi(&couple->outer));
if (unlikely(kvx->clc.k.lmax == 0))
return tbl_setup(txn->env, kvx, tree);
return MDBX_SUCCESS;
return tbl_setup_ifneed(txn->env, kvx, tree);
}
__cold int cursor_init4walk(cursor_couple_t *couple, const MDBX_txn *const txn, tree_t *const tree, kvx_t *const kvx) {
@ -382,6 +381,7 @@ int cursor_dupsort_setup(MDBX_cursor *mc, const node_t *node, const page_t *mp)
}
mc->tree->dupfix_size = mx->nested_tree.dupfix_size;
mc->clc->v.lmin = mc->clc->v.lmax = mx->nested_tree.dupfix_size;
cASSERT(mc, mc->clc->v.lmax >= mc->clc->v.lmin);
}
DEBUG("Sub-db dbi -%zu root page %" PRIaPGNO, cursor_dbi(&mx->cursor), mx->nested_tree.root);
@ -728,8 +728,17 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
if (mc->clc->k.cmp(key, &current_key) != 0)
return MDBX_EKEYMISMATCH;
if (unlikely((flags & MDBX_MULTIPLE)))
goto drop_current;
if (unlikely((flags & MDBX_MULTIPLE))) {
if (unlikely(!mc->subcur))
return MDBX_EINVAL;
err = cursor_del(mc, flags & MDBX_ALLDUPS);
if (unlikely(err != MDBX_SUCCESS))
return err;
if (unlikely(data[1].iov_len == 0))
return MDBX_SUCCESS;
flags -= MDBX_CURRENT;
goto skip_check_samedata;
}
if (mc->subcur) {
node_t *node = page_node(mc->pg[mc->top], mc->ki[mc->top]);
@ -739,7 +748,6 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
* отличается, то вместо обновления требуется удаление и
* последующая вставка. */
if (mc->subcur->nested_tree.items > 1 || current_data.iov_len != data->iov_len) {
drop_current:
err = cursor_del(mc, flags & MDBX_ALLDUPS);
if (unlikely(err != MDBX_SUCCESS))
return err;
@ -758,7 +766,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
goto skip_check_samedata;
}
}
if (!(flags & MDBX_RESERVE) && unlikely(cmp_lenfast(&current_data, data) == 0))
if (!(flags & MDBX_RESERVE) && unlikely(eq_fast(&current_data, data)))
return MDBX_SUCCESS /* the same data, nothing to update */;
skip_check_samedata:;
}
@ -830,7 +838,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
return csr.err;
}
}
} else if ((flags & MDBX_RESERVE) == 0) {
} else if (!(flags & (MDBX_RESERVE | MDBX_MULTIPLE))) {
if (unlikely(eq_fast(data, &old_data))) {
cASSERT(mc, mc->clc->v.cmp(data, &old_data) == 0);
/* the same data, nothing to update */
@ -847,6 +855,8 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
size_t *batch_dupfix_done = nullptr, batch_dupfix_given = 0;
if (unlikely(flags & MDBX_MULTIPLE)) {
batch_dupfix_given = data[1].iov_len;
if (unlikely(data[1].iov_len == 0))
return /* nothing todo */ MDBX_SUCCESS;
batch_dupfix_done = &data[1].iov_len;
*batch_dupfix_done = 0;
}
@ -936,7 +946,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
}
if (AUDIT_ENABLED()) {
err = cursor_check(mc);
err = cursor_validate(mc);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -945,7 +955,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
more:
if (AUDIT_ENABLED()) {
err = cursor_check(mc);
err = cursor_validate(mc);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -1006,7 +1016,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
memcpy(page_data(lp.page), data->iov_base, data->iov_len);
if (AUDIT_ENABLED()) {
err = cursor_check(mc);
err = cursor_validate(mc);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -1273,7 +1283,7 @@ __hot int cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsig
}
if (AUDIT_ENABLED()) {
err = cursor_check(mc);
err = cursor_validate(mc);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -1291,7 +1301,7 @@ insert_node:;
if (page_room(mc->pg[mc->top]) < nsize) {
rc = page_split(mc, key, ref_data, P_INVALID, insert_key ? naf : naf | MDBX_SPLIT_REPLACE);
if (rc == MDBX_SUCCESS && AUDIT_ENABLED())
rc = insert_key ? cursor_check(mc) : cursor_check_updating(mc);
rc = insert_key ? cursor_validate(mc) : cursor_validate_updating(mc);
} else {
/* There is room already in this leaf page. */
if (is_dupfix_leaf(mc->pg[mc->top])) {
@ -1409,11 +1419,12 @@ insert_node:;
data[0].iov_base = ptr_disp(data[0].iov_base, data[0].iov_len);
insert_key = insert_data = false;
old_singledup.iov_base = nullptr;
sub_root = nullptr;
goto more;
}
}
if (AUDIT_ENABLED())
rc = cursor_check(mc);
rc = cursor_validate(mc);
}
return rc;
@ -1428,6 +1439,21 @@ insert_node:;
return rc;
}
int cursor_check_multiple(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsigned flags) {
(void)key;
if (unlikely(flags & MDBX_RESERVE))
return MDBX_EINVAL;
if (unlikely(!(mc->tree->flags & MDBX_DUPFIXED)))
return MDBX_INCOMPATIBLE;
const size_t number = data[1].iov_len;
if (unlikely(number > MAX_MAPSIZE / 2 / (BRANCH_NODE_MAX(MDBX_MAX_PAGESIZE) - NODESIZE))) {
/* checking for multiplication overflow */
if (unlikely(number > MAX_MAPSIZE / 2 / data->iov_len))
return MDBX_TOO_LARGE;
}
return MDBX_SUCCESS;
}
__hot int cursor_put_checklen(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsigned flags) {
cASSERT(mc, (mc->flags & z_inner) == 0);
if (unlikely(key->iov_len > mc->clc->k.lmax || key->iov_len < mc->clc->k.lmin)) {
@ -1685,7 +1711,7 @@ del_key:
cASSERT(mc, rc == MDBX_SUCCESS);
if (AUDIT_ENABLED())
rc = cursor_check(mc);
rc = cursor_validate(mc);
return rc;
fail:
@ -2047,20 +2073,18 @@ __hot int cursor_ops(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, const MDBX_
cASSERT(mc, is_poor(mc) && !is_filled(mc));
return rc;
case MDBX_SEEK_AND_GET_MULTIPLE:
if (unlikely(!key))
return MDBX_EINVAL;
rc = cursor_seek(mc, key, data, MDBX_SET).err;
if (unlikely(rc != MDBX_SUCCESS))
return rc;
__fallthrough /* fall through */;
case MDBX_GET_MULTIPLE:
if (unlikely(!data))
return MDBX_EINVAL;
if (unlikely((mc->tree->flags & MDBX_DUPFIXED) == 0))
return MDBX_INCOMPATIBLE;
if (unlikely(!is_pointed(mc))) {
if (unlikely(!key))
return MDBX_EINVAL;
if (unlikely((mc->flags & z_fresh) == 0))
return MDBX_ENODATA;
rc = cursor_seek(mc, key, data, MDBX_SET).err;
if (unlikely(rc != MDBX_SUCCESS))
return rc;
} else {
if (unlikely(!is_filled(mc)))
return MDBX_ENODATA;
if (key) {
@ -2068,7 +2092,6 @@ __hot int cursor_ops(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, const MDBX_
const node_t *node = page_node(mp, mc->ki[mc->top]);
*key = get_key(node);
}
}
cASSERT(mc, is_filled(mc));
if (unlikely(!inner_filled(mc))) {
if (inner_pointed(mc))
@ -2102,15 +2125,6 @@ __hot int cursor_ops(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, const MDBX_
return MDBX_EINVAL;
if (unlikely(mc->subcur == nullptr))
return MDBX_INCOMPATIBLE;
if (unlikely(!is_pointed(mc))) {
if (unlikely((mc->flags & z_fresh) == 0))
return MDBX_ENODATA;
rc = outer_last(mc, key, data);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
mc->subcur->cursor.ki[mc->subcur->cursor.top] = 0;
goto fetch_multiple;
}
if (unlikely(!is_filled(mc) || !inner_filled(mc)))
return MDBX_ENODATA;
rc = cursor_sibling_left(&mc->subcur->cursor);
@ -2343,3 +2357,40 @@ __hot int cursor_ops(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, const MDBX_
return MDBX_EINVAL;
}
}
int cursor_check(const MDBX_cursor *mc, int txn_bad_bits) {
if (unlikely(mc == nullptr))
return MDBX_EINVAL;
if (unlikely(mc->signature != cur_signature_live)) {
if (mc->signature != cur_signature_ready4dispose)
return MDBX_EBADSIGN;
return (txn_bad_bits > MDBX_TXN_FINISHED) ? MDBX_EINVAL : MDBX_SUCCESS;
}
/* проверяем что курсор в связном списке для отслеживания, исключение допускается только для read-only операций для
* служебных/временных курсоров на стеке. */
MDBX_MAYBE_UNUSED char stack_top[sizeof(void *)];
cASSERT(mc, cursor_is_tracked(mc) || (!(txn_bad_bits & MDBX_TXN_RDONLY) && stack_top < (char *)mc &&
(char *)mc - stack_top < (ptrdiff_t)globals.sys_pagesize * 4));
if (txn_bad_bits) {
int rc = check_txn(mc->txn, txn_bad_bits & ~MDBX_TXN_HAS_CHILD);
if (unlikely(rc != MDBX_SUCCESS)) {
cASSERT(mc, rc != MDBX_RESULT_TRUE);
return rc;
}
if (likely((mc->txn->flags & MDBX_TXN_HAS_CHILD) == 0))
return likely(!cursor_dbi_changed(mc)) ? MDBX_SUCCESS : MDBX_BAD_DBI;
cASSERT(mc, (mc->txn->flags & MDBX_TXN_RDONLY) == 0 && mc->txn != mc->txn->env->txn && mc->txn->env->txn);
rc = dbi_check(mc->txn->env->txn, cursor_dbi(mc));
if (unlikely(rc != MDBX_SUCCESS))
return rc;
cASSERT(mc, (mc->txn->flags & MDBX_TXN_RDONLY) == 0 && mc->txn == mc->txn->env->txn);
}
return MDBX_SUCCESS;
}

View File

@ -233,7 +233,7 @@ enum cursor_checking {
z_pagecheck = 0x80 /* perform page checking, see MDBX_VALIDATION */
};
MDBX_INTERNAL int __must_check_result cursor_check(const MDBX_cursor *mc);
MDBX_INTERNAL int __must_check_result cursor_validate(const MDBX_cursor *mc);
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline size_t cursor_dbi(const MDBX_cursor *mc) {
cASSERT(mc, mc->txn && mc->txn->signature == txn_signature);
@ -292,20 +292,38 @@ MDBX_NOTHROW_PURE_FUNCTION static inline bool check_leaf_type(const MDBX_cursor
return (((page_type(mp) ^ mc->checking) & (z_branch | z_leaf | z_largepage | z_dupfix)) == 0);
}
MDBX_INTERNAL void cursor_eot(MDBX_cursor *cursor);
MDBX_INTERNAL int cursor_shadow(MDBX_cursor *cursor, MDBX_txn *nested_txn, const size_t dbi);
MDBX_INTERNAL int cursor_check(const MDBX_cursor *mc, int txn_bad_bits);
/* без необходимости доступа к данным, без активации припаркованных транзакций. */
static inline int cursor_check_pure(const MDBX_cursor *mc) {
return cursor_check(mc, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
}
/* для чтения данных, с активацией припаркованных транзакций. */
static inline int cursor_check_ro(const MDBX_cursor *mc) { return cursor_check(mc, MDBX_TXN_BLOCKED); }
/* для записи данных. */
static inline int cursor_check_rw(const MDBX_cursor *mc) {
return cursor_check(mc, (MDBX_TXN_BLOCKED - MDBX_TXN_PARKED) | MDBX_TXN_RDONLY);
}
MDBX_INTERNAL MDBX_cursor *cursor_eot(MDBX_cursor *cursor, MDBX_txn *txn);
MDBX_INTERNAL int cursor_shadow(MDBX_cursor *cursor, MDBX_txn *nested, const size_t dbi);
MDBX_INTERNAL MDBX_cursor *cursor_cpstk(const MDBX_cursor *csrc, MDBX_cursor *cdst);
MDBX_INTERNAL int __must_check_result cursor_ops(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
const MDBX_cursor_op op);
MDBX_INTERNAL int __must_check_result cursor_check_multiple(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
unsigned flags);
MDBX_INTERNAL int __must_check_result cursor_put_checklen(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
unsigned flags);
MDBX_INTERNAL int __must_check_result cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, unsigned flags);
MDBX_INTERNAL int __must_check_result cursor_check_updating(MDBX_cursor *mc);
MDBX_INTERNAL int __must_check_result cursor_validate_updating(MDBX_cursor *mc);
MDBX_INTERNAL int __must_check_result cursor_del(MDBX_cursor *mc, unsigned flags);
@ -355,3 +373,19 @@ MDBX_MAYBE_UNUSED static inline void cursor_inner_refresh(const MDBX_cursor *mc,
}
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool cursor_is_tracked(const MDBX_cursor *mc);
static inline void cursor_reset(cursor_couple_t *couple) {
couple->outer.top_and_flags = z_fresh_mark;
couple->inner.cursor.top_and_flags = z_fresh_mark | z_inner;
}
static inline void cursor_drown(cursor_couple_t *couple) {
couple->outer.top_and_flags = z_poor_mark;
couple->inner.cursor.top_and_flags = z_poor_mark | z_inner;
couple->outer.txn = nullptr;
couple->inner.cursor.txn = nullptr;
couple->outer.tree = nullptr;
/* сохраняем clc-указатель, так он используется для вычисления dbi в mdbx_cursor_renew(). */
couple->outer.dbi_state = nullptr;
couple->inner.cursor.dbi_state = nullptr;
}

View File

@ -677,7 +677,7 @@ __cold const tree_t *dbi_dig(const MDBX_txn *txn, const size_t dbi, tree_t *fall
case DBI_OLDEN:
return dig->dbs + dbi;
case 0:
return nullptr;
return fallback;
case DBI_VALID | DBI_STALE:
case DBI_OLDEN | DBI_STALE:
break;

View File

@ -101,7 +101,7 @@ static inline bool dbi_changed(const MDBX_txn *txn, const size_t dbi) {
const MDBX_env *const env = txn->env;
eASSERT(env, dbi_state(txn, dbi) & DBI_LINDO);
const uint32_t snap_seq = atomic_load32(&env->dbi_seqs[dbi], mo_AcquireRelease);
return snap_seq != txn->dbi_seqs[dbi];
return unlikely(snap_seq != txn->dbi_seqs[dbi]);
}
static inline int dbi_check(const MDBX_txn *txn, const size_t dbi) {

View File

@ -368,7 +368,7 @@ void dxb_sanitize_tail(MDBX_env *env, MDBX_txn *txn) {
if (env->pid != osal_getpid()) {
/* resurrect after fork */
return;
} else if (env->txn && env_txn0_owned(env)) {
} else if (env_owned_wrtxn(env)) {
/* inside write-txn */
last = meta_recent(env, &env->basal_txn->wr.troika).ptr_v->geometry.first_unallocated;
} else if (env->flags & MDBX_RDONLY) {
@ -567,6 +567,7 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit
return err;
}
size_t expected_filesize = 0;
const size_t used_bytes = pgno2bytes(env, header.geometry.first_unallocated);
const size_t used_aligned2os_bytes = ceil_powerof2(used_bytes, globals.sys_pagesize);
if ((env->flags & MDBX_RDONLY) /* readonly */
@ -601,6 +602,8 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit
/* pre-shrink if enabled */
env->geo_in_bytes.now = used_bytes + env->geo_in_bytes.shrink - used_bytes % env->geo_in_bytes.shrink;
/* сейчас БД еще не открыта, поэтому этот вызов не изменит геометрию, но проверит и скорректирует параметры
* с учетом реального размера страницы. */
err = mdbx_env_set_geometry(env, env->geo_in_bytes.lower, env->geo_in_bytes.now, env->geo_in_bytes.upper,
env->geo_in_bytes.grow, env->geo_in_bytes.shrink, header.pagesize);
if (unlikely(err != MDBX_SUCCESS)) {
@ -608,27 +611,26 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit
return (err == MDBX_EINVAL) ? MDBX_INCOMPATIBLE : err;
}
/* update meta fields */
/* altering fields to match geometry given from user */
expected_filesize = pgno_align2os_bytes(env, header.geometry.now);
header.geometry.now = bytes2pgno(env, env->geo_in_bytes.now);
header.geometry.lower = bytes2pgno(env, env->geo_in_bytes.lower);
header.geometry.upper = bytes2pgno(env, env->geo_in_bytes.upper);
header.geometry.grow_pv = pages2pv(bytes2pgno(env, env->geo_in_bytes.grow));
header.geometry.shrink_pv = pages2pv(bytes2pgno(env, env->geo_in_bytes.shrink));
VERBOSE("amended: root %" PRIaPGNO "/%" PRIaPGNO ", geo %" PRIaPGNO "/%" PRIaPGNO "-%" PRIaPGNO "/%" PRIaPGNO
VERBOSE("amending: root %" PRIaPGNO "/%" PRIaPGNO ", geo %" PRIaPGNO "/%" PRIaPGNO "-%" PRIaPGNO "/%" PRIaPGNO
" +%u -%u, txn_id %" PRIaTXN ", %s",
header.trees.main.root, header.trees.gc.root, header.geometry.lower, header.geometry.first_unallocated,
header.geometry.now, header.geometry.upper, pv2pages(header.geometry.grow_pv),
pv2pages(header.geometry.shrink_pv), unaligned_peek_u64(4, header.txnid_a), durable_caption(&header));
} else {
/* fetch back 'now/current' size, since it was ignored during comparison
* and may differ. */
/* fetch back 'now/current' size, since it was ignored during comparison and may differ. */
env->geo_in_bytes.now = pgno_align2os_bytes(env, header.geometry.now);
}
ENSURE(env, header.geometry.now >= header.geometry.first_unallocated);
} else {
/* geo-params are not pre-configured by user,
* get current values from the meta. */
/* geo-params are not pre-configured by user, get current values from the meta. */
env->geo_in_bytes.now = pgno2bytes(env, header.geometry.now);
env->geo_in_bytes.lower = pgno2bytes(env, header.geometry.lower);
env->geo_in_bytes.upper = pgno2bytes(env, header.geometry.upper);
@ -638,17 +640,19 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit
ENSURE(env, pgno_align2os_bytes(env, header.geometry.now) == env->geo_in_bytes.now);
ENSURE(env, env->geo_in_bytes.now >= used_bytes);
if (!expected_filesize)
expected_filesize = env->geo_in_bytes.now;
const uint64_t filesize_before = env->dxb_mmap.filesize;
if (unlikely(filesize_before != env->geo_in_bytes.now)) {
if (lck_rc != /* lck exclusive */ MDBX_RESULT_TRUE) {
VERBOSE("filesize mismatch (expect %" PRIuPTR "b/%" PRIaPGNO "p, have %" PRIu64 "b/%" PRIaPGNO "p), "
"assume other process working",
VERBOSE("filesize mismatch (expect %" PRIuPTR "b/%" PRIaPGNO "p, have %" PRIu64 "b/%" PRIu64
"p), assume other process working",
env->geo_in_bytes.now, bytes2pgno(env, env->geo_in_bytes.now), filesize_before,
bytes2pgno(env, (size_t)filesize_before));
filesize_before >> env->ps2ln);
} else {
WARNING("filesize mismatch (expect %" PRIuSIZE "b/%" PRIaPGNO "p, have %" PRIu64 "b/%" PRIaPGNO "p)",
env->geo_in_bytes.now, bytes2pgno(env, env->geo_in_bytes.now), filesize_before,
bytes2pgno(env, (size_t)filesize_before));
if (filesize_before != expected_filesize)
WARNING("filesize mismatch (expect %" PRIuSIZE "b/%" PRIaPGNO "p, have %" PRIu64 "b/%" PRIu64 "p)",
expected_filesize, bytes2pgno(env, expected_filesize), filesize_before, filesize_before >> env->ps2ln);
if (filesize_before < used_bytes) {
ERROR("last-page beyond end-of-file (last %" PRIaPGNO ", have %" PRIaPGNO ")",
header.geometry.first_unallocated, bytes2pgno(env, (size_t)filesize_before));
@ -656,8 +660,9 @@ __cold int dxb_setup(MDBX_env *env, const int lck_rc, const mdbx_mode_t mode_bit
}
if (env->flags & MDBX_RDONLY) {
if (filesize_before & (globals.sys_pagesize - 1)) {
ERROR("%s", "filesize should be rounded-up to system page");
if (filesize_before & (globals.sys_allocation_granularity - 1)) {
ERROR("filesize should be rounded-up to system allocation granularity %u",
globals.sys_allocation_granularity);
return MDBX_WANNA_RECOVERY;
}
WARNING("%s", "ignore filesize mismatch in readonly-mode");
@ -1152,7 +1157,8 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika
if (!head.is_steady && meta_is_steady(pending))
target = (meta_t *)head.ptr_c;
else {
WARNING("%s", "skip update meta");
NOTICE("skip update meta%" PRIaPGNO " for txn#%" PRIaTXN "since it is already steady",
data_page(head.ptr_c)->pgno, head.txnid);
return MDBX_SUCCESS;
}
} else {

View File

@ -3,9 +3,14 @@
#include "internals.h"
bool env_txn0_owned(const MDBX_env *env) {
return (env->flags & MDBX_NOSTICKYTHREADS) ? (env->basal_txn->owner != 0)
MDBX_txn *env_owned_wrtxn(const MDBX_env *env) {
if (likely(env->basal_txn)) {
const bool is_owned = (env->flags & MDBX_NOSTICKYTHREADS) ? (env->basal_txn->owner != 0)
: (env->basal_txn->owner == osal_thread_self());
if (is_owned)
return env->txn ? env->txn : env->basal_txn;
}
return nullptr;
}
int env_page_auxbuffer(MDBX_env *env) {
@ -60,7 +65,7 @@ __cold int env_sync(MDBX_env *env, bool force, bool nonblock) {
if (unlikely(env->flags & MDBX_RDONLY))
return MDBX_EACCESS;
const bool txn0_owned = env_txn0_owned(env);
MDBX_txn *const txn_owned = env_owned_wrtxn(env);
bool should_unlock = false;
int rc = MDBX_RESULT_TRUE /* means "nothing to sync" */;
@ -71,7 +76,7 @@ retry:;
goto bailout;
}
const troika_t troika = (txn0_owned | should_unlock) ? env->basal_txn->wr.troika : meta_tap(env);
const troika_t troika = (txn_owned || should_unlock) ? env->basal_txn->wr.troika : meta_tap(env);
const meta_ptr_t head = meta_recent(env, &troika);
const uint64_t unsynced_pages = atomic_load64(&env->lck->unsynced_pages, mo_Relaxed);
if (unsynced_pages == 0) {
@ -104,7 +109,7 @@ retry:;
osal_monotime() - eoos_timestamp >= autosync_period))
flags &= MDBX_WRITEMAP /* clear flags for full steady sync */;
if (!txn0_owned) {
if (!txn_owned) {
if (!should_unlock) {
#if MDBX_ENABLE_PGOP_STAT
unsigned wops = 0;
@ -163,8 +168,8 @@ retry:;
flags |= txn_shrink_allowed;
}
eASSERT(env, txn0_owned || should_unlock);
eASSERT(env, !txn0_owned || (flags & txn_shrink_allowed) == 0);
eASSERT(env, txn_owned || should_unlock);
eASSERT(env, !txn_owned || (flags & txn_shrink_allowed) == 0);
if (!head.is_steady && unlikely(env->stuck_meta >= 0) && troika.recent != (uint8_t)env->stuck_meta) {
NOTICE("skip %s since wagering meta-page (%u) is mispatch the recent "

View File

@ -58,10 +58,11 @@ __cold void debug_log(int level, const char *function, int line, const char *fmt
__cold void log_error(const int err, const char *func, unsigned line) {
assert(err != MDBX_SUCCESS);
if (unlikely(globals.loglevel >= MDBX_LOG_DEBUG) &&
(globals.loglevel >= MDBX_LOG_TRACE || !(err == MDBX_RESULT_TRUE || err == MDBX_NOTFOUND))) {
if (unlikely(globals.loglevel >= MDBX_LOG_DEBUG)) {
const bool is_error = err != MDBX_RESULT_TRUE && err != MDBX_NOTFOUND;
char buf[256];
debug_log(MDBX_LOG_ERROR, func, line, "error %d (%s)\n", err, mdbx_strerror_r(err, buf, sizeof(buf)));
debug_log(is_error ? MDBX_LOG_ERROR : MDBX_LOG_VERBOSE, func, line, "%s %d (%s)\n",
is_error ? "error" : "condition", err, mdbx_strerror_r(err, buf, sizeof(buf)));
}
}

View File

@ -12,6 +12,8 @@ mdbx_dump \- MDBX environment export tool
[\c
.BR \-q ]
[\c
.BR \-c ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-l ]
@ -41,6 +43,9 @@ Write the library version number to the standard output, and exit.
.BR \-q
Be quiet.
.TP
.BR \-c
Concise mode without repeating keys in a dump, but incompatible with Berkeley DB and LMDB.
.TP
.BR \-f \ file
Write to the specified file instead of to the standard output.
.TP

View File

@ -63,8 +63,8 @@ class trouble_location {
#endif
public:
MDBX_CXX11_CONSTEXPR trouble_location(unsigned line, const char *condition,
const char *function, const char *filename)
MDBX_CXX11_CONSTEXPR trouble_location(unsigned line, const char *condition, const char *function,
const char *filename)
:
#if TROUBLE_PROVIDE_LINENO
line_(line)
@ -146,8 +146,7 @@ __cold std::string format_va(const char *fmt, va_list ap) {
result.reserve(size_t(needed + 1));
result.resize(size_t(needed), '\0');
assert(int(result.capacity()) > needed);
int actual = vsnprintf(const_cast<char *>(result.data()), result.capacity(),
fmt, ones);
int actual = vsnprintf(const_cast<char *>(result.data()), result.capacity(), fmt, ones);
assert(actual == needed);
(void)actual;
va_end(ones);
@ -176,16 +175,13 @@ public:
};
__cold bug::bug(const trouble_location &location) noexcept
: std::runtime_error(format("mdbx.bug: %s.%s at %s:%u", location.function(),
location.condition(), location.filename(),
location.line())),
: std::runtime_error(format("mdbx.bug: %s.%s at %s:%u", location.function(), location.condition(),
location.filename(), location.line())),
location_(location) {}
__cold bug::~bug() noexcept {}
[[noreturn]] __cold void raise_bug(const trouble_location &what_and_where) {
throw bug(what_and_where);
}
[[maybe_unused, noreturn]] __cold void raise_bug(const trouble_location &what_and_where) { throw bug(what_and_where); }
#define RAISE_BUG(line, condition, function, file) \
do { \
@ -193,6 +189,7 @@ __cold bug::~bug() noexcept {}
raise_bug(bug); \
} while (0)
#undef ENSURE
#define ENSURE(condition) \
do \
if (MDBX_UNLIKELY(!(condition))) \
@ -376,7 +373,7 @@ __cold std::string error::message() const {
__cold void error::throw_exception() const {
switch (code()) {
case MDBX_EINVAL:
throw std::invalid_argument("mdbx");
throw std::invalid_argument("MDBX_EINVAL");
case MDBX_ENOMEM:
throw std::bad_alloc();
case MDBX_SUCCESS:
@ -1165,12 +1162,32 @@ bool from_base64::is_erroneous() const noexcept {
//------------------------------------------------------------------------------
template class LIBMDBX_API_TYPE buffer<legacy_allocator>;
#if defined(_MSC_VER)
#pragma warning(push)
/* warning C4251: 'mdbx::buffer<...>::silo_':
* struct 'mdbx::buffer<..>::silo' needs to have dll-interface to be used by clients of class 'mdbx::buffer<...>'
*
* Microsoft не хочет признавать ошибки и пересматривать приятные решения, поэтому MSVC продолжает кошмарить
* и стращать разработчиков предупреждениями, тем самым перекладывая ответственность на их плечи.
*
* В данном случае предупреждение выдаётся из-за инстанцирования std::string::allocator_type::pointer и
* std::pmr::string::allocator_type::pointer внутри mdbx::buffer<..>::silo. А так как эти типы являются частью
* стандартной библиотеки C++ они всегда будут доступны и без необходимости их инстанцирования и экспорта из libmdbx.
*
* Поэтому нет других вариантов как заглушить это предупреждение и еще раз плюнуть в сторону microsoft. */
#pragma warning(disable : 4251)
#endif /* MSVC */
MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<legacy_allocator>);
#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI
template class LIBMDBX_API_TYPE buffer<polymorphic_allocator>;
MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<polymorphic_allocator>);
#endif /* __cpp_lib_memory_resource >= 201603L */
#if defined(_MSC_VER)
#pragma warning(pop)
#endif /* MSVC */
//------------------------------------------------------------------------------
static inline MDBX_env_flags_t mode2flags(env::mode mode) {
@ -1590,15 +1607,6 @@ __cold bool txn::rename_map(const ::std::string &old_name, const ::std::string &
//------------------------------------------------------------------------------
void cursor_managed::close() {
if (MDBX_UNLIKELY(!handle_))
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_EINVAL);
::mdbx_cursor_close(handle_);
handle_ = nullptr;
}
//------------------------------------------------------------------------------
__cold ::std::ostream &operator<<(::std::ostream &out, const slice &it) {
out << "{";
if (!it.is_valid())

View File

@ -248,7 +248,7 @@ __cold void mdbx_panic(const char *fmt, ...) {
unlikely(num < 1 || !message) ? "<troubles with panic-message preparation>" : message;
if (globals.logger.ptr)
debug_log(MDBX_LOG_FATAL, "panic", 0, "%s", const_message);
debug_log(MDBX_LOG_FATAL, "mdbx-panic", 0, "%s", const_message);
while (1) {
#if defined(_WIN32) || defined(_WIN64)
@ -262,7 +262,7 @@ __cold void mdbx_panic(const char *fmt, ...) {
#endif
FatalExit(ERROR_UNHANDLED_ERROR);
#else
__assert_fail(const_message, "mdbx", 0, "panic");
__assert_fail(const_message, "mdbx-panic", 0, const_message);
abort();
#endif
}

View File

@ -92,7 +92,7 @@ MDBX_INTERNAL int env_open(MDBX_env *env, mdbx_mode_t mode);
MDBX_INTERNAL int env_info(const MDBX_env *env, const MDBX_txn *txn, MDBX_envinfo *out, size_t bytes, troika_t *troika);
MDBX_INTERNAL int env_sync(MDBX_env *env, bool force, bool nonblock);
MDBX_INTERNAL int env_close(MDBX_env *env, bool resurrect_after_fork);
MDBX_INTERNAL bool env_txn0_owned(const MDBX_env *env);
MDBX_INTERNAL MDBX_txn *env_owned_wrtxn(const MDBX_env *env);
MDBX_INTERNAL int __must_check_result env_page_auxbuffer(MDBX_env *env);
MDBX_INTERNAL unsigned env_setup_pagesize(MDBX_env *env, const size_t pagesize);
@ -111,7 +111,7 @@ MDBX_INTERNAL void recalculate_subpage_thresholds(MDBX_env *env);
/* table.c */
MDBX_INTERNAL int __must_check_result tbl_fetch(MDBX_txn *txn, size_t dbi);
MDBX_INTERNAL int __must_check_result tbl_setup(const MDBX_env *env, kvx_t *const kvx, const tree_t *const db);
MDBX_INTERNAL int __must_check_result tbl_setup(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db);
/* coherency.c */
MDBX_INTERNAL bool coherency_check_meta(const MDBX_env *env, const volatile meta_t *meta, bool report);

View File

@ -3,28 +3,37 @@
#include "internals.h"
int tbl_setup(const MDBX_env *env, kvx_t *const kvx, const tree_t *const db) {
int tbl_setup(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db) {
osal_memory_fence(mo_AcquireRelease, false);
if (unlikely(!check_table_flags(db->flags))) {
ERROR("incompatible or invalid db.flags (0x%x) ", db->flags);
return MDBX_INCOMPATIBLE;
}
if (unlikely(!kvx->clc.k.cmp)) {
kvx->clc.k.cmp = builtin_keycmp(db->flags);
kvx->clc.v.cmp = builtin_datacmp(db->flags);
size_t v_lmin = valsize_min(db->flags);
size_t v_lmax = env_valsize_max(env, db->flags);
if ((db->flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->dupfix_size) {
if (!MDBX_DISABLE_VALIDATION && unlikely(db->dupfix_size < v_lmin || db->dupfix_size > v_lmax)) {
ERROR("db.dupfix_size (%u) <> min/max value-length (%zu/%zu)", db->dupfix_size, v_lmin, v_lmax);
return MDBX_CORRUPTED;
}
v_lmin = v_lmax = db->dupfix_size;
}
kvx->clc.k.lmin = keysize_min(db->flags);
kvx->clc.k.lmax = env_keysize_max(env, db->flags);
kvx->clc.v.lmin = valsize_min(db->flags);
kvx->clc.v.lmax = env_valsize_max(env, db->flags);
if (unlikely(!kvx->clc.k.cmp)) {
kvx->clc.v.cmp = builtin_datacmp(db->flags);
kvx->clc.k.cmp = builtin_keycmp(db->flags);
}
kvx->clc.v.lmin = v_lmin;
osal_memory_fence(mo_Relaxed, true);
kvx->clc.v.lmax = v_lmax;
osal_memory_fence(mo_AcquireRelease, true);
if ((db->flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->dupfix_size) {
if (!MDBX_DISABLE_VALIDATION && unlikely(db->dupfix_size < kvx->clc.v.lmin || db->dupfix_size > kvx->clc.v.lmax)) {
ERROR("db.dupfix_size (%u) <> min/max value-length (%zu/%zu)", db->dupfix_size, kvx->clc.v.lmin, kvx->clc.v.lmax);
return MDBX_CORRUPTED;
}
kvx->clc.v.lmin = kvx->clc.v.lmax = db->dupfix_size;
}
eASSERT(env, kvx->clc.k.lmax >= kvx->clc.k.lmin);
eASSERT(env, kvx->clc.v.lmax >= kvx->clc.v.lmin);
return MDBX_SUCCESS;
}
@ -38,7 +47,7 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) {
rc = tree_search(&couple.outer, &kvx->name, 0);
if (unlikely(rc != MDBX_SUCCESS)) {
bailout:
NOTICE("dbi %zu refs to inaccessible table `%*s` for txn %" PRIaTXN " (err %d)", dbi, (int)kvx->name.iov_len,
NOTICE("dbi %zu refs to inaccessible table `%.*s` for txn %" PRIaTXN " (err %d)", dbi, (int)kvx->name.iov_len,
(const char *)kvx->name.iov_base, txn->txnid, rc);
return (rc == MDBX_NOTFOUND) ? MDBX_BAD_DBI : rc;
}
@ -50,7 +59,7 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) {
goto bailout;
}
if (unlikely((node_flags(nsr.node) & (N_DUP | N_TREE)) != N_TREE)) {
NOTICE("dbi %zu refs to not a named table `%*s` for txn %" PRIaTXN " (%s)", dbi, (int)kvx->name.iov_len,
NOTICE("dbi %zu refs to not a named table `%.*s` for txn %" PRIaTXN " (%s)", dbi, (int)kvx->name.iov_len,
(const char *)kvx->name.iov_base, txn->txnid, "wrong flags");
return MDBX_INCOMPATIBLE; /* not a named DB */
}
@ -60,7 +69,7 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) {
return rc;
if (unlikely(data.iov_len != sizeof(tree_t))) {
NOTICE("dbi %zu refs to not a named table `%*s` for txn %" PRIaTXN " (%s)", dbi, (int)kvx->name.iov_len,
NOTICE("dbi %zu refs to not a named table `%.*s` for txn %" PRIaTXN " (%s)", dbi, (int)kvx->name.iov_len,
(const char *)kvx->name.iov_base, txn->txnid, "wrong rec-size");
return MDBX_INCOMPATIBLE; /* not a named DB */
}
@ -70,7 +79,7 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) {
* have dropped and recreated the DB with other flags. */
tree_t *const db = &txn->dbs[dbi];
if (unlikely((db->flags & DB_PERSISTENT_FLAGS) != flags)) {
NOTICE("dbi %zu refs to the re-created table `%*s` for txn %" PRIaTXN
NOTICE("dbi %zu refs to the re-created table `%.*s` for txn %" PRIaTXN
" with different flags (present 0x%X != wanna 0x%X)",
dbi, (int)kvx->name.iov_len, (const char *)kvx->name.iov_base, txn->txnid, db->flags & DB_PERSISTENT_FLAGS,
flags);
@ -86,10 +95,13 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) {
return MDBX_CORRUPTED;
}
#endif /* !MDBX_DISABLE_VALIDATION */
rc = tbl_setup(txn->env, kvx, db);
rc = tbl_setup_ifneed(txn->env, kvx, db);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
if (unlikely(dbi_changed(txn, dbi)))
return MDBX_BAD_DBI;
txn->dbi_state[dbi] &= ~DBI_STALE;
return MDBX_SUCCESS;
}

View File

@ -20,6 +20,7 @@
#define PRINT 1
#define GLOBAL 2
#define CONCISE 4
static int mode = GLOBAL;
typedef struct flagbit {
@ -55,42 +56,23 @@ static void signal_handler(int sig) {
#endif /* !WINDOWS */
static const char hexc[] = "0123456789abcdef";
static void dumpbyte(unsigned char c) {
putchar(hexc[c >> 4]);
putchar(hexc[c & 15]);
}
static void text(MDBX_val *v) {
unsigned char *c, *end;
static void dumpval(const MDBX_val *v) {
static const char digits[] = "0123456789abcdef";
putchar(' ');
c = v->iov_base;
end = c + v->iov_len;
while (c < end) {
for (const unsigned char *c = v->iov_base, *end = c + v->iov_len; c < end; ++c) {
if (mode & PRINT) {
if (isprint(*c) && *c != '\\') {
putchar(*c);
} else {
continue;
} else
putchar('\\');
dumpbyte(*c);
}
c++;
putchar(digits[*c >> 4]);
putchar(digits[*c & 15]);
}
putchar('\n');
}
static void dumpval(MDBX_val *v) {
unsigned char *c, *end;
putchar(' ');
c = v->iov_base;
end = c + v->iov_len;
while (c < end)
dumpbyte(*c++);
putchar('\n');
}
bool quiet = false, rescue = false;
const char *prog;
static void error(const char *func, int rc) {
@ -185,12 +167,19 @@ static int dump_tbl(MDBX_txn *txn, MDBX_dbi dbi, char *name) {
rc = MDBX_EINTR;
break;
}
if (mode & PRINT) {
text(&key);
text(&data);
} else {
dumpval(&key);
dumpval(&data);
if ((flags & MDBX_DUPSORT) && (mode & CONCISE)) {
while ((rc = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT_DUP)) == MDBX_SUCCESS) {
if (user_break) {
rc = MDBX_EINTR;
break;
}
putchar(' ');
dumpval(&data);
}
if (rc != MDBX_NOTFOUND)
break;
}
}
printf("DATA=END\n");
@ -206,10 +195,12 @@ static int dump_tbl(MDBX_txn *txn, MDBX_dbi dbi, char *name) {
static void usage(void) {
fprintf(stderr,
"usage: %s "
"[-V] [-q] [-f file] [-l] [-p] [-r] [-a|-s table] [-u|U] "
"[-V] [-q] [-c] [-f file] [-l] [-p] [-r] [-a|-s table] [-u|U] "
"dbpath\n"
" -V\t\tprint version and exit\n"
" -q\t\tbe quiet\n"
" -c\t\tconcise mode without repeating keys,\n"
" \t\tbut incompatible with Berkeley DB and LMDB\n"
" -f\t\twrite to file instead of stdout\n"
" -l\t\tlist tables and exit\n"
" -p\t\tuse printable characters\n"
@ -268,6 +259,7 @@ int main(int argc, char *argv[]) {
"s:"
"V"
"r"
"c"
"q")) != EOF) {
switch (i) {
case 'V':
@ -298,6 +290,9 @@ int main(int argc, char *argv[]) {
break;
case 'n':
break;
case 'c':
mode |= CONCISE;
break;
case 'p':
mode |= PRINT;
break;

View File

@ -380,7 +380,16 @@ __hot static int readline(MDBX_val *out, MDBX_val *buf) {
return badend();
}
}
if (fgets(buf->iov_base, (int)buf->iov_len, stdin) == nullptr)
/* modern concise mode, where space in second position mean the same (previously) value */
c = fgetc(stdin);
if (c == EOF)
return errno ? errno : EOF;
if (c == ' ')
return (ungetc(c, stdin) == c) ? MDBX_SUCCESS : (errno ? errno : EOF);
*(char *)buf->iov_base = c;
if (fgets((char *)buf->iov_base + 1, (int)buf->iov_len - 1, stdin) == nullptr)
return errno ? errno : EOF;
lineno++;
@ -721,8 +730,8 @@ int main(int argc, char *argv[]) {
}
int batch = 0;
MDBX_val key = {.iov_base = nullptr, .iov_len = 0}, data = {.iov_base = nullptr, .iov_len = 0};
while (err == MDBX_SUCCESS) {
MDBX_val key, data;
err = readline(&key, &kbuf);
if (err == EOF)
break;

View File

@ -880,7 +880,7 @@ retry:
if (nkeys >= minkeys) {
mc->ki[mc->top] = (indx_t)ki_top;
if (AUDIT_ENABLED())
return cursor_check_updating(mc);
return cursor_validate_updating(mc);
return MDBX_SUCCESS;
}
@ -920,7 +920,7 @@ int page_split(MDBX_cursor *mc, const MDBX_val *const newkey, MDBX_val *const ne
const size_t newindx = mc->ki[mc->top];
size_t nkeys = page_numkeys(mp);
if (AUDIT_ENABLED()) {
rc = cursor_check_updating(mc);
rc = cursor_validate_updating(mc);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
}
@ -979,7 +979,7 @@ int page_split(MDBX_cursor *mc, const MDBX_val *const newkey, MDBX_val *const ne
mc->top = 1;
prev_top = 0;
if (AUDIT_ENABLED()) {
rc = cursor_check_updating(mc);
rc = cursor_validate_updating(mc);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
}
@ -1092,10 +1092,10 @@ int page_split(MDBX_cursor *mc, const MDBX_val *const newkey, MDBX_val *const ne
}
if (AUDIT_ENABLED()) {
rc = cursor_check_updating(mc);
rc = cursor_validate_updating(mc);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
rc = cursor_check_updating(mn);
rc = cursor_validate_updating(mn);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
}
@ -1221,7 +1221,7 @@ int page_split(MDBX_cursor *mc, const MDBX_val *const newkey, MDBX_val *const ne
goto done;
cASSERT(mc, mc->top - top == mc->tree->height - height);
if (AUDIT_ENABLED()) {
rc = cursor_check_updating(mc);
rc = cursor_validate_updating(mc);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
}
@ -1475,7 +1475,7 @@ done:
mc->txn->flags |= MDBX_TXN_ERROR;
else {
if (AUDIT_ENABLED())
rc = cursor_check_updating(mc);
rc = cursor_validate_updating(mc);
if (unlikely(naf & MDBX_RESERVE)) {
node_t *node = page_node(mc->pg[mc->top], mc->ki[mc->top]);
if (!(node_flags(node) & N_BIG))
@ -1525,7 +1525,7 @@ int tree_propagate_key(MDBX_cursor *mc, const MDBX_val *key) {
node_del(mc, 0);
int err = page_split(mc, key, nullptr, pgno, MDBX_SPLIT_REPLACE);
if (err == MDBX_SUCCESS && AUDIT_ENABLED())
err = cursor_check_updating(mc);
err = cursor_validate_updating(mc);
return err;
}

View File

@ -480,18 +480,26 @@ int txn_nested_join(MDBX_txn *txn, struct commit_timestamp *ts) {
eASSERT(env, dpl_check(txn));
if (txn->wr.dirtylist->length == 0 && !(txn->flags & MDBX_TXN_DIRTY) && parent->n_dbi == txn->n_dbi) {
TXN_FOREACH_DBI_ALL(txn, i) {
tASSERT(txn, (txn->dbi_state[i] & DBI_DIRTY) == 0);
if ((txn->dbi_state[i] & DBI_STALE) && !(parent->dbi_state[i] & DBI_STALE))
tASSERT(txn, memcmp(&parent->dbs[i], &txn->dbs[i], sizeof(tree_t)) == 0);
}
VERBOSE("fast-complete pure nested txn %" PRIaTXN, txn->txnid);
tASSERT(txn, memcmp(&parent->geo, &txn->geo, sizeof(parent->geo)) == 0);
tASSERT(txn, memcmp(&parent->canary, &txn->canary, sizeof(parent->canary)) == 0);
tASSERT(txn, !txn->wr.spilled.list || MDBX_PNL_GETSIZE(txn->wr.spilled.list) == 0);
tASSERT(txn, txn->wr.loose_count == 0);
VERBOSE("fast-complete pure nested txn %" PRIaTXN, txn->txnid);
/* Update parent's DBs array */
eASSERT(env, parent->n_dbi == txn->n_dbi);
TXN_FOREACH_DBI_ALL(txn, dbi) {
tASSERT(txn, (txn->dbi_state[dbi] & (DBI_CREAT | DBI_DIRTY)) == 0);
if (txn->dbi_state[dbi] & DBI_FRESH) {
parent->dbs[dbi] = txn->dbs[dbi];
/* preserve parent's status */
const uint8_t state = txn->dbi_state[dbi] | DBI_FRESH;
DEBUG("dbi %zu dbi-state %s 0x%02x -> 0x%02x", dbi, (parent->dbi_state[dbi] != state) ? "update" : "still",
parent->dbi_state[dbi], state);
parent->dbi_state[dbi] = state;
}
}
return txn_end(txn, TXN_END_PURE_COMMIT | TXN_END_SLOT | TXN_END_FREE);
}

View File

@ -9,17 +9,14 @@ __hot txnid_t txn_snapshot_oldest(const MDBX_txn *const txn) {
void txn_done_cursors(MDBX_txn *txn) {
tASSERT(txn, txn->flags & txn_may_have_cursors);
tASSERT(txn, txn->cursors[FREE_DBI] == nullptr);
TXN_FOREACH_DBI_FROM(txn, i, /* skip FREE_DBI */ 1) {
TXN_FOREACH_DBI_ALL(txn, i) {
MDBX_cursor *cursor = txn->cursors[i];
if (cursor) {
txn->cursors[i] = nullptr;
do {
MDBX_cursor *const next = cursor->next;
cursor_eot(cursor);
cursor = next;
} while (cursor);
do
cursor = cursor_eot(cursor, txn);
while (cursor);
}
}
txn->flags &= ~txn_may_have_cursors;
@ -36,8 +33,10 @@ int txn_shadow_cursors(const MDBX_txn *parent, const size_t dbi) {
MDBX_cursor *next = nullptr;
do {
next = cursor->next;
if (cursor->signature != cur_signature_live)
if (cursor->signature != cur_signature_live) {
ENSURE(parent->env, cursor->signature == cur_signature_wait4eot);
continue;
}
tASSERT(parent, cursor->txn == parent && dbi == cursor_dbi(cursor));
int err = cursor_shadow(cursor, txn, dbi);
@ -390,7 +389,10 @@ int txn_check_badbits_parked(const MDBX_txn *txn, int bad_bits) {
* - но при распарковке поломанные транзакции завершаются.
* - получается что транзакцию можно припарковать, потом поломать вызвав
* mdbx_txn_break(), но далее любое её использование приведет к завершению
* при распарковке. */
* при распарковке.
*
* Поэтому для припаркованных транзакций возвращается ошибка если не-включена
* авто-распарковка, либо есть другие плохие биты. */
if ((txn->flags & (bad_bits | MDBX_TXN_AUTOUNPARK)) != (MDBX_TXN_PARKED | MDBX_TXN_AUTOUNPARK))
return LOG_IFERR(MDBX_BAD_TXN);

View File

@ -3,11 +3,16 @@
#include "internals.h"
#if MDBX_VERSION_MAJOR != ${MDBX_VERSION_MAJOR} || MDBX_VERSION_MINOR != ${MDBX_VERSION_MINOR}
#if !defined(MDBX_VERSION_UNSTABLE) && \
(MDBX_VERSION_MAJOR != ${MDBX_VERSION_MAJOR} || MDBX_VERSION_MINOR != ${MDBX_VERSION_MINOR})
#error "API version mismatch! Had `git fetch --tags` done?"
#endif
static const char sourcery[] = MDBX_STRINGIFY(MDBX_BUILD_SOURCERY);
static const char sourcery[] =
#ifdef MDBX_VERSION_UNSTABLE
"UNSTABLE@"
#endif
MDBX_STRINGIFY(MDBX_BUILD_SOURCERY);
__dll_export
#ifdef __attribute_used__

View File

@ -301,10 +301,10 @@ else()
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)
add_extra_test(cursor_closing TIMEOUT 10800)
add_extra_test(early_close_dbi)
add_extra_test(maindb_ordinal)
add_extra_test(dupfix_multiple)
add_extra_test(dupfix_multiple TIMEOUT 10800)
add_extra_test(doubtless_positioning TIMEOUT 10800)
add_extra_test(crunched_delete TIMEOUT 10800)
add_extra_test(dbi)

View File

@ -313,12 +313,12 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, b
return true;
}
if (strcasecmp(value_cstr, "yes") == 0 || strcasecmp(value_cstr, "1") == 0) {
if (strcasecmp(value_cstr, "yes") == 0 || strcasecmp(value_cstr, "1") == 0 || strcasecmp(value_cstr, "on") == 0) {
value = true;
return true;
}
if (strcasecmp(value_cstr, "no") == 0 || strcasecmp(value_cstr, "0") == 0) {
if (strcasecmp(value_cstr, "no") == 0 || strcasecmp(value_cstr, "0") == 0 || strcasecmp(value_cstr, "off") == 0) {
value = false;
return true;
}
@ -342,6 +342,7 @@ const struct option_verb mode_bits[] = {{"rdonly", unsigned(MDBX_RDONLY)},
{"perturb", unsigned(MDBX_PAGEPERTURB)},
{"accede", unsigned(MDBX_ACCEDE)},
{"exclusive", unsigned(MDBX_EXCLUSIVE)},
{"validation", unsigned(MDBX_VALIDATION)},
{nullptr, 0}};
const struct option_verb table_bits[] = {{"key.reverse", unsigned(MDBX_REVERSEKEY)},

View File

@ -5,11 +5,21 @@
#include <random>
#include <vector>
#if MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
#define NN 1024
#if defined(ENABLE_MEMCHECK) || defined(MDBX_CI)
#if MDBX_DEBUG || !defined(NDEBUG)
#define RELIEF_FACTOR 16
#else
#define NN 4096
#define RELIEF_FACTOR 8
#endif
#elif MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
#define RELIEF_FACTOR 4
#elif UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
#define RELIEF_FACTOR 2
#else
#define RELIEF_FACTOR 1
#endif
#define NN (2048 / RELIEF_FACTOR)
std::string format_va(const char *fmt, va_list ap) {
va_list ones;
@ -347,12 +357,7 @@ bool simple(mdbx::env env) {
return true;
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
int doit() {
mdbx::path db_filename = "test-crunched-del";
mdbx::env::remove(db_filename);
@ -390,3 +395,15 @@ int main(int argc, const char *argv[]) {
std::cout << "OK\n";
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -1,6 +1,29 @@
#include "mdbx.h++"
#include <chrono>
#include <deque>
#include <iostream>
#include <vector>
#if defined(__cpp_lib_latch) && __cpp_lib_latch >= 201907L
#include <latch>
#include <thread>
#endif
#if defined(ENABLE_MEMCHECK) || defined(MDBX_CI)
#if MDBX_DEBUG || !defined(NDEBUG)
#define RELIEF_FACTOR 16
#else
#define RELIEF_FACTOR 8
#endif
#elif MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
#define RELIEF_FACTOR 4
#elif UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
#define RELIEF_FACTOR 2
#else
#define RELIEF_FACTOR 1
#endif
#define NN (1000 / RELIEF_FACTOR)
static void logger_nofmt(MDBX_log_level_t loglevel, const char *function, int line, const char *msg,
unsigned length) noexcept {
@ -11,21 +34,11 @@ static void logger_nofmt(MDBX_log_level_t loglevel, const char *function, int li
static char log_buffer[1024];
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
//--------------------------------------------------------------------------------------------
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
mdbx::path db_filename = "test-cursor-closing";
mdbx::env::remove(db_filename);
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(),
mdbx::env::operate_parameters(42, 0, mdbx::env::nested_transactions));
{
bool case0(mdbx::env env) {
auto txn = env.start_write();
auto table = txn.create_map("dummy", mdbx::key_mode::usual, mdbx::value_mode::single);
auto table = txn.create_map("case0", mdbx::key_mode::usual, mdbx::value_mode::single);
auto cursor_1 = txn.open_cursor(table);
auto cursor_2 = cursor_1.clone();
@ -49,8 +62,325 @@ int main(int argc, const char *argv[]) {
cursor_1.close();
txn.commit();
cursor_2.close();
return true;
}
//--------------------------------------------------------------------------------------------
/* Сценарий:
*
* 0. Создаём N таблиц, курсор для каждой таблицы и заполняем (1000 ключей, от 1 до 1000 значений в каждом ключе).
* 1. Запускаем N-1 фоновых потоков и используем текущий/основной.
* 2. В каждом потоке 100500 раз повторяем последовательность действий:
* - 100500 раз запускаем читающую транзакцию и выполняем "читающий цикл":
* - в читающей транзакции создаем 0..3 курсоров, потом подключаем заранее созданный курсор,
* потом еще 0..3 курсоров;
* - выполняем по паре поисков через каждый курсор;
* - отключаем заранее созданный курсор;
* - снова выполняем несколько поисков по каждому курсору;
* - псевдослучайно закрываем один из курсоров и один отключаем;
* - псевдослучайно выполняем один из путей:
* - закрываем все курсоры посредством mdbx_txn_release_all_cursors();
* - отсоединяем все курсоры посредством mdbx_txn_release_all_cursors();
* - псевдослучайно закрываем один из курсоров и один отключаем;
* - ничего не делаем;
* - завершаем читающую транзакцию псевдослучайно выбирая между commit и abort;
* - закрываем оставшиеся курсоры.
* 3. Выполняем "пишущий цикл":
* - запускаем пишущую или вложенную транзакцию;
* - из оставшихся с предыдущих итераций курсоров половину закрываем,
* половину подключаем к транзакции;
* - для каждой таблицы с вероятностью 1/2 выполняем "читающий цикл";
* - для каждой таблицы с вероятностью 1/2 выполняем "модифицирующий" цикл:
* - подключаем курсор, либо создаем при отсутствии подходящих;
* - 100 раз выполняем поиск случайных пар ключ/значение;
* - при успешном поиске удаляем значение, иначе вставляем;
* - с вероятностью 1/2 повторяем "читающий цикл";
* - с вероятностью 7/16 запускаем вложенную транзакцию:
* - действуем рекурсивно как с пишущей транзакцией;
* - в "читающих циклах" немного меняем поведение:
* - игнорируем ожидаемые ошибки mdbx_cursor_unbind();
* - в 2-3 раза уменьшаем вероятность использования mdbx_txn_release_all_cursors();
* - завершаем вложенную транзакцию псевдослучайно выбирая между commit и abort;
* - для каждой таблицы с вероятностью 1/2 выполняем "читающий цикл";
* - завершаем транзакцию псевдослучайно выбирая между commit и abort;
* 4. Ждем завершения фоновых потоков.
* 5. Закрываем оставшиеся курсоры и закрываем БД. */
thread_local size_t salt;
static size_t prng() {
salt = salt * 134775813 + 1;
return salt ^ ((salt >> 11) * 1822226723);
}
static inline bool flipcoin() { return prng() & 1; }
static inline size_t prng(size_t range) { return prng() % range; }
void case1_shuffle_pool(std::vector<MDBX_cursor *> &pool) {
for (size_t n = 1; n < pool.size(); ++n) {
const auto i = prng(n);
if (i != n)
std::swap(pool[n], pool[i]);
}
}
void case1_read_pool(std::vector<MDBX_cursor *> &pool) {
for (auto c : pool)
if (flipcoin())
mdbx::cursor(c).find_multivalue(mdbx::slice::wrap(prng(NN)), mdbx::slice::wrap(prng(NN)), false);
for (auto c : pool)
if (flipcoin())
mdbx::cursor(c).find_multivalue(mdbx::slice::wrap(prng(NN)), mdbx::slice::wrap(prng(NN)), false);
}
MDBX_cursor *case1_try_unbind(MDBX_cursor *cursor) {
if (cursor) {
auto err = mdbx::error(static_cast<MDBX_error_t>(mdbx_cursor_unbind(cursor)));
if (err.code() != MDBX_EINVAL)
err.success_or_throw();
}
return cursor;
}
MDBX_cursor *case1_pool_remove(std::vector<MDBX_cursor *> &pool) {
switch (pool.size()) {
case 0:
return nullptr;
case 1:
if (flipcoin()) {
const auto c = pool[0];
pool.pop_back();
return c;
}
return nullptr;
default:
const auto i = prng(pool.size());
const auto c = pool[i];
pool.erase(pool.begin() + i);
return c;
}
}
mdbx::map_handle case1_cycle_dbi(std::deque<mdbx::map_handle> &dbi) {
const auto h = dbi.front();
dbi.pop_front();
dbi.push_back(h);
return h;
}
void case1_read_cycle(mdbx::txn txn, std::deque<mdbx::map_handle> &dbi, std::vector<MDBX_cursor *> &pool,
mdbx::cursor pre, bool nested = false) {
for (auto c : pool)
mdbx::cursor(c).bind(txn, case1_cycle_dbi(dbi));
pre.bind(txn, case1_cycle_dbi(dbi));
for (auto n = prng(3 + dbi.size()); n > 0; --n) {
auto c = txn.open_cursor(dbi[prng(dbi.size())]);
pool.push_back(c.withdraw_handle());
}
case1_shuffle_pool(pool);
case1_read_pool(pool);
pool.push_back(pre);
case1_read_pool(pool);
pool.pop_back();
for (auto n = prng(3 + dbi.size()); n > 0; --n) {
auto c = txn.open_cursor(dbi[prng(dbi.size())]);
pool.push_back(c.withdraw_handle());
}
pool.push_back(pre);
case1_read_pool(pool);
pool.pop_back();
case1_try_unbind(pre);
case1_shuffle_pool(pool);
case1_read_pool(pool);
if (flipcoin()) {
mdbx_cursor_close(case1_pool_remove(pool));
auto u = case1_try_unbind(case1_pool_remove(pool));
case1_read_pool(pool);
if (u)
pool.push_back(u);
} else {
auto u = case1_try_unbind(case1_pool_remove(pool));
mdbx_cursor_close(case1_pool_remove(pool));
case1_read_pool(pool);
if (u)
pool.push_back(u);
}
switch (prng(nested ? 7 : 3)) {
case 0:
for (auto i = pool.begin(); i != pool.end();)
if (mdbx_cursor_txn(*i))
i = pool.erase(i);
else
++i;
txn.close_all_cursors();
break;
case 1:
txn.unbind_all_cursors();
break;
}
}
void case1_write_cycle(mdbx::txn_managed txn, std::deque<mdbx::map_handle> &dbi, std::vector<MDBX_cursor *> &pool,
mdbx::cursor pre, bool nested = false) {
if (flipcoin())
case1_cycle_dbi(dbi);
if (flipcoin())
case1_shuffle_pool(pool);
for (auto n = prng(dbi.size() + 1); n > 1; n -= 2) {
if (!nested)
pre.unbind();
if (!pre.txn())
pre.bind(txn, dbi[prng(dbi.size())]);
for (auto i = 0; i < NN; ++i) {
auto k = mdbx::default_buffer::wrap(prng(NN));
auto v = mdbx::default_buffer::wrap(prng(NN));
if (pre.find_multivalue(k, v, false))
pre.erase();
else
pre.upsert(k, v);
}
}
if (prng(16) > 8)
case1_write_cycle(txn.start_nested(), dbi, pool, pre, true);
if (flipcoin())
txn.commit();
else
txn.abort();
}
bool case1_thread(mdbx::env env, std::deque<mdbx::map_handle> dbi, mdbx::cursor pre) {
salt = size_t(std::chrono::high_resolution_clock::now().time_since_epoch().count());
std::vector<MDBX_cursor *> pool;
for (auto loop = 0; loop < 333 / RELIEF_FACTOR; ++loop) {
for (auto read = 0; read < 333 / RELIEF_FACTOR; ++read) {
auto txn = env.start_read();
case1_read_cycle(txn, dbi, pool, pre);
if (flipcoin())
txn.commit();
else
txn.abort();
}
case1_write_cycle(env.start_write(), dbi, pool, pre);
for (auto c : pool)
mdbx_cursor_close(c);
pool.clear();
}
pre.unbind();
return true;
}
bool case1(mdbx::env env) {
bool ok = true;
std::deque<mdbx::map_handle> dbi;
std::vector<mdbx::cursor_managed> cursors;
#if defined(__cpp_lib_latch) && __cpp_lib_latch >= 201907L
static const auto N = 10;
#else
static const auto N = 3;
#endif
for (auto t = 0; t < N; ++t) {
auto txn = env.start_write();
auto table = txn.create_map(std::to_string(t), mdbx::key_mode::ordinal, mdbx::value_mode::multi_samelength);
auto cursor = txn.open_cursor(table);
for (size_t i = 0; i < NN * 11; ++i)
cursor.upsert(mdbx::default_buffer::wrap(prng(NN)), mdbx::default_buffer::wrap(prng(NN)));
txn.commit();
cursors.push_back(std::move(cursor));
dbi.push_back(table);
}
#if defined(__cpp_lib_latch) && __cpp_lib_latch >= 201907L
std::latch s(1);
std::vector<std::thread> threads;
for (auto t = 1; t < N; ++t) {
case1_cycle_dbi(dbi);
threads.push_back(std::thread([&, t]() {
s.wait();
if (!case1_thread(env, dbi, cursors[t]))
ok = false;
}));
}
case1_cycle_dbi(dbi);
s.count_down();
#endif
if (!case1_thread(env, dbi, cursors[0]))
ok = false;
#if defined(__cpp_lib_latch) && __cpp_lib_latch >= 201907L
for (auto &t : threads)
t.join();
#endif
return ok;
}
//--------------------------------------------------------------------------------------------
bool case2(mdbx::env env) {
bool ok = true;
auto txn = env.start_write();
auto dbi = txn.create_map("case2", mdbx::key_mode::usual, mdbx::value_mode::single);
txn.commit_embark_read();
auto cursor1 = txn.open_cursor(dbi);
auto cursor2 = txn.open_cursor(0);
cursor1.move(mdbx::cursor::next, false);
cursor2.move(mdbx::cursor::next, false);
txn.commit_embark_read();
cursor2.bind(txn, dbi);
cursor1.bind(txn, 0);
cursor1.move(mdbx::cursor::last, false);
cursor2.move(mdbx::cursor::last, false);
return ok;
}
//--------------------------------------------------------------------------------------------
int doit() {
mdbx::path db_filename = "test-cursor-closing";
mdbx::env::remove(db_filename);
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(),
mdbx::env::operate_parameters(42, 0, mdbx::env::nested_transactions));
bool ok = case0(env);
ok = case1(env) && ok;
ok = case2(env) && ok;
if (ok) {
std::cout << "OK\n";
return EXIT_SUCCESS;
} else {
std::cout << "FAIL!\n";
return EXIT_FAILURE;
}
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -11,28 +11,70 @@ static void logger_nofmt(MDBX_log_level_t loglevel, const char *function, int li
fprintf(stdout, "%s:%u %s", function, line, msg);
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
int doit() {
mdbx::path db_filename = "test-dbi";
mdbx::env::remove(db_filename);
mdbx::env::operate_parameters operateParameters(100, 10);
mdbx::env::operate_parameters operateParameters(100, 10, mdbx::env::nested_transactions);
mdbx::env_managed::create_parameters createParameters;
{
mdbx::env_managed env2(db_filename, createParameters, operateParameters);
mdbx::txn_managed txn2 = env2.start_write(false);
/* mdbx::map_handle testHandle2 = */ txn2.create_map("fap1", mdbx::key_mode::reverse, mdbx::value_mode::single);
txn2.commit();
}
mdbx::env_managed env(db_filename, createParameters, operateParameters);
mdbx::txn_managed txn = env.start_write(false);
/* mdbx::map_handle testHandle = */ txn.create_map("fap1", mdbx::key_mode::usual, mdbx::value_mode::single);
mdbx::txn_managed txn = env.start_write();
/* mdbx::map_handle dbi = */ txn.create_map("fap1", mdbx::key_mode::reverse, mdbx::value_mode::single);
txn.commit();
}
mdbx::env_managed env(db_filename, createParameters, operateParameters);
{
// проверяем доступность в родительской транзакции хендла открытого в дочерней транзакции после коммита
mdbx::txn_managed txn = env.start_write();
mdbx::txn_managed nested = txn.start_nested();
mdbx::map_handle dbi = nested.open_map_accede("fap1");
nested.commit();
MDBX_MAYBE_UNUSED auto stat = txn.get_map_stat(dbi);
txn.commit();
env.close_map(dbi);
}
{
// проверяем НЕ доступность в родительской транзакции хендла открытого в дочерней транзакции после прерывания
mdbx::txn_managed txn = env.start_write();
mdbx::txn_managed nested = txn.start_nested();
mdbx::map_handle dbi = nested.open_map_accede("fap1");
nested.abort();
MDBX_stat stat;
int err = mdbx_dbi_stat(txn, dbi, &stat, sizeof(stat));
if (err != MDBX_BAD_DBI) {
std::cerr << "unexpected result err-code " << err;
return EXIT_FAILURE;
}
txn.commit();
}
{
// снова проверяем что таблица открывается и хендл доступень в родительской транзакции после коммита открывшей его
// дочерней
mdbx::txn_managed txn = env.start_write();
mdbx::txn_managed nested = txn.start_nested();
mdbx::map_handle dbi = nested.open_map_accede("fap1");
nested.commit();
MDBX_MAYBE_UNUSED auto stat = txn.get_map_stat(dbi);
txn.commit();
env.close_map(dbi);
}
std::cout << "OK\n";
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -211,10 +211,7 @@ static bool test(mdbx::txn txn, mdbx::map_handle dbi) {
return ok;
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
int doit() {
mdbx::path db_filename = "test-posi";
mdbx::env_managed::remove(db_filename);
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(), mdbx::env::operate_parameters(3));
@ -243,3 +240,14 @@ int main(int argc, const char *argv[]) {
std::cout << "OK\n";
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -25,6 +25,9 @@ int main() {
MDBX_val key, data;
MDBX_txn *txn = NULL;
const char *db_filename = "./test-dupfix-addodd";
mdbx_env_delete(db_filename, MDBX_ENV_JUST_DELETE);
rc = mdbx_env_create(&env);
if (rc != MDBX_SUCCESS) {
fprintf(stderr, "mdbx_env_create: (%d) %s\n", rc, mdbx_strerror(rc));
@ -37,7 +40,7 @@ int main() {
exit(EXIT_FAILURE);
}
rc = mdbx_env_open(env, "./example-db", MDBX_NOSUBDIR | MDBX_LIFORECLAIM, 0664);
rc = mdbx_env_open(env, db_filename, MDBX_NOSUBDIR | MDBX_LIFORECLAIM, 0664);
if (rc != MDBX_SUCCESS) {
fprintf(stderr, "mdbx_env_open: (%d) %s\n", rc, mdbx_strerror(rc));
exit(EXIT_FAILURE);

View File

@ -2,17 +2,28 @@
/// \copyright SPDX-License-Identifier: Apache-2.0
#include "mdbx.h++"
#include <array>
#include <chrono>
#include <iostream>
int doit() {
mdbx::path db_filename = "test-dupfix-multiple";
mdbx::env_managed::remove(db_filename);
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(), mdbx::env::operate_parameters());
#if defined(ENABLE_MEMCHECK) || defined(MDBX_CI)
#if MDBX_DEBUG || !defined(NDEBUG)
#define RELIEF_FACTOR 16
#else
#define RELIEF_FACTOR 8
#endif
#elif MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
#define RELIEF_FACTOR 4
#elif UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
#define RELIEF_FACTOR 2
#else
#define RELIEF_FACTOR 1
#endif
using buffer = mdbx::buffer<mdbx::default_allocator, mdbx::default_capacity_policy>;
using buffer = mdbx::default_buffer;
bool case1_ordering(mdbx::env env) {
auto txn = env.start_write();
auto map = txn.create_map(nullptr, mdbx::key_mode::ordinal, mdbx::value_mode::multi_ordinal);
auto map = txn.create_map("case1", mdbx::key_mode::ordinal, mdbx::value_mode::multi_ordinal);
txn.insert(map, buffer::key_from_u64(21), buffer::key_from_u64(18));
txn.insert(map, buffer::key_from_u64(7), buffer::key_from_u64(19));
@ -30,10 +41,9 @@ int doit() {
cursor.to_next().value.as_uint64() != 17 || cursor.to_next().value.as_uint64() != 16 ||
cursor.to_next().value.as_uint64() != 15 || cursor.to_next().value.as_uint64() != 14 ||
cursor.to_next().value.as_uint64() != 13 || cursor.to_next().value.as_uint64() != 12 ||
cursor.to_next(false).done || !cursor.eof()) {
std::cerr << "Fail\n";
return EXIT_FAILURE;
}
cursor.to_next(false).done || !cursor.eof())
return false;
txn.abort();
const uint64_t array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 42, 17, 99, 0, 33, 333};
@ -87,10 +97,9 @@ int doit() {
/* key = 24 */ cursor.to_next().value.as_uint64() != 15 ||
/* key = 25 */ cursor.to_next().value.as_uint64() != 14 ||
/* key = 26 */ cursor.to_next().value.as_uint64() != 13 ||
/* key = 27 */ cursor.to_next().value.as_uint64() != 12 || cursor.to_next(false).done || !cursor.eof()) {
std::cerr << "Fail\n";
return EXIT_FAILURE;
}
/* key = 27 */ cursor.to_next().value.as_uint64() != 12 || cursor.to_next(false).done || !cursor.eof())
return false;
txn.abort();
txn = env.start_write();
@ -163,40 +172,24 @@ int doit() {
cursor.to_next().value.as_uint64() != 0 || cursor.to_next().value.as_uint64() != 33 ||
cursor.to_next().value.as_uint64() != 333 ||
cursor.to_next(false).done || !cursor.eof()) {
std::cerr << "Fail\n";
return EXIT_FAILURE;
}
cursor.to_next(false).done || !cursor.eof())
return false;
txn.abort();
//----------------------------------------------------------------------------
// let dir = tempdir().unwrap();
// let db = Database::open(&dir).unwrap();
// let txn = db.begin_rw_txn().unwrap();
// let table = txn
// .create_table(None, TableFlags::DUP_SORT | TableFlags::DUP_FIXED)
// .unwrap();
// for (k, v) in [
// (b"key1", b"val1"),
// (b"key1", b"val2"),
// (b"key1", b"val3"),
// (b"key2", b"val1"),
// (b"key2", b"val2"),
// (b"key2", b"val3"),
// ] {
// txn.put(&table, k, v, WriteFlags::empty()).unwrap();
// }
// let mut cursor = txn.cursor(&table).unwrap();
// assert_eq!(cursor.first().unwrap(), Some((*b"key1", *b"val1")));
// assert_eq!(cursor.get_multiple().unwrap(), Some(*b"val1val2val3"));
// assert_eq!(cursor.next_multiple::<(), ()>().unwrap(), None);
txn = env.start_write();
txn.clear_map(map);
map = txn.create_map(nullptr, mdbx::key_mode::usual, mdbx::value_mode::multi_samelength);
txn.commit();
return true;
}
//--------------------------------------------------------------------------------------------
bool case2_batch_read(mdbx::env env) {
auto txn = env.start_write();
auto map = txn.create_map("case2", mdbx::key_mode::usual, mdbx::value_mode::multi_samelength);
txn.upsert(map, mdbx::slice("key1"), mdbx::slice("val1"));
txn.upsert(map, mdbx::pair("key1", "val2"));
txn.upsert(map, mdbx::pair("key1", "val3"));
@ -205,36 +198,110 @@ int doit() {
txn.upsert(map, mdbx::pair("key2", "val3"));
// cursor.close();
cursor = txn.open_cursor(map);
auto cursor = txn.open_cursor(map);
const auto t1 = cursor.to_first();
if (!t1 || t1.key != "key1" || t1.value != "val1") {
std::cerr << "Fail-t1\n";
return EXIT_FAILURE;
return false;
}
const auto t2 = cursor.get_multiple_samelength();
if (!t2 || t2.key != "key1" || t2.value != "val1val2val3") {
std::cerr << "Fail-t2\n";
return EXIT_FAILURE;
return false;
}
// const auto t3 = cursor.get_multiple_samelength("key2");
// if (!t3 || t3.key != "key2" || t3.value != "val1val2val3") {
// std::cerr << "Fail-t3\n";
// return EXIT_FAILURE;
// }
const auto t4 = cursor.next_multiple_samelength();
if (t4) {
const auto t3 = cursor.next_multiple_samelength();
if (t3) {
std::cerr << "Fail-t3\n";
return false;
}
const auto t4 = cursor.seek_multiple_samelength("key2");
if (!t4 || t4.key != "key2" || t4.value != "val1val2val3") {
std::cerr << "Fail-t4\n";
return EXIT_FAILURE;
return false;
}
txn.clear_map(map);
txn.commit();
return true;
}
//--------------------------------------------------------------------------------------------
size_t salt;
static size_t prng() {
salt = salt * 134775813 + 1;
return salt ^ ((salt >> 11) * 1822226723);
}
static inline size_t prng(size_t range) { return prng() % range; }
static mdbx::default_buffer_pair prng_kv(size_t n, size_t space) {
space = (space + !space) * 1024 * 32 / RELIEF_FACTOR;
const size_t w = (n ^ 1455614549) * 1664525 + 1013904223;
const size_t k = (prng(42 + w % space) ^ 1725278851) * 433750991;
const size_t v = prng();
return mdbx::default_buffer_pair(mdbx::slice::wrap(k), mdbx::slice::wrap(v));
}
bool case3_put_a_lot(mdbx::env env) {
salt = size_t(std::chrono::high_resolution_clock::now().time_since_epoch().count());
auto txn = env.start_write();
auto map = txn.create_map("case3", mdbx::key_mode::ordinal, mdbx::value_mode::multi_ordinal);
for (size_t n = 0; n < 5555555 / RELIEF_FACTOR; ++n)
txn.upsert(map, prng_kv(n, 1));
txn.commit();
for (size_t t = 0; t < 555 / RELIEF_FACTOR; ++t) {
txn = env.start_write();
auto cursor = txn.open_cursor(map);
for (size_t n = 0; n < 111; ++n) {
auto v = std::vector<size_t>();
const auto r = 1 + prng(3);
if (r & 1) {
const auto k = prng_kv(n + t, 2).key;
for (size_t i = prng(42 + prng(111) * prng(111 / RELIEF_FACTOR)); i > 0; --i)
v.push_back(prng());
txn.put_multiple_samelength(map, k, v, mdbx::upsert);
}
if (r & 2) {
const auto k = prng_kv(n + t, 2).key;
if (cursor.seek(k)) {
v.clear();
for (size_t i = prng(42 + prng(111) * prng(111 / RELIEF_FACTOR)); i > 0; --i)
v.push_back(prng());
cursor.put_multiple_samelength(k, v, mdbx::upsert);
}
}
}
txn.commit();
}
return true;
}
int doit() {
mdbx::path db_filename = "test-dupfix-multiple";
mdbx::env_managed::remove(db_filename);
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(), mdbx::env::operate_parameters(3));
bool ok = case1_ordering(env);
ok = case2_batch_read(env) && ok;
ok = case3_put_a_lot(env) && ok;
if (ok) {
std::cout << "OK\n";
return EXIT_SUCCESS;
} else {
std::cerr << "Fail\n";
return EXIT_FAILURE;
}
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
try {
return doit();
} catch (const std::exception &ex) {

View File

@ -1,13 +1,11 @@
#include "mdbx.h++"
#include <cstring>
#include <iostream>
static const char *const testkey = "testkey";
static uint64_t testval = 11;
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
int doit() {
mdbx::path db_filename = "test-early_close_dbi";
mdbx::env_managed::remove(db_filename);
@ -67,13 +65,13 @@ int main(int argc, char *argv[]) {
assert(err == MDBX_SUCCESS);
err = mdbx_get(transaction, textindex, &mdbxkey, &mdbxval);
assert(err == MDBX_SUCCESS);
assert(testval == *reinterpret_cast<uint64_t *>(mdbxval.iov_base));
assert(testval == mdbx::slice(mdbxval).as_uint64());
err = mdbx_put(transaction, textindex, &mdbxkey, &mdbxput, MDBX_NOOVERWRITE);
assert(err == MDBX_KEYEXIST);
err = mdbx_get(transaction, textindex, &mdbxkey, &mdbxval);
assert(err == MDBX_SUCCESS);
assert(testval == *reinterpret_cast<uint64_t *>(mdbxval.iov_base));
assert(testval == mdbx::slice(mdbxval).as_uint64());
err = mdbx_dbi_flags_ex(transaction, textindex, &dbi_flags, &dbi_state);
assert(err == MDBX_SUCCESS);
@ -89,7 +87,7 @@ int main(int argc, char *argv[]) {
assert(err == MDBX_SUCCESS);
err = mdbx_get(transaction, textindex, &mdbxkey, &mdbxval);
assert(err == MDBX_SUCCESS);
assert(testval == *reinterpret_cast<uint64_t *>(mdbxval.iov_base));
assert(testval == mdbx::slice(mdbxval).as_uint64());
err = mdbx_dbi_close(environment, textindex);
assert(err == MDBX_SUCCESS);
@ -126,3 +124,14 @@ int main(int argc, char *argv[]) {
return 0;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -75,10 +75,7 @@ static bool basic() {
return ok;
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
int doit() {
auto ok = basic();
for (size_t n = 0; n < 1000; ++n) {
for (size_t length = 0; ok && length < 111; ++length) {
@ -108,3 +105,14 @@ int main(int argc, const char *argv[]) {
std::cout << "OK\n";
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -4,11 +4,8 @@
#include "mdbx.h++"
#include <iostream>
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
mdbx::path db_filename = "test-dupfix-multiple";
static int doit() {
mdbx::path db_filename = "test-maindb-ordinal";
mdbx::env_managed::remove(db_filename);
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(), mdbx::env::operate_parameters());
@ -51,3 +48,14 @@ int main(int argc, const char *argv[]) {
return EXIT_SUCCESS;
#endif /* __cpp_lib_string_view >= 201606L */
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -25,12 +25,7 @@ static void logger_nofmt(MDBX_log_level_t loglevel, const char *function, int li
fprintf(stdout, "%s:%u %s", function, line, msg);
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_VERBOSE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
int doit() {
mdbx::path path = "test-open";
mdbx::env::remove(path);
@ -44,6 +39,13 @@ int main(int argc, const char *argv[]) {
txn2.commit();
}
{
mdbx::env::operate_parameters operateParameters(100, 10);
mdbx::env_managed::create_parameters createParameters;
createParameters.geometry.make_dynamic(21 * mdbx::env::geometry::MiB, mdbx::env::geometry::GiB / 2);
mdbx::env_managed env(path, createParameters, operateParameters);
}
mdbx::env::operate_parameters operateParameters(100, 10);
mdbx::env_managed::create_parameters createParameters;
createParameters.geometry.make_dynamic(21 * mdbx::env::geometry::MiB, 84 * mdbx::env::geometry::MiB);
@ -79,4 +81,16 @@ int main(int argc, const char *argv[]) {
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_VERBOSE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}
#endif /* __cpp_lib_latch */

View File

@ -1,7 +1,22 @@
#include "mdbx.h++"
#include MDBX_CONFIG_H
#include <iostream>
#if defined(ENABLE_MEMCHECK) || defined(MDBX_CI)
#if MDBX_DEBUG || !defined(NDEBUG)
#define RELIEF_FACTOR 16
#else
#define RELIEF_FACTOR 8
#endif
#elif MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
#define RELIEF_FACTOR 4
#elif UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
#define RELIEF_FACTOR 2
#else
#define RELIEF_FACTOR 1
#endif
#if !defined(__cpp_lib_latch) && __cpp_lib_latch < 201907L
int main(int argc, const char *argv[]) {
@ -25,22 +40,11 @@ static void logger_nofmt(MDBX_log_level_t loglevel, const char *function, int li
fprintf(stdout, "%s:%u %s", function, line, msg);
}
int main(int argc, const char *argv[]) {
(void)argc;
(void)argv;
bool ok = true;
int err;
mdbx_setup_debug_nofmt(MDBX_LOG_VERBOSE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
mdbx::path path = "test-txn";
mdbx::env::remove(path);
mdbx::env::operate_parameters operateParameters(100, 10);
{
bool case0(const mdbx::path &path) {
mdbx::env_managed::create_parameters createParameters;
createParameters.geometry.make_dynamic(21 * mdbx::env::geometry::MiB, 84 * mdbx::env::geometry::MiB);
mdbx::env::operate_parameters operateParameters(100, 10);
operateParameters.options.no_sticky_threads = false;
mdbx::env_managed env(path, createParameters, operateParameters);
auto txn = env.start_write(false);
@ -50,9 +54,9 @@ int main(int argc, const char *argv[]) {
//-------------------------------------
txn = env.start_write();
MDBX_txn *c_txn = txn;
err = mdbx_txn_reset(txn);
int err = mdbx_txn_reset(txn);
assert(err == MDBX_EINVAL);
ok = ok && err == MDBX_EINVAL;
bool ok = err == MDBX_EINVAL;
err = mdbx_txn_break(txn);
assert(err == MDBX_SUCCESS);
@ -131,6 +135,7 @@ int main(int argc, const char *argv[]) {
std::thread t([&]() {
s.wait();
#if MDBX_TXN_CHECKOWNER
err = mdbx_txn_reset(c_txn);
assert(err == MDBX_THREAD_MISMATCH);
ok = ok && err == MDBX_THREAD_MISMATCH;
@ -143,19 +148,26 @@ int main(int argc, const char *argv[]) {
err = mdbx_txn_abort(c_txn);
assert(err == MDBX_THREAD_MISMATCH);
ok = ok && err == MDBX_THREAD_MISMATCH;
#endif /* MDBX_TXN_CHECKOWNER */
err = mdbx_txn_begin(env, txn, MDBX_TXN_READWRITE, &c_txn);
#if MDBX_TXN_CHECKOWNER
assert(err == MDBX_THREAD_MISMATCH);
ok = ok && err == MDBX_THREAD_MISMATCH;
#else
assert(err == MDBX_BAD_TXN);
ok = ok && err == MDBX_BAD_TXN;
#endif /* MDBX_TXN_CHECKOWNER */
});
s.count_down();
t.join();
return ok;
}
//=====================================
//=====================================
{
bool case1(const mdbx::path &path) {
mdbx::env::operate_parameters operateParameters(100, 10);
operateParameters.options.no_sticky_threads = true;
operateParameters.options.nested_write_transactions = true;
mdbx::env_managed env(path, operateParameters);
@ -163,9 +175,9 @@ int main(int argc, const char *argv[]) {
//-------------------------------------
auto txn = env.start_write();
MDBX_txn *c_txn = txn;
err = mdbx_txn_reset(txn);
int err = mdbx_txn_reset(txn);
assert(err == MDBX_EINVAL);
ok = ok && err == MDBX_EINVAL;
bool ok = err == MDBX_EINVAL;
err = mdbx_txn_break(txn);
assert(err == MDBX_SUCCESS);
@ -283,10 +295,56 @@ int main(int argc, const char *argv[]) {
t.join();
txn.abort();
return ok;
}
bool case2(const mdbx::path &path, bool no_sticky_threads) {
mdbx::env::operate_parameters operateParameters(100, 10);
operateParameters.options.no_sticky_threads = no_sticky_threads;
mdbx::env_managed env(path, operateParameters);
std::latch s(1);
std::vector<std::thread> l;
for (size_t n = 0; n < 8; ++n)
l.push_back(std::thread([&]() {
s.wait();
for (size_t i = 0; i < 1000000 / RELIEF_FACTOR; ++i) {
auto txn = env.start_read();
txn.abort();
}
}));
s.count_down();
for (auto &t : l)
t.join();
return true;
}
int doit() {
mdbx::path path = "test-txn";
mdbx::env::remove(path);
bool ok = case0(path);
ok = case1(path) && ok;
ok = case2(path, false) && ok;
ok = case2(path, true) && ok;
std::cout << (ok ? "OK\n" : "FAIL\n");
return ok ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
mdbx_setup_debug_nofmt(MDBX_LOG_VERBOSE, MDBX_DBG_ASSERT, logger_nofmt, log_buffer, sizeof(log_buffer));
try {
return doit();
} catch (const std::exception &ex) {
std::cerr << "Exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
}
#endif /* __cpp_lib_latch */

View File

@ -562,7 +562,7 @@ int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) {
continue;
}
if (pid == 0)
if (pid == 0 || sigbreak)
break;
if (err != EINTR)

View File

@ -440,7 +440,7 @@ else
fi
if [ "$EXTRA" != "no" ]; then
options=(perturb nomeminit nordahead writemap lifo nostickythreads)
options=(perturb nomeminit nordahead writemap lifo nostickythreads validation)
else
options=(writemap lifo nostickythreads)
fi