mdbxmou 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/.github/workflows/ci.yml +32 -0
  2. package/.github/workflows/publish.yml +27 -0
  3. package/.gitmodules +3 -0
  4. package/CMakeLists.txt +53 -0
  5. package/LICENSE +201 -0
  6. package/README.md +639 -0
  7. package/build.js +11 -0
  8. package/deps/libmdbx/.clang-format +3 -0
  9. package/deps/libmdbx/.cmake-format.yaml +3 -0
  10. package/deps/libmdbx/.le.ini +40 -0
  11. package/deps/libmdbx/CMakeLists.txt +1269 -0
  12. package/deps/libmdbx/COPYRIGHT +159 -0
  13. package/deps/libmdbx/ChangeLog.md +2786 -0
  14. package/deps/libmdbx/GNUmakefile +950 -0
  15. package/deps/libmdbx/LICENSE +177 -0
  16. package/deps/libmdbx/Makefile +16 -0
  17. package/deps/libmdbx/NOTICE +39 -0
  18. package/deps/libmdbx/README.md +863 -0
  19. package/deps/libmdbx/TODO.md +43 -0
  20. package/deps/libmdbx/cmake/compiler.cmake +1221 -0
  21. package/deps/libmdbx/cmake/profile.cmake +58 -0
  22. package/deps/libmdbx/cmake/utils.cmake +524 -0
  23. package/deps/libmdbx/conanfile.py +323 -0
  24. package/deps/libmdbx/docs/Doxyfile.in +2734 -0
  25. package/deps/libmdbx/docs/_preface.md +47 -0
  26. package/deps/libmdbx/docs/_restrictions.md +248 -0
  27. package/deps/libmdbx/docs/_starting.md +245 -0
  28. package/deps/libmdbx/docs/_toc.md +34 -0
  29. package/deps/libmdbx/docs/header.html +96 -0
  30. package/deps/libmdbx/example/CMakeLists.txt +6 -0
  31. package/deps/libmdbx/example/README.md +1 -0
  32. package/deps/libmdbx/example/example-mdbx.c +154 -0
  33. package/deps/libmdbx/example/sample-bdb.txt +77 -0
  34. package/deps/libmdbx/mdbx.h +6655 -0
  35. package/deps/libmdbx/mdbx.h++ +6428 -0
  36. package/deps/libmdbx/packages/buildroot/0001-package-libmdbx-new-package-library-database.patch +173 -0
  37. package/deps/libmdbx/src/alloy.c +54 -0
  38. package/deps/libmdbx/src/api-cold.c +543 -0
  39. package/deps/libmdbx/src/api-copy.c +912 -0
  40. package/deps/libmdbx/src/api-cursor.c +754 -0
  41. package/deps/libmdbx/src/api-dbi.c +315 -0
  42. package/deps/libmdbx/src/api-env.c +1434 -0
  43. package/deps/libmdbx/src/api-extra.c +165 -0
  44. package/deps/libmdbx/src/api-key-transform.c +197 -0
  45. package/deps/libmdbx/src/api-misc.c +286 -0
  46. package/deps/libmdbx/src/api-opts.c +575 -0
  47. package/deps/libmdbx/src/api-range-estimate.c +365 -0
  48. package/deps/libmdbx/src/api-txn-data.c +454 -0
  49. package/deps/libmdbx/src/api-txn.c +921 -0
  50. package/deps/libmdbx/src/atomics-ops.h +364 -0
  51. package/deps/libmdbx/src/atomics-types.h +97 -0
  52. package/deps/libmdbx/src/audit.c +109 -0
  53. package/deps/libmdbx/src/bits.md +34 -0
  54. package/deps/libmdbx/src/chk.c +1796 -0
  55. package/deps/libmdbx/src/cogs.c +309 -0
  56. package/deps/libmdbx/src/cogs.h +506 -0
  57. package/deps/libmdbx/src/coherency.c +170 -0
  58. package/deps/libmdbx/src/config.h.in +88 -0
  59. package/deps/libmdbx/src/cursor.c +2396 -0
  60. package/deps/libmdbx/src/cursor.h +391 -0
  61. package/deps/libmdbx/src/dbi.c +717 -0
  62. package/deps/libmdbx/src/dbi.h +142 -0
  63. package/deps/libmdbx/src/debug_begin.h +36 -0
  64. package/deps/libmdbx/src/debug_end.h +15 -0
  65. package/deps/libmdbx/src/dpl.c +486 -0
  66. package/deps/libmdbx/src/dpl.h +134 -0
  67. package/deps/libmdbx/src/dxb.c +1335 -0
  68. package/deps/libmdbx/src/env.c +607 -0
  69. package/deps/libmdbx/src/essentials.h +125 -0
  70. package/deps/libmdbx/src/gc-get.c +1345 -0
  71. package/deps/libmdbx/src/gc-put.c +970 -0
  72. package/deps/libmdbx/src/gc.h +40 -0
  73. package/deps/libmdbx/src/global.c +474 -0
  74. package/deps/libmdbx/src/internals.h +585 -0
  75. package/deps/libmdbx/src/layout-dxb.h +288 -0
  76. package/deps/libmdbx/src/layout-lck.h +289 -0
  77. package/deps/libmdbx/src/lck-posix.c +859 -0
  78. package/deps/libmdbx/src/lck-windows.c +607 -0
  79. package/deps/libmdbx/src/lck.c +174 -0
  80. package/deps/libmdbx/src/lck.h +110 -0
  81. package/deps/libmdbx/src/logging_and_debug.c +250 -0
  82. package/deps/libmdbx/src/logging_and_debug.h +159 -0
  83. package/deps/libmdbx/src/man1/mdbx_chk.1 +106 -0
  84. package/deps/libmdbx/src/man1/mdbx_copy.1 +95 -0
  85. package/deps/libmdbx/src/man1/mdbx_drop.1 +48 -0
  86. package/deps/libmdbx/src/man1/mdbx_dump.1 +101 -0
  87. package/deps/libmdbx/src/man1/mdbx_load.1 +105 -0
  88. package/deps/libmdbx/src/man1/mdbx_stat.1 +86 -0
  89. package/deps/libmdbx/src/mdbx.c++ +1837 -0
  90. package/deps/libmdbx/src/meta.c +656 -0
  91. package/deps/libmdbx/src/meta.h +168 -0
  92. package/deps/libmdbx/src/mvcc-readers.c +414 -0
  93. package/deps/libmdbx/src/node.c +365 -0
  94. package/deps/libmdbx/src/node.h +102 -0
  95. package/deps/libmdbx/src/ntdll.def +1246 -0
  96. package/deps/libmdbx/src/options.h +534 -0
  97. package/deps/libmdbx/src/osal.c +3485 -0
  98. package/deps/libmdbx/src/osal.h +587 -0
  99. package/deps/libmdbx/src/page-get.c +483 -0
  100. package/deps/libmdbx/src/page-iov.c +185 -0
  101. package/deps/libmdbx/src/page-iov.h +34 -0
  102. package/deps/libmdbx/src/page-ops.c +744 -0
  103. package/deps/libmdbx/src/page-ops.h +142 -0
  104. package/deps/libmdbx/src/pnl.c +236 -0
  105. package/deps/libmdbx/src/pnl.h +146 -0
  106. package/deps/libmdbx/src/preface.h +990 -0
  107. package/deps/libmdbx/src/proto.h +105 -0
  108. package/deps/libmdbx/src/refund.c +212 -0
  109. package/deps/libmdbx/src/sort.h +484 -0
  110. package/deps/libmdbx/src/spill.c +431 -0
  111. package/deps/libmdbx/src/spill.h +74 -0
  112. package/deps/libmdbx/src/table.c +107 -0
  113. package/deps/libmdbx/src/tls.c +551 -0
  114. package/deps/libmdbx/src/tls.h +43 -0
  115. package/deps/libmdbx/src/tools/chk.c +673 -0
  116. package/deps/libmdbx/src/tools/copy.c +166 -0
  117. package/deps/libmdbx/src/tools/drop.c +199 -0
  118. package/deps/libmdbx/src/tools/dump.c +515 -0
  119. package/deps/libmdbx/src/tools/load.c +831 -0
  120. package/deps/libmdbx/src/tools/stat.c +516 -0
  121. package/deps/libmdbx/src/tools/wingetopt.c +87 -0
  122. package/deps/libmdbx/src/tools/wingetopt.h +30 -0
  123. package/deps/libmdbx/src/tree-ops.c +1554 -0
  124. package/deps/libmdbx/src/tree-search.c +140 -0
  125. package/deps/libmdbx/src/txl.c +99 -0
  126. package/deps/libmdbx/src/txl.h +26 -0
  127. package/deps/libmdbx/src/txn.c +1083 -0
  128. package/deps/libmdbx/src/unaligned.h +205 -0
  129. package/deps/libmdbx/src/utils.c +32 -0
  130. package/deps/libmdbx/src/utils.h +76 -0
  131. package/deps/libmdbx/src/version.c.in +44 -0
  132. package/deps/libmdbx/src/walk.c +290 -0
  133. package/deps/libmdbx/src/walk.h +20 -0
  134. package/deps/libmdbx/src/windows-import.c +152 -0
  135. package/deps/libmdbx/src/windows-import.h +128 -0
  136. package/deps/libmdbx/test/CMakeLists.txt +317 -0
  137. package/deps/libmdbx/test/append.c++ +237 -0
  138. package/deps/libmdbx/test/base.h++ +92 -0
  139. package/deps/libmdbx/test/battery-tmux.sh +64 -0
  140. package/deps/libmdbx/test/cases.c++ +118 -0
  141. package/deps/libmdbx/test/chrono.c++ +134 -0
  142. package/deps/libmdbx/test/chrono.h++ +85 -0
  143. package/deps/libmdbx/test/config.c++ +643 -0
  144. package/deps/libmdbx/test/config.h++ +334 -0
  145. package/deps/libmdbx/test/copy.c++ +62 -0
  146. package/deps/libmdbx/test/dead.c++ +39 -0
  147. package/deps/libmdbx/test/dump-load.sh +40 -0
  148. package/deps/libmdbx/test/extra/crunched_delete.c++ +409 -0
  149. package/deps/libmdbx/test/extra/cursor_closing.c++ +410 -0
  150. package/deps/libmdbx/test/extra/dbi.c++ +229 -0
  151. package/deps/libmdbx/test/extra/doubtless_positioning.c++ +253 -0
  152. package/deps/libmdbx/test/extra/dupfix_addodd.c +94 -0
  153. package/deps/libmdbx/test/extra/dupfix_multiple.c++ +311 -0
  154. package/deps/libmdbx/test/extra/early_close_dbi.c++ +137 -0
  155. package/deps/libmdbx/test/extra/hex_base64_base58.c++ +118 -0
  156. package/deps/libmdbx/test/extra/maindb_ordinal.c++ +61 -0
  157. package/deps/libmdbx/test/extra/open.c++ +96 -0
  158. package/deps/libmdbx/test/extra/pcrf/README.md +2 -0
  159. package/deps/libmdbx/test/extra/pcrf/pcrf_test.c +380 -0
  160. package/deps/libmdbx/test/extra/probe.c++ +10 -0
  161. package/deps/libmdbx/test/extra/txn.c++ +407 -0
  162. package/deps/libmdbx/test/extra/upsert_alldups.c +193 -0
  163. package/deps/libmdbx/test/fork.c++ +263 -0
  164. package/deps/libmdbx/test/hill.c++ +447 -0
  165. package/deps/libmdbx/test/jitter.c++ +197 -0
  166. package/deps/libmdbx/test/keygen.c++ +393 -0
  167. package/deps/libmdbx/test/keygen.h++ +130 -0
  168. package/deps/libmdbx/test/log.c++ +358 -0
  169. package/deps/libmdbx/test/log.h++ +91 -0
  170. package/deps/libmdbx/test/main.c++ +706 -0
  171. package/deps/libmdbx/test/nested.c++ +318 -0
  172. package/deps/libmdbx/test/osal-unix.c++ +647 -0
  173. package/deps/libmdbx/test/osal-windows.c++ +440 -0
  174. package/deps/libmdbx/test/osal.h++ +41 -0
  175. package/deps/libmdbx/test/stochastic.sh +690 -0
  176. package/deps/libmdbx/test/stub/LICENSE +24 -0
  177. package/deps/libmdbx/test/stub/README.md +8 -0
  178. package/deps/libmdbx/test/stub/pthread_barrier.c +104 -0
  179. package/deps/libmdbx/test/stub/pthread_barrier.h +77 -0
  180. package/deps/libmdbx/test/test.c++ +1551 -0
  181. package/deps/libmdbx/test/test.h++ +298 -0
  182. package/deps/libmdbx/test/tmux.conf +3 -0
  183. package/deps/libmdbx/test/try.c++ +30 -0
  184. package/deps/libmdbx/test/ttl.c++ +240 -0
  185. package/deps/libmdbx/test/utils.c++ +203 -0
  186. package/deps/libmdbx/test/utils.h++ +326 -0
  187. package/deps/libmdbx/test/valgrind_suppress.txt +536 -0
  188. package/lib/mdbx_evn_async.js +211 -0
  189. package/lib/mdbx_worker.js +195 -0
  190. package/lib/nativemou.js +6 -0
  191. package/package.json +38 -0
  192. package/src/async/envmou_close.cpp +34 -0
  193. package/src/async/envmou_close.hpp +32 -0
  194. package/src/async/envmou_copy_to.cpp +29 -0
  195. package/src/async/envmou_copy_to.hpp +38 -0
  196. package/src/async/envmou_keys.cpp +201 -0
  197. package/src/async/envmou_keys.hpp +50 -0
  198. package/src/async/envmou_open.cpp +38 -0
  199. package/src/async/envmou_open.hpp +33 -0
  200. package/src/async/envmou_query.cpp +167 -0
  201. package/src/async/envmou_query.hpp +53 -0
  202. package/src/dbimou.cpp +522 -0
  203. package/src/dbimou.hpp +82 -0
  204. package/src/env_arg0.hpp +24 -0
  205. package/src/envmou.cpp +445 -0
  206. package/src/envmou.hpp +116 -0
  207. package/src/modulemou.cpp +113 -0
  208. package/src/querymou.cpp +177 -0
  209. package/src/querymou.hpp +93 -0
  210. package/src/txnmou.cpp +254 -0
  211. package/src/txnmou.hpp +122 -0
  212. package/src/typemou.hpp +239 -0
  213. package/src/valuemou.hpp +194 -0
  214. package/test/async.js +67 -0
  215. package/test/e3.js +38 -0
  216. package/test/e4.js +89 -0
  217. package/test/e5.js +162 -0
  218. package/test/test-batch-ops.js +243 -0
  219. package/test/test-cursor-mode.js +84 -0
  220. package/test/test-multi-mode.js +87 -0
@@ -0,0 +1,263 @@
1
+ /// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
2
+ /// \copyright SPDX-License-Identifier: Apache-2.0
3
+
4
+ #include "test.h++"
5
+
6
+ #if !defined(_WIN32) && !defined(_WIN64)
7
+
8
+ #include <sys/types.h>
9
+ #include <sys/wait.h>
10
+
11
+ class testcase_smoke4fork : public testcase {
12
+ using inherited = testcase;
13
+
14
+ protected:
15
+ bool dbi_invalid{true};
16
+ bool dbi_stable{false};
17
+ unsigned dbi_state{0};
18
+
19
+ public:
20
+ testcase_smoke4fork(const actor_config &config, const mdbx_pid_t pid) : testcase(config, pid) {}
21
+ virtual void txn_end(bool abort) override;
22
+ bool run() override;
23
+ virtual bool smoke() = 0;
24
+ bool open_dbi();
25
+ };
26
+
27
+ bool testcase_smoke4fork::open_dbi() {
28
+ if (!dbi || dbi_invalid) {
29
+ if (dbi_stable || (mdbx_txn_flags(txn_guard.get()) & MDBX_TXN_RDONLY) == 0) {
30
+ dbi = db_table_open(!dbi_stable);
31
+ dbi_invalid = false;
32
+ }
33
+ }
34
+
35
+ dbi_state = 0;
36
+ if (dbi && !dbi_invalid) {
37
+ unsigned unused_dbi_flags;
38
+ int err = mdbx_dbi_flags_ex(txn_guard.get(), dbi, &unused_dbi_flags, &dbi_state);
39
+ if (unlikely(err != MDBX_SUCCESS))
40
+ failure_perror("mdbx_dbi_flags_ex()", err);
41
+ if ((dbi_state & (MDBX_DBI_CREAT | MDBX_DBI_FRESH)) == 0)
42
+ dbi_stable = true;
43
+ }
44
+ return !dbi_invalid;
45
+ }
46
+
47
+ void testcase_smoke4fork::txn_end(bool abort) {
48
+ if (dbi) {
49
+ if (abort) {
50
+ if (dbi_state & MDBX_DBI_CREAT)
51
+ dbi_stable = false;
52
+ if (dbi_state & MDBX_DBI_FRESH)
53
+ dbi_invalid = true;
54
+ } else {
55
+ if (dbi_state & (MDBX_DBI_CREAT | MDBX_DBI_FRESH))
56
+ dbi_stable = true;
57
+ }
58
+ dbi_state = 0;
59
+ }
60
+ inherited::txn_end(abort);
61
+ }
62
+
63
+ bool testcase_smoke4fork::run() {
64
+ static std::vector<pid_t> history;
65
+ const pid_t current_pid = getpid();
66
+ if (history.empty() || current_pid != history.front()) {
67
+ history.push_back(current_pid);
68
+ if (history.size() > /* TODO: add test option */ 2) {
69
+ log_notice("force exit to avoid fork-bomb: deep %zu, pid stack", history.size());
70
+ for (const auto pid : history)
71
+ logging::feed(" %d", pid);
72
+ logging::ln();
73
+ log_flush();
74
+ exit(0);
75
+ }
76
+ }
77
+ const int deep = (int)history.size();
78
+
79
+ int err = db_open__begin__table_create_open_clean(dbi);
80
+ if (unlikely(err != MDBX_SUCCESS)) {
81
+ log_notice("fork[deep %d, pid %d]: bailout-prepare due '%s'", deep, current_pid, mdbx_strerror(err));
82
+ return false;
83
+ }
84
+ open_dbi();
85
+
86
+ if (flipcoin()) {
87
+ if (!smoke()) {
88
+ log_notice("%s[deep %d, pid %d] probe %s", "pre-fork", deep, current_pid, "failed");
89
+ return false;
90
+ }
91
+ log_verbose("%s[deep %d, pid %d] probe %s", "pre-fork", deep, current_pid, "done");
92
+ } else {
93
+ log_verbose("%s[deep %d, pid %d] probe %s", "pre-fork", deep, current_pid, "skipped");
94
+ #ifdef __SANITIZE_ADDRESS__
95
+ const bool commit_txn_to_avoid_memleak = true;
96
+ #else
97
+ const bool commit_txn_to_avoid_memleak = !RUNNING_ON_VALGRIND && flipcoin();
98
+ #endif
99
+ if (commit_txn_to_avoid_memleak && txn_guard)
100
+ txn_end(false);
101
+ }
102
+
103
+ log_flush();
104
+ const pid_t child = fork();
105
+ if (child < 0)
106
+ failure_perror("fork()", errno);
107
+
108
+ if (child == 0) {
109
+ const pid_t new_pid = getpid();
110
+ log_verbose(">>> %s, deep %d, parent-pid %d, child-pid %d", "mdbx_env_resurrect_after_fork()", deep, current_pid,
111
+ new_pid);
112
+ log_flush();
113
+ int err = mdbx_env_resurrect_after_fork(db_guard.get());
114
+ log_verbose("<<< %s, deep %d, parent-pid %d, child-pid %d, err %d", "mdbx_env_resurrect_after_fork()", deep,
115
+ current_pid, new_pid, err);
116
+ log_flush();
117
+ if (err != MDBX_SUCCESS)
118
+ failure_perror("mdbx_env_resurrect_after_fork()", err);
119
+ if (txn_guard) {
120
+ if (dbi_state & MDBX_DBI_CREAT)
121
+ dbi_invalid = true;
122
+ // if (dbi_state & MDBX_DBI_FRESH)
123
+ // dbi_invalid = true;
124
+ dbi_state = 0;
125
+ mdbx_txn_abort(txn_guard.release());
126
+ }
127
+ if (!smoke()) {
128
+ log_notice("%s[deep %d, pid %d] probe %s", "fork-child", deep, new_pid, "failed");
129
+ return false;
130
+ }
131
+ log_verbose("%s[deep %d, pid %d] probe %s", "fork-child", deep, new_pid, "done");
132
+ log_flush();
133
+ return true;
134
+ }
135
+
136
+ if (txn_guard)
137
+ txn_end(false);
138
+
139
+ int status = 0xdeadbeef;
140
+ if (waitpid(child, &status, 0) != child)
141
+ failure_perror("waitpid()", errno);
142
+
143
+ if (WIFEXITED(status)) {
144
+ const int code = WEXITSTATUS(status);
145
+ if (code != EXIT_SUCCESS) {
146
+ log_notice("%s[deep %d, pid %d] child-pid %d failed, err %d", "fork-child", deep, current_pid, child, code);
147
+ return false;
148
+ }
149
+ log_notice("%s[deep %d, pid %d] child-pid %d done", "fork-child", deep, current_pid, child);
150
+ } else if (WIFSIGNALED(status)) {
151
+ const int sig = WTERMSIG(status);
152
+ switch (sig) {
153
+ case SIGABRT:
154
+ case SIGBUS:
155
+ case SIGFPE:
156
+ case SIGILL:
157
+ case SIGSEGV:
158
+ log_notice("%s[deep %d, pid %d] child-pid %d %s by SIG%s", "fork-child", deep, current_pid, child, "terminated",
159
+ signal_name(sig));
160
+ break;
161
+ default:
162
+ log_notice("%s[deep %d, pid %d] child-id %d %s by SIG%s", "fork-child", deep, current_pid, child, "killed",
163
+ signal_name(sig));
164
+ }
165
+ return false;
166
+ } else {
167
+ assert(false);
168
+ }
169
+
170
+ if (!smoke()) {
171
+ log_notice("%s[deep %d, pid %d] probe %s", "post-fork", deep, current_pid, "failed");
172
+ return false;
173
+ }
174
+ log_verbose("%s[deep %d, pid %d] probe %s", "post-fork", deep, current_pid, "done");
175
+ return true;
176
+ }
177
+
178
+ //-----------------------------------------------------------------------------
179
+
180
+ class testcase_forkread : public testcase_smoke4fork {
181
+ using inherited = testcase_smoke4fork;
182
+
183
+ public:
184
+ testcase_forkread(const actor_config &config, const mdbx_pid_t pid) : testcase_smoke4fork(config, pid) {}
185
+ bool smoke() override;
186
+ };
187
+ REGISTER_TESTCASE(forkread);
188
+
189
+ bool testcase_forkread::smoke() {
190
+ MDBX_envinfo env_info;
191
+ int err = mdbx_env_info_ex(db_guard.get(), txn_guard.get(), &env_info, sizeof(env_info));
192
+ if (err)
193
+ failure_perror("mdbx_env_info_ex()", err);
194
+
195
+ if (!txn_guard)
196
+ txn_begin(true);
197
+
198
+ MDBX_txn_info txn_info;
199
+ err = mdbx_txn_info(txn_guard.get(), &txn_info, sizeof(txn_info));
200
+ if (err)
201
+ failure_perror("mdbx_txn_info()", err);
202
+ fetch_canary();
203
+ err = mdbx_env_info_ex(db_guard.get(), txn_guard.get(), &env_info, sizeof(env_info));
204
+ if (err)
205
+ failure_perror("mdbx_env_info_ex()", err);
206
+
207
+ uint64_t seq;
208
+ if (dbi_invalid) {
209
+ err = mdbx_dbi_sequence(txn_guard.get(), dbi, &seq, 0);
210
+ if (unlikely(err != (dbi ? MDBX_BAD_DBI : MDBX_SUCCESS)))
211
+ failure("unexpected '%s' from mdbx_dbi_sequence(get, bad_dbi %d)", mdbx_strerror(err), dbi);
212
+ open_dbi();
213
+ }
214
+ if (!dbi_invalid) {
215
+ err = mdbx_dbi_sequence(txn_guard.get(), dbi, &seq, 0);
216
+ if (unlikely(err != MDBX_SUCCESS))
217
+ failure("unexpected '%s' from mdbx_dbi_sequence(get, dbi %d)", mdbx_strerror(err), dbi);
218
+ }
219
+ txn_end(false);
220
+ return true;
221
+ }
222
+
223
+ //-----------------------------------------------------------------------------
224
+
225
+ class testcase_forkwrite : public testcase_forkread {
226
+ using inherited = testcase_forkread;
227
+
228
+ public:
229
+ testcase_forkwrite(const actor_config &config, const mdbx_pid_t pid) : testcase_forkread(config, pid) {}
230
+ bool smoke() override;
231
+ };
232
+ REGISTER_TESTCASE(forkwrite);
233
+
234
+ bool testcase_forkwrite::smoke() {
235
+ const bool firstly_read = flipcoin();
236
+ if (firstly_read) {
237
+ if (!testcase_forkread::smoke())
238
+ return false;
239
+ }
240
+
241
+ if (!txn_guard)
242
+ txn_begin(false);
243
+
244
+ uint64_t seq;
245
+ if (dbi_invalid) {
246
+ int err = mdbx_dbi_sequence(txn_guard.get(), dbi, &seq, 0);
247
+ if (unlikely(err != (dbi ? MDBX_BAD_DBI : MDBX_EACCESS)))
248
+ failure("unexpected '%s' from mdbx_dbi_sequence(get, bad_dbi %d)", mdbx_strerror(err), dbi);
249
+ open_dbi();
250
+ }
251
+ if (!dbi_invalid) {
252
+ int err = mdbx_dbi_sequence(txn_guard.get(), dbi, &seq, 1);
253
+ if (unlikely(err != MDBX_SUCCESS))
254
+ failure("unexpected '%s' from mdbx_dbi_sequence(inc, dbi %d)", mdbx_strerror(err), dbi);
255
+ }
256
+ txn_end(false);
257
+
258
+ if (!firstly_read && !testcase_forkread::smoke())
259
+ return false;
260
+ return true;
261
+ }
262
+
263
+ #endif /* Windows */
@@ -0,0 +1,447 @@
1
+ /// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
2
+ /// \copyright SPDX-License-Identifier: Apache-2.0
3
+
4
+ #include "test.h++"
5
+
6
+ /* LY: тест "холмиком":
7
+ * - сначала наполняем таблицу циклическими CRUD-манипуляциями,
8
+ * которые в каждом цикле делают несколько операций, включая удаление,
9
+ * но в результате добавляют записи.
10
+ * - затем очищаем таблицу также CRUD-манипуляциями, но уже с другой
11
+ * пропорцией удалений.
12
+ *
13
+ * При этом очень многое зависит от порядка перебора ключей:
14
+ * - (псевдо)случайное распределение требуется лишь для полноты картины,
15
+ * но в целом не покрывает важных кейсов.
16
+ * - кроме (псевдо)случайного перебора требуется последовательное
17
+ * итерирование ключей интервалами различной ширины, с тем чтобы
18
+ * проверить различные варианты как разделения, так и слияния страниц
19
+ * внутри движка.
20
+ * - при не-уникальных ключах (MDBX_DUPSORT с подвариантами), для каждого
21
+ * повтора внутри движка формируется вложенное btree-дерево,
22
+ * соответственно требуется соблюдение аналогичных принципов
23
+ * итерирования для значений.
24
+ */
25
+
26
+ class testcase_hill : public testcase {
27
+ public:
28
+ testcase_hill(const actor_config &config, const mdbx_pid_t pid) : testcase(config, pid) {}
29
+ bool run() override;
30
+ };
31
+ REGISTER_TESTCASE(hill);
32
+
33
+ bool testcase_hill::run() {
34
+ int err = db_open__begin__table_create_open_clean(dbi);
35
+ if (unlikely(err != MDBX_SUCCESS)) {
36
+ log_notice("hill: bailout-prepare due '%s'", mdbx_strerror(err));
37
+ return false;
38
+ }
39
+ speculum.clear();
40
+ speculum_committed.clear();
41
+
42
+ /* TODO: работа в несколько потоков */
43
+ keyvalue_maker.setup(config.params, 0 /* thread_number */);
44
+
45
+ keygen::buffer a_key = keygen::alloc(config.params.keylen_max);
46
+ keygen::buffer a_data_0 = keygen::alloc(config.params.datalen_max);
47
+ keygen::buffer a_data_1 = keygen::alloc(config.params.datalen_max);
48
+ keygen::buffer b_key = keygen::alloc(config.params.keylen_max);
49
+ keygen::buffer b_data = keygen::alloc(config.params.datalen_max);
50
+
51
+ const MDBX_put_flags_t insert_flags =
52
+ (config.params.table_flags & MDBX_DUPSORT) ? MDBX_NODUPDATA : MDBX_NODUPDATA | MDBX_NOOVERWRITE;
53
+ const MDBX_put_flags_t update_flags =
54
+ (config.params.table_flags & MDBX_DUPSORT) ? MDBX_CURRENT | MDBX_NODUPDATA | MDBX_NOOVERWRITE : MDBX_NODUPDATA;
55
+
56
+ uint64_t serial_count = 0;
57
+ uint64_t committed_serial = serial_count;
58
+ unsigned txn_nops = 0;
59
+
60
+ bool rc = speculum_verify();
61
+ if (!rc) {
62
+ log_notice("uphill: bailout before main loop");
63
+ goto bailout;
64
+ }
65
+
66
+ while (should_continue()) {
67
+ const keygen::serial_t a_serial = serial_count;
68
+ if (unlikely(!keyvalue_maker.increment(serial_count, 1))) {
69
+ log_notice("uphill: unexpected key-space overflow");
70
+ break;
71
+ }
72
+
73
+ const keygen::serial_t b_serial = serial_count;
74
+ assert(b_serial > a_serial);
75
+
76
+ // создаем первую запись из пары
77
+ const keygen::serial_t age_shift = keyvalue_maker.remix_age(a_serial);
78
+ log_trace("uphill: insert-a (age %" PRIu64 ") %" PRIu64, age_shift, a_serial);
79
+ generate_pair(a_serial, a_key, a_data_1, age_shift);
80
+
81
+ err = insert(a_key, a_data_1, insert_flags);
82
+ if (unlikely(err != MDBX_SUCCESS)) {
83
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
84
+ log_notice("uphill: bailout at insert-a due '%s'", mdbx_strerror(err));
85
+ txn_restart(true, false);
86
+ serial_count = committed_serial;
87
+ speculum = speculum_committed;
88
+ break;
89
+ }
90
+ failure_perror("mdbx_put(insert-a.1)", err);
91
+ }
92
+ if (!speculum_verify()) {
93
+ log_notice("uphill: bailout after insert-a, before commit");
94
+ goto bailout;
95
+ }
96
+
97
+ if (++txn_nops >= config.params.batch_write) {
98
+ err = breakable_restart();
99
+ if (unlikely(err != MDBX_SUCCESS)) {
100
+ log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err));
101
+ serial_count = committed_serial;
102
+ speculum = speculum_committed;
103
+ break;
104
+ }
105
+ speculum_committed = speculum;
106
+ committed_serial = a_serial;
107
+ txn_nops = 0;
108
+ if (!speculum_verify()) {
109
+ log_notice("uphill: bailout after insert-a, after commit");
110
+ goto bailout;
111
+ }
112
+ }
113
+
114
+ // создаем вторую запись из пары
115
+ log_trace("uphill: insert-b %" PRIu64, b_serial);
116
+ generate_pair(b_serial, b_key, b_data, 0);
117
+ err = insert(b_key, b_data, insert_flags);
118
+ if (unlikely(err != MDBX_SUCCESS)) {
119
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
120
+ log_notice("uphill: bailout at insert-b due '%s'", mdbx_strerror(err));
121
+ txn_restart(true, false);
122
+ serial_count = committed_serial;
123
+ speculum = speculum_committed;
124
+ break;
125
+ }
126
+ failure_perror("mdbx_put(insert-b)", err);
127
+ }
128
+ if (!speculum_verify()) {
129
+ log_notice("uphill: bailout after insert-b, before commit");
130
+ goto bailout;
131
+ }
132
+
133
+ if (++txn_nops >= config.params.batch_write) {
134
+ err = breakable_restart();
135
+ if (unlikely(err != MDBX_SUCCESS)) {
136
+ log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err));
137
+ serial_count = committed_serial;
138
+ speculum = speculum_committed;
139
+ break;
140
+ }
141
+ speculum_committed = speculum;
142
+ committed_serial = a_serial;
143
+ txn_nops = 0;
144
+ if (!speculum_verify()) {
145
+ log_notice("uphill: bailout after insert-b, after commit");
146
+ goto bailout;
147
+ }
148
+ }
149
+
150
+ // обновляем данные в первой записи
151
+ log_trace("uphill: update-a (age %" PRIu64 "->0) %" PRIu64, age_shift, a_serial);
152
+ generate_pair(a_serial, a_key, a_data_0, 0);
153
+ checkdata("uphill: update-a", dbi, a_key->value, a_data_1->value);
154
+ err = replace(a_key, a_data_0, a_data_1, update_flags);
155
+ if (unlikely(err != MDBX_SUCCESS)) {
156
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
157
+ log_notice("uphill: bailout at update-a due '%s'", mdbx_strerror(err));
158
+ txn_restart(true, false);
159
+ serial_count = committed_serial;
160
+ speculum = speculum_committed;
161
+ break;
162
+ }
163
+ failure_perror("mdbx_replace(update-a: 1->0)", err);
164
+ }
165
+ if (!speculum_verify()) {
166
+ log_notice("uphill: bailout after update-a, before commit");
167
+ goto bailout;
168
+ }
169
+
170
+ if (++txn_nops >= config.params.batch_write) {
171
+ err = breakable_restart();
172
+ if (unlikely(err != MDBX_SUCCESS)) {
173
+ log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err));
174
+ serial_count = committed_serial;
175
+ speculum = speculum_committed;
176
+ break;
177
+ }
178
+ speculum_committed = speculum;
179
+ committed_serial = a_serial;
180
+ txn_nops = 0;
181
+ if (!speculum_verify()) {
182
+ log_notice("uphill: bailout after update-a, after commit");
183
+ goto bailout;
184
+ }
185
+ }
186
+
187
+ // удаляем вторую запись
188
+ log_trace("uphill: delete-b %" PRIu64, b_serial);
189
+ checkdata("uphill: delete-b", dbi, b_key->value, b_data->value);
190
+ err = remove(b_key, b_data);
191
+ if (unlikely(err != MDBX_SUCCESS)) {
192
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
193
+ log_notice("uphill: bailout at delete-b due '%s'", mdbx_strerror(err));
194
+ txn_restart(true, false);
195
+ serial_count = committed_serial;
196
+ speculum = speculum_committed;
197
+ break;
198
+ }
199
+ failure_perror("mdbx_del(b)", err);
200
+ }
201
+ if (!speculum_verify()) {
202
+ log_notice("uphill: bailout after delete-b, before commit");
203
+ goto bailout;
204
+ }
205
+
206
+ if (++txn_nops >= config.params.batch_write) {
207
+ err = breakable_restart();
208
+ if (unlikely(err != MDBX_SUCCESS)) {
209
+ log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err));
210
+ serial_count = committed_serial;
211
+ speculum = speculum_committed;
212
+ break;
213
+ }
214
+ speculum_committed = speculum;
215
+ committed_serial = a_serial;
216
+ txn_nops = 0;
217
+ if (!speculum_verify()) {
218
+ log_notice("uphill: bailout after delete-b, after commit");
219
+ goto bailout;
220
+ }
221
+ }
222
+
223
+ report(1);
224
+ if (!keyvalue_maker.increment(serial_count, 1)) {
225
+ // дошли до границы пространства ключей
226
+ serial_count = a_serial;
227
+ goto overflow;
228
+ }
229
+ }
230
+
231
+ if (txn_guard) {
232
+ MDBX_stat stat;
233
+ err = mdbx_dbi_stat(txn_guard.get(), dbi, &stat, sizeof(stat));
234
+ if (unlikely(err != MDBX_SUCCESS))
235
+ failure_perror("mdbx_dbi_stat()", err);
236
+
237
+ uint32_t nested_deepmask;
238
+ err = mdbx_dbi_dupsort_depthmask(txn_guard.get(), dbi, &nested_deepmask);
239
+ if (unlikely(err != MDBX_SUCCESS && err != MDBX_RESULT_TRUE))
240
+ failure_perror("mdbx_dbi_stat_nested_deepmask()", err);
241
+
242
+ if (err != MDBX_SUCCESS) {
243
+ log_notice("hill: reached %d tree depth", stat.ms_depth);
244
+ } else {
245
+ std::string str;
246
+ int prev = -2, i = 0;
247
+ do {
248
+ while (!(nested_deepmask & 1))
249
+ ++i, nested_deepmask >>= 1;
250
+ if (prev + 1 == i) {
251
+ if (str.back() != '-')
252
+ str.push_back('-');
253
+ prev = i;
254
+ continue;
255
+ }
256
+ if (!str.empty()) {
257
+ if (str.back() == '-')
258
+ str.append(std::to_string(prev));
259
+ str.push_back(',');
260
+ }
261
+ str.append(std::to_string(i));
262
+ prev = i;
263
+ } while (++i, nested_deepmask >>= 1);
264
+ if (str.back() == '-')
265
+ str.append(std::to_string(prev));
266
+
267
+ log_notice("hill: reached %d tree depth & %s sub-tree depth(s)", stat.ms_depth, str.c_str());
268
+ }
269
+
270
+ if ((config.params.table_flags & MDBX_DUPSORT) == 0) {
271
+ if (!check_batch_get())
272
+ failure("batch-get verification failed");
273
+ }
274
+ }
275
+
276
+ while (serial_count > 1) {
277
+ if (unlikely(!keyvalue_maker.increment(serial_count, -2)))
278
+ failure("downhill: unexpected key-space underflow");
279
+
280
+ overflow:
281
+ const keygen::serial_t a_serial = serial_count;
282
+ const keygen::serial_t b_serial = a_serial + 1;
283
+ assert(b_serial > a_serial);
284
+
285
+ // обновляем первую запись из пары
286
+ const keygen::serial_t age_shift = keyvalue_maker.remix_age(a_serial);
287
+ log_trace("downhill: update-a (age 0->%" PRIu64 ") %" PRIu64, age_shift, a_serial);
288
+ generate_pair(a_serial, a_key, a_data_0, 0);
289
+ generate_pair(a_serial, a_key, a_data_1, age_shift);
290
+ checkdata("downhill: update-a", dbi, a_key->value, a_data_0->value);
291
+ err = replace(a_key, a_data_1, a_data_0, update_flags);
292
+ if (unlikely(err != MDBX_SUCCESS)) {
293
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
294
+ log_notice("downhill: bailout at update-a due '%s'", mdbx_strerror(err));
295
+ txn_end(true);
296
+ speculum = speculum_committed;
297
+ break;
298
+ }
299
+ failure_perror("mdbx_put(update-a: 0->1)", err);
300
+ }
301
+ if (!speculum_verify()) {
302
+ log_notice("downhill: bailout after update-a, before commit");
303
+ break;
304
+ }
305
+
306
+ if (++txn_nops >= config.params.batch_write) {
307
+ err = breakable_restart();
308
+ if (unlikely(err != MDBX_SUCCESS)) {
309
+ log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err));
310
+ speculum = speculum_committed;
311
+ break;
312
+ }
313
+ speculum_committed = speculum;
314
+ txn_nops = 0;
315
+ if (!speculum_verify()) {
316
+ log_notice("downhill: bailout after update-a, after commit");
317
+ break;
318
+ }
319
+ }
320
+
321
+ // создаем вторую запись из пары
322
+ log_trace("downhill: insert-b %" PRIu64, b_serial);
323
+ generate_pair(b_serial, b_key, b_data, 0);
324
+ err = insert(b_key, b_data, insert_flags);
325
+ if (unlikely(err != MDBX_SUCCESS)) {
326
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
327
+ log_notice("downhill: bailout at insert-a due '%s'", mdbx_strerror(err));
328
+ txn_end(true);
329
+ speculum = speculum_committed;
330
+ break;
331
+ }
332
+ failure_perror("mdbx_put(insert-b)", err);
333
+ }
334
+ if (!speculum_verify()) {
335
+ log_notice("downhill: bailout after insert-b, before commit");
336
+ break;
337
+ }
338
+
339
+ if (++txn_nops >= config.params.batch_write) {
340
+ err = breakable_restart();
341
+ if (unlikely(err != MDBX_SUCCESS)) {
342
+ log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err));
343
+ speculum = speculum_committed;
344
+ break;
345
+ }
346
+ speculum_committed = speculum;
347
+ txn_nops = 0;
348
+ if (!speculum_verify()) {
349
+ log_notice("downhill: bailout after insert-b, after commit");
350
+ break;
351
+ }
352
+ }
353
+
354
+ // удаляем первую запись
355
+ log_trace("downhill: delete-a (age %" PRIu64 ") %" PRIu64, age_shift, a_serial);
356
+ checkdata("downhill: delete-a", dbi, a_key->value, a_data_1->value);
357
+ err = remove(a_key, a_data_1);
358
+ if (unlikely(err != MDBX_SUCCESS)) {
359
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
360
+ log_notice("downhill: bailout at delete-a due '%s'", mdbx_strerror(err));
361
+ txn_end(true);
362
+ speculum = speculum_committed;
363
+ break;
364
+ }
365
+ failure_perror("mdbx_del(a)", err);
366
+ }
367
+ if (!speculum_verify()) {
368
+ log_notice("downhill: bailout after delete-a, before commit");
369
+ break;
370
+ }
371
+
372
+ if (++txn_nops >= config.params.batch_write) {
373
+ err = breakable_restart();
374
+ if (unlikely(err != MDBX_SUCCESS)) {
375
+ log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err));
376
+ speculum = speculum_committed;
377
+ break;
378
+ }
379
+ speculum_committed = speculum;
380
+ txn_nops = 0;
381
+ if (!speculum_verify()) {
382
+ log_notice("downhill: bailout after delete-a, after commit");
383
+ break;
384
+ }
385
+ }
386
+
387
+ // удаляем вторую запись
388
+ log_trace("downhill: delete-b %" PRIu64, b_serial);
389
+ checkdata("downhill: delete-b", dbi, b_key->value, b_data->value);
390
+ err = remove(b_key, b_data);
391
+ if (unlikely(err != MDBX_SUCCESS)) {
392
+ if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) {
393
+ log_notice("downhill: bailout at delete-b due '%s'", mdbx_strerror(err));
394
+ txn_end(true);
395
+ speculum = speculum_committed;
396
+ break;
397
+ }
398
+ failure_perror("mdbx_del(b)", err);
399
+ }
400
+ if (!speculum_verify()) {
401
+ log_notice("downhill: bailout after delete-b, before commit");
402
+ break;
403
+ }
404
+
405
+ if (++txn_nops >= config.params.batch_write) {
406
+ err = breakable_restart();
407
+ if (unlikely(err != MDBX_SUCCESS)) {
408
+ log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err));
409
+ speculum = speculum_committed;
410
+ break;
411
+ }
412
+ speculum_committed = speculum;
413
+ txn_nops = 0;
414
+ if (!speculum_verify()) {
415
+ log_notice("downhill: bailout after delete-b, after commit");
416
+ goto bailout;
417
+ }
418
+ }
419
+
420
+ report(1);
421
+ }
422
+
423
+ rc = speculum_verify();
424
+ bailout:
425
+ if (txn_guard) {
426
+ err = breakable_commit();
427
+ if (unlikely(err != MDBX_SUCCESS))
428
+ log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err));
429
+ }
430
+
431
+ if (!rc && err == MDBX_MAP_FULL && config.params.ignore_dbfull)
432
+ rc = true;
433
+ if (dbi) {
434
+ if (config.params.drop_table && !mode_readonly()) {
435
+ txn_begin(false);
436
+ db_table_drop(dbi);
437
+ err = breakable_commit();
438
+ if (unlikely(err != MDBX_SUCCESS)) {
439
+ log_notice("hill: bailout-clean due '%s'", mdbx_strerror(err));
440
+ if (err != MDBX_MAP_FULL || !config.params.ignore_dbfull)
441
+ rc = false;
442
+ }
443
+ } else
444
+ db_table_close(dbi);
445
+ }
446
+ return rc;
447
+ }