mirror of
https://gitflic.ru/project/erthink/libmdbx.git
synced 2025-06-20 14:58:53 +00:00
Compare commits
3559 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b3329fddf2 | ||
|
8b4ec09d08 | ||
|
ecbffc65f4 | ||
|
e03b8e1227 | ||
|
c88c51d33c | ||
|
ef82fea032 | ||
|
f82cf6a4b3 | ||
|
60c0483987 | ||
|
9da03deac0 | ||
|
34f0f682da | ||
|
9fb0919468 | ||
|
a13147d115 | ||
|
800c96f22f | ||
|
d1023dc6b5 | ||
|
859c350df0 | ||
|
76e2544cc0 | ||
|
0a96b2ad97 | ||
|
402a8e62be | ||
|
06300de34e | ||
|
da9f78d2f6 | ||
|
a5af0c1a85 | ||
|
2b36fd5974 | ||
|
3338551860 | ||
|
1c7a5e18fe | ||
|
6627d14edf | ||
|
7db9c40fe0 | ||
|
52c9ef8807 | ||
|
5c44dd201c | ||
|
f4384800b5 | ||
|
a971c76aff | ||
|
b6f918aa1c | ||
|
011c3072da | ||
|
02b56e185f | ||
|
576fc94fef | ||
|
a56f5acc3d | ||
|
072103ab67 | ||
|
ab2f661c97 | ||
|
668a1e42e3 | ||
|
5548ef20f6 | ||
|
679c1eb939 | ||
|
76a588f91b | ||
|
6b5515908b | ||
|
dc747483dd | ||
|
89de43293d | ||
|
270cf399aa | ||
|
b5503b5670 | ||
|
a71cefc288 | ||
|
6d6a19e3c3 | ||
|
0d7d4db3f1 | ||
|
0f505c1377 | ||
|
f6ce9381af | ||
|
214fa153e2 | ||
|
819551ce13 | ||
|
a22c0c5c48 | ||
|
9540cabf5f | ||
|
0e3b093eb5 | ||
|
5d38add405 | ||
|
2ceda89b05 | ||
|
5bd99d4da2 | ||
|
a04053ee98 | ||
|
f35c1fe5bc | ||
|
4691c0b5c8 | ||
|
b55a41f604 | ||
|
29bed7cf5d | ||
|
f91c2bb8da | ||
|
8d0eceee9f | ||
|
6cb1b6754e | ||
|
187bd59aa0 | ||
|
1c49548ea5 | ||
|
4b9427685a | ||
|
650569cc6a | ||
|
d8f46344b5 | ||
|
ebf1e9d8ba | ||
|
4c3df230d3 | ||
|
9ea8e9b2cf | ||
|
b8c1b835ed | ||
|
db163cbcfd | ||
|
936c25e671 | ||
|
56a6377622 | ||
|
b308559dd9 | ||
|
19dc93fc76 | ||
|
5f1d8dcb3e | ||
|
3d2b221256 | ||
|
ca1808d57f | ||
|
aa98d6a88e | ||
|
b4e65f5d21 | ||
|
390490edf4 | ||
|
b9b14f0061 | ||
|
94531a9cdc | ||
|
f8e332a205 | ||
|
021d83b841 | ||
|
e3324cef91 | ||
|
4e33bad6e7 | ||
|
a313dd2fae | ||
|
bb664152b8 | ||
|
2e4962a2f3 | ||
|
00917f8c96 | ||
|
999f8644f6 | ||
|
5d9fb63fb8 | ||
|
06f8573f5f | ||
|
1e0a1014a4 | ||
|
7eb7931a23 | ||
|
35349cf538 | ||
|
8157d07b00 | ||
|
3c3628c798 | ||
|
3a0dbee58c | ||
|
e11d419d20 | ||
|
59343d9106 | ||
|
2127d3b7d7 | ||
|
065aef35ea | ||
|
9653c8f45b | ||
|
7ed769e9c6 | ||
|
52a19fecca | ||
|
3282adf8bd | ||
|
529f2c2380 | ||
|
1dfe1e872e | ||
|
767ba21977 | ||
|
0a9c9840da | ||
|
9c177de034 | ||
|
5f37ea60d2 | ||
|
c457804fad | ||
|
6c036add8b | ||
|
5fd319bbc2 | ||
|
682233ba28 | ||
|
c5936eb5da | ||
|
d8890bc169 | ||
|
73d52c1963 | ||
|
58729a2fbd | ||
|
5dfe3433a8 | ||
|
1720762080 | ||
|
91570a084f | ||
|
0fff8d0704 | ||
|
484b488f92 | ||
|
2fbdaccf60 | ||
|
753b2270fd | ||
|
33ceba0a5a | ||
|
2476fba287 | ||
|
2b6a768750 | ||
|
b6dcdcf2dc | ||
|
175e4a2e1b | ||
|
f9d7eb5525 | ||
|
69895e2b55 | ||
|
15bd9cfc89 | ||
|
d8f9f3ba58 | ||
|
4150f411dc | ||
|
32ca9691c3 | ||
|
4f59864ef5 | ||
|
f82b760b6e | ||
|
d6b359756c | ||
|
4d454d6e80 | ||
|
44467d0883 | ||
|
49e6bd9296 | ||
|
e37194affe | ||
|
917e2827f5 | ||
|
2fd1772503 | ||
|
694626727f | ||
|
2aa47f20c3 | ||
|
e6891b295b | ||
|
c0b1ab1466 | ||
|
71d95d1a5f | ||
|
7a923b3d41 | ||
|
8008afc6e1 | ||
|
7ae11e0fdb | ||
|
5c1745a7cd | ||
|
23a417fe19 | ||
|
db44f4ed71 | ||
|
ef9fd1f3fb | ||
|
2e6d9fd4d4 | ||
|
83e42d03bb | ||
|
dfd265d46f | ||
|
08d10ad0a1 | ||
|
8ebedde181 | ||
|
dcf35e5306 | ||
|
aeac971f0b | ||
|
6c8047a402 | ||
|
438d185250 | ||
|
ee6843062d | ||
|
70adf71770 | ||
|
fa2c27fa08 | ||
|
7a72d1b273 | ||
|
3e91500fac | ||
|
546b48b6eb | ||
|
2ffa5cf371 | ||
|
b546dc69d2 | ||
|
42706c45a0 | ||
|
8dda33329b | ||
|
b2bd8bae38 | ||
|
1299653457 | ||
|
333069e7a8 | ||
|
436998ca83 | ||
|
b0665f7016 | ||
|
4fcfb07b97 | ||
|
ca30365d3b | ||
|
6424747636 | ||
|
183610b050 | ||
|
920d9b5b2f | ||
|
283c962fea | ||
|
805d84480d | ||
|
8efcdeae9d | ||
|
7504a8f8f2 | ||
|
94a2abaf31 | ||
|
9c161cdafd | ||
|
a3265e11dc | ||
|
709d524d21 | ||
|
0604accecf | ||
|
bc2f1c59cb | ||
|
e0843429a1 | ||
|
329eee4e4f | ||
|
4fd165f8d2 | ||
|
05e7a94619 | ||
|
40f655e2da | ||
|
5e714ed946 | ||
|
826cdb708f | ||
|
da24fda578 | ||
|
0fa21a3c0d | ||
|
d313008d82 | ||
|
9277daa185 | ||
|
1792bdc763 | ||
|
90635e7248 | ||
|
1ec13c63ab | ||
|
c712147eeb | ||
|
23600241e1 | ||
|
22c6763d57 | ||
|
c585fcd613 | ||
|
dd9f608320 | ||
|
28ca18972a | ||
|
fbb93f9cfb | ||
|
bc464521c0 | ||
|
9273e2ee60 | ||
|
e035f102ab | ||
|
1240ed2ba3 | ||
|
6ca63b46d8 | ||
|
9fee0bc3a6 | ||
|
c14bb7814f | ||
|
9b31c517e6 | ||
|
66c747e4a9 | ||
|
54d8c0d290 | ||
|
26cd5ebc43 | ||
|
806f819bae | ||
|
0ef0f49e2e | ||
|
05cdf9d202 | ||
|
818740976b | ||
|
287bab36a1 | ||
|
80de77b1ee | ||
|
5388d2273b | ||
|
d2864029da | ||
|
822213f75d | ||
|
b63ca3c12e | ||
|
aa2ff20faf | ||
|
fcdd2e2db3 | ||
|
75122b311d | ||
|
79572b4850 | ||
|
4730abe3e5 | ||
|
24f2b9b099 | ||
|
401454dadf | ||
|
9568209ee4 | ||
|
781c04f6e2 | ||
|
b7206c68a5 | ||
|
3a0b857e1d | ||
|
6ccbce9afc | ||
|
9d7495fa09 | ||
|
c8f6d90e18 | ||
|
b46d2def80 | ||
|
21630ea115 | ||
|
6d346d8630 | ||
|
778aee25fe | ||
|
cb8eec6d11 | ||
|
b59937adb8 | ||
|
f6d91b3c5b | ||
|
11e1346f9d | ||
|
750fab2427 | ||
|
fffa78d912 | ||
|
fc85d1c61f | ||
|
340bd080c9 | ||
|
7074b94b2e | ||
|
a59c5f9316 | ||
|
f39542a9f0 | ||
|
27a2166be7 | ||
|
d89670bcea | ||
|
c615e4d0a6 | ||
|
fce40169bd | ||
|
560aa72f3d | ||
|
cb7ba6b53f | ||
|
03685aba5a | ||
|
4a0a32a54b | ||
|
1b9ad144ea | ||
|
36abcc57f0 | ||
|
0233eda949 | ||
|
78552a5c84 | ||
|
beb5a81d12 | ||
|
56d1dbef45 | ||
|
761248cc21 | ||
|
72fb45e13d | ||
|
e529cd7d19 | ||
|
2c3b36da64 | ||
|
314b8ce1f0 | ||
|
7e772114bc | ||
|
0accf98ff7 | ||
|
e4054b56c3 | ||
|
950db52fe8 | ||
|
380385c1db | ||
|
10e7e5c899 | ||
|
6d92a778a5 | ||
|
c60f6afe5f | ||
|
a5bb555db3 | ||
|
b9b784c18e | ||
|
c6cd482ea0 | ||
|
2b9401e372 | ||
|
6fe7baa1b8 | ||
|
1e5fef2c76 | ||
|
0a4156fe6f | ||
|
a89d418c91 | ||
|
585ccdf716 | ||
|
81e2623a54 | ||
|
b681b59434 | ||
|
88d782e5eb | ||
|
67460dd0fd | ||
|
3a1ac35009 | ||
|
3c60e1e94c | ||
|
a994a9bbcc | ||
|
84e2c70b98 | ||
|
d3daa23c63 | ||
|
bd45668fee | ||
|
92e2b6287e | ||
|
c751977bf7 | ||
|
e5fe279632 | ||
|
8408a2eed3 | ||
|
0297136648 | ||
|
92a49c7c8c | ||
|
b75e16f4f8 | ||
|
dcc8708d6a | ||
|
9c8f90b713 | ||
|
820bd45818 | ||
|
3a02ca88ea | ||
|
16997a88b0 | ||
|
b00e8ea13f | ||
|
faa9753d2d | ||
|
5ba257fafc | ||
|
bad6e3c2e2 | ||
|
5350ed8a3b | ||
|
fef7c25a65 | ||
|
10ac9a9c50 | ||
|
dc98f06d2c | ||
|
0a364aefbb | ||
|
1bf008ac16 | ||
|
1e4e2eb3c8 | ||
|
63dba2876d | ||
|
5ff5080935 | ||
|
df8b15f639 | ||
|
26f6fd351a | ||
|
c8c541649c | ||
|
42561e3b8e | ||
|
98b28213ce | ||
|
214f5d4de4 | ||
|
00c5bbcc5e | ||
|
471b14a147 | ||
|
bfc6795762 | ||
|
a76e06a48e | ||
|
b9e4c1ea73 | ||
|
bc56a613ab | ||
|
225fb79eb2 | ||
|
ffb7918525 | ||
|
0339aa56d9 | ||
|
4059686534 | ||
|
ab57ce7d5f | ||
|
462af2be48 | ||
|
d3a27d27f6 | ||
|
0d1c08677d | ||
|
e43cf69a0c | ||
|
891fa1d435 | ||
|
5a9eea8acc | ||
|
e15079ec68 | ||
|
ba6df2bb6d | ||
|
4607184999 | ||
|
5168c80be8 | ||
|
6ed4dcb4ea | ||
|
122562cf9c | ||
|
526ed28de1 | ||
|
90b187c3ba | ||
|
a845522db7 | ||
|
c66dac50c3 | ||
|
ccdb6255e9 | ||
|
9803259cab | ||
|
ea3f99f58f | ||
|
513518ca5e | ||
|
f2dc60aa53 | ||
|
b687e835e9 | ||
|
8867c2ddc2 | ||
|
3c4d019d00 | ||
|
dbf18b4c22 | ||
|
5652b360b9 | ||
|
529b0357e8 | ||
|
fe627ed2f2 | ||
|
7aed3a7609 | ||
|
1566a0006c | ||
|
9481c0e5c4 | ||
|
207ad31257 | ||
|
3a09b8fb0c | ||
|
d1b2ec0489 | ||
|
367a118a8f | ||
|
e2ca81ae83 | ||
|
44865dadc7 | ||
|
35177611d2 | ||
|
99fa43f322 | ||
|
acb3cb0290 | ||
|
5327f42465 | ||
|
4c5be88038 | ||
|
ad0b374eb5 | ||
|
c716531bd4 | ||
|
9d79d2ba95 | ||
|
76c9b42e86 | ||
|
81a8127084 | ||
|
28bd805ed8 | ||
|
e754b442a2 | ||
|
9daff17c82 | ||
|
bcf0a1273f | ||
|
6508bd5a97 | ||
|
3110c2206f | ||
|
652587b33f | ||
|
aa3b39d9ed | ||
|
b6a851b3d6 | ||
|
8369b8ff64 | ||
|
2194349644 | ||
|
1c9c49dd1a | ||
|
881d4d4207 | ||
|
2b71df417e | ||
|
5815ff2ef7 | ||
|
ddea36c54a | ||
|
efaa46d7cd | ||
|
92dec0bca9 | ||
|
c13efb791f | ||
|
12442bd1f4 | ||
|
f5b1e36b9e | ||
|
f32d3f260f | ||
|
47f96b6afa | ||
|
0306ba8136 | ||
|
a2984c604d | ||
|
5862a4b542 | ||
|
e2b4245abe | ||
|
b5def26565 | ||
|
e27537dd9d | ||
|
f550c65476 | ||
|
ab4bf2d7f0 | ||
|
871bb7f56c | ||
|
7aa5d9ab97 | ||
|
6893a79c70 | ||
|
6f41276dbc | ||
|
9da743515c | ||
|
9b9d6c6d65 | ||
|
bd7b272bca | ||
|
4cc1c7d8de | ||
|
10a93f4b9f | ||
|
ca8e9fe7b1 | ||
|
c0e5108d71 | ||
|
00be608af9 | ||
|
6c56ed97bb | ||
|
acb15790b4 | ||
|
029f14280b | ||
|
af41bcf11e | ||
|
2669f285f9 | ||
|
157ede4e42 | ||
|
6067ba5f9d | ||
|
2c919c0efe | ||
|
de36d94aca | ||
|
8571eac81b | ||
|
dc6f29a046 | ||
|
90642bffab | ||
|
6cfb2935f6 | ||
|
b16c2570f0 | ||
|
b8f9b8659c | ||
|
ac8402283f | ||
|
945899e4fd | ||
|
222150bb28 | ||
|
3d6e196422 | ||
|
03077773cb | ||
|
5dc1f36b67 | ||
|
80708f9591 | ||
|
3049bb87b5 | ||
|
7232d7b5fc | ||
|
964ee00116 | ||
|
b43eed2c2b | ||
|
06dd50580c | ||
|
bfce1cd24d | ||
|
22233b0991 | ||
|
d2b74e4da5 | ||
|
d40e4db13a | ||
|
57848b1d2d | ||
|
ca2dbf0933 | ||
|
ecf862a4f6 | ||
|
bf58ec59f5 | ||
|
486fb3c36d | ||
|
ce579bcb8e | ||
|
b11998de01 | ||
|
54dfc1f16d | ||
|
0178d5b5c8 | ||
|
9fa76a56fc | ||
|
42ca4edec8 | ||
|
c96714423d | ||
|
c964523978 | ||
|
ec41ec1561 | ||
|
07309427fd | ||
|
f738552721 | ||
|
29d0a96818 | ||
|
202cdbc4be | ||
|
14a55ee244 | ||
|
74f7d13455 | ||
|
2e14404837 | ||
|
81807f16b2 | ||
|
b36e3702e5 | ||
|
c69f23ed70 | ||
|
fcc4748f23 | ||
|
450c1081fa | ||
|
5fc7a6b107 | ||
|
2c17c7b149 | ||
|
94936fd4c9 | ||
|
a17b190dc8 | ||
|
b4dcf148c5 | ||
|
2b5d7ed29a | ||
|
77a35608f6 | ||
|
7511f480a4 | ||
|
0c9f531c72 | ||
|
f477fa13e0 | ||
|
b054a69e72 | ||
|
aca692212f | ||
|
8cc5e8c262 | ||
|
497aabcb2e | ||
|
cf6d441e1b | ||
|
edfa526138 | ||
|
7bff3b3df6 | ||
|
7b09ecd9b7 | ||
|
7ed4a551f4 | ||
|
57e558a57d | ||
|
dd5329c164 | ||
|
dd0ee3f278 | ||
|
ee8c9225d6 | ||
|
69f85af242 | ||
|
9eef3c3541 | ||
|
a0e278ff00 | ||
|
d21ae28bb9 | ||
|
b6b126195b | ||
|
4c0290b576 | ||
|
e7488bc30c | ||
|
f34ebc853d | ||
|
0a9d96affd | ||
|
dc7f15c63e | ||
|
9e3a36b74d | ||
|
7873118cdb | ||
|
2e7d325cf1 | ||
|
cb743d44fc | ||
|
a430b3b288 | ||
|
485d6d1f50 | ||
|
69aa9e0fe1 | ||
|
9309aa7e12 | ||
|
ad0ba7a661 | ||
|
a6a7a291c7 | ||
|
a8ef8d7b72 | ||
|
5c40f6983c | ||
|
cda8ebe0bd | ||
|
f5bbadf3a5 | ||
|
58cad2995b | ||
|
0a36ed3ca1 | ||
|
37217cb199 | ||
|
6941ec17bc | ||
|
6b2b15ebc8 | ||
|
5c643f72b5 | ||
|
9d9a19ae17 | ||
|
b1cc8b2e9f | ||
|
c46270ec56 | ||
|
7dee88e27f | ||
|
242ebefdb7 | ||
|
f20addd7fc | ||
|
8a04337e79 | ||
|
8e8ac09e14 | ||
|
bdfec14992 | ||
|
32df0ad1eb | ||
|
2311706272 | ||
|
ec0ada7b8c | ||
|
f335a16c92 | ||
|
fe31958d46 | ||
|
319753661a | ||
|
3798d47a71 | ||
|
9acbe88566 | ||
|
9fbf0099f2 | ||
|
b4f395be50 | ||
|
49c6e14b30 | ||
|
69df6e6ac0 | ||
|
a0b520fa32 | ||
|
796b7e4497 | ||
|
7abeac762f | ||
|
d5fb37460b | ||
|
bdd0b487ae | ||
|
9670cf5709 | ||
|
a10506fb6a | ||
|
d4c09f9b78 | ||
|
f65642e38c | ||
|
bcd955aeb9 | ||
|
a79a318d61 | ||
|
3517db6178 | ||
|
25efb58790 | ||
|
74ff4dba0a | ||
|
a2753c9ae1 | ||
|
8e29fb1f26 | ||
|
e12b4ab748 | ||
|
e9c122af68 | ||
|
b940ae8fad | ||
|
5792eb31eb | ||
|
0e831f42cc | ||
|
2f66eb9fec | ||
|
12eb2df57d | ||
|
3de3d425a1 | ||
|
341a8b8b5c | ||
|
ad0b13a544 | ||
|
ba42390a56 | ||
|
07cfe65ea0 | ||
|
75fd4ecf54 | ||
|
97b0b0192e | ||
|
12e6c631f1 | ||
|
458f713c53 | ||
|
e9f5c0c308 | ||
|
95bc96dda3 | ||
|
a5ed725ae3 | ||
|
dd9ba2c769 | ||
|
d1565fd326 | ||
|
45377f20c5 | ||
|
7c69493473 | ||
|
f19753636d | ||
|
4dccc4ab6d | ||
|
e3d4cd5758 | ||
|
2d2cec094e | ||
|
41ebd6dcf3 | ||
|
f40b2fc164 | ||
|
8f32f4ac98 | ||
|
c1d3afcbe1 | ||
|
e34d4de760 | ||
|
b9fd42b9b3 | ||
|
ae5d541efb | ||
|
e56c73b4e6 | ||
|
1727b697a0 | ||
|
d603de4a87 | ||
|
bdff60e6a7 | ||
|
3670a30c00 | ||
|
01458065c4 | ||
|
639ba8b7a5 | ||
|
2cc6d68c07 | ||
|
2ce6ed33fa | ||
|
5c3c7b9292 | ||
|
b36679ddcb | ||
|
d4f7b4114b | ||
|
e9a49e3715 | ||
|
cce5c8249c | ||
|
7b1f8ba642 | ||
|
5c84c405ac | ||
|
af060b4960 | ||
|
f548f00d8e | ||
|
5721296e16 | ||
|
31e8f290e7 | ||
|
dedcdd4c94 | ||
|
80e9667ead | ||
|
d0799fd373 | ||
|
fb17e8877c | ||
|
c153a34382 | ||
|
1d0ee509c2 | ||
|
27893f52f1 | ||
|
f8836aefa0 | ||
|
04f1200c3d | ||
|
183d1e1a44 | ||
|
179d8d6d6b | ||
|
dfcd652e5c | ||
|
abca22e32d | ||
|
20d6d39ab3 | ||
|
236afee80b | ||
|
94a6bc140d | ||
|
5fc3965f5b | ||
|
25089e6491 | ||
|
61a073687f | ||
|
baaa26bb32 | ||
|
93f76f43ac | ||
|
aae6a0395a | ||
|
471085788c | ||
|
3865e85248 | ||
|
a6f7d74a32 | ||
|
c70ef83e4a | ||
|
8cc3dba7ae | ||
|
872bddaee6 | ||
|
aea40fb79f | ||
|
0741c81cfd | ||
|
781b3f64d5 | ||
|
446d6c9421 | ||
|
fff3fbd866 | ||
|
3e850981c7 | ||
|
36a7e7ac24 | ||
|
2d7fe42327 | ||
|
2b88c6261f | ||
|
e515bd56e9 | ||
|
20160ae98f | ||
|
ea97fbae74 | ||
|
a0a4af7701 | ||
|
74f2bc813b | ||
|
1a18369015 | ||
|
e2f2fd8652 | ||
|
fb6be62046 | ||
|
f7e6bd770a | ||
|
ba5c74e54d | ||
|
049b71c148 | ||
|
5df3eb6449 | ||
|
1549d3970c | ||
|
1c174e84c4 | ||
|
0b87ddc6d4 | ||
|
eca0f46368 | ||
|
fe498de323 | ||
|
4ed05689bc | ||
|
0c24b49bbf | ||
|
d8db63a67d | ||
|
e29cb076d3 | ||
|
9480599afa | ||
|
72e51ee370 | ||
|
aa9d2387e5 | ||
|
fa0017591d | ||
|
d7f259110c | ||
|
d53dc4572c | ||
|
2e863cf7e0 | ||
|
826441741d | ||
|
00c4e2636e | ||
|
3373631cff | ||
|
c5ac7b25c9 | ||
|
544c6bc1e4 | ||
|
164d112507 | ||
|
fb5dbbdf20 | ||
|
b1dcd07be4 | ||
|
603ce05435 | ||
|
ba719ef12a | ||
|
f0cfedc26f | ||
|
fbc83dd069 | ||
|
4f770999a8 | ||
|
185e43f3a8 | ||
|
f16c4303bf | ||
|
72332a8f9e | ||
|
6b72d88fde | ||
|
4c139b3619 | ||
|
169e69c52e | ||
|
eeec44f56d | ||
|
508cf83c32 | ||
|
eb90ec6192 | ||
|
e316bc8b29 | ||
|
5a86afaac3 | ||
|
349759648d | ||
|
43dbf8ec4f | ||
|
14558fa90b | ||
|
1db44c7914 | ||
|
f97c127455 | ||
|
100f07e89a | ||
|
e68771df18 | ||
|
ef69336189 | ||
|
0e250a4457 | ||
|
be8428257d | ||
|
dd47f1bfd9 | ||
|
1f2ff07796 | ||
|
b412807fc1 | ||
|
8a6bddef44 | ||
|
304cf25149 | ||
|
869cfb3fae | ||
|
0a58601cdf | ||
|
355090f02e | ||
|
55142d8d6f | ||
|
b9e2f6dc09 | ||
|
bf21ee7bde | ||
|
0b68980489 | ||
|
4999264460 | ||
|
25015c54e1 | ||
|
5cf6542fa0 | ||
|
bc562d5c06 | ||
|
ed59ad22c6 | ||
|
eee3e6eb6b | ||
|
adcbb39379 | ||
|
10abf73191 | ||
|
225f548339 | ||
|
990d4ea042 | ||
|
8d67d23224 | ||
|
4a7a2034c0 | ||
|
d963f3a971 | ||
|
fb15c8ca0b | ||
|
b6034e8045 | ||
|
ae01a8e891 | ||
|
1277fe965d | ||
|
7fc6a1b658 | ||
|
b1abcb8260 | ||
|
7cd3dbbccb | ||
|
352dd75ee8 | ||
|
cf1541e4d7 | ||
|
446dbc9d6c | ||
|
64a5ad8c04 | ||
|
0fd0d527d9 | ||
|
4bed5d1779 | ||
|
6e4473777e | ||
|
992eee4f0f | ||
|
d6a79a9c5f | ||
|
6facd20b2b | ||
|
e66df2c21b | ||
|
649bbb9d90 | ||
|
c8319aabe7 | ||
|
7f21515940 | ||
|
5abb6a9bbf | ||
|
c014685c01 | ||
|
476da5f8cf | ||
|
b905a6a391 | ||
|
d94f34b2c0 | ||
|
f5ce7322c4 | ||
|
ab5d290f11 | ||
|
6cef39c32f | ||
|
44beae00ec | ||
|
b7605e8033 | ||
|
100e95957c | ||
|
cfce4ef4d3 | ||
|
7eb2f4130e | ||
|
d9f49b17de | ||
|
af4dfe541b | ||
|
a22ec56938 | ||
|
ce74fae036 | ||
|
54efb8bd81 | ||
|
a3e2300f58 | ||
|
7ad54f54b4 | ||
|
eddade7b99 | ||
|
97418d5c9c | ||
|
1b2f5f25d4 | ||
|
2fe01eee89 | ||
|
6477e6c5de | ||
|
dea6570fc1 | ||
|
9a6f8a1bf8 | ||
|
24d5b26bc5 | ||
|
0916d24321 | ||
|
c216e1afb7 | ||
|
c9c02dddfb | ||
|
903d964f4d | ||
|
96504bf338 | ||
|
3622669a9f | ||
|
e6af7d7c53 | ||
|
796e56b9b9 | ||
|
4b79d46d38 | ||
|
f317170706 | ||
|
81f386f831 | ||
|
54920cd07b | ||
|
7a413406be | ||
|
07fc7b9227 | ||
|
ad4d00677b | ||
|
1943db7d41 | ||
|
ed8c7ead4e | ||
|
5ebc2c523d | ||
|
24f08aed28 | ||
|
c254c728d2 | ||
|
04511a7a99 | ||
|
0e4c6d61a4 | ||
|
4d3f7e1edc | ||
|
d28a397b2d | ||
|
5f274eb4c6 | ||
|
a67b9b9729 | ||
|
224f26813e | ||
|
fc1685a178 | ||
|
cdbcf54af1 | ||
|
786da2b089 | ||
|
253a56206b | ||
|
f0d523c507 | ||
|
dd9fc963d2 | ||
|
e9ad618b58 | ||
|
42ef1dcd39 | ||
|
93429d3a23 | ||
|
080875cd6d | ||
|
753cfd00eb | ||
|
311a6e5d10 | ||
|
e58b582639 | ||
|
e2ed55853d | ||
|
02c7cf2a9c | ||
|
83f19fc993 | ||
|
d440485156 | ||
|
25ec8e253f | ||
|
248208cf5d | ||
|
f02a0ffa21 | ||
|
2b0eae08f5 | ||
|
5d9740bbcf | ||
|
39f2bb142a | ||
|
e9b10db255 | ||
|
687622b8b1 | ||
|
fd8a99acff | ||
|
e21e91ad1f | ||
|
6027348651 | ||
|
1aead6869a | ||
|
45721d4064 | ||
|
6de15514df | ||
|
215bee9ab7 | ||
|
7d3f136a3a | ||
|
eb348ca34c | ||
|
cb48ee8f3d | ||
|
a387284458 | ||
|
e7ae8214fd | ||
|
e195f5bcf7 | ||
|
c256e8358c | ||
|
bc6d320bb2 | ||
|
3d187abc1b | ||
|
7b12e7323f | ||
|
45aa39c68b | ||
|
d02bdcf2bd | ||
|
5561cec9c5 | ||
|
ff6674b377 | ||
|
ca6f04c52a | ||
|
db6cf469c9 | ||
|
d516e903d4 | ||
|
7aaae2ecd5 | ||
|
bf1c753be3 | ||
|
79edab2adf | ||
|
37792cc568 | ||
|
e8d2a5bd09 | ||
|
2c2612ba23 | ||
|
60b483025c | ||
|
2abf80a199 | ||
|
4fd21d2f7b | ||
|
c019631a8c | ||
|
35d4834647 | ||
|
aee8caf9a0 | ||
|
99c9bc2411 | ||
|
cf9145bb46 | ||
|
4151e0e348 | ||
|
9b8291457b | ||
|
0f13d91a0e | ||
|
d40b69ec7a | ||
|
7489c8ce28 | ||
|
caddf07889 | ||
|
74256efc64 | ||
|
e47a91bf7c | ||
|
3ace3c27b8 | ||
|
bcebfb4b4c | ||
|
b5400f9a35 | ||
|
8a44d57fab | ||
|
fdb2b5b0f1 | ||
|
95cb73646e | ||
|
b2d16d32aa | ||
|
e0be0d9a5e | ||
|
2ba7051719 | ||
|
04ed388761 | ||
|
da4e2ab254 | ||
|
c81b007587 | ||
|
53177e483c | ||
|
ad93633d10 | ||
|
f17c55a872 | ||
|
7db014c4fc | ||
|
22405885f6 | ||
|
2ae7bfd9be | ||
|
8f87ab252e | ||
|
800bd55ab9 | ||
|
5c52adf358 | ||
|
6d74b10db1 | ||
|
359489e271 | ||
|
5f690bbc4f | ||
|
1b6e32071c | ||
|
b415265d16 | ||
|
29d12f1fc3 | ||
|
257a534fbe | ||
|
33b5aeb768 | ||
|
f532e907e9 | ||
|
60736dbabb | ||
|
24df8073ac | ||
|
d504ca1747 | ||
|
b7ace5b216 | ||
|
2fabac18c0 | ||
|
51789f3605 | ||
|
6899142872 | ||
|
c44c8132e4 | ||
|
d376feb7bc | ||
|
2ea9fbe51b | ||
|
57ca0d6e1b | ||
|
b8092dd0db | ||
|
8fba5ac8d8 | ||
|
c9d11cbac1 | ||
|
25e958f081 | ||
|
7f5ea6d3b8 | ||
|
e51140fe48 | ||
|
bd35fe8970 | ||
|
1684d17b0f | ||
|
ebbe98afa5 | ||
|
351a30f186 | ||
|
2a41b24876 | ||
|
fb827959a9 | ||
|
209f784ee7 | ||
|
68ebbe1fde | ||
|
486711945d | ||
|
3ade7c7ba1 | ||
|
c01f025bfa | ||
|
a484a1f89b | ||
|
0979a93a78 | ||
|
a98c73f4f6 | ||
|
9e15bd9b29 | ||
|
0159f97e94 | ||
|
56050f201f | ||
|
525c4a55a4 | ||
|
702c67fc38 | ||
|
3da23da7b3 | ||
|
16cda5c2e8 | ||
|
f1fdb88938 | ||
|
68a8a15621 | ||
|
b86b71a948 | ||
|
61e77e7b70 | ||
|
08fb7d5838 | ||
|
f2a49b687a | ||
|
c6b73c8a24 | ||
|
24f2e878c1 | ||
|
2c8d3e1e12 | ||
|
ab55016599 | ||
|
f0c2927fc7 | ||
|
8519fde741 | ||
|
bcddeaba9f | ||
|
5317e516d2 | ||
|
be05037906 | ||
|
f53dc70038 | ||
|
2322138a8e | ||
|
37867a0b84 | ||
|
f0c43fb24a | ||
|
adf433a1bc | ||
|
48bd3fc4c8 | ||
|
7ffea70087 | ||
|
ef460a9229 | ||
|
df63ff0e7e | ||
|
66a5704949 | ||
|
61d21b0a02 | ||
|
0941319940 | ||
|
bb2e3967eb | ||
|
e458af602e | ||
|
d29acf4fdc | ||
|
a06fe4f168 | ||
|
0498114469 | ||
|
85828f677a | ||
|
9cbbdfa025 | ||
|
686145ec2e | ||
|
fe55f25665 | ||
|
e9a2042df1 | ||
|
fd98a635d9 | ||
|
722c6ecf43 | ||
|
44493c6448 | ||
|
7011743262 | ||
|
b247b081af | ||
|
bf2f3bfbbf | ||
|
ffdff3f831 | ||
|
07f2ccb752 | ||
|
23fedf6bba | ||
|
167011c2d5 | ||
|
245a782912 | ||
|
957c99d86f | ||
|
b959e217b1 | ||
|
54b15d7e41 | ||
|
69f7d6cdd8 | ||
|
0884f28f85 | ||
|
1c93cff825 | ||
|
1ae6a398ed | ||
|
cd0ed2f155 | ||
|
4ee8fff305 | ||
|
1bb41ee8fc | ||
|
a572902fde | ||
|
ebc4976acb | ||
|
fd7aaf5f35 | ||
|
4b27c4c7c9 | ||
|
3a77af7d8a | ||
|
a9163f6307 | ||
|
48eeb93628 | ||
|
a772a9d3e1 | ||
|
be3ff92772 | ||
|
dc27d5d30a | ||
|
48a56d1d05 | ||
|
db83bd34d2 | ||
|
23d236f70e | ||
|
822952ef01 | ||
|
9f2d30c1a9 | ||
|
47851135f3 | ||
|
6139443ef1 | ||
|
30f292d496 | ||
|
163486fa3a | ||
|
512e6dbd08 | ||
|
2776480f18 | ||
|
b7734369a2 | ||
|
01a39e7dc2 | ||
|
d6b9a71825 | ||
|
9cee1ff799 | ||
|
8c74de57ea | ||
|
05804e2f30 | ||
|
7685b4080e | ||
|
c521a21f05 | ||
|
c5ddf12602 | ||
|
07674ada47 | ||
|
3757eb72f7 | ||
|
b324844296 | ||
|
30972102e5 | ||
|
61eafe80c1 | ||
|
a1333fc827 | ||
|
da023657f5 | ||
|
141cce0c0f | ||
|
12ed2bcfbd | ||
|
1f93dfe5fd | ||
|
543e52730d | ||
|
c46c03e7c8 | ||
|
4a257133cb | ||
|
f73cd7a491 | ||
|
3e05d1a427 | ||
|
e518edcfed | ||
|
3563ed00e3 | ||
|
0f92baaa5e | ||
|
eaf063ca9b | ||
|
6c840cf58e | ||
|
9b062cf0c7 | ||
|
d23f695ab3 | ||
|
b274a35410 | ||
|
9fca1734c7 | ||
|
3704433aa9 | ||
|
70e8006776 | ||
|
8ffb0bb3d8 | ||
|
d6b47c7bd1 | ||
|
53d78bbad5 | ||
|
8f0c5bc7c7 | ||
|
29eab4afdd | ||
|
12717aac8c | ||
|
90f39c88a0 | ||
|
bc80fbbeea | ||
|
e992da9efe | ||
|
d7e4cb2e22 | ||
|
af1d01ffb3 | ||
|
cce052e869 | ||
|
094c2f345d | ||
|
7b2eee91af | ||
|
d863629387 | ||
|
1b3b6e4479 | ||
|
5dcc0171fa | ||
|
13c256026e | ||
|
ec0ec90f15 | ||
|
67f4098bfa | ||
|
1b0d747e7b | ||
|
2dfdac2821 | ||
|
144cbbabb8 | ||
|
c270306580 | ||
|
2558903081 | ||
|
d315a9255a | ||
|
652ca2b5cb | ||
|
987509f90f | ||
|
8c75ed59da | ||
|
623ab21707 | ||
|
425730c2b3 | ||
|
471e854551 | ||
|
1bd0eb35bc | ||
|
79c65821ee | ||
|
3ee223514d | ||
|
f7f94bb698 | ||
|
5d36d242a7 | ||
|
f0c6aa4646 | ||
|
771c85a880 | ||
|
4f1f9141f4 | ||
|
f680c99116 | ||
|
acaa1d82d9 | ||
|
36eb40bccb | ||
|
47e7a646fd | ||
|
9cbd4e63ca | ||
|
d4e67d14ce | ||
|
91a6e84cab | ||
|
28e2e31949 | ||
|
8f8b9f3d2a | ||
|
836f6c2723 | ||
|
9eaf86bde1 | ||
|
7902b97a3d | ||
|
d661d4bac7 | ||
|
b04f7814ef | ||
|
4e95a079ee | ||
|
753fa13048 | ||
|
bbd139b2ae | ||
|
64d0e639c2 | ||
|
cd616447da | ||
|
8833dc6871 | ||
|
206dbecccf | ||
|
6fdae12996 | ||
|
2684c89d91 | ||
|
7b60363a31 | ||
|
39c6387d23 | ||
|
80f9f73a5e | ||
|
51a765a5a7 | ||
|
c4beb5a4a0 | ||
|
6c986ce904 | ||
|
f5fee949e3 | ||
|
d94e65b870 | ||
|
5a45c4a210 | ||
|
686c908a95 | ||
|
e5fc056035 | ||
|
290630f118 | ||
|
bdcc345455 | ||
|
db9e2c6f07 | ||
|
50e9e0e561 | ||
|
8505203080 | ||
|
dd9780606b | ||
|
5242c5bfdc | ||
|
f5a6e0c04f | ||
|
329af93436 | ||
|
22a84d656b | ||
|
e46ca81abd | ||
|
25ab65b470 | ||
|
c3dd60fcb6 | ||
|
9cdee2adb5 | ||
|
98e29fe628 | ||
|
98a2bd785a | ||
|
138a83c2be | ||
|
92d203a12c | ||
|
63b4d2289d | ||
|
688ec3e85c | ||
|
ae8e373143 | ||
|
14eda2cd17 | ||
|
ad09164604 | ||
|
db72763de0 | ||
|
940ef30659 | ||
|
f6eec7195b | ||
|
92dabe1ad1 | ||
|
0f7e5073db | ||
|
bee7431f76 | ||
|
3579496945 | ||
|
24d7a4d605 | ||
|
559f3005ca | ||
|
a95ee8daa3 | ||
|
c17617b816 | ||
|
6eeb08de46 | ||
|
66f2e3d596 | ||
|
143e3dfb77 | ||
|
bcd5bad74a | ||
|
375fa3a225 | ||
|
2236b90567 | ||
|
8aeb22b8bf | ||
|
474391c83c | ||
|
9f64e2a10c | ||
|
41b918f1fc | ||
|
00515d50a9 | ||
|
32a3674dc8 | ||
|
f51ace3db8 | ||
|
beda291692 | ||
|
fe20de136c | ||
|
cf8540d84e | ||
|
bec9312df5 | ||
|
a089f73002 | ||
|
50ba4bc2f2 | ||
|
cf6576984d | ||
|
4f3e1a60f1 | ||
|
04f60af669 | ||
|
c6cd642ff1 | ||
|
c4efa8dce8 | ||
|
2d5438d2c2 | ||
|
29da09328e | ||
|
52cb6b90a7 | ||
|
2d7c25b263 | ||
|
3230fb5788 | ||
|
b73727d73e | ||
|
b36a07a512 | ||
|
b60d8e78c3 | ||
|
aaa9112c83 | ||
|
26f52a19c3 | ||
|
5368551081 | ||
|
0ccec20409 | ||
|
ceba040e32 | ||
|
b617f25eaa | ||
|
b759dfafd7 | ||
|
4cef1c2376 | ||
|
08a8f844dc | ||
|
8e2c276562 | ||
|
4f02199648 | ||
|
7b36f946cb | ||
|
ef16dd2a22 | ||
|
f9ad835680 | ||
|
9b3faee630 | ||
|
316ddf9e01 | ||
|
3fbbe32adf | ||
|
8467cc6d03 | ||
|
9f0e2ecc67 | ||
|
345c3d433f | ||
|
1c5ef060c5 | ||
|
34a4e7e102 | ||
|
ae730ae2f3 | ||
|
18e557c6e8 | ||
|
096d6a9bd6 | ||
|
d8f0c9dc44 | ||
|
78dc699709 | ||
|
c2bf9ebf17 | ||
|
3c28619562 | ||
|
0287a00ee3 | ||
|
2ff8d3c4f2 | ||
|
98c53555ab | ||
|
c8b1392cbe | ||
|
6d85e35876 | ||
|
dd01aabaeb | ||
|
3de759a7be | ||
|
d6603a0c0a | ||
|
15146d3823 | ||
|
d62d3e2aab | ||
|
fa854e40c3 | ||
|
5afc5c4e8c | ||
|
c05a3b7bb9 | ||
|
1215bda188 | ||
|
0dd4532473 | ||
|
eac3d0499f | ||
|
a11c045f1e | ||
|
c0f8ecd6f2 | ||
|
8404cc1fd7 | ||
|
654b020bc7 | ||
|
77635116c6 | ||
|
a44eb1accb | ||
|
c06d072daf | ||
|
d28110373e | ||
|
480dc2531e | ||
|
f0a46da6a5 | ||
|
06734bf8ff | ||
|
c37fb50532 | ||
|
9eb6953778 | ||
|
bfac10418f | ||
|
268b33cbf7 | ||
|
e444c70cb7 | ||
|
a441c9ffb1 | ||
|
71c3d20c01 | ||
|
75d19b5806 | ||
|
dc39ecfb9f | ||
|
47d5fa7fd4 | ||
|
fe6c6b2068 | ||
|
262fafd00e | ||
|
289636834c | ||
|
c4dd83fbdf | ||
|
cc51a7f76e | ||
|
a82f59a998 | ||
|
08e936a809 | ||
|
5e565433f7 | ||
|
6a1bf6035f | ||
|
e963375302 | ||
|
0af84be269 | ||
|
23e7870e81 | ||
|
fc53e57a64 | ||
|
69b495d559 | ||
|
0018164fef | ||
|
ac4b6d7121 | ||
|
5ccfb5f30a | ||
|
149e708830 | ||
|
12d2879a9f | ||
|
194f2f45d2 | ||
|
b29c15f919 | ||
|
e8dd208e96 | ||
|
9bbf09b5c4 | ||
|
9d9df11509 | ||
|
61d0d63ac2 | ||
|
e9a1042cc2 | ||
|
f7f9eaff95 | ||
|
9108a241a2 | ||
|
9aa2aae93e | ||
|
9421bb424d | ||
|
434ad8edc8 | ||
|
a4a35ce9cb | ||
|
d572052178 | ||
|
6f6c581c6e | ||
|
baea4c81c9 | ||
|
ad5a83586b | ||
|
b5346ee765 | ||
|
db0f4e3d1e | ||
|
c0a274e8ec | ||
|
61825e9bc8 | ||
|
c499f2bb36 | ||
|
6e2a1ebfbd | ||
|
a2c4f84f9c | ||
|
f17bd06116 | ||
|
2cfcfcf91c | ||
|
81ea7bd41e | ||
|
f16bee8fa1 | ||
|
c95143f41b | ||
|
19c5e4d424 | ||
|
6076c510f8 | ||
|
bc744a843a | ||
|
a812198c49 | ||
|
fbe97a79a3 | ||
|
3cc7f105a5 | ||
|
b31b270ffd | ||
|
0b5cdee6ef | ||
|
6eefa05f3d | ||
|
ca3f188370 | ||
|
498514dae1 | ||
|
1740f8227a | ||
|
2d300d807b | ||
|
d4ef9bf233 | ||
|
6c5ff863ff | ||
|
d61c096313 | ||
|
b9835389f4 | ||
|
720b4d56be | ||
|
4f6b92248d | ||
|
48c6051482 | ||
|
23bbceb367 | ||
|
46f61c3006 | ||
|
065e5849da | ||
|
acce7d4b16 | ||
|
e3a09db3da | ||
|
ece2fe2514 | ||
|
f1ccc717b4 | ||
|
7b735c272d | ||
|
55d3783699 | ||
|
5d933d09d3 | ||
|
f5280ebf6e | ||
|
8ef3bfcc95 | ||
|
51f8407a08 | ||
|
bd80e01eda | ||
|
ca7984f9a9 | ||
|
99cfee6f4b | ||
|
280fa99831 | ||
|
cdfaad18a2 | ||
|
9b9fe22669 | ||
|
a5a112796c | ||
|
9285db6ec2 | ||
|
3817236b68 | ||
|
c082eb7d8a | ||
|
abac366eac | ||
|
63f8eb253d | ||
|
fc1a91169b | ||
|
117be44c44 | ||
|
638d71a0c0 | ||
|
cdb16c9f00 | ||
|
bd66675081 | ||
|
77f56541d0 | ||
|
73fbf5c8fa | ||
|
d1e67645a2 | ||
|
a4da10bc62 | ||
|
e795fe7c3e | ||
|
f6db64bea1 | ||
|
b6138c39f0 | ||
|
535ad41ae6 | ||
|
106f39327e | ||
|
501691a3c0 | ||
|
06b404499e | ||
|
7260db2e74 | ||
|
d9407ee648 | ||
|
c7bde8be8d | ||
|
f91dbc6864 | ||
|
5d2eb580fd | ||
|
a8d3007e61 | ||
|
70d9efdce4 | ||
|
d890d3c103 | ||
|
21e1dc3248 | ||
|
cb14ea9e67 | ||
|
5cd4190f2d | ||
|
caecdd1ac0 | ||
|
405de05ca9 | ||
|
548d0a50b8 | ||
|
f57ca4fbf6 | ||
|
9230201ca9 | ||
|
43c85a68f3 | ||
|
1ab76184ed | ||
|
6e4094c714 | ||
|
838f8d8fab | ||
|
34e8467409 | ||
|
1b0519a94a | ||
|
447d6bfca5 | ||
|
1791a2f1f8 | ||
|
4677313feb | ||
|
c8cad3704e | ||
|
e154a07fe8 | ||
|
ca0a80944e | ||
|
d23c653fd8 | ||
|
d99c9efe4a | ||
|
a5ccbc2dfe | ||
|
e08bf3d835 | ||
|
bcf42ddf83 | ||
|
ce229c7500 | ||
|
9c569b41ed | ||
|
c9a214f038 | ||
|
08081109b7 | ||
|
2ce00b6359 | ||
|
d744d103c8 | ||
|
40ec559c8c | ||
|
42f1abd7e9 | ||
|
b327cafe1a | ||
|
fb4bd6158f | ||
|
a878b47343 | ||
|
f866ad2fa3 | ||
|
f341129afa | ||
|
03e7e3be74 | ||
|
7770cae4b5 | ||
|
039ebe1f14 | ||
|
5eeb260c08 | ||
|
1ec5687d36 | ||
|
e3b0602664 | ||
|
b1101fc33e | ||
|
bdf5fb7a72 | ||
|
e00f827de7 | ||
|
abc2341cb4 | ||
|
03f9ed8820 | ||
|
e875d2128e | ||
|
bb377fd20e | ||
|
a2aa6667e1 | ||
|
51d66494fd | ||
|
ed9e51d31d | ||
|
2921711638 | ||
|
6d15836171 | ||
|
3f840ecd89 | ||
|
9aa53d1616 | ||
|
35b5abc103 | ||
|
8ece0dfa93 | ||
|
ed23956e11 | ||
|
1a471ed04b | ||
|
eb8bc865d1 | ||
|
682ff99f1c | ||
|
4628ac6863 | ||
|
bb8f431817 | ||
|
8fb63c3675 | ||
|
3792dd1007 | ||
|
15cc7d5ed3 | ||
|
9fa4e21165 | ||
|
3872c0ab74 | ||
|
d71b293de5 | ||
|
5ebcb90620 | ||
|
39a4a89650 | ||
|
68ac48235e | ||
|
eb3fc985d6 | ||
|
c9dfb7d8c2 | ||
|
437cd0d3d7 | ||
|
f054ceeab8 | ||
|
7b95720f59 | ||
|
64e23c9be0 | ||
|
cabead30b5 | ||
|
688d4495c5 | ||
|
b6d36e2235 | ||
|
ebf7bf7583 | ||
|
6f37c8e57f | ||
|
21da42d23d | ||
|
2497437060 | ||
|
33a9395afe | ||
|
7654c9d9a1 | ||
|
27a513682a | ||
|
92cb0cc0db | ||
|
cf32f4cdb3 | ||
|
57978b0f7f | ||
|
eb532b8907 | ||
|
6f06641bf2 | ||
|
1ccc9b3e3b | ||
|
ec0379ad93 | ||
|
18789654fc | ||
|
bac546bdfa | ||
|
a6b506be45 | ||
|
6380f2e844 | ||
|
fef90d2a3c | ||
|
d522069080 | ||
|
d5e4c198d8 | ||
|
415d0d1dfb | ||
|
50d5b2345e | ||
|
d13534967a | ||
|
43070c7b26 | ||
|
45f8197635 | ||
|
3db02d2236 | ||
|
b79f6712e3 | ||
|
de63041b7d | ||
|
a5c064c33e | ||
|
9c832c24a6 | ||
|
c4a5325aaf | ||
|
93cf99a07c | ||
|
00ed61c685 | ||
|
f84d9f6208 | ||
|
9569b864ff | ||
|
8b69be8def | ||
|
4c619e32f7 | ||
|
ed86d9c066 | ||
|
6fbaa54d3e | ||
|
9d8fc7b984 | ||
|
8c2efe3aaa | ||
|
4b130bd82c | ||
|
44fb240955 | ||
|
bfea3ca9fb | ||
|
7ade182d64 | ||
|
4adc7aa58d | ||
|
110cf09cf8 | ||
|
fb25648b9c | ||
|
78170a5750 | ||
|
71d07b3a8e | ||
|
96c93ac2f1 | ||
|
6d61b18325 | ||
|
d01e44db0c | ||
|
464886ab61 | ||
|
3c574fca99 | ||
|
6b45498985 | ||
|
1fd4101e2e | ||
|
77f236db2a | ||
|
a0728023cd | ||
|
3705d705d3 | ||
|
72bc655ece | ||
|
c5f1f73fca | ||
|
925a673d57 | ||
|
c27787eb31 | ||
|
2b6fd968d2 | ||
|
ef7b4289c0 | ||
|
cbbfaf3202 | ||
|
f6be8e3e9a | ||
|
daa7f04f61 | ||
|
18290489b2 | ||
|
92ab17a644 | ||
|
ac02490c97 | ||
|
d498c2580b | ||
|
e740df6e50 | ||
|
b6a0d11b99 | ||
|
65defdd0a8 | ||
|
5aeb7ccf25 | ||
|
79e1cc3bbc | ||
|
1c409a38d3 | ||
|
bd51e181fb | ||
|
dbba0fe18e | ||
|
4e2a7465ab | ||
|
d93a13294a | ||
|
0fdeb7cd50 | ||
|
fe5199b9d1 | ||
|
bfc67a6f75 | ||
|
8adf242d02 | ||
|
f63f4f8924 | ||
|
28fcf33cda | ||
|
207124e7fb | ||
|
86d4e37327 | ||
|
e7e82cb289 | ||
|
9ae56332d7 | ||
|
03d828834b | ||
|
97beb8ee53 | ||
|
03381fa469 | ||
|
75b23524bd | ||
|
cd2c5f594c | ||
|
50b843ecb7 | ||
|
981b10d10a | ||
|
e1bcdb8e9a | ||
|
ddef217047 | ||
|
3e28cc2a25 | ||
|
f4cc7b3609 | ||
|
8779f665dc | ||
|
52552ebfe3 | ||
|
a6acc9d1a3 | ||
|
c588af6aca | ||
|
c076979225 | ||
|
f2995ac75b | ||
|
b5b0a9a284 | ||
|
b139d8165b | ||
|
ef2e390b10 | ||
|
51e6d4645c | ||
|
2151be6cde | ||
|
7a06cac680 | ||
|
c25df39cd0 | ||
|
1813bf9e53 | ||
|
70dab667b9 | ||
|
30b3cc3407 | ||
|
406cafb642 | ||
|
9822412ebd | ||
|
899f0fef13 | ||
|
0df17ed359 | ||
|
9cf18176f0 | ||
|
b19ebf0c2e | ||
|
489f7a3136 | ||
|
066bb696e3 | ||
|
024900ee9c | ||
|
ab83d173ac | ||
|
d316bde561 | ||
|
38e9aa79e7 | ||
|
a6340c6b03 | ||
|
070ba5ded4 | ||
|
f836c928a8 | ||
|
1786374021 | ||
|
6866fa3eaa | ||
|
9511bc491a | ||
|
ae5df65af0 | ||
|
2ba90e63b1 | ||
|
b30ccbde9e | ||
|
c3c088b8c3 | ||
|
3c82ced097 | ||
|
d2377f11d3 | ||
|
c70d2d62d1 | ||
|
29eb4c2bed | ||
|
4e44dc69f9 | ||
|
bee5df20fd | ||
|
2f5606702e | ||
|
6f2c1e52ad | ||
|
32e495021f | ||
|
ca19796514 | ||
|
3e3560753b | ||
|
0265c847b8 | ||
|
739e02655e | ||
|
df6b9028ec | ||
|
f4057b2c3b | ||
|
839da86cac | ||
|
a899056fd1 | ||
|
c484a92933 | ||
|
da855b13a3 | ||
|
8ef8733ddc | ||
|
45a11f3dc2 | ||
|
3fdd810653 | ||
|
f355f247c3 | ||
|
4b886e2a58 | ||
|
d47eed079e | ||
|
d2b15b5958 | ||
|
d96bc98244 | ||
|
c2cab7a6a8 | ||
|
38df3ca5ad | ||
|
5e4b2c9ddf | ||
|
d777f5bb38 | ||
|
e912f87b2a | ||
|
0cc3aa3af8 | ||
|
adac03729d | ||
|
17a14ec25f | ||
|
4e73cdf9c6 | ||
|
66c354baff | ||
|
76399bd643 | ||
|
4f2aba2d22 | ||
|
085a97f835 | ||
|
a2141ceaac | ||
|
9d55d06a20 | ||
|
e93d53ed92 | ||
|
8cb7c0f4fb | ||
|
773172cc99 | ||
|
937b38371f | ||
|
c312fead97 | ||
|
bfff6cfe85 | ||
|
86c735637e | ||
|
ff26d30362 | ||
|
79a5802ad4 | ||
|
0e2ca3eb51 | ||
|
e488604448 | ||
|
eaa77c91cd | ||
|
8ed0a5946b | ||
|
0cd7dfb465 | ||
|
f38b1dab13 | ||
|
74d5a42578 | ||
|
6ecadba69a | ||
|
b46e5c4ce8 | ||
|
96139eef48 | ||
|
0c3a5da3cb | ||
|
c456625ef2 | ||
|
630ef98951 | ||
|
7179823d56 | ||
|
8870d33fcd | ||
|
710fc95d9a | ||
|
93a24abbab | ||
|
113162b651 | ||
|
5babf0872e | ||
|
331232858a | ||
|
fcb8cd2145 | ||
|
514910621e | ||
|
7251f47d5b | ||
|
edda9515d6 | ||
|
4632df5661 | ||
|
11a5c50591 | ||
|
3092078709 | ||
|
cbb71058ca | ||
|
9dd15a4415 | ||
|
30745e0621 | ||
|
c9659e1aca | ||
|
1fed51ac0d | ||
|
590b225fcc | ||
|
2f8a429f91 | ||
|
64e6fa93fd | ||
|
ee917209fe | ||
|
fa0a38c1ac | ||
|
f936217309 | ||
|
fe0ec8ceca | ||
|
6737d304a6 | ||
|
fe7186d549 | ||
|
c81226906a | ||
|
699361c5d0 | ||
|
903bcd2466 | ||
|
c714ee9b55 | ||
|
bf603bdffc | ||
|
cd73caac1c | ||
|
aec884f0d1 | ||
|
e3ce6d645a | ||
|
d2cba98f70 | ||
|
217e951e68 | ||
|
99b75b5004 | ||
|
42d545e579 | ||
|
e3300259ff | ||
|
68273acc2a | ||
|
e20664fe55 | ||
|
8c761f5774 | ||
|
b6ffec12e4 | ||
|
5d4d02617d | ||
|
327b283136 | ||
|
4bb0c57e29 | ||
|
a9cae5e314 | ||
|
b9ac607a5e | ||
|
7e035115bb | ||
|
099dd68630 | ||
|
5d7ef50054 | ||
|
2395564c17 | ||
|
5e7a685ba8 | ||
|
f7cbf41f03 | ||
|
70933d81a8 | ||
|
a8115267a6 | ||
|
a9e99241b7 | ||
|
bca4adcdc7 | ||
|
79281d59c7 | ||
|
e6a83654a8 | ||
|
f4f166a66d | ||
|
65fa0cf8ed | ||
|
046dc02f73 | ||
|
c2fa453725 | ||
|
e731260056 | ||
|
9a04c9a350 | ||
|
13912be35d | ||
|
2e68adefb3 | ||
|
bf9e6146df | ||
|
2c190cfb56 | ||
|
5fa2e30709 | ||
|
faafa21480 | ||
|
3f929e3766 | ||
|
9a1dffc7dc | ||
|
c81ab53eb2 | ||
|
7759e52850 | ||
|
e7336e1d5e | ||
|
68aef96f0a | ||
|
0b120b8fa9 | ||
|
28c36af67c | ||
|
d67b9eaf17 | ||
|
6034985686 | ||
|
7da64b725d | ||
|
ba22ae9ab3 | ||
|
8aaf5d071b | ||
|
6a6ead6cfb | ||
|
10fefe87a6 | ||
|
de3c028f0d | ||
|
b6233ae2e5 | ||
|
fe5f008d39 | ||
|
c8743cb9c4 | ||
|
1995754bc3 | ||
|
f749b3deee | ||
|
ebfffe3f2b | ||
|
de4a6baf80 | ||
|
584326e9b6 | ||
|
a7becdc6b3 | ||
|
4de2dcebb5 | ||
|
678a80dd19 | ||
|
55d1f5e9c0 | ||
|
c18bf4f898 | ||
|
728e7f92b2 | ||
|
1740043678 | ||
|
891d68838a | ||
|
108398c213 | ||
|
8bdee27248 | ||
|
00c6dc9788 | ||
|
fb67682a79 | ||
|
5ed50a4739 | ||
|
c30c3def8b | ||
|
2f74f405ae | ||
|
c7e05f63e6 | ||
|
d65305564f | ||
|
660c302525 | ||
|
d7aad3a7cf | ||
|
15ed0f6a84 | ||
|
ffb8d23632 | ||
|
682632756f | ||
|
39c5e66ed1 | ||
|
b66780633e | ||
|
bd2bb51f0f | ||
|
ac69464143 | ||
|
62889b5b7f | ||
|
c4a696be1d | ||
|
cf5f31c577 | ||
|
fa49e5a57b | ||
|
750a17e41e | ||
|
5d4281fbbe | ||
|
2c18225654 | ||
|
cc6610f42c | ||
|
77a1f32e2a | ||
|
63e7276c7d | ||
|
a5e10b4ea1 | ||
|
b8e621cb2f | ||
|
68a164da2b | ||
|
5db855d728 | ||
|
06aa596519 | ||
|
d47864dedf | ||
|
581ca4fdf4 | ||
|
6771b7526c | ||
|
d7c06b1337 | ||
|
18bc28bea2 | ||
|
93e6e64eb0 | ||
|
0cd1eac6a8 | ||
|
0e83a8e5ef | ||
|
f951f246b8 | ||
|
50328de63c | ||
|
a6c8c20bd9 | ||
|
ea1fcc2246 | ||
|
8915db5f83 | ||
|
a7167ce715 | ||
|
3e0c7758ef | ||
|
b0830db25a | ||
|
bcc546bdfa | ||
|
2b161db6d8 | ||
|
6bedb02ac0 | ||
|
bc6a690733 | ||
|
84b699a47c | ||
|
7addfc8358 | ||
|
841fc15dd3 | ||
|
0875d16656 | ||
|
9c7c709b3e | ||
|
c5268f1da7 | ||
|
16d686bc42 | ||
|
137f80e177 | ||
|
7d8099fc77 | ||
|
5731ad11c8 | ||
|
23e11e3a57 | ||
|
76b9cb5dcc | ||
|
9746dd20df | ||
|
dc9869f1a1 | ||
|
1d9f495c46 | ||
|
ebab75642e | ||
|
ed58ff9f81 | ||
|
605326b6e8 | ||
|
2071958f35 | ||
|
4fee14d1cf | ||
|
244fab6c04 | ||
|
74ea79ac1b | ||
|
0d100a898f | ||
|
0a28b28da3 | ||
|
aa1f6fbd5f | ||
|
daac459797 | ||
|
43f1823cdc | ||
|
7c45f75010 | ||
|
3531164fed | ||
|
e75033b1f4 | ||
|
cc46f398ed | ||
|
ecd3c06932 | ||
|
3aa8701c6d | ||
|
11a521f10a | ||
|
5ae120af80 | ||
|
33e8b19ea4 | ||
|
ab1fc94a5b | ||
|
8e078fb708 | ||
|
db4e2cec9c | ||
|
c15ad25073 | ||
|
a14d6bcb11 | ||
|
1275bdb623 | ||
|
009e3d6c0f | ||
|
d7d8725ca9 | ||
|
7d54518d60 | ||
|
d6e67e3982 | ||
|
c914d417d2 | ||
|
65919abd9a | ||
|
df387ad943 | ||
|
732d3cd2d4 | ||
|
a56c72d190 | ||
|
9ba8d5892a | ||
|
fe04c98327 | ||
|
3e5dbb42f5 | ||
|
dfeb6b5acb | ||
|
547c8dd3f2 | ||
|
8924ccd088 | ||
|
70b7ec0c1c | ||
|
0054f5388a | ||
|
161b00a4b6 | ||
|
6b6165cdeb | ||
|
d27e48ae1b | ||
|
c3b2c1fdef | ||
|
aa5a39160a | ||
|
39230c9a22 | ||
|
9fed78c92d | ||
|
329a2a50e6 | ||
|
17116b9b46 | ||
|
e57e42d0f8 | ||
|
7d249c97ad | ||
|
4111d7238c | ||
|
760cf515a7 | ||
|
7cbd500534 | ||
|
89558657ea | ||
|
6134220b8a | ||
|
975413b48d | ||
|
f95a277ac5 | ||
|
3bbf5d03e2 | ||
|
13a784eeed | ||
|
8b95be91db | ||
|
52dbaf20ae | ||
|
427b480f68 | ||
|
4fa5e95241 | ||
|
3e272d339a | ||
|
31cfce4ca5 | ||
|
66e5078e28 | ||
|
f29283a620 | ||
|
4583f4bc8c | ||
|
55f41f40a1 | ||
|
d1fea74d0e | ||
|
934ca25ab2 | ||
|
7a8fe2a72c | ||
|
b21ad733ea | ||
|
9b80da87f0 | ||
|
a4db174b58 | ||
|
0b9272209a | ||
|
416a802789 | ||
|
aaf465b942 | ||
|
c2d8c35daa | ||
|
eabb29d9c4 | ||
|
3b5e2680e9 | ||
|
62ccea031b | ||
|
3a109cecdc | ||
|
84d154d732 | ||
|
097c3ea059 | ||
|
7a0d6c6196 | ||
|
b3b6a48797 | ||
|
b7110eb4d4 | ||
|
4703dac49d | ||
|
b68e7c8186 | ||
|
4b7e39e304 | ||
|
c0704db03a | ||
|
e82359bca0 | ||
|
dc4ab96d4d | ||
|
bedd13d411 | ||
|
f3c078fcbf | ||
|
d69deec136 | ||
|
91194de7e7 | ||
|
e412d1a19e | ||
|
17d8ed4bad | ||
|
aa58498d23 | ||
|
4cf8496422 | ||
|
df539e60e0 | ||
|
401423ca2e | ||
|
d1561dc357 | ||
|
3f6758e18d | ||
|
b59836e6d9 | ||
|
3eb48340ad | ||
|
b3aba4691b | ||
|
e4db019f47 | ||
|
dc7098b3fb | ||
|
6d3ff10165 | ||
|
3622433cf4 | ||
|
9c9cdfdb6d | ||
|
a0a4bbaa7c | ||
|
3e0fad1cf6 | ||
|
28affe79d8 | ||
|
958fd5b947 | ||
|
f587a74597 | ||
|
5e334fa830 | ||
|
f9977975ae | ||
|
e35c92eabb | ||
|
49296cad14 | ||
|
dfed1dbc17 | ||
|
0dd27a46ee | ||
|
20299f87cb | ||
|
7f5cbf7dd8 | ||
|
c14e4235ee | ||
|
8ff44026c3 | ||
|
43caec46e7 | ||
|
024ccf6826 | ||
|
0f76002dde | ||
|
9405d51716 | ||
|
2ecfbdb1bd | ||
|
6ee62650af | ||
|
66df21ba78 | ||
|
4f62b059ef | ||
|
af9b7b5605 | ||
|
e0795227e4 | ||
|
0c3fb1804e | ||
|
3ed99f8c20 | ||
|
636c212235 | ||
|
19575e799f | ||
|
83cd4f7d58 | ||
|
bfc9921305 | ||
|
72f3a2fc3f | ||
|
3c389d17e8 | ||
|
2524a0e749 | ||
|
8d3b878965 | ||
|
9e5fe2c61d | ||
|
8bb4a4039d | ||
|
50e729a687 | ||
|
c77494e2aa | ||
|
b71206d119 | ||
|
f32435e9b1 | ||
|
422d030820 | ||
|
b3798a9116 | ||
|
dcb806302f | ||
|
cb0ee2373d | ||
|
d0b3c45f04 | ||
|
2149d893bc | ||
|
6c9e8817b7 | ||
|
0be7616521 | ||
|
0fd90de97e | ||
|
7dfd3f18f8 | ||
|
b164baa1f5 | ||
|
ecc755881e | ||
|
f4781b63a8 | ||
|
b48958c177 | ||
|
f3356d1f86 | ||
|
0e0682ff7a | ||
|
bd2c3d1c9c | ||
|
3eb343020d | ||
|
f2d2a4c698 | ||
|
4b8b7d5a77 | ||
|
55620c1d13 | ||
|
439cccf65f | ||
|
f7cd08ea48 | ||
|
e43cf081f1 | ||
|
a96b6f79c6 | ||
|
93f0f21a4c | ||
|
8b5197b108 | ||
|
0603dd8305 | ||
|
f01e0efc2d | ||
|
fd021d793a | ||
|
cd90f831af | ||
|
8e51a10908 | ||
|
57af1d2310 | ||
|
ec95a50bb6 | ||
|
b0d449565d | ||
|
de13d6c823 | ||
|
c765b3d0b7 | ||
|
b2a0279253 | ||
|
dec11e639a | ||
|
0cebc50291 | ||
|
46dcd6e7ca | ||
|
8cabd99d24 | ||
|
c29d3a4ecb | ||
|
c4b24b6a4d | ||
|
c8dccc9bc4 | ||
|
72d978ee48 | ||
|
7fcf11013e | ||
|
d816e0605b | ||
|
34dcb410a9 | ||
|
2727072ad9 | ||
|
e7dfa98030 | ||
|
3fd739ea2c | ||
|
c89f30e485 | ||
|
016f644815 | ||
|
7e7d526ed5 | ||
|
0e8c913c57 | ||
|
892402a5d8 | ||
|
0cc695e22e | ||
|
cc7c41a9c0 | ||
|
d78150de79 | ||
|
be220dc905 | ||
|
8133d93678 | ||
|
38a559b93e | ||
|
2b290e08ea | ||
|
25c4df0d3e | ||
|
88bdf4b96f | ||
|
28bd5d81d2 | ||
|
0620ec2f2e | ||
|
077989bfed | ||
|
b57a338546 | ||
|
edb6d2d661 | ||
|
df180d1d36 | ||
|
8b6608a9ca | ||
|
9f0ff865e8 | ||
|
bc33875a9e | ||
|
9ae054caf6 | ||
|
f698f07ff9 | ||
|
6cfac546a1 | ||
|
4704e3a966 | ||
|
9c9f6faf38 | ||
|
7e2de94e1f | ||
|
0a2f2e28b4 | ||
|
62b2e31bf4 | ||
|
86db3670aa | ||
|
9b69eed5ce | ||
|
aaaa54a397 | ||
|
7ae5ddefe5 | ||
|
943bb552a2 | ||
|
251eda6fb8 | ||
|
0cfb853d7f | ||
|
c918c98ffa | ||
|
f2b9babfd3 | ||
|
4fd23c4716 | ||
|
5f1b684719 | ||
|
ee22ffb878 | ||
|
a9cb6a6a90 | ||
|
0679ea07c6 | ||
|
67b99eadbd | ||
|
95ae324580 | ||
|
4e13d1239a | ||
|
4ae2a107bf | ||
|
dc34041600 | ||
|
b1a2892038 | ||
|
b82d1d5063 | ||
|
56aaad03bc | ||
|
0166071ec9 | ||
|
e0d4eaf819 | ||
|
0861a0652b | ||
|
1d58ad6d9b | ||
|
e1a022be27 | ||
|
58e4eda1c6 | ||
|
d26ae6875b | ||
|
afe93137fc | ||
|
bed14e60c2 | ||
|
c816797879 | ||
|
730b9ea3fd | ||
|
6bd0277020 | ||
|
7d09de489d | ||
|
0d09c2cda9 | ||
|
42670aa64a | ||
|
6d7daf6401 | ||
|
f28da7c9e3 | ||
|
7bae2a7cd2 | ||
|
7183f62e13 | ||
|
4afc30c79f | ||
|
764dab90fc | ||
|
c6a8f0f9af | ||
|
966d7027a3 | ||
|
dfc92d9f96 | ||
|
ac0f4e14a8 | ||
|
ca597f4314 | ||
|
c1fef9b51d | ||
|
b8bb4b4cc4 | ||
|
4425fb0b83 | ||
|
5bb254e629 | ||
|
8fce97dca9 | ||
|
f6ff075335 | ||
|
d64b81c673 | ||
|
0ab263b329 | ||
|
7bf147d8c2 | ||
|
6ef7b2f588 | ||
|
51ec5c442b | ||
|
f0d79df9b9 | ||
|
05958a708e | ||
|
21fbc36311 | ||
|
cea29fe485 | ||
|
fe386a66df | ||
|
92527a5206 | ||
|
266bb70b7d | ||
|
56758372cf | ||
|
1314b29557 | ||
|
ef7814c018 | ||
|
d50fff8410 | ||
|
44089b03df | ||
|
a906569c58 | ||
|
b98895b8c7 | ||
|
e766df658c | ||
|
adc7208169 | ||
|
3a441d6d3a | ||
|
082df3a573 | ||
|
697fce7ebc | ||
|
70e76bcb4d | ||
|
1ebc1e7c49 | ||
|
760f1654c2 | ||
|
d77af0bc1f | ||
|
735da5fedd | ||
|
cda64ca663 | ||
|
166ed1c7d4 | ||
|
3758e7697e | ||
|
315ef41455 | ||
|
c1210d7c73 | ||
|
a4df0acb00 | ||
|
c4944a58d5 | ||
|
d116e7235a | ||
|
3e7459b428 | ||
|
338de2e1fb | ||
|
12ee5e6cac | ||
|
262e4981db | ||
|
df8260e29a | ||
|
87c765c7be | ||
|
0b02e080c4 | ||
|
d9bd306da3 | ||
|
90309ec0bf | ||
|
eb31b765a5 | ||
|
76716c23b0 | ||
|
84235c7903 | ||
|
c5061f9289 | ||
|
6a5cec1f87 | ||
|
d8d89cca7d | ||
|
7210f994fb | ||
|
e6eeb17030 | ||
|
a987301204 | ||
|
5c693ccd96 | ||
|
5636dbf12b | ||
|
c530c83337 | ||
|
0b62453f6d | ||
|
4cd08f0ef9 | ||
|
461ba000e3 | ||
|
42019e0b8d | ||
|
8f60050991 | ||
|
a8ed534cd3 | ||
|
adcb052915 | ||
|
3d41abd318 | ||
|
5282f99bd6 | ||
|
9e5ea95f0f | ||
|
8381ed1d2e | ||
|
28a275cba2 | ||
|
f74d0f5526 | ||
|
2dab009e76 | ||
|
092ab094c4 | ||
|
6d2914c99b | ||
|
e14c6646f6 | ||
|
fff793be16 | ||
|
2a740d3807 | ||
|
659fbb3df7 | ||
|
23fd4444b5 | ||
|
ddee04d991 | ||
|
f76bf72021 | ||
|
537e3d18ae | ||
|
75e1da1f2d | ||
|
1bbf20bf39 | ||
|
9054b25441 | ||
|
862cfb9a3b | ||
|
87161f5920 | ||
|
9668aa58ef | ||
|
0ca80a9188 | ||
|
26767a5e06 | ||
|
b47a44582a | ||
|
5619fefe0a | ||
|
94fae97f88 | ||
|
11fde67edc | ||
|
015ed5bc98 | ||
|
b1008b1256 | ||
|
96c2a56aa1 | ||
|
5e02e7fb56 | ||
|
9ea6922a2f | ||
|
d9b919d5a6 | ||
|
89fbaa4de5 | ||
|
d86759da22 | ||
|
56a52b0b01 | ||
|
7cdbe1badb | ||
|
149b3d09e6 | ||
|
1c925a0f2e | ||
|
0c3deac9db | ||
|
21bbba82fb | ||
|
4ea2bea22e | ||
|
e328c1f829 | ||
|
ca115dd6a4 | ||
|
9a2dbb845c | ||
|
5f09ec73c9 | ||
|
c55f99073b | ||
|
b1446b7752 | ||
|
b6f0070f85 | ||
|
2489e0ba6e | ||
|
96143a9bb2 | ||
|
459e769844 | ||
|
37a1d546b7 | ||
|
b209e91ad4 | ||
|
0c78da99b0 | ||
|
10681a53ae | ||
|
06691aeafc | ||
|
d3b5d0ca83 | ||
|
3a39874025 | ||
|
462df477b0 | ||
|
74e495569e | ||
|
3ed58c281a | ||
|
21d2af9e90 | ||
|
4cb8067dce | ||
|
74bf948611 | ||
|
586e25c48c | ||
|
e3f32ec6b4 | ||
|
4e198915f2 | ||
|
9f92d5fb7e | ||
|
4a9c387519 | ||
|
5ad167410c | ||
|
61f0ee891f | ||
|
a32c69813d | ||
|
1f6e325d71 | ||
|
70241e25db | ||
|
d9ceb84445 | ||
|
0f64d0ee95 | ||
|
d5658c496f | ||
|
16c900b0a1 | ||
|
105947b50c | ||
|
b1d21d571f | ||
|
e1d9ac8b29 | ||
|
faddc71eac | ||
|
2120e396fc | ||
|
3fd079262c | ||
|
1804b78406 | ||
|
efe7cf2a95 | ||
|
77e84ccca8 | ||
|
1b21703c7b | ||
|
3c55a27230 | ||
|
73c2e5355f | ||
|
31857210c5 | ||
|
56809cb711 | ||
|
7522246ccd | ||
|
1db0a6fc92 | ||
|
2e3a552c3c | ||
|
005517539b | ||
|
fed14c8f4c | ||
|
55d190bad9 | ||
|
8a7caec54a | ||
|
97b47dea87 | ||
|
b0928219c3 | ||
|
fe8a101960 | ||
|
daf37363b4 | ||
|
f9a36f3eb1 | ||
|
b964b2abf5 | ||
|
603e250745 | ||
|
7001d971e1 | ||
|
f73a8a8680 | ||
|
7cf92b66cf | ||
|
38485c9f30 | ||
|
fe98185319 | ||
|
112ce742f5 | ||
|
62da4db09a | ||
|
041a4c0aa5 | ||
|
071ad525c8 | ||
|
de1856a73c | ||
|
1d71c677f6 | ||
|
6e82dd5d0c | ||
|
9afbde9b0a | ||
|
0627d902dd | ||
|
cd0c727880 | ||
|
c4e3b95301 | ||
|
9bf7d53dc2 | ||
|
7ba13d8e72 | ||
|
05b3b4e51e | ||
|
8f490d1474 | ||
|
c139eacb2d | ||
|
9b64b95bbc | ||
|
e8fecd1eec | ||
|
0f3b82f661 | ||
|
76b3fd4311 | ||
|
b274fdd142 | ||
|
f5ce471ebb | ||
|
fe65c122d2 | ||
|
787eaaa373 | ||
|
8d4e7994c0 | ||
|
70b615e8d4 | ||
|
280ed17ea2 | ||
|
dc2cd19d56 | ||
|
5807e2eda0 | ||
|
649dd04020 | ||
|
7fcf94be64 | ||
|
de441fffbd | ||
|
44b1a3bcff | ||
|
775238891a | ||
|
309f9a3172 | ||
|
fc965c57ac | ||
|
f6850f5367 | ||
|
72e136b9da | ||
|
c8a0951566 | ||
|
6294e1710a | ||
|
10c50aaf2e | ||
|
11b410dcfe | ||
|
5374d06d92 | ||
|
b13e9f01fc | ||
|
75e360b8b3 | ||
|
fde4186883 | ||
|
2a0c9d83e0 | ||
|
c0cc531b50 | ||
|
cea56c3840 | ||
|
2c088a17c4 | ||
|
2138fccfec | ||
|
c5bee38e28 | ||
|
92bc728f39 | ||
|
1e3f633665 | ||
|
12770cae88 | ||
|
5627521f16 | ||
|
411b89647c | ||
|
5d582b1b3a | ||
|
6eaa838e3e | ||
|
12339d4e7c | ||
|
989a7c992e | ||
|
c424a80705 | ||
|
9f071c7dbe | ||
|
693f353811 | ||
|
12652852bd | ||
|
af331a9e7c | ||
|
75a4463811 | ||
|
860aa017db | ||
|
448728f584 | ||
|
4eab5b238a | ||
|
58708f7162 | ||
|
9b5097fe9d | ||
|
c67a611313 | ||
|
7762e6e836 | ||
|
d4b16c981d | ||
|
45909eca61 | ||
|
7fe4fccda8 | ||
|
24c0749eb8 | ||
|
5c89717ecd | ||
|
8d1a8d3f4f | ||
|
e86bc5b901 | ||
|
a7a8631bc3 | ||
|
c01750be2e | ||
|
5c3655dff9 | ||
|
27eef5f991 | ||
|
b97edde1f7 | ||
|
deb41a10d7 | ||
|
a2e2e5c8a0 | ||
|
25e3968199 | ||
|
6db7b13266 | ||
|
f2ba1f6fdb | ||
|
35e9c6decf | ||
|
8d63f876d5 | ||
|
765a18ed59 | ||
|
d9daf2944d | ||
|
b321f67ed2 | ||
|
73647a5e46 | ||
|
6a99303ec1 | ||
|
e762442917 | ||
|
488a272f8b | ||
|
e2216f70f5 | ||
|
efe400264f | ||
|
fd57088906 | ||
|
10b170c6cd | ||
|
234d65dc9d | ||
|
cacc4aa829 | ||
|
04b0d258ff | ||
|
e40dfb771e | ||
|
5798155892 | ||
|
da151e52a7 | ||
|
17d9ed31f9 | ||
|
1bc49f680d | ||
|
6c70a7fe11 | ||
|
cd4caeb03d | ||
|
58bcfb006e | ||
|
369612a9b2 | ||
|
50c25f479d | ||
|
3fb12d1f5f | ||
|
16912b8a40 | ||
|
c17ab74521 | ||
|
7272403cfd | ||
|
64e35a1e44 | ||
|
f393ae1c51 | ||
|
9249297d31 | ||
|
538d2e4d62 | ||
|
81f82ae7b3 | ||
|
5cb6f02770 | ||
|
2bcd2e510e | ||
|
ab9f47a5fe | ||
|
54ccb92444 | ||
|
c24e566591 | ||
|
6d7ec5a257 | ||
|
b1877d08ae | ||
|
9a5df2b284 | ||
|
6867c13bd1 | ||
|
a758e32f91 | ||
|
6e339fc849 | ||
|
f0de3ff098 | ||
|
d6645f84f2 | ||
|
cd6aa4a708 | ||
|
cd4f732a87 | ||
|
06a8cb1e5a | ||
|
b095ad872c | ||
|
0671114bbb | ||
|
5ec0f5b299 | ||
|
33b1cf2931 | ||
|
89dba59ce2 | ||
|
426d207fc4 | ||
|
362df3a031 | ||
|
bf31cca375 | ||
|
6b1c2ff762 | ||
|
e6696178db | ||
|
9fdaa1c7e5 | ||
|
500609736c | ||
|
883c9f960c | ||
|
cd9ac17d59 | ||
|
baeca4109a | ||
|
62be36cc9e | ||
|
ee902621db | ||
|
6ca0f144fa | ||
|
eca3904dfa | ||
|
12e19cedc0 | ||
|
317816d6c7 | ||
|
1d48cb88fe | ||
|
ec7126420f | ||
|
04a77d3bf3 | ||
|
aa7695c513 | ||
|
08faeda430 | ||
|
88a4b8cb9b | ||
|
e3f0db7708 | ||
|
8707fb89f3 | ||
|
0c6cecaacb | ||
|
061750bccb | ||
|
c53627f5ac | ||
|
bda4ebc939 | ||
|
cb64ba8258 | ||
|
0a75417d5f | ||
|
100ac532ed | ||
|
3292677fe6 | ||
|
54a09967a1 | ||
|
17380fe079 | ||
|
ffa10a25d6 | ||
|
3bcf808005 | ||
|
b3dde2bafc | ||
|
87de3fc25f | ||
|
1e7a1da14e | ||
|
5e43ee61a2 | ||
|
135bead730 | ||
|
7ce33be933 | ||
|
f4021c8028 | ||
|
0a01b46112 | ||
|
b91918b027 | ||
|
9e90718bd5 | ||
|
0387d6f6d4 | ||
|
1e3c4dc0ef | ||
|
c778d3cfd4 | ||
|
67d27c81d7 | ||
|
dcd61289d8 | ||
|
aa07d7a3a2 | ||
|
a902538e34 | ||
|
2cd7fcb16d | ||
|
5f4f828bae | ||
|
bb3d4ab9ba | ||
|
e3efef40c4 | ||
|
19e4fc1414 | ||
|
3d31884c3b | ||
|
b6085afb5a | ||
|
9720ed39f5 | ||
|
2e0d2e65af | ||
|
e989cb05ed | ||
|
fab6ddee14 | ||
|
e34e58f529 | ||
|
fc9ae9ebc6 | ||
|
3351c1f869 | ||
|
2d0a5c42a9 | ||
|
5dbb0b4cfe | ||
|
0fb127b935 | ||
|
6439a95b65 | ||
|
01373aa3a6 | ||
|
c22749a630 | ||
|
e6f5998b76 | ||
|
e6eeed6cf2 | ||
|
5a25c7c5fb | ||
|
500fed496c | ||
|
2ee45b1820 | ||
|
95199c754a | ||
|
505ee31635 | ||
|
eec7b87288 | ||
|
0afc21eed9 | ||
|
428f753c03 | ||
|
63bf01a60a | ||
|
0fc2d39cce | ||
|
171a254425 | ||
|
cba048ad8a | ||
|
100832b677 | ||
|
35313d18bc | ||
|
0bb950ad55 | ||
|
4884cd2bcd | ||
|
cda94ee61d | ||
|
e008f3132d | ||
|
fdc92b136f | ||
|
753cd8fec1 | ||
|
7ab9d24d3b | ||
|
03cc19babb | ||
|
0aef609dad | ||
|
0117473cbc | ||
|
21fba6577a | ||
|
0fb2e4e4cc | ||
|
5c322ed6cf | ||
|
f5066996b8 | ||
|
44be200e28 | ||
|
97b4679ca8 | ||
|
36dc9e26b6 | ||
|
0146b3e2c0 | ||
|
c9f461c4eb | ||
|
a159ae4b06 | ||
|
9f6a3ea9a2 | ||
|
ea353471d5 | ||
|
1f301e7e6f | ||
|
7f920b5cfc | ||
|
ffa29f9466 | ||
|
02ebd4db78 | ||
|
f7b8b699b8 | ||
|
782429487f | ||
|
2c8f115400 | ||
|
4da4ab3650 | ||
|
bfad1f7086 | ||
|
d83a765dbe | ||
|
a9705c4f34 | ||
|
f7aac55374 | ||
|
5cc1cb3f87 | ||
|
4dc7f0cb4b | ||
|
a85ae436eb | ||
|
2e7e1079c4 | ||
|
51a016245a | ||
|
a2bdbc97dc | ||
|
86a4085bf9 | ||
|
3990f1cc07 | ||
|
d1d65f8090 | ||
|
78e592579a | ||
|
d3e609d7ff | ||
|
5e2067decd | ||
|
cefbe02130 | ||
|
6fa2748fc7 | ||
|
efe4fd2cc9 | ||
|
3f0d2a6ac2 | ||
|
8f31aad0fb | ||
|
463a8af35b | ||
|
0ee51f816e | ||
|
c2c74075e7 | ||
|
68241762e7 | ||
|
c6edd6fb91 | ||
|
231a34b627 | ||
|
a2c534b391 | ||
|
42dd0219e4 | ||
|
e28d223d42 | ||
|
8c8c86038a | ||
|
e6b48b613a | ||
|
f6fe3b44de | ||
|
57d76d1f7a | ||
|
30bb2363e1 | ||
|
b7fb9bdeb1 | ||
|
3afa4498fe | ||
|
0c62355fb5 | ||
|
d986d09b7b | ||
|
9736a36efb | ||
|
e4ce115876 | ||
|
5b4b3fa9ea | ||
|
a238179c23 | ||
|
8ee3c6ddb0 | ||
|
9428efd2ae | ||
|
86dad2d727 | ||
|
db635c9fbb | ||
|
b67f2da821 | ||
|
1cab079d6f | ||
|
83cf4cd3d5 | ||
|
c99f258a47 | ||
|
bc9c5bad5d | ||
|
051d9e6f91 | ||
|
6a38111528 | ||
|
ebd47557f0 | ||
|
5819f7a468 | ||
|
2d75e9b5ba | ||
|
f11607dfa6 | ||
|
8c29c3711d | ||
|
175c361018 | ||
|
375fbd9419 | ||
|
cc4e0f2642 | ||
|
20d74dad23 | ||
|
9922d3337e | ||
|
df90263b00 | ||
|
a2c39f34a0 | ||
|
07174cbfdf | ||
|
b943466727 | ||
|
a283b2a894 | ||
|
5edf164e8b | ||
|
5618ef55fa | ||
|
eeb96795e5 | ||
|
8b2a5a0fc7 | ||
|
1488457641 | ||
|
b3f375c57f | ||
|
eec465704d | ||
|
fac0d47913 | ||
|
37abc146b4 | ||
|
b44520b7d1 | ||
|
eea1432e80 | ||
|
3b741a6d5f | ||
|
673570ec2a | ||
|
a5fb85f0fc | ||
|
21f9a445e8 | ||
|
30f7f15778 | ||
|
5765d92ac7 | ||
|
b64e47078f | ||
|
83e303b059 | ||
|
1e9cc6b0a5 | ||
|
90bf7eb817 | ||
|
5d7d45f0e5 | ||
|
971f924c44 | ||
|
f414876e99 | ||
|
1b451cce06 | ||
|
ab2c98e41c | ||
|
595482ca57 | ||
|
0986cb9379 | ||
|
abf38e97cc | ||
|
f89c3eda17 | ||
|
5a81413f17 | ||
|
c590eb656f | ||
|
17d7d48281 | ||
|
9b94e45e25 | ||
|
129597bbed | ||
|
48b6f76159 | ||
|
19454f26e6 | ||
|
99f07f5054 | ||
|
469c045aa9 | ||
|
5a94808c27 | ||
|
81dca1f7e9 | ||
|
d752de0ee0 | ||
|
ed6863851e | ||
|
921f43da6f | ||
|
94e58d7acc | ||
|
bf0cff9a8a | ||
|
a846fb0d21 | ||
|
4ad1c2daed | ||
|
d37d3b54f4 | ||
|
cf7cce9079 | ||
|
3f7c9846e7 | ||
|
75de63dff1 | ||
|
089d7212e7 | ||
|
d329ea1fe0 | ||
|
8f5ae79b51 | ||
|
86b1f2017a | ||
|
9b1cddf520 | ||
|
251d34d6e6 | ||
|
8358dda7f1 | ||
|
76e29c21f1 | ||
|
56222db3ba | ||
|
5072b45026 | ||
|
63449a44c5 | ||
|
ecffc831fa | ||
|
6c76af5181 | ||
|
061d3cc8fd | ||
|
6248b67f75 | ||
|
fbf55e17cb | ||
|
289f2896d0 | ||
|
4fed2d9fc0 | ||
|
9aa816dc73 | ||
|
f750086bc1 | ||
|
bd3f234bce | ||
|
dbf9873e63 | ||
|
4bd194f4c1 | ||
|
8a6de15346 | ||
|
60a6560a3b | ||
|
d1173a1596 | ||
|
2356f3281b | ||
|
2db93efb14 | ||
|
cd0d84dbb6 | ||
|
8214d674be | ||
|
0714c07e28 | ||
|
62a39d84b3 | ||
|
6009bac1ed | ||
|
e475db7ade | ||
|
01f65bc872 | ||
|
49d9779c84 | ||
|
a594f79e5f | ||
|
17fe5f106b | ||
|
db27654330 | ||
|
1cbad5bd3f | ||
|
08753b46a1 | ||
|
aeda2aa8c5 | ||
|
fb1d600597 | ||
|
c1ad86c368 | ||
|
7ea1a4e0e8 | ||
|
2c08ec21fd | ||
|
d159a8252d | ||
|
308548e226 | ||
|
a8c5daf46a | ||
|
898b6ee433 | ||
|
2ce9ace4d3 | ||
|
1ee1b269e6 | ||
|
ebcbcbfe31 | ||
|
b77f4faadd | ||
|
bf28856ac5 | ||
|
700f3514b3 | ||
|
2d334185cb | ||
|
2c1d3a3fda | ||
|
7d880a37dd | ||
|
d12b546a7d | ||
|
6184024a80 | ||
|
2bfcbe980e | ||
|
0710b07d7c | ||
|
7c894f0542 | ||
|
c05875befd | ||
|
20b09820c9 | ||
|
1c4653d466 | ||
|
8cd7cfc65d | ||
|
995a26cf19 | ||
|
230e4654f1 | ||
|
297fe3885c | ||
|
cda829b327 | ||
|
f282ae45e0 | ||
|
9de65acf3e | ||
|
1c4b80ec61 | ||
|
f3a95fe26b | ||
|
0a487d0b9e | ||
|
6fa79d49b4 | ||
|
66f3c0a77e | ||
|
5eafc6e738 | ||
|
7d1eff5116 | ||
|
36c7d7a435 | ||
|
03a68e87ba | ||
|
8c7cdfdc79 | ||
|
b3a5ab692b | ||
|
c7ae4ace9d | ||
|
7b6880bdc9 | ||
|
379a789839 | ||
|
bdb4ccb352 | ||
|
53d5cb0151 | ||
|
f2fd2280e4 | ||
|
2acaaeb2ff | ||
|
a5fb5887f9 | ||
|
e3d328621e | ||
|
ccb45730f2 | ||
|
d11bfef36b | ||
|
e70a7f620e | ||
|
7abd625c05 | ||
|
c79879f290 | ||
|
e8686a4170 | ||
|
78e146692a | ||
|
cddf9ca8a2 | ||
|
c554b5c45d | ||
|
0350fc41f8 | ||
|
edbdb682d5 | ||
|
7f9502fbfe | ||
|
867c537655 | ||
|
2db5736554 | ||
|
116d14bb76 | ||
|
f5cd5cb736 | ||
|
2ce913450d | ||
|
4e8e17be90 | ||
|
13d68a1200 | ||
|
b6a00a881e | ||
|
996b0fa8d3 | ||
|
83a11c1645 | ||
|
66ca7a519e | ||
|
4eccf901ea | ||
|
a77921dc67 | ||
|
61d2e07cf0 | ||
|
76099d951c | ||
|
bf6d09a878 | ||
|
041188c5e2 | ||
|
60f4134841 | ||
|
8ac13aba75 | ||
|
feb8dbf6d9 | ||
|
f22c127c44 | ||
|
64eeb623be | ||
|
8fed86b368 | ||
|
a8da25c9d4 | ||
|
3dccbb25a7 | ||
|
ab5f3bc444 | ||
|
a659a7a619 | ||
|
df4057db6f | ||
|
84323a8a4c | ||
|
5cb7989e8d | ||
|
398b90fb1c | ||
|
d1e2749337 | ||
|
94d90d87db | ||
|
bf6bbecbd0 | ||
|
116a6a8cfe | ||
|
8b4be26ca1 | ||
|
ba935ab8f1 | ||
|
ed986ecbb5 | ||
|
2b5eec2295 | ||
|
90b69aaac2 | ||
|
ed9d1a1542 | ||
|
d83de5bd09 | ||
|
229416a6f4 | ||
|
2cfe7758cd | ||
|
7d74aa6ae8 | ||
|
e25daab399 | ||
|
01ae72a570 | ||
|
2f383d8de3 | ||
|
4e4a56eda2 | ||
|
d80654fa07 | ||
|
9bd88d80d0 | ||
|
e69a7c1ba2 | ||
|
fa8b68af1f | ||
|
f008876a93 | ||
|
c00f484813 | ||
|
67a8f581b6 | ||
|
acde45ff3a | ||
|
4af2deb8cd | ||
|
f285ab59e6 | ||
|
497aa53e28 | ||
|
72c944974a | ||
|
d390300e60 | ||
|
d20b9d9ed7 | ||
|
dc03299dc6 | ||
|
7ef61e12c0 | ||
|
c250500afd | ||
|
baee0be7ad | ||
|
7703312b88 | ||
|
af6aa30c7c | ||
|
84ae40172e | ||
|
42d9e06598 | ||
|
cb8fac6f5f | ||
|
f0c6292fa3 | ||
|
c09fbc2ad2 | ||
|
fe40af160d | ||
|
7d0206d2cf | ||
|
057812fe02 | ||
|
d47c4eeb78 | ||
|
b95fb5b124 | ||
|
d3e4a4659f | ||
|
c882f77f54 | ||
|
ac75fe2604 | ||
|
d655289d08 | ||
|
0dc544fefd | ||
|
2f45c37320 | ||
|
e2ae62bdb2 | ||
|
df44539486 | ||
|
353def5c29 | ||
|
a6bf73a817 | ||
|
a7e0b3ccdf | ||
|
5af02290e6 | ||
|
bb2d2877c4 | ||
|
4dfa97cda9 | ||
|
6a7499c8fc | ||
|
51440b5542 | ||
|
6da4c1f06b | ||
|
fc48751f8f | ||
|
885d5b2121 | ||
|
3e7944f732 | ||
|
1f82b4ff21 | ||
|
647d832556 | ||
|
b7ed67543f | ||
|
fff19d878f | ||
|
2cfe632296 | ||
|
cf884f082f | ||
|
ca06572e2a | ||
|
5233a3cdb4 | ||
|
2c48f295b0 | ||
|
55395820e3 | ||
|
582fdd7ed2 | ||
|
e7b3a15d54 | ||
|
3cc8e5222c | ||
|
415cb5f886 | ||
|
b4729bd1d6 | ||
|
d869e20941 | ||
|
b274c4a730 | ||
|
4ee3bc8be9 | ||
|
b4a5728455 | ||
|
9c89e7c739 | ||
|
40f31ea936 | ||
|
3ee269ddb6 | ||
|
64ecfe171a | ||
|
84bff89eb1 | ||
|
c694325ab7 | ||
|
a0ec89e468 | ||
|
42f8154c39 | ||
|
b905dea2ee | ||
|
edf5ddad4f | ||
|
8de8072078 | ||
|
7b2034c699 | ||
|
81fd0beb1a | ||
|
b7d27c1b36 | ||
|
70350bad81 | ||
|
83a0c31b35 | ||
|
d24df41a67 | ||
|
ed515d4642 | ||
|
411ed3e578 | ||
|
0137d1f303 | ||
|
efa855b497 | ||
|
2eab9c2957 | ||
|
72f2a315c4 | ||
|
e6462dfe58 | ||
|
f5d75a85b0 | ||
|
081603e6bc | ||
|
7681132704 | ||
|
4e386be914 | ||
|
b690415f44 | ||
|
15ce797863 | ||
|
36477ef408 | ||
|
7878b2e31d | ||
|
b11d655173 | ||
|
781a814864 | ||
|
363841caf8 | ||
|
7f0dd35702 | ||
|
5c54566c5c | ||
|
659933d0c9 | ||
|
6a01955810 | ||
|
73b60d991a | ||
|
a907109cce | ||
|
0f3994d506 | ||
|
b84bb4f805 | ||
|
8467552f0b | ||
|
1794aeb79b | ||
|
5f8ecf0144 | ||
|
2280ab0513 | ||
|
a0e9c33e54 | ||
|
ee77920a2e | ||
|
06193f4267 | ||
|
137b19aac2 | ||
|
3df5f60a70 | ||
|
bb922c208c | ||
|
3ce4c3a0e4 | ||
|
23733bf4af | ||
|
c51a6e6d95 | ||
|
e04ea50c18 | ||
|
f6ba670cd0 | ||
|
8921da2787 | ||
|
4560ddcd1e | ||
|
99e9956410 | ||
|
d67d3f97cc | ||
|
9e42fc4fe0 | ||
|
8861f1377c | ||
|
4d692821cb | ||
|
456f77abec | ||
|
3c4d87520e | ||
|
39c1238d8e | ||
|
ab01078cd7 | ||
|
13b52b1d51 | ||
|
7ed1586ea6 | ||
|
a6aad9c918 | ||
|
e519b8f315 | ||
|
0616fa72d4 | ||
|
e5bd1a6d6f | ||
|
95bb371b0d | ||
|
e5caec031f | ||
|
0f8b2ff399 | ||
|
66430fd10d | ||
|
1573d110f6 | ||
|
26641d839b | ||
|
273af26244 | ||
|
1c9c637701 | ||
|
cee1aeaf26 | ||
|
64016c86f6 | ||
|
6efe0686c9 | ||
|
0a5dcdb120 | ||
|
ffa8fb1390 | ||
|
7d621f6f0a | ||
|
3c82ed0323 | ||
|
85cd04b712 | ||
|
f629914217 | ||
|
440bfec193 | ||
|
8291de71cb | ||
|
d9e1d7f23a | ||
|
aa92d6b7e7 | ||
|
e3b5381f30 | ||
|
529d484884 | ||
|
39d43a5b57 | ||
|
9e92ea2372 | ||
|
8768641872 | ||
|
daddd53793 | ||
|
bf818ddf1e | ||
|
5420a3b2fb | ||
|
a644c50307 | ||
|
632e688202 | ||
|
315e58578d | ||
|
4d49112a56 | ||
|
e1e2e2e935 | ||
|
f918d89408 | ||
|
f0246463c0 | ||
|
5693fad51d | ||
|
22f6e53520 | ||
|
83da954725 | ||
|
91ee841fc2 | ||
|
87f8c01ac4 | ||
|
86496e4480 | ||
|
dcd3c497d9 | ||
|
e41369fbc6 | ||
|
20447c877d | ||
|
1ab33333bc | ||
|
40ee895aae | ||
|
a0025d84fd | ||
|
b3c2118eb4 | ||
|
3549744f40 | ||
|
f3a51be7ff | ||
|
aceab9be44 | ||
|
5a94d734cc | ||
|
f5bd9b863c | ||
|
83fbcb660f | ||
|
0b500798df | ||
|
44d94a76e5 | ||
|
c80a5cabd6 | ||
|
8423a0a8bd | ||
|
da9dc75fbc | ||
|
b20363d187 | ||
|
9b9baddd0b | ||
|
d15a6b935c | ||
|
90a3ed992e | ||
|
ab456bf376 | ||
|
7c39c16829 | ||
|
2f2df1ee76 | ||
|
8fa718c5f9 | ||
|
89db804c1a | ||
|
d1327b016d | ||
|
86f2710c2e | ||
|
e03addc8f9 | ||
|
78da9e52c4 | ||
|
0d4092f4ea | ||
|
b19e180fab | ||
|
4022110955 | ||
|
5f22a5568d | ||
|
1402a511a4 | ||
|
70929d45fb | ||
|
4bcc1eef09 | ||
|
c8928675c6 | ||
|
1d08f9e673 | ||
|
eb4159ac88 | ||
|
4b979826ec | ||
|
712bad5935 | ||
|
8231aa5b47 | ||
|
7d9d3528c5 | ||
|
4ea1d2f8d2 | ||
|
6fc3b89f79 | ||
|
0d8b59fa3b | ||
|
8f08e1c7ed | ||
|
228a74c41d | ||
|
d461ec1094 | ||
|
17ba1f4d22 | ||
|
5cfcc0e3c1 | ||
|
5e9e417cf6 | ||
|
acc6969594 | ||
|
aa1c609c28 | ||
|
7433436ec5 | ||
|
d1d1258591 | ||
|
e08787afca | ||
|
3ec081c187 | ||
|
e7095e9ada | ||
|
be72cd81dc | ||
|
fc4f5ffa45 | ||
|
449307a69f | ||
|
0667f4f211 | ||
|
1829a2a5c1 | ||
|
9ab5802aeb | ||
|
a45c0d9026 | ||
|
4969d75ef3 | ||
|
a55cffaae8 | ||
|
7fc4c4fccb | ||
|
b8ac1daae0 | ||
|
7c17e314aa | ||
|
6495e2f87c | ||
|
967900e2e9 | ||
|
263837094e | ||
|
874418a301 | ||
|
098f8a0d77 | ||
|
6f39d8228d | ||
|
344a4e7138 | ||
|
6585d58c03 | ||
|
d974ca322b | ||
|
5a87faf9af | ||
|
f81374a9ce | ||
|
b42462c484 | ||
|
113b29e68d | ||
|
a66cefb198 | ||
|
368b48b41b | ||
|
10ab5dc032 | ||
|
51e7159f36 | ||
|
327e5feb97 | ||
|
648f991a5b | ||
|
553def9f0e | ||
|
60d6685665 | ||
|
25e20315ae | ||
|
9ddfa4f9c8 | ||
|
fd0c92927a | ||
|
53b60cdecc | ||
|
09cc8c51e2 | ||
|
8329f5b6a1 | ||
|
49b83e5adf | ||
|
b3a9b3ca1a | ||
|
55ca2f0ed1 | ||
|
eb37dad115 | ||
|
5eb3b77efe | ||
|
2536dc47ac | ||
|
a03c5d1f3f | ||
|
ed58af2512 | ||
|
137d652d77 | ||
|
7780b76cf0 | ||
|
93f82f47bd | ||
|
b357897745 | ||
|
91ab9e2804 | ||
|
33a4f31f92 | ||
|
4ceaf842fe | ||
|
68e0076ca3 | ||
|
6f8238e1e9 | ||
|
78e354689e | ||
|
45487eb052 | ||
|
0eff1930b5 | ||
|
263dbd97c5 | ||
|
de88707946 | ||
|
0c63859459 | ||
|
65fd8be9b5 | ||
|
9168f8c69d | ||
|
a6155bcf2a | ||
|
07aa657b63 | ||
|
887cbc7f00 | ||
|
e04bfc05fa | ||
|
6f0a11f155 | ||
|
657d37c9e3 | ||
|
ccbf3a2bcf | ||
|
261f697d8c | ||
|
226cb3ac90 | ||
|
691898d129 | ||
|
351939dd5d | ||
|
32a5b9089b | ||
|
3f64d45819 | ||
|
054a88c502 | ||
|
91088af935 | ||
|
f5a25b8d5e | ||
|
dcd4574510 | ||
|
7960f67ea6 | ||
|
bceace7a09 | ||
|
5244c1a9c2 | ||
|
cfaed9d761 | ||
|
5b09105a6d | ||
|
bc3d1a84cc | ||
|
17407c9ecc | ||
|
d6a2f98ef5 | ||
|
a6449f36f6 | ||
|
69f831af0b | ||
|
036144eed4 | ||
|
8917fb30bf | ||
|
bf0e0382fc | ||
|
2b2eedbbf0 | ||
|
bdc77df921 | ||
|
673f867e3c | ||
|
1e3637af59 | ||
|
c6369e68a3 | ||
|
ce64c5aa37 | ||
|
a008b0b16f | ||
|
1798904cf4 | ||
|
214df7c1e1 | ||
|
334aa68a85 | ||
|
4bfb3ec238 | ||
|
416a14fb85 | ||
|
f0fb56415c | ||
|
663f3f3c58 | ||
|
a6bad26e1d | ||
|
f39044124e | ||
|
cc75679214 | ||
|
7b4f5d9c4b | ||
|
6413dcc2c6 | ||
|
0b8ac37450 | ||
|
7c7d5f4434 | ||
|
4adb1ab2d8 | ||
|
6eebac1510 | ||
|
814f8fe7ab | ||
|
fac0c4c5c0 | ||
|
f37a760dda | ||
|
2544a621e7 | ||
|
41f00485fd | ||
|
97e4f66d16 | ||
|
f2c3fcb6a4 | ||
|
cf7160bda2 | ||
|
097b274aa4 | ||
|
8f7ea1844b | ||
|
b3286bad16 | ||
|
3ac2af61aa | ||
|
ae34255350 | ||
|
1a123b5395 | ||
|
961f08a5d2 | ||
|
eefbb7f063 | ||
|
ce75405ccc | ||
|
c7674f671d | ||
|
9dc0b6a3f3 | ||
|
4ecf78163d | ||
|
421f4b01e6 | ||
|
53563e75bb | ||
|
bd5078347b | ||
|
06f16464aa | ||
|
1f89c494ff | ||
|
8606803344 | ||
|
41de36e93d | ||
|
30ac62ceec | ||
|
7e1e142104 | ||
|
e2f37908b9 | ||
|
6d4e151ba8 | ||
|
40112ebd62 | ||
|
6960c45e59 | ||
|
2e60256978 | ||
|
38110579ba | ||
|
08c334c8bc | ||
|
aaf49bb816 | ||
|
fa3adb759a | ||
|
834f6d0784 | ||
|
41d8f65e1e | ||
|
c9c985ae5d | ||
|
04a91adc70 | ||
|
d138a2a8e1 | ||
|
5c488d7033 | ||
|
5413407f23 | ||
|
da99dcdb87 | ||
|
5b88fe819c | ||
|
f627b33379 | ||
|
cf004dddbc | ||
|
2d5a3ebd8f | ||
|
728f98d3de | ||
|
bbf8ef0a4b | ||
|
ebc8e9935e | ||
|
26838a2164 | ||
|
0eeb5f83c2 | ||
|
c2f9d088d5 | ||
|
05cf301774 | ||
|
47beba1782 | ||
|
e3f8dc5501 | ||
|
15403aadad | ||
|
b5479260ea | ||
|
828889de5c | ||
|
179185985e | ||
|
e6ad443178 | ||
|
243b01dd63 | ||
|
3fc610f860 | ||
|
bfa9fc25d6 | ||
|
2219802bca | ||
|
be0ec1d38d | ||
|
6c160d02af | ||
|
fead1c3853 | ||
|
06c35dd59c | ||
|
efcb417838 | ||
|
aa7a55b480 | ||
|
e095282437 | ||
|
cbf96368b9 | ||
|
46eb178f07 | ||
|
9cf9d6eac2 | ||
|
eecec74e21 | ||
|
52bc4a7f41 | ||
|
49d0e872a1 | ||
|
c91cc85c1f | ||
|
86cfd86cda | ||
|
cd75c4f081 | ||
|
2bea60a1a4 | ||
|
c05702eacf | ||
|
ce0e5d67f5 | ||
|
48655b41fb | ||
|
b443477869 | ||
|
870c2a6f9c | ||
|
e26b7501eb | ||
|
b4002a8484 | ||
|
6e3725457d | ||
|
73f8839a97 | ||
|
501eb8c6ad | ||
|
7f8cd66e11 | ||
|
ee899a21ed | ||
|
3535e7a6d6 | ||
|
8ddfd1f34a | ||
|
7d383350e8 | ||
|
9ffd17d58b | ||
|
9f410597df | ||
|
cca2c91058 | ||
|
46b551e386 | ||
|
14ae9fb2a1 | ||
|
01797cf1bc | ||
|
7617cce0c6 | ||
|
83193f4a65 | ||
|
b1ffe87556 | ||
|
131485e516 | ||
|
251f189428 | ||
|
2a5cbe6445 | ||
|
ac6d423451 | ||
|
44a067283a | ||
|
49fa9b9c35 | ||
|
0639f54280 | ||
|
73bef80347 | ||
|
460751bc01 | ||
|
309955be75 | ||
|
9ba8434c1d | ||
|
64f6648d0c | ||
|
ba00b597a7 | ||
|
4f79e3756c | ||
|
bfffaa66b8 | ||
|
6a0fb17132 | ||
|
629e587882 | ||
|
9cc9116df3 | ||
|
08508d34b3 | ||
|
5fd05db6ea | ||
|
4708e0cf79 | ||
|
624968b74c | ||
|
e3ff44d01b | ||
|
f778f4a795 | ||
|
9379ba7733 | ||
|
cbdb10a05c | ||
|
68be9b39a8 | ||
|
f183cef7d7 | ||
|
f55e1ec5cc | ||
|
35f95e8ca2 | ||
|
ee7ebe438c | ||
|
850fe8408e | ||
|
f626acb398 | ||
|
eda424ff71 | ||
|
0043f62a43 | ||
|
d5320d9252 | ||
|
de8d0479ab | ||
|
feab109c61 | ||
|
6120c2be0a | ||
|
ee0c8bb249 | ||
|
3d59c9f9e7 | ||
|
86e63f0b6b | ||
|
3c684010e3 | ||
|
aa52cb395f | ||
|
73c7742db4 | ||
|
c3432c158e | ||
|
24d42c1583 | ||
|
40e3f735ab | ||
|
12174187e8 | ||
|
2770e193b6 | ||
|
83f1effff1 | ||
|
629637d95e | ||
|
06cb8b45b2 | ||
|
affd28654c | ||
|
08130df595 | ||
|
5acf2b126f | ||
|
cc84f85722 | ||
|
2d5cba61ed | ||
|
124c5a6751 | ||
|
3bae0723b7 | ||
|
5400ef6512 | ||
|
718f997502 | ||
|
c2f850b566 | ||
|
f93cca3d14 | ||
|
ecf214ca04 | ||
|
06e39e2728 | ||
|
2c643d5b53 | ||
|
3c4e9443ae | ||
|
d2bfb2e489 | ||
|
30a80ff07c | ||
|
582adda628 | ||
|
ae83982811 | ||
|
25ab7da33e | ||
|
96491db229 | ||
|
d520df6a13 | ||
|
cad9cea33b | ||
|
e229dbe9dc | ||
|
b47badb3ee | ||
|
f49741b4f8 | ||
|
80ccb31008 | ||
|
4dea5c2719 | ||
|
ded5269937 | ||
|
ae2875e248 | ||
|
aa64597e8b | ||
|
180c605cac | ||
|
23d2f0fbb5 | ||
|
9fae7f92d6 | ||
|
ace3d1bfa3 | ||
|
777d1db5c9 | ||
|
c9e3dc373b | ||
|
83f3d820f1 | ||
|
0f82db941b | ||
|
7a3c8743f3 | ||
|
03287b73a1 | ||
|
025cf0b00e | ||
|
a3aa2b5a57 | ||
|
02276500c9 | ||
|
de44ecccd1 | ||
|
5049c86517 | ||
|
d2854e0760 | ||
|
5a29214ad9 | ||
|
b51d92d449 | ||
|
6da477d37f | ||
|
6150a8c903 | ||
|
f3e9731da4 | ||
|
353b6b8af0 | ||
|
3f10e58df2 | ||
|
d232737087 | ||
|
e32ca55258 | ||
|
f57ffc987c | ||
|
cdd510d20e | ||
|
d757ba1266 | ||
|
337f7589f8 | ||
|
912728a322 | ||
|
204b5a532d | ||
|
014be165c3 | ||
|
a9244f807b | ||
|
6d438605dd | ||
|
34300150a1 | ||
|
1b2b98234f | ||
|
bd672a5583 | ||
|
888003c072 | ||
|
bc6db4e4d7 | ||
|
c5e72817ca | ||
|
e1ce55d0e6 | ||
|
d23a3f3bc4 | ||
|
b1620bbe47 | ||
|
57655583e5 | ||
|
b91e645919 | ||
|
652bb08f8c | ||
|
59026d5f84 | ||
|
e18551061e | ||
|
e054ad2ebb | ||
|
bff6aa460a | ||
|
3979ba4784 | ||
|
d2fcbf5f82 | ||
|
38067c4566 | ||
|
d4bfc17818 | ||
|
eb99480253 | ||
|
c9790b28d0 | ||
|
9a1ef8acfb | ||
|
e442395cbd | ||
|
e57e521609 | ||
|
d1809e6e2d | ||
|
c579b974a2 | ||
|
5bb92a4277 | ||
|
c381573d1f | ||
|
31873e8e2c | ||
|
de43ab0d21 | ||
|
db50fb8726 | ||
|
408848a425 | ||
|
e880e734ce | ||
|
61b2a7fc54 | ||
|
e1e17fd6a4 | ||
|
17d3e7190c | ||
|
22d8b0b2e1 | ||
|
9384d0efa6 | ||
|
ffafd5be10 | ||
|
e94d6efec6 | ||
|
7eb1b36309 | ||
|
0c4b39bd11 | ||
|
2bccc85ff8 | ||
|
df08b5144c | ||
|
9baca673ac | ||
|
55893f8c39 | ||
|
70042069eb | ||
|
ecbc0b9c12 | ||
|
a783325a6d | ||
|
f55c30f286 | ||
|
4874852b79 | ||
|
6760ca87ae |
@ -1,20 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/buildpack-deps:artful
|
||||
environment:
|
||||
- TESTDB: /tmp/test.db
|
||||
- TESTLOG: /tmp/test.log
|
||||
steps:
|
||||
- checkout
|
||||
- run: make all
|
||||
- run: ulimit -c unlimited && make check
|
||||
- run:
|
||||
command: |
|
||||
mkdir -p /tmp/artifacts
|
||||
mv -t /tmp/artifacts $TESTLOG $TESTDB core.*
|
||||
when: on_fail
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
destination: test-artifacts
|
@ -1,3 +1,3 @@
|
||||
BasedOnStyle: LLVM
|
||||
Standard: Cpp11
|
||||
ReflowComments: true
|
||||
Standard: c++20
|
||||
ColumnLimit: 120
|
||||
|
3
.cmake-format.yaml
Normal file
3
.cmake-format.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
format:
|
||||
line_width: 120
|
||||
tab_size: 2
|
67
.gitignore
vendored
67
.gitignore
vendored
@ -1,38 +1,69 @@
|
||||
*[~#]
|
||||
@*
|
||||
*.[ao]
|
||||
*.autosave
|
||||
*.bak
|
||||
build.ninja
|
||||
cmake-build-*
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
CMakeLists.txt.user
|
||||
core
|
||||
CTestTestfile.cmake
|
||||
DartConfiguration.tcl
|
||||
dist/
|
||||
*.dll
|
||||
docs/Doxyfile
|
||||
docs/html/
|
||||
*.dSYM
|
||||
*.dylib
|
||||
*.err
|
||||
*.exe
|
||||
*.gcda
|
||||
*.gcno
|
||||
*.gcov
|
||||
*.lo
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*[~#]
|
||||
.idea
|
||||
.le.ini
|
||||
.vs/
|
||||
Win32/
|
||||
build-*
|
||||
cmake-build-*
|
||||
core
|
||||
example
|
||||
libmdbx.creator.user
|
||||
mdbx-dll.VC.VC.opendb
|
||||
mdbx-dll.VC.db
|
||||
mdbx-dll.vcxproj.filters
|
||||
*.lo
|
||||
mdbx_chk
|
||||
mdbx_copy
|
||||
mdbx_drop
|
||||
mdbx_dump
|
||||
mdbx_example
|
||||
mdbx_load
|
||||
mdbx_stat
|
||||
mdbx_test
|
||||
.ninja_deps
|
||||
.ninja_log
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
src/config.h
|
||||
src/version.c
|
||||
*.tar*
|
||||
test/cmake_install.cmake
|
||||
test/CTestTestfile.cmake
|
||||
test_extra_crunched_delete
|
||||
test_extra_cursor_closing
|
||||
test_extra_dbi
|
||||
test_extra_doubtless_positioning
|
||||
test_extra_dupfix_addodd
|
||||
test_extra_dupfix_multiple
|
||||
test_extra_early_close_dbi
|
||||
test_extra_hex_base64_base58
|
||||
test_extra_maindb_ordinal
|
||||
test_extra_open
|
||||
test_extra_pcrf
|
||||
test_extra_upsert_alldups
|
||||
Testing/
|
||||
test.log
|
||||
test/test.vcxproj.user
|
||||
test/tmp.db
|
||||
test/tmp.db-lck
|
||||
tmp.db
|
||||
tmp.db-lck
|
||||
valgrind.*
|
||||
x64/
|
||||
x86/
|
||||
version.c
|
||||
.vs/
|
||||
.vscode/
|
||||
*.zip
|
||||
|
40
.le.ini
Normal file
40
.le.ini
Normal file
@ -0,0 +1,40 @@
|
||||
tabsize=8
|
||||
indentsize=8
|
||||
autoindent=1
|
||||
bsunindents=1
|
||||
insert=1
|
||||
inputmode=0
|
||||
editmode=1
|
||||
makebak=0
|
||||
bakpath=
|
||||
make=exec make
|
||||
shell=exec $SHELL
|
||||
run=exec make run
|
||||
compile=exec make "$FNAME.o"
|
||||
scroll=1
|
||||
hscroll=32
|
||||
rblock=0
|
||||
savepos=1
|
||||
savehst=1
|
||||
noreg=1
|
||||
match_case=1
|
||||
linelen=72
|
||||
leftmrg=0
|
||||
flnmarg=0
|
||||
leftadj=1
|
||||
rightadj=0
|
||||
helpcmd=exec /usr/share/le/help
|
||||
usecolor=1
|
||||
usetabs=1
|
||||
scrollbar=0
|
||||
statusline=0
|
||||
backupext=.~%d~
|
||||
backupnum=9
|
||||
preferpagetop=1
|
||||
wordwrap=0
|
||||
syntaxhl=1
|
||||
undo_enable=1
|
||||
undo_min_levels=4
|
||||
undo_max_memory=128
|
||||
undo_glue=1
|
||||
usemouse=0
|
30
.travis.yml
30
.travis.yml
@ -1,30 +0,0 @@
|
||||
language: c
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make all check; fi
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "M+W+heGGyRQJoBq2W0uqWVrpL4KBXmL0MFL7FSs7f9vmAaDyEgziUXeZRj3GOKzW4kTef3LpIeiu9SmvqSMoQivGGiomZShqPVl045o/OUgRCAT7Al1RLzEZ0efSHpIPf0PZ6byEf6GR2ML76OfuL6JxTVdnz8iVyO2sgLE1HbX1VeB+wgd/jfMeOBhCCXskfK6MLyZihfMYsiYZYSaV98ZDhDLSlzuuRIgzb0bMi8aL6AErs0WLW0NelRBeHkKPYfAUc85pdQHscgrJw6Rh/zT6+8BQ/q5f4IgWhiu4xoRg3Ngl7SNoedRQh93ADM3UG2iGl6HDFpVORaXcFWKAtuYY+kHQ0HB84BRYpQmeBuXNpltsfxQ3d1Q3u0RlE45zRvmr2+X1mFnkcNUAWISLPbsOUlriDQM8irGwRpho77/uYnRC00bJsHW//s6+uPf9zrAw1nI4f0y3PAWukGF/xs6HAI3FZPsuSSnx18Tj3Opgbc9Spop+V3hkhdiJoPGpNKTkFX4ZRXfkPgoRVJmtp4PpbpH0Ps/mCriKjMEfGGi0HcVCi0pEGLXiecdqJ5KPg5+22zNycEujQBJcNTKd9shN+R3glrbmhAxTEzGdGwxXXJ2ybwJ2PWJLMYZ7g98nLyX+uQPaA3BlsbYJHNeS5283/9pJsd9DzfHKsN2nFSc="
|
||||
|
||||
before_install:
|
||||
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
|
||||
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "ReOpen/libmdbx"
|
||||
version: 0.1
|
||||
description: "Build submitted via Travis CI"
|
||||
notification_email: leo@yuriev.ru
|
||||
build_command_prepend: "make clean"
|
||||
build_command: "make all -j 4"
|
||||
branch_pattern: coverity_scan
|
31
AUTHORS
31
AUTHORS
@ -1,31 +0,0 @@
|
||||
Contributors
|
||||
============
|
||||
|
||||
Alexey Naumov <alexey.naumov@gmail.com>
|
||||
Chris Mikkelson <cmikk@qwest.net>
|
||||
Claude Brisson <claude.brisson@gmail.com>
|
||||
David Barbour <dmbarbour@gmail.com>
|
||||
David Wilson <dw@botanicus.net>
|
||||
dreamsxin <dreamsxin@126.com>
|
||||
Hallvard Furuseth <hallvard@openldap.org>, <h.b.furuseth@usit.uio.no>
|
||||
Heiko Becker <heirecka@exherbo.org>
|
||||
Howard Chu <hyc@openldap.org>, <hyc@symas.com>
|
||||
Ignacio Casal Quinteiro <ignacio.casal@nice-software.com>
|
||||
James Rouzier <rouzier@gmail.com>
|
||||
Jean-Christophe DUBOIS <jcd@tribudubois.net>
|
||||
John Hewson <john@jahewson.com>
|
||||
Klaus Malorny <klaus.malorny@knipp.de>
|
||||
Kurt Zeilenga <kurt.zeilenga@isode.com>
|
||||
Leonid Yuriev <leo@yuriev.ru>, <lyuryev@ptsecurity.com>
|
||||
Lorenz Bauer <lmb@cloudflare.com>
|
||||
Luke Yeager <lyeager@nvidia.com>
|
||||
Martin Hedenfalk <martin@bzero.se>
|
||||
Ondrej Kuznik <ondrej.kuznik@acision.com>
|
||||
Orivej Desh <orivej@gmx.fr>
|
||||
Oskari Timperi <oskari.timperi@iki.fi>
|
||||
Pavel Medvedev <pmedvedev@gmail.com>
|
||||
Philipp Storz <philipp.storz@bareos.com>
|
||||
Quanah Gibson-Mount <quanah@openldap.org>
|
||||
Salvador Ortiz <sog@msg.com.mx>
|
||||
Sebastien Launay <sebastien@slaunay.fr>
|
||||
Vladimir Romanov <vromanov@gmail.com>
|
1279
CMakeLists.txt
Normal file
1279
CMakeLists.txt
Normal file
File diff suppressed because it is too large
Load Diff
159
COPYRIGHT
159
COPYRIGHT
@ -1,7 +1,139 @@
|
||||
Copyright 2015-2018 Leonid Yuriev <leo@yuriev.ru>.
|
||||
Copyright 2011-2015 Howard Chu, Symas Corp.
|
||||
Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
All rights reserved.
|
||||
Copyright (c) 2015-2025 Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
СМЕНА ЛИЦЕНЗИИ (THE LICENSE CHANGE)
|
||||
|
||||
OpenLDAP Public License → Apache 2.0
|
||||
|
||||
Briefly:
|
||||
Historically, in 2015 an early MDBX source code was derived from the
|
||||
"LMDB engine" created by Howard Chu <hyc@symas.com> in 2011-2015,
|
||||
which based on btree.c written by Martin Hedenfalk <martin@bzero.se>.
|
||||
|
||||
By 2024, MDBX source code has actually been rewritten and has so
|
||||
little in common with the original LMDB that I thought it admissible
|
||||
to change the license. Below are more detailed explanations.
|
||||
|
||||
Кратко:
|
||||
|
||||
Исторически в 2015 году ранний исходный код MDBX был заимствован из
|
||||
«LMDB engine», созданной Howard Chu <hyc@symas.com> в 2011-2015,
|
||||
на основе btree.c, ранее созданного Martin Hedenfalk <martin@bzero.se>.
|
||||
|
||||
К 2024 году исходный код MDBX фактически переписан и имеет настолько
|
||||
мало общего с первоначальным заимствованием из LMDB, что я счел
|
||||
уместным сменить лицензию. Ниже более подробные пояснения.
|
||||
|
||||
---
|
||||
|
||||
Первоисточник текста формулирован на Русском языке, который является
|
||||
родным для автора. Предполагается что все заинтересованные могут легко
|
||||
воспользоваться машинным переводом, который при всех недостатках сможет
|
||||
донести суть, намерения и местами даже передать тональность.
|
||||
|
||||
The original source of this text is in Russian, which is the author's
|
||||
native language. It is assumed that all concerned can easily use machine
|
||||
translation, which, with all the disadvantages, will be able to convey
|
||||
the essence, intentions and, in some places, even convey the tonality of
|
||||
a wording.
|
||||
|
||||
1. Причины
|
||||
|
||||
1.1. Лицензия Apache-2.0 является одной из самых популярных, так как
|
||||
содержит ряд уточнений, проясняющих и упрощающих использование исходного
|
||||
кода в производных работах и больших проектах. Эти особенности лицензии
|
||||
Apache-2.0 я нахожу достаточно ценными и удобными. Соответственно,
|
||||
переход на лицензию Apache-2.0 полезным в целом.
|
||||
|
||||
1.2. Проект OpenLDAP имеет определенную известность, в том числе, к
|
||||
сожалению, среди специалистов славится кране плохим качеством кода и
|
||||
сбоями при отходе от простых/базовых сценариев использования. Поэтому
|
||||
использование лицензии OpenLDAP, в глазах части аудитории, бросает тень
|
||||
на качества кода libmdbx, несмотря на то, что исходный код библиотеки
|
||||
переписан, в том числе, с целью повышения качества, надежности,
|
||||
стабильности и пригодности к тестированию.
|
||||
|
||||
Отмечу, что здесь не место для обсуждения объективности подобных мнений
|
||||
и причин, равно как и не место для оценки компетентности специалистов
|
||||
высказывающих такие суждения. Однако, здесь необходимо озвучить сам факт
|
||||
наличия такой негативной коннотации качества кода при упоминании
|
||||
OpenLDAP, совершенно без намерения как-либо задеть или обидеть
|
||||
контрибьюторов OpenLDAP.
|
||||
|
||||
1.3. С точки зрения исходного кода, к настоящему времени libmdbx стала
|
||||
совсем другим продуктом, о котором сейчас правильнее сказать что
|
||||
разработка вдохновлена LMDB, нежели основывается на заимствовании кода.
|
||||
Смена лицензии на переписанный код подчеркивает, что это действительно
|
||||
новый исходный код.
|
||||
|
||||
2. Легитимность
|
||||
|
||||
2.1. Исходная лицензия OpenLDAP 2.8 и актуальная лицензия Apache 2.0
|
||||
совпадают по базовым условиям. При этом лицензия Apache 2.0 уточняет,
|
||||
определяет и проясняет многие аспекты. Поэтому смену лицензии я склонен
|
||||
трактовать как уточнение, но НЕ как принципиальное изменение, которое
|
||||
могло-бы нарушить чьи-либо права.
|
||||
|
||||
2.2. С процедурной точки зрения, у меня есть право сменить лицензию на
|
||||
новый, написанный мной, исходный код. При этом объективно существует как
|
||||
техническая, так и юридическая проблемы отделения «нового кода» от
|
||||
«заимствованного», а также выделение/классификация кода, который
|
||||
является общественным достоянием и/или общеупотребительным воплощением
|
||||
«математических моделей и других публичных знаний».
|
||||
|
||||
Основываясь на собственной субъективной оценке кодовой базы, включая
|
||||
соотношения «нового», «заимствованного» и «общеупотребительного»
|
||||
исходного кода, я считаю что смена лицензии допустима. Одновременно с
|
||||
этим, я понимаю и признаю, что можно найти повод, чтобы трактовать
|
||||
ситуацию как «стакан наполовину полон/пуст». Поэтому декларирую
|
||||
готовность принимать претензии и устранять их путем полного
|
||||
переписывания оставшегося исходного кода, который попадает под критерии
|
||||
«заимствованного» и кто-то из контрибьюторов которого будет против
|
||||
изменения лицензии.
|
||||
|
||||
2.3. Вне зависимости от истории происхождения каждой строки исходного
|
||||
кода и её буквального авторства, прошу не считать производимую смену
|
||||
лицензии, и связанных с этим технических действий, как попытку плагиата,
|
||||
присвоения чужого труда, присвоения авторства или принижения вклада
|
||||
других авторов/контрибьторов. Безусловно проект MDBX/libmdbx не появился
|
||||
бы без LMDB и всех участников проекта LMDB, в особенности Говарда Чу
|
||||
(Howard Chu), Холлварда Фурусет (Hallvard Furuseth) и Мартина Хеденфок
|
||||
(Martin Hedenfalk). Как-бы исходный код не переписывался он всё равно
|
||||
будет основываться на базовых идеях и включать основные концепции LMDB.
|
||||
|
||||
3. Последствия и актуальные требования
|
||||
|
||||
Всё очень просто. Потребуется обеспечить требования новой лицензии в
|
||||
соответствии с 4-м пунктом лицензции Apache 2.0.
|
||||
|
||||
В частности, при использовании/распространении libmdbx потребуется
|
||||
обеспечить наличие файлов с текстом лицензии и файла NOTICE, а также
|
||||
обеспечить пользователям возможность ознакомиться с их содержимым в
|
||||
работах/продуктах использующих libmdbx.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Далее в справочных целях приведены уведомления об авторских правах из
|
||||
первоначально заимствованного кода.
|
||||
|
||||
---
|
||||
|
||||
Original source code was derived from LMDB in 2015,
|
||||
and later evolutionarily rewritten in 2015-2024:
|
||||
Copyright (c) 2011-2015 Howard Chu, Symas Corp. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted only as authorized by the OpenLDAP
|
||||
@ -11,12 +143,17 @@ A copy of this license is available in the file LICENSE in the
|
||||
top-level directory of the distribution or, alternatively, at
|
||||
<http://www.OpenLDAP.org/license.html>.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
LMDB itself devived code from btree.c written by Martin Hedenfalk:
|
||||
Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
|
||||
|
||||
Individual files and/or contributed packages may be copyright by
|
||||
other parties and/or subject to additional restrictions.
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
This work also contains materials derived from public sources.
|
||||
|
||||
Additional information about OpenLDAP can be obtained at
|
||||
<http://www.openldap.org/>.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
1067
ChangeLog-01.md
Normal file
1067
ChangeLog-01.md
Normal file
File diff suppressed because it is too large
Load Diff
1923
ChangeLog.md
Normal file
1923
ChangeLog.md
Normal file
File diff suppressed because it is too large
Load Diff
947
GNUmakefile
Normal file
947
GNUmakefile
Normal file
@ -0,0 +1,947 @@
|
||||
# This makefile is for GNU Make 3.81 or above, and nowadays provided
|
||||
# just for compatibility and preservation of traditions.
|
||||
#
|
||||
# Please use CMake in case of any difficulties or
|
||||
# problems with this old-school's magic.
|
||||
#
|
||||
################################################################################
|
||||
#
|
||||
# Basic internal definitions. For a customizable variables and options see below.
|
||||
#
|
||||
$(info // The GNU Make $(MAKE_VERSION))
|
||||
SHELL := $(shell env bash -c 'echo $$BASH')
|
||||
MAKE_VERx3 := $(shell printf "%3s%3s%3s" $(subst ., ,$(MAKE_VERSION)))
|
||||
make_lt_3_81 := $(shell expr "$(MAKE_VERx3)" "<" " 3 81")
|
||||
ifneq ($(make_lt_3_81),0)
|
||||
$(error Please use GNU Make 3.81 or above)
|
||||
endif
|
||||
make_ge_4_1 := $(shell expr "$(MAKE_VERx3)" ">=" " 4 1")
|
||||
make_ge_4_4 := $(shell expr "$(MAKE_VERx3)" ">=" " 4 4")
|
||||
SRC_PROBE_C := $(shell [ -f mdbx.c ] && echo mdbx.c || echo src/osal.c)
|
||||
SRC_PROBE_CXX := $(shell [ -f mdbx.c++ ] && echo mdbx.c++ || echo src/mdbx.c++)
|
||||
UNAME := $(shell uname -s 2>/dev/null || echo Unknown)
|
||||
|
||||
define cxx_filesystem_probe
|
||||
int main(int argc, const char*argv[]) {
|
||||
mdbx::filesystem::path probe(argv[0]);
|
||||
if (argc != 1) throw mdbx::filesystem::filesystem_error(std::string("fake"), std::error_code());
|
||||
return mdbx::filesystem::is_directory(probe.relative_path());
|
||||
}
|
||||
endef
|
||||
#
|
||||
################################################################################
|
||||
#
|
||||
# Use `make options` to list the available libmdbx build options.
|
||||
#
|
||||
# Note that the defaults should already be correct for most platforms;
|
||||
# you should not need to change any of these. Read their descriptions
|
||||
# in README and source code (see src/options.h) if you do.
|
||||
#
|
||||
|
||||
# install sandbox
|
||||
DESTDIR ?=
|
||||
INSTALL ?= install
|
||||
# install prefixes (inside sandbox)
|
||||
prefix ?= /usr/local
|
||||
mandir ?= $(prefix)/man
|
||||
# lib/bin suffix for multiarch/biarch, e.g. '.x86_64'
|
||||
suffix ?=
|
||||
|
||||
# toolchain
|
||||
CC ?= gcc
|
||||
CXX ?= g++
|
||||
CFLAGS_EXTRA ?=
|
||||
LD ?= ld
|
||||
CMAKE ?= cmake
|
||||
CMAKE_OPT ?=
|
||||
CTEST ?= ctest
|
||||
CTEST_OPT ?=
|
||||
# target directory for `make dist`
|
||||
DIST_DIR ?= dist
|
||||
|
||||
# build options
|
||||
MDBX_BUILD_OPTIONS ?=-DNDEBUG=1
|
||||
MDBX_BUILD_TIMESTAMP ?=$(if $(SOURCE_DATE_EPOCH),$(SOURCE_DATE_EPOCH),$(shell date +%Y-%m-%dT%H:%M:%S%z))
|
||||
MDBX_BUILD_CXX ?=YES
|
||||
MDBX_BUILD_METADATA ?=
|
||||
|
||||
# probe and compose common compiler flags with variable expansion trick (seems this work two times per session for GNU Make 3.81)
|
||||
CFLAGS ?= $(strip $(eval CFLAGS := -std=gnu11 -O2 -g -Wall -Werror -Wextra -Wpedantic -ffunction-sections -fPIC -fvisibility=hidden -pthread -Wno-error=attributes $$(shell for opt in -fno-semantic-interposition -Wno-unused-command-line-argument -Wno-tautological-compare; do [ -z "$$$$($(CC) '-DMDBX_BUILD_FLAGS="probe"' $$$${opt} -c $(SRC_PROBE_C) -o /dev/null >/dev/null 2>&1 || echo failed)" ] && echo "$$$${opt} "; done)$(CFLAGS_EXTRA))$(CFLAGS))
|
||||
|
||||
# choosing C++ standard with variable expansion trick (seems this work two times per session for GNU Make 3.81)
|
||||
CXXSTD ?= $(eval CXXSTD := $$(shell for std in gnu++23 c++23 gnu++2b c++2b gnu++20 c++20 gnu++2a c++2a gnu++17 c++17 gnu++1z c++1z gnu++14 c++14 gnu++1y c++1y gnu+11 c++11 gnu++0x c++0x; do $(CXX) -std=$$$${std} -DMDBX_BUILD_CXX=1 -c $(SRC_PROBE_CXX) -o /dev/null 2>probe4std-$$$${std}.err >/dev/null && echo "-std=$$$${std}" && exit; done))$(CXXSTD)
|
||||
CXXFLAGS ?= $(strip $(CXXSTD) $(filter-out -std=gnu11,$(CFLAGS)))
|
||||
|
||||
# libraries and options for linking
|
||||
EXE_LDFLAGS ?= -pthread
|
||||
ifneq ($(make_ge_4_1),1)
|
||||
# don't use variable expansion trick as workaround for bugs of GNU Make before 4.1
|
||||
LIBS ?= $(shell $(uname2libs))
|
||||
LDFLAGS ?= $(shell $(uname2ldflags))
|
||||
LIB_STDCXXFS ?= $(shell echo '$(cxx_filesystem_probe)' | cat mdbx.h++ - | sed $$'1s/\xef\xbb\xbf//' | $(CXX) -x c++ $(CXXFLAGS) -Wno-error - -Wl,--allow-multiple-definition -lstdc++fs $(LIBS) $(LDFLAGS) $(EXE_LDFLAGS) -o /dev/null 2>probe4lstdfs.err >/dev/null && echo '-Wl,--allow-multiple-definition -lstdc++fs')
|
||||
else
|
||||
# using variable expansion trick to avoid repeaded probes
|
||||
LIBS ?= $(eval LIBS := $$(shell $$(uname2libs)))$(LIBS)
|
||||
LDFLAGS ?= $(eval LDFLAGS := $$(shell $$(uname2ldflags)))$(LDFLAGS)
|
||||
LIB_STDCXXFS ?= $(eval LIB_STDCXXFS := $$(shell echo '$$(cxx_filesystem_probe)' | cat mdbx.h++ - | sed $$$$'1s/\xef\xbb\xbf//' | $(CXX) -x c++ $(CXXFLAGS) -Wno-error - -Wl,--allow-multiple-definition -lstdc++fs $(LIBS) $(LDFLAGS) $(EXE_LDFLAGS) -o /dev/null 2>probe4lstdfs.err >/dev/null && echo '-Wl,--allow-multiple-definition -lstdc++fs'))$(LIB_STDCXXFS)
|
||||
endif
|
||||
|
||||
ifneq ($(make_ge_4_4),1)
|
||||
.NOTPARALLEL:
|
||||
WAIT =
|
||||
else
|
||||
WAIT = .WAIT
|
||||
endif
|
||||
|
||||
################################################################################
|
||||
|
||||
define uname2sosuffix
|
||||
case "$(UNAME)" in
|
||||
Darwin*|Mach*) echo dylib;;
|
||||
CYGWIN*|MINGW*|MSYS*|Windows*) echo dll;;
|
||||
*) echo so;;
|
||||
esac
|
||||
endef
|
||||
|
||||
define uname2ldflags
|
||||
case "$(UNAME)" in
|
||||
CYGWIN*|MINGW*|MSYS*|Windows*)
|
||||
echo '-Wl,--gc-sections,-O1';
|
||||
;;
|
||||
*)
|
||||
$(LD) --help 2>/dev/null | grep -q -- --gc-sections && echo '-Wl,--gc-sections,-z,relro,-O1';
|
||||
$(LD) --help 2>/dev/null | grep -q -- -dead_strip && echo '-Wl,-dead_strip';
|
||||
;;
|
||||
esac
|
||||
endef
|
||||
|
||||
# TIP: try add the'-Wl, --no-as-needed,-lrt' for ability to built with modern glibc, but then use with the old.
|
||||
define uname2libs
|
||||
case "$(UNAME)" in
|
||||
CYGWIN*|MINGW*|MSYS*|Windows*)
|
||||
echo '-lm -lntdll -lwinmm';
|
||||
;;
|
||||
*SunOS*|*Solaris*)
|
||||
echo '-lm -lkstat -lrt';
|
||||
;;
|
||||
*Darwin*|OpenBSD*)
|
||||
echo '-lm';
|
||||
;;
|
||||
*)
|
||||
echo '-lm -lrt';
|
||||
;;
|
||||
esac
|
||||
endef
|
||||
|
||||
SO_SUFFIX := $(shell $(uname2sosuffix))
|
||||
HEADERS := mdbx.h mdbx.h++
|
||||
LIBRARIES := libmdbx.a libmdbx.$(SO_SUFFIX)
|
||||
TOOLS := chk copy drop dump load stat
|
||||
MDBX_TOOLS := $(addprefix mdbx_,$(TOOLS))
|
||||
MANPAGES := mdbx_stat.1 mdbx_copy.1 mdbx_dump.1 mdbx_load.1 mdbx_chk.1 mdbx_drop.1
|
||||
TIP := // TIP:
|
||||
|
||||
.PHONY: all help options lib libs tools clean install uninstall check_buildflags_tag tools-static
|
||||
.PHONY: install-strip install-no-strip strip libmdbx mdbx show-options lib-static lib-shared cmake-build ninja
|
||||
|
||||
boolean = $(if $(findstring $(strip $($1)),YES Yes yes y ON On on 1 true True TRUE),1,$(if $(findstring $(strip $($1)),NO No no n OFF Off off 0 false False FALSE),,$(error Wrong value `$($1)` of $1 for YES/NO option)))
|
||||
select_by = $(if $(call boolean,$(1)),$(2),$(3))
|
||||
|
||||
ifeq ("$(origin V)", "command line")
|
||||
MDBX_BUILD_VERBOSE := $(V)
|
||||
endif
|
||||
ifndef MDBX_BUILD_VERBOSE
|
||||
MDBX_BUILD_VERBOSE := 0
|
||||
endif
|
||||
|
||||
ifeq ($(call boolean,MDBX_BUILD_VERBOSE),1)
|
||||
QUIET :=
|
||||
HUSH :=
|
||||
$(info $(TIP) Use `make V=0` for quiet.)
|
||||
else
|
||||
QUIET := @
|
||||
HUSH := >/dev/null
|
||||
$(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:
|
||||
@echo " make all - build libraries and tools"
|
||||
@echo " make help - print this help"
|
||||
@echo " make options - list build options"
|
||||
@echo " make lib - build libraries, also lib-static and lib-shared"
|
||||
@echo " make tools - build the tools"
|
||||
@echo " make tools-static - build the tools with statically linking with system libraries and compiler runtime"
|
||||
@echo " make clean "
|
||||
@echo " make install "
|
||||
@echo " make uninstall "
|
||||
@echo " make cmake-build | ninja - build by CMake & Ninja"
|
||||
@echo ""
|
||||
@echo " make strip - strip debug symbols from binaries"
|
||||
@echo " make install-no-strip - install explicitly without strip"
|
||||
@echo " make install-strip - install explicitly with strip"
|
||||
@echo ""
|
||||
@echo " make bench - run ioarena-benchmark"
|
||||
@echo " make bench-couple - run ioarena-benchmark for mdbx and lmdb"
|
||||
@echo " make bench-triplet - run ioarena-benchmark for mdbx, lmdb, sqlite3"
|
||||
@echo " make bench-quartet - run ioarena-benchmark for mdbx, lmdb, rocksdb, wiredtiger"
|
||||
@echo " make bench-clean - remove temp database(s) after benchmark"
|
||||
#> dist-cutoff-begin
|
||||
@echo ""
|
||||
@echo " make check - smoke test with amalgamation and installation checking"
|
||||
@echo " make smoke - fast smoke test"
|
||||
@echo " make smoke-memcheck - build with Valgrind support and run smoke test under memcheck tool"
|
||||
@echo " make smoke-fault - execute transaction owner failure smoke testcase"
|
||||
@echo " make smoke-singleprocess - execute single-process smoke test"
|
||||
@echo " make test - basic test"
|
||||
@echo " make test-memcheck - build with Valgrind support and run basic test under memcheck tool"
|
||||
@echo " make test-long - execute long test which runs for several weeks, or until interruption"
|
||||
@echo " make test-asan - build with AddressSanitizer and run basic test"
|
||||
@echo " make test-leak - build with LeakSanitizer and run basic test"
|
||||
@echo " make test-ubsan - build with UndefinedBehaviourSanitizer and run basic test"
|
||||
@echo " make test-singleprocess - execute single-process basic test (also used by make cross-qemu)"
|
||||
@echo " make cross-gcc - check cross-compilation without test execution"
|
||||
@echo " make cross-qemu - run cross-compilation and execution basic test with QEMU"
|
||||
@echo " make gcc-analyzer - run gcc-analyzer (mostly useless for now)"
|
||||
@echo " make build-test - build test executable(s)"
|
||||
@echo ""
|
||||
@echo " make dist - build amalgamated source code"
|
||||
@echo " make doxygen - build HTML documentation"
|
||||
@echo " make release-assets - build release assets"
|
||||
@echo " make reformat - reformat source code with clang-format"
|
||||
#< dist-cutoff-end
|
||||
|
||||
show-options:
|
||||
@echo " MDBX_BUILD_OPTIONS = $(MDBX_BUILD_OPTIONS)"
|
||||
@echo " MDBX_BUILD_CXX = $(MDBX_BUILD_CXX)"
|
||||
@echo " MDBX_BUILD_TIMESTAMP = $(MDBX_BUILD_TIMESTAMP)"
|
||||
@echo " MDBX_BUILD_METADATA = $(MDBX_BUILD_METADATA)"
|
||||
@echo '$(TIP) Use `make options` to listing available build options.'
|
||||
@echo $(call select_by,MDBX_BUILD_CXX," CXX =`which $(CXX)` | `$(CXX) --version | head -1`"," CC =`which $(CC)` | `$(CC) --version | head -1`")
|
||||
@echo $(call select_by,MDBX_BUILD_CXX," CXXFLAGS =$(CXXFLAGS)"," CFLAGS =$(CFLAGS)")
|
||||
@echo $(call select_by,MDBX_BUILD_CXX," LDFLAGS =$(LDFLAGS) $(LIB_STDCXXFS) $(LIBS) $(EXE_LDFLAGS)"," LDFLAGS =$(LDFLAGS) $(LIBS) $(EXE_LDFLAGS)")
|
||||
@echo '$(TIP) Use `make help` to listing available targets.'
|
||||
|
||||
options:
|
||||
@echo " INSTALL =$(INSTALL)"
|
||||
@echo " DESTDIR =$(DESTDIR)"
|
||||
@echo " prefix =$(prefix)"
|
||||
@echo " mandir =$(mandir)"
|
||||
@echo " suffix =$(suffix)"
|
||||
@echo ""
|
||||
@echo " CC =$(CC)"
|
||||
@echo " CFLAGS_EXTRA =$(CFLAGS_EXTRA)"
|
||||
@echo " CFLAGS =$(CFLAGS)"
|
||||
@echo " CXX =$(CXX)"
|
||||
@echo " CXXSTD =$(CXXSTD)"
|
||||
@echo " CXXFLAGS =$(CXXFLAGS)"
|
||||
@echo ""
|
||||
@echo " LD =$(LD)"
|
||||
@echo " LDFLAGS =$(LDFLAGS)"
|
||||
@echo " EXE_LDFLAGS =$(EXE_LDFLAGS)"
|
||||
@echo " LIBS =$(LIBS)"
|
||||
@echo ""
|
||||
@echo " MDBX_BUILD_OPTIONS = $(MDBX_BUILD_OPTIONS)"
|
||||
@echo " MDBX_BUILD_TIMESTAMP = $(MDBX_BUILD_TIMESTAMP)"
|
||||
@echo " MDBX_BUILD_METADATA = $(MDBX_BUILD_METADATA)"
|
||||
@echo ""
|
||||
@echo "## Assortment items for MDBX_BUILD_OPTIONS:"
|
||||
@echo "## Note that the defaults should already be correct for most platforms;"
|
||||
@echo "## you should not need to change any of these. Read their descriptions"
|
||||
#> dist-cutoff-begin
|
||||
ifeq ($(wildcard mdbx.c),mdbx.c)
|
||||
#< dist-cutoff-end
|
||||
@echo "## in README and source code (see mdbx.c) if you do."
|
||||
@grep -h '#ifndef MDBX_' mdbx.c | grep -v BUILD | sort -u | sed 's/#ifndef / /'
|
||||
#> dist-cutoff-begin
|
||||
else
|
||||
@echo "## in README and source code (see src/options.h) if you do."
|
||||
@grep -h '#ifndef MDBX_' src/*.h | grep -v BUILD | sort -u | sed 's/#ifndef / /'
|
||||
endif
|
||||
#< dist-cutoff-end
|
||||
|
||||
lib libs libmdbx mdbx: libmdbx.a libmdbx.$(SO_SUFFIX)
|
||||
|
||||
tools: $(MDBX_TOOLS)
|
||||
tools-static: $(addsuffix .static,$(MDBX_TOOLS)) $(addsuffix .static-lto,$(MDBX_TOOLS))
|
||||
|
||||
strip: all
|
||||
@echo ' STRIP libmdbx.$(SO_SUFFIX) $(MDBX_TOOLS)'
|
||||
$(TRACE )strip libmdbx.$(SO_SUFFIX) $(MDBX_TOOLS)
|
||||
|
||||
clean:
|
||||
@echo ' REMOVE ...'
|
||||
$(QUIET)rm -rf $(MDBX_TOOLS) mdbx_test @* *.[ao] *.[ls]o *.$(SO_SUFFIX) *.dSYM *~ tmp.db/* \
|
||||
*.gcov *.log *.err src/*.o test/*.o mdbx_example dist @dist-check \
|
||||
config.h src/config.h src/version.c *.tar* @buildflags.tag @dist-checked.tag \
|
||||
mdbx_*.static mdbx_*.static-lto CMakeFiles
|
||||
|
||||
MDBX_BUILD_FLAGS =$(strip MDBX_BUILD_CXX=$(MDBX_BUILD_CXX) $(MDBX_BUILD_OPTIONS) $(call select_by,MDBX_BUILD_CXX,$(CXXFLAGS) $(LDFLAGS) $(LIB_STDCXXFS) $(LIBS),$(CFLAGS) $(LDFLAGS) $(LIBS)))
|
||||
check_buildflags_tag:
|
||||
$(QUIET)if [ "$(MDBX_BUILD_FLAGS)" != "$$(cat @buildflags.tag 2>&1)" ]; then \
|
||||
echo -n " CLEAN for build with specified flags..." && \
|
||||
$(MAKE) IOARENA=false CXXSTD= -s clean >/dev/null && echo " Ok" && \
|
||||
echo '$(MDBX_BUILD_FLAGS)' > @buildflags.tag; \
|
||||
fi
|
||||
|
||||
@buildflags.tag: check_buildflags_tag
|
||||
|
||||
lib-static libmdbx.a: mdbx-static.o $(call select_by,MDBX_BUILD_CXX,mdbx++-static.o)
|
||||
@echo ' AR $@'
|
||||
$(QUIET)$(AR) rcs $@ $? $(HUSH)
|
||||
|
||||
lib-shared libmdbx.$(SO_SUFFIX): mdbx-dylib.o $(call select_by,MDBX_BUILD_CXX,mdbx++-dylib.o)
|
||||
@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"
|
||||
$(QUIET)mkdir -p @cmake-ninja-build && $(CMAKE) $(CMAKE_OPT) -G Ninja -S . -B @cmake-ninja-build && $(CMAKE) --build @cmake-ninja-build
|
||||
|
||||
ctest: cmake-build
|
||||
@echo " RUN: ctest .."
|
||||
$(QUIET)$(CTEST) --test-dir @cmake-ninja-build --parallel `(nproc | sysctl -n hw.ncpu | echo 2) 2>/dev/null` --schedule-random $(CTEST_OPT)
|
||||
|
||||
#> dist-cutoff-begin
|
||||
ifeq ($(wildcard mdbx.c),mdbx.c)
|
||||
#< dist-cutoff-end
|
||||
|
||||
################################################################################
|
||||
# Amalgamated source code, i.e. distributed after `make dist`
|
||||
MAN_SRCDIR := man1/
|
||||
|
||||
config.h: @buildflags.tag $(WAIT) mdbx.c $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)(echo '#define MDBX_BUILD_TIMESTAMP "$(MDBX_BUILD_TIMESTAMP)"' \
|
||||
&& echo "#define MDBX_BUILD_FLAGS \"$$(cat @buildflags.tag)\"" \
|
||||
&& echo '#define MDBX_BUILD_COMPILER "$(shell (LC_ALL=C $(CC) --version || echo 'Please use GCC or CLANG compatible compiler') | head -1)"' \
|
||||
&& echo '#define MDBX_BUILD_TARGET "$(shell set -o pipefail; (LC_ALL=C $(CC) -v 2>&1 | grep -i '^Target:' | cut -d ' ' -f 2- || (LC_ALL=C $(CC) --version | grep -qi e2k && echo E2K) || echo 'Please use GCC or CLANG compatible compiler') | head -1)"' \
|
||||
&& echo '#define MDBX_BUILD_CXX $(call select_by,MDBX_BUILD_CXX,1,0)' \
|
||||
&& echo '#define MDBX_BUILD_METADATA "$(MDBX_BUILD_METADATA)"' \
|
||||
) >$@
|
||||
|
||||
mdbx-dylib.o: config.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c -o $@
|
||||
|
||||
mdbx-static.o: config.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c -o $@
|
||||
|
||||
mdbx++-dylib.o: config.h mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c++ -o $@
|
||||
|
||||
mdbx++-static.o: config.h mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c++ -o $@
|
||||
|
||||
mdbx_%: mdbx_%.c mdbx-static.o
|
||||
@echo ' CC+LD $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) $(LIBS) -o $@
|
||||
|
||||
mdbx_%.static: mdbx_%.c mdbx-static.o
|
||||
@echo ' CC+LD $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) -static -Wl,--strip-all -o $@
|
||||
|
||||
mdbx_%.static-lto: mdbx_%.c config.h mdbx.c mdbx.h
|
||||
@echo ' CC+LD $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) -Os -flto $(MDBX_BUILD_OPTIONS) '-DLIBMDBX_API=' '-DMDBX_CONFIG_H="config.h"' \
|
||||
$< mdbx.c $(EXE_LDFLAGS) $(LIBS) -static -Wl,--strip-all -o $@
|
||||
|
||||
#> dist-cutoff-begin
|
||||
else
|
||||
################################################################################
|
||||
# Plain (non-amalgamated) sources with test
|
||||
|
||||
.PHONY: build-test build-test-with-valgrind check cross-gcc cross-qemu dist doxygen gcc-analyzer long-test
|
||||
.PHONY: reformat release-assets tags smoke test test-asan smoke-fault test-leak
|
||||
.PHONY: smoke-singleprocess test-singleprocess test-ubsan test-valgrind test-memcheck memcheck smoke-memcheck
|
||||
.PHONY: smoke-assertion test-assertion long-test-assertion test-ci test-ci-extra
|
||||
|
||||
test-ci-extra: test-ci cross-gcc cross-qemu
|
||||
|
||||
test-ci: check \
|
||||
smoke-singleprocess smoke-fault smoke-memcheck smoke \
|
||||
test-leak test-asan test-ubsan test-singleprocess test test-memcheck
|
||||
|
||||
define uname2osal
|
||||
case "$(UNAME)" in
|
||||
CYGWIN*|MINGW*|MSYS*|Windows*) echo windows;;
|
||||
*) echo unix;;
|
||||
esac
|
||||
endef
|
||||
|
||||
define uname2titer
|
||||
case "$(UNAME)" in
|
||||
Darwin*|Mach*) echo 2;;
|
||||
*) echo 12;;
|
||||
esac
|
||||
endef
|
||||
|
||||
DIST_EXTRA := LICENSE NOTICE README.md CMakeLists.txt GNUmakefile Makefile ChangeLog.md VERSION.json config.h.in ntdll.def \
|
||||
$(addprefix man1/, $(MANPAGES)) cmake/compiler.cmake cmake/profile.cmake cmake/utils.cmake .clang-format-ignore
|
||||
DIST_SRC := mdbx.h mdbx.h++ mdbx.c mdbx.c++ $(addsuffix .c, $(MDBX_TOOLS))
|
||||
|
||||
TEST_DB ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.db
|
||||
TEST_LOG ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.log
|
||||
TEST_OSAL := $(shell $(uname2osal))
|
||||
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)
|
||||
|
||||
reformat:
|
||||
@echo ' RUNNING clang-format...'
|
||||
$(QUIET)if [ -n "$(CLANG_FORMAT)" ]; then \
|
||||
git ls-files | grep -E '\.(c|c++|h|h++)(\.in)?$$' | xargs -r $(CLANG_FORMAT) -i --style=file; \
|
||||
else \
|
||||
echo "clang-format version 19 not found for 'reformat'"; \
|
||||
fi
|
||||
|
||||
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_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_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))
|
||||
MDBX_BUILD_SOURCERY = $(shell set -o pipefail; $(MAKE) IOARENA=false CXXSTD= -s src/version.c >/dev/null && (openssl dgst -r -sha256 src/version.c || sha256sum src/version.c || shasum -a 256 src/version.c) 2>/dev/null | cut -d ' ' -f 1 || (echo 'Please install openssl or sha256sum or shasum' >&2 && echo sha256sum_is_no_available))_$(MDBX_VERSION_NODOT)
|
||||
MDBX_DIST_DIR = libmdbx-$(MDBX_VERSION_NODOT)
|
||||
|
||||
# Extra options mdbx_test utility
|
||||
MDBX_SMOKE_EXTRA ?=
|
||||
|
||||
check: DESTDIR = $(shell pwd)/@check-install
|
||||
check: CMAKE_OPT = -Werror=dev
|
||||
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
|
||||
test-assertion: MDBX_BUILD_OPTIONS:=$(strip $(MDBX_BUILD_OPTIONS) -DMDBX_FORCE_ASSERTIONS=1 -UNDEBUG -DMDBX_DEBUG=0)
|
||||
test-assertion: smoke
|
||||
long-test-assertion: MDBX_BUILD_OPTIONS:=$(strip $(MDBX_BUILD_OPTIONS) -DMDBX_FORCE_ASSERTIONS=1 -UNDEBUG -DMDBX_DEBUG=0)
|
||||
long-test-assertion: smoke
|
||||
|
||||
smoke: build-test
|
||||
@echo ' SMOKE `mdbx_test basic`...'
|
||||
$(QUIET)rm -f $(TEST_DB) $(TEST_LOG).gz && (set -o pipefail; \
|
||||
(./mdbx_test --table=+data.integer --keygen.split=29 --datalen.min=min --datalen.max=max --progress --console=no --repeat=$(TEST_ITER) --pathname=$(TEST_DB) --dont-cleanup-after $(MDBX_SMOKE_EXTRA) basic && \
|
||||
./mdbx_test --mode=-writemap,-nosync-safe,-lifo --progress --console=no --repeat=$(TEST_ITER) --pathname=$(TEST_DB) --dont-cleanup-after $(MDBX_SMOKE_EXTRA) basic) \
|
||||
| tee >(gzip --stdout >$(TEST_LOG).gz) | tail -n 42) \
|
||||
&& ./mdbx_chk -vvn $(TEST_DB) && ./mdbx_chk -vvn $(TEST_DB)-copy
|
||||
|
||||
smoke-singleprocess: build-test
|
||||
@echo ' SMOKE `mdbx_test --nested`...'
|
||||
$(QUIET)rm -f $(TEST_DB) $(TEST_LOG).gz && (set -o pipefail; \
|
||||
(./mdbx_test --table=+data.integer --keygen.split=29 --datalen.min=min --datalen.max=max --progress --console=no --repeat=42 --pathname=$(TEST_DB) --dont-cleanup-after $(MDBX_SMOKE_EXTRA) --hill && \
|
||||
./mdbx_test --progress --console=no --repeat=2 --pathname=$(TEST_DB) --dont-cleanup-before --dont-cleanup-after --copy && \
|
||||
./mdbx_test --mode=-writemap,-nosync-safe,-lifo --progress --console=no --repeat=42 --pathname=$(TEST_DB) --dont-cleanup-after $(MDBX_SMOKE_EXTRA) --nested) \
|
||||
| tee >(gzip --stdout >$(TEST_LOG).gz) | tail -n 42) \
|
||||
&& ./mdbx_chk -vvn $(TEST_DB) && ./mdbx_chk -vvn $(TEST_DB)-copy
|
||||
|
||||
smoke-fault: build-test
|
||||
@echo ' SMOKE `mdbx_test --inject-writefault=42 basic`...'
|
||||
$(QUIET)rm -f $(TEST_DB) $(TEST_LOG).gz && (set -o pipefail; ./mdbx_test --progress --console=no --pathname=$(TEST_DB) --inject-writefault=42 --dump-config --dont-cleanup-after $(MDBX_SMOKE_EXTRA) basic \
|
||||
| tee >(gzip --stdout >$(TEST_LOG).gz) | tail -n 42) \
|
||||
; ./mdbx_chk -vvnw $(TEST_DB) && ([ ! -e $(TEST_DB)-copy ] || ./mdbx_chk -vvn $(TEST_DB)-copy)
|
||||
|
||||
test: build-test
|
||||
@echo ' RUNNING `test/stochastic.sh --loops 2`...'
|
||||
$(QUIET)test/stochastic.sh --dont-check-ram-size --loops 2 --db-upto-mb 256 --skip-make --taillog >$(TEST_LOG) || (cat $(TEST_LOG) && false)
|
||||
|
||||
long-test: test-long
|
||||
test-long: build-test
|
||||
@echo ' RUNNING `test/stochastic.sh --loops 42`...'
|
||||
$(QUIET)test/stochastic.sh --loops 42 --db-upto-mb 1024 --extra --skip-make --taillog
|
||||
|
||||
test-singleprocess: build-test
|
||||
@echo ' RUNNING `test/stochastic.sh --single --loops 2`...'
|
||||
$(QUIET)test/stochastic.sh --dont-check-ram-size --single --loops 2 --db-upto-mb 256 --skip-make --taillog >$(TEST_LOG) || (cat $(TEST_LOG) && false)
|
||||
|
||||
test-valgrind: test-memcheck
|
||||
test-memcheck: CFLAGS_EXTRA=-Ofast -DENABLE_MEMCHECK
|
||||
test-memcheck: build-test
|
||||
@echo ' RUNNING `test/stochastic.sh --with-valgrind --loops 2`...'
|
||||
$(QUIET)test/stochastic.sh --with-valgrind --loops 2 --db-upto-mb 256 --skip-make >$(TEST_LOG) || (cat $(TEST_LOG) && false)
|
||||
|
||||
memcheck: smoke-memcheck
|
||||
smoke-memcheck: VALGRIND=valgrind --trace-children=yes --log-file=valgrind-%p.log --leak-check=full --track-origins=yes --read-var-info=yes --error-exitcode=42 --suppressions=test/valgrind_suppress.txt
|
||||
smoke-memcheck: CFLAGS_EXTRA=-Ofast -DENABLE_MEMCHECK
|
||||
smoke-memcheck: build-test
|
||||
@echo " SMOKE \`mdbx_test basic\` under Valgrind's memcheck..."
|
||||
$(QUIET)rm -f valgrind-*.log $(TEST_DB) $(TEST_LOG).gz && (set -o pipefail; ( \
|
||||
$(VALGRIND) ./mdbx_test --table=+data.fixed --keygen.split=29 --datalen=35 --progress --console=no --repeat=2 --pathname=$(TEST_DB) --dont-cleanup-after $(MDBX_SMOKE_EXTRA) basic && \
|
||||
$(VALGRIND) ./mdbx_test --progress --console=no --pathname=$(TEST_DB) --dont-cleanup-before --dont-cleanup-after --copy && \
|
||||
$(VALGRIND) ./mdbx_test --mode=-writemap,-nosync-safe,-lifo --progress --console=no --repeat=4 --pathname=$(TEST_DB) --dont-cleanup-after $(MDBX_SMOKE_EXTRA) basic && \
|
||||
$(VALGRIND) ./mdbx_chk -vvn $(TEST_DB) && \
|
||||
$(VALGRIND) ./mdbx_chk -vvn $(TEST_DB)-copy \
|
||||
) | tee >(gzip --stdout >$(TEST_LOG).gz) | tail -n 42)
|
||||
|
||||
gcc-analyzer:
|
||||
@echo ' RE-BUILD with `-fanalyzer` option...'
|
||||
@echo "NOTE: There a lot of false-positive warnings at 2020-05-01 by pre-release GCC-10 (20200328, Red Hat 10.0.1-0.11)"
|
||||
$(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CFLAGS_EXTRA="-Og -fanalyzer -Wno-error" build-test
|
||||
|
||||
test-ubsan:
|
||||
@echo ' RE-TEST with `-fsanitize=undefined` option...'
|
||||
$(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CFLAGS_EXTRA="-DENABLE_UBSAN -Ofast -fsanitize=undefined -fsanitize-undefined-trap-on-error" test
|
||||
|
||||
test-asan:
|
||||
@echo ' RE-TEST with `-fsanitize=address` option...'
|
||||
$(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CFLAGS_EXTRA="-Os -fsanitize=address" test
|
||||
|
||||
test-leak:
|
||||
@echo ' RE-TEST with `-fsanitize=leak` option...'
|
||||
$(QUIET)$(MAKE) IOARENA=false CXXSTD=$(CXXSTD) CFLAGS_EXTRA="-fsanitize=leak" test
|
||||
|
||||
mdbx_example: mdbx.h example/example-mdbx.c libmdbx.$(SO_SUFFIX)
|
||||
@echo ' CC+LD $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) -I. example/example-mdbx.c ./libmdbx.$(SO_SUFFIX) -o $@
|
||||
|
||||
build-test: all mdbx_example mdbx_test
|
||||
|
||||
define test-rule
|
||||
$(patsubst %.c++,%.o,$(1)): $(1) $(TEST_INC) $(HEADERS) $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' CC $$@'
|
||||
$(QUIET)$$(CXX) $$(CXXFLAGS) $$(MDBX_BUILD_OPTIONS) -DMDBX_BUILD_CXX=1 -DMDBX_WITHOUT_MSVC_CRT=0 -c $(1) -o $$@
|
||||
|
||||
endef
|
||||
$(foreach file,$(TEST_SRC),$(eval $(call test-rule,$(file))))
|
||||
|
||||
define tool-rule
|
||||
mdbx_$(1): src/tools/$(1).c libmdbx.a
|
||||
@echo ' CC+LD $$@'
|
||||
$(QUIET)$$(CC) $$(CFLAGS) $$(MDBX_BUILD_OPTIONS) -Isrc '-DMDBX_CONFIG_H="config.h"' $$^ $$(EXE_LDFLAGS) $$(LIBS) -o $$@
|
||||
|
||||
mdbx_$(1).static: src/tools/$(1).c mdbx-static.o
|
||||
@echo ' CC+LD $$@'
|
||||
$(QUIET)$$(CC) $$(CFLAGS) $$(MDBX_BUILD_OPTIONS) -Isrc '-DMDBX_CONFIG_H="config.h"' $$^ $$(EXE_LDFLAGS) $$(LIBS) -static -Wl,--strip-all -o $$@
|
||||
|
||||
mdbx_$(1).static-lto: src/tools/$(1).c src/config.h src/version.c src/alloy.c $(ALLOY_DEPS)
|
||||
@echo ' CC+LD $$@'
|
||||
$(QUIET)$$(CC) $$(CFLAGS) -Os -flto $$(MDBX_BUILD_OPTIONS) -Isrc '-DLIBMDBX_API=' '-DMDBX_CONFIG_H="config.h"' \
|
||||
$$< src/alloy.c $$(EXE_LDFLAGS) $$(LIBS) -static -Wl,--strip-all -o $$@
|
||||
|
||||
endef
|
||||
$(foreach file,$(TOOLS),$(eval $(call tool-rule,$(file))))
|
||||
|
||||
mdbx_test: $(TEST_OBJ) libmdbx.$(SO_SUFFIX)
|
||||
@echo ' LD $@'
|
||||
$(QUIET)$(CXX) $(CXXFLAGS) $(TEST_OBJ) -Wl,-rpath . -L . -l mdbx $(EXE_LDFLAGS) $(LIBS) -o $@
|
||||
|
||||
$(MDBX_GIT_DIR)/HEAD $(MDBX_GIT_DIR)/index $(MDBX_GIT_DIR)/refs/tags:
|
||||
@echo '*** ' >&2
|
||||
@echo '*** Please don''t use tarballs nor zips which are automatically provided by Github !' >&2
|
||||
@echo '*** These archives do not contain version information and thus are unfit to build libmdbx.' >&2
|
||||
@echo '*** ' >&2
|
||||
@echo '*** Instead just follow the https://libmdbx.dqdkfa.ru/usage.html' >&2
|
||||
@echo '*** PLEASE, AVOID USING ANY OTHER TECHNIQUES.' >&2
|
||||
@echo '*** ' >&2
|
||||
@false
|
||||
|
||||
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) \
|
||||
-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')|" \
|
||||
-e "s|@MDBX_GIT_DESCRIBE@|$(MDBX_GIT_DESCRIBE)|" \
|
||||
-e "s|\$${MDBX_VERSION_MAJOR}|$(shell echo '$(MDBX_GIT_3DOT)' | cut -d . -f 1)|" \
|
||||
-e "s|\$${MDBX_VERSION_MINOR}|$(shell echo '$(MDBX_GIT_3DOT)' | cut -d . -f 2)|" \
|
||||
-e "s|\$${MDBX_VERSION_PATCH}|$(shell echo '$(MDBX_GIT_3DOT)' | cut -d . -f 3)|" \
|
||||
-e "s|\$${MDBX_VERSION_TWEAK}|$(MDBX_GIT_TWEAK)|" \
|
||||
-e "s|@MDBX_VERSION_PRERELEASE@|$(MDBX_GIT_PRERELEASE)|" \
|
||||
-e "s|@MDBX_VERSION_PURE@|$(MDBX_VERSION_PURE)|" \
|
||||
src/version.c.in >$@
|
||||
|
||||
src/config.h: @buildflags.tag $(WAIT) src/version.c $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)(echo '#define MDBX_BUILD_TIMESTAMP "$(MDBX_BUILD_TIMESTAMP)"' \
|
||||
&& echo "#define MDBX_BUILD_FLAGS \"$$(cat @buildflags.tag)\"" \
|
||||
&& echo '#define MDBX_BUILD_COMPILER "$(shell (LC_ALL=C $(CC) --version || echo 'Please use GCC or CLANG compatible compiler') | head -1)"' \
|
||||
&& echo '#define MDBX_BUILD_TARGET "$(shell set -o pipefail; (LC_ALL=C $(CC) -v 2>&1 | grep -i '^Target:' | cut -d ' ' -f 2- || (LC_ALL=C $(CC) --version | grep -qi e2k && echo E2K) || echo 'Please use GCC or CLANG compatible compiler') | head -1)"' \
|
||||
&& echo '#define MDBX_BUILD_SOURCERY $(MDBX_BUILD_SOURCERY)' \
|
||||
&& echo '#define MDBX_BUILD_CXX $(call select_by,MDBX_BUILD_CXX,1,0)' \
|
||||
&& echo '#define MDBX_BUILD_METADATA "$(MDBX_BUILD_METADATA)"' \
|
||||
) >$@
|
||||
|
||||
mdbx-dylib.o: src/config.h src/version.c src/alloy.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c src/alloy.c -o $@
|
||||
|
||||
mdbx-static.o: src/config.h src/version.c src/alloy.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST)) LICENSE NOTICE
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CC) $(CFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c src/alloy.c -o $@
|
||||
|
||||
docs/Doxyfile: docs/Doxyfile.in src/version.c $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' MAKE $@'
|
||||
$(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')|" \
|
||||
-e "s|@MDBX_GIT_DESCRIBE@|$(MDBX_GIT_DESCRIBE)|" \
|
||||
-e "s|\$${MDBX_VERSION_MAJOR}|$(shell echo '$(MDBX_GIT_3DOT)' | cut -d . -f 1)|" \
|
||||
-e "s|\$${MDBX_VERSION_MINOR}|$(shell echo '$(MDBX_GIT_3DOT)' | cut -d . -f 2)|" \
|
||||
-e "s|\$${MDBX_VERSION_PATCH}|$(shell echo '$(MDBX_GIT_3DOT)' | cut -d . -f 3)|" \
|
||||
-e "s|\$${MDBX_VERSION_TWEAK}|$(MDBX_GIT_TWEAK)|" \
|
||||
-e "s|@MDBX_VERSION_PRERELEASE@|$(MDBX_GIT_PRERELEASE)|" \
|
||||
-e "s|@MDBX_VERSION_PURE@|$(MDBX_VERSION_PURE)|" \
|
||||
docs/Doxyfile.in >$@
|
||||
|
||||
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 $$@
|
||||
|
||||
endef
|
||||
$(foreach section,overview mithril characteristics improvements history usage performance bindings,$(eval $(call md-extract-section,$(section),README.md)))
|
||||
|
||||
docs/contrib.fame: src/version.c $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)echo "" > $@ && git fame --show-email --format=md --silent-progress -w -M -C | grep '^|' >> $@
|
||||
|
||||
docs/overall.md: docs/__overview.md docs/_toc.md docs/__mithril.md docs/__history.md COPYRIGHT LICENSE NOTICE $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)echo -e "\\mainpage Overall\n\\section brief Brief" | cat - $(filter %.md, $^) >$@ && echo -e "\n\n\nLicense\n=======\n" | cat - LICENSE >>$@
|
||||
|
||||
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}/' >$@
|
||||
|
||||
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}/' >$@
|
||||
|
||||
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/ && \
|
||||
$(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 $@'
|
||||
$(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c src/mdbx.c++ -o $@
|
||||
|
||||
mdbx++-static.o: src/config.h src/mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' CC $@'
|
||||
$(QUIET)$(CXX) $(CXXFLAGS) $(MDBX_BUILD_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c src/mdbx.c++ -o $@
|
||||
|
||||
dist: tags $(WAIT) @dist-checked.tag libmdbx-sources-$(MDBX_VERSION_IDENT).tar.gz $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' AMALGAMATION is done'
|
||||
|
||||
tags:
|
||||
@echo ' FETCH git tags...'
|
||||
$(QUIET)git fetch --tags --force
|
||||
|
||||
release-assets: libmdbx-amalgamated-$(MDBX_GIT_3DOT).zpaq \
|
||||
libmdbx-amalgamated-$(MDBX_GIT_3DOT).tar.xz \
|
||||
libmdbx-amalgamated-$(MDBX_GIT_3DOT).tar.bz2 \
|
||||
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)" \
|
||||
== \
|
||||
"$$(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)) \
|
||||
&& echo ' RELEASE ASSETS are done'
|
||||
|
||||
@dist-checked.tag: $(addprefix $(DIST_DIR)/, $(DIST_SRC) $(DIST_EXTRA))
|
||||
@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-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
|
||||
@echo ' CREATE $@'
|
||||
$(QUIET)$(TAR) -c $(shell LC_ALL=C $(TAR) --help | grep -q -- '--owner' && echo '--owner=0 --group=0') -f - -C dist $(DIST_SRC) $(DIST_EXTRA) | gzip -c -9 >$@
|
||||
|
||||
%.tar.xz: @dist-checked.tag
|
||||
@echo ' CREATE $@'
|
||||
$(QUIET)$(TAR) -c $(shell LC_ALL=C $(TAR) --help | grep -q -- '--owner' && echo '--owner=0 --group=0') -f - -C dist $(DIST_SRC) $(DIST_EXTRA) | xz -9 -z >$@
|
||||
|
||||
%.tar.bz2: @dist-checked.tag
|
||||
@echo ' CREATE $@'
|
||||
$(QUIET)$(TAR) -c $(shell LC_ALL=C $(TAR) --help | grep -q -- '--owner' && echo '--owner=0 --group=0') -f - -C dist $(DIST_SRC) $(DIST_EXTRA) | bzip2 -9 -z >$@
|
||||
|
||||
%.zip: @dist-checked.tag
|
||||
@echo ' CREATE $@'
|
||||
$(QUIET)rm -rf $@ && (cd dist && $(ZIP) -9 ../$@ $(DIST_SRC) $(DIST_EXTRA)) &>@zip.log
|
||||
|
||||
%.zpaq: @dist-checked.tag
|
||||
@echo ' CREATE $@'
|
||||
$(QUIET)rm -rf $@ && (cd dist && zpaq a ../$@ $(DIST_SRC) $(DIST_EXTRA) -m59) &>@zpaq.log
|
||||
|
||||
$(DIST_DIR)/@tmp-essentials.inc: src/version.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' ALLOYING...'
|
||||
$(QUIET)mkdir -p dist \
|
||||
&& (grep -v '#include ' src/alloy.c && echo '#define MDBX_BUILD_SOURCERY $(MDBX_BUILD_SOURCERY)' \
|
||||
&& $(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' \
|
||||
-e '/#include "options.h"/r src/options.h' \
|
||||
-e '/#include "atomics-types.h"/r src/atomics-types.h' \
|
||||
-e '/#include "layout-dxb.h"/r src/layout-dxb.h' \
|
||||
-e '/#include "layout-lck.h"/r src/layout-lck.h' \
|
||||
-e '/#include "logging_and_debug.h"/r src/logging_and_debug.h' \
|
||||
-e '/#include "utils.h"/r src/utils.h' \
|
||||
-e '/#include "pnl.h"/r src/pnl.h' \
|
||||
src/essentials.h \
|
||||
| $(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) \
|
||||
-e '/#include "essentials.h"/d' \
|
||||
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
|
||||
-e '/#include "proto.h"/r src/proto.h' \
|
||||
-e '/#include "rkl.h"/r src/rkl.h' \
|
||||
-e '/#include "txl.h"/r src/txl.h' \
|
||||
-e '/#include "unaligned.h"/r src/unaligned.h' \
|
||||
-e '/#include "cogs.h"/r src/cogs.h' \
|
||||
-e '/#include "cursor.h"/r src/cursor.h' \
|
||||
-e '/#include "dbi.h"/r src/dbi.h' \
|
||||
-e '/#include "dpl.h"/r src/dpl.h' \
|
||||
-e '/#include "gc.h"/r src/gc.h' \
|
||||
-e '/#include "lck.h"/r src/lck.h' \
|
||||
-e '/#include "meta.h"/r src/meta.h' \
|
||||
-e '/#include "node.h"/r src/node.h' \
|
||||
-e '/#include "page-iov.h"/r src/page-iov.h' \
|
||||
-e '/#include "page-ops.h"/r src/page-ops.h' \
|
||||
-e '/#include "spill.h"/r src/spill.h' \
|
||||
-e '/#include "sort.h"/r src/sort.h' \
|
||||
-e '/#include "tls.h"/r src/tls.h' \
|
||||
-e '/#include "walk.h"/r src/walk.h' \
|
||||
-e '/#include "windows-import.h"/r src/windows-import.h' \
|
||||
src/internals.h \
|
||||
| $(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) \
|
||||
-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|' \
|
||||
-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) \
|
||||
-e '/#define xMDBX_ALLOY/d' \
|
||||
-e '/#include "/d;/#pragma once/d' \
|
||||
-e 's|@INCLUDE|#include|;s|"mdbx.h"|"mdbx.h++"|' \
|
||||
-e '/ clang-format o/d;/ \*INDENT-O/d' -e '3i /* clang-format off */' | cat -s >$@
|
||||
|
||||
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) \
|
||||
-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|' \
|
||||
-e '/ clang-format o/d;/ \*INDENT-O/d' -e '9i /* clang-format off */' | cat -s >$$@
|
||||
|
||||
endef
|
||||
$(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 >$$@
|
||||
|
||||
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))))
|
||||
|
||||
$(DIST_DIR)/VERSION.json: src/version.c
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)mkdir -p $(DIST_DIR)/ && echo "{ \"git_describe\": \"$(MDBX_GIT_DESCRIBE)\", \"git_timestamp\": \"$(MDBX_GIT_TIMESTAMP)\", \"git_tree\": \"$(shell git show --no-patch --format=%T HEAD 2>&1)\", \"git_commit\": \"$(shell git show --no-patch --format=%H HEAD 2>&1)\", \"semver\": \"$(MDBX_VERSION_PURE)\" }" >$@
|
||||
|
||||
$(DIST_DIR)/.clang-format-ignore: $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' MAKE $@'
|
||||
$(QUIET)echo "$(filter-out %.h %h++,$(DIST_SRC))" | tr ' ' \\n > $@
|
||||
|
||||
$(DIST_DIR)/ntdll.def: src/ntdll.def
|
||||
@echo ' COPY $@'
|
||||
$(QUIET)mkdir -p $(DIST_DIR)/ && cp $< $@
|
||||
|
||||
$(DIST_DIR)/config.h.in: src/config.h.in
|
||||
@echo ' COPY $@'
|
||||
$(QUIET)mkdir -p $(DIST_DIR)/ && cp $< $@
|
||||
|
||||
$(DIST_DIR)/man1/mdbx_%.1: src/man1/mdbx_%.1
|
||||
@echo ' COPY $@'
|
||||
$(QUIET)mkdir -p $(DIST_DIR)/man1/ && cp $< $@
|
||||
|
||||
endif
|
||||
|
||||
################################################################################
|
||||
# Cross-compilation simple test
|
||||
|
||||
CROSS_LIST = \
|
||||
mips64-linux-gnuabi64-gcc mips-linux-gnu-gcc \
|
||||
hppa-linux-gnu-gcc s390x-linux-gnu-gcc \
|
||||
powerpc64-linux-gnu-gcc powerpc-linux-gnu-gcc \
|
||||
arm-linux-gnueabihf-gcc aarch64-linux-gnu-gcc
|
||||
|
||||
## On Ubuntu Focal (22.04) with QEMU 6.2 (1:6.2+dfsg-2ubuntu6.6) & GCC 11.3 (11.3.0-1ubuntu1~22.04)
|
||||
# sh4-linux-gnu-gcc - coredump (qemu mmap-troubles)
|
||||
# sparc64-linux-gnu-gcc - coredump (qemu mmap-troubles, previously: qemu fails fcntl for F_SETLK/F_GETLK)
|
||||
# alpha-linux-gnu-gcc - coredump (qemu mmap-troubles)
|
||||
# risc64-linux-gnu-gcc - coredump (qemu qemu fails fcntl for F_SETLK/F_GETLK)
|
||||
CROSS_LIST_NOQEMU = sh4-linux-gnu-gcc sparc64-linux-gnu-gcc alpha-linux-gnu-gcc riscv64-linux-gnu-gcc
|
||||
|
||||
cross-gcc:
|
||||
@echo ' Re-building by cross-compiler for: $(CROSS_LIST_NOQEMU) $(CROSS_LIST)'
|
||||
@echo "CORRESPONDING CROSS-COMPILERs ARE REQUIRED."
|
||||
@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 $$?; \
|
||||
done
|
||||
|
||||
# Unfortunately qemu don't provide robust support for futexes.
|
||||
# Therefore it is impossible to run full multi-process tests.
|
||||
cross-qemu:
|
||||
@echo ' Re-building by cross-compiler and re-check by QEMU for: $(CROSS_LIST)'
|
||||
@echo "CORRESPONDING CROSS-COMPILERs AND QEMUs ARE REQUIRED."
|
||||
@echo "FOR INSTANCE: "
|
||||
@echo " 1) sudo apt install \$$(apt list 'g++-*' | grep 'g++-[a-z0-9]\+-linux-gnu/' | cut -f 1 -d / | sort -u)"
|
||||
@echo " 2) sudo apt install binfmt-support qemu-user-static qemu-user \$$(apt list 'qemu-system-*' | grep 'qemu-system-[a-z0-9]\+/' | cut -f 1 -d / | sort -u)"
|
||||
$(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)" \
|
||||
$(MAKE) IOARENA=false smoke-singleprocess test-singleprocess || exit $$?; \
|
||||
done
|
||||
|
||||
#< dist-cutoff-end
|
||||
|
||||
install: $(LIBRARIES) $(MDBX_TOOLS) $(HEADERS)
|
||||
@echo ' INSTALLING...'
|
||||
$(QUIET)mkdir -p $(DESTDIR)$(prefix)/bin$(suffix) && \
|
||||
$(INSTALL) -p $(EXE_INSTALL_FLAGS) $(MDBX_TOOLS) $(DESTDIR)$(prefix)/bin$(suffix)/ && \
|
||||
mkdir -p $(DESTDIR)$(prefix)/lib$(suffix)/ && \
|
||||
$(INSTALL) -p $(EXE_INSTALL_FLAGS) $(filter-out libmdbx.a,$(LIBRARIES)) $(DESTDIR)$(prefix)/lib$(suffix)/ && \
|
||||
mkdir -p $(DESTDIR)$(prefix)/lib$(suffix)/ && \
|
||||
$(INSTALL) -p libmdbx.a $(DESTDIR)$(prefix)/lib$(suffix)/ && \
|
||||
mkdir -p $(DESTDIR)$(prefix)/include/ && \
|
||||
$(INSTALL) -p -m 444 $(HEADERS) $(DESTDIR)$(prefix)/include/ && \
|
||||
mkdir -p $(DESTDIR)$(mandir)/man1/ && \
|
||||
$(INSTALL) -p -m 444 $(addprefix $(MAN_SRCDIR), $(MANPAGES)) $(DESTDIR)$(mandir)/man1/
|
||||
|
||||
install-strip: EXE_INSTALL_FLAGS = -s
|
||||
install-strip: install
|
||||
|
||||
install-no-strip: EXE_INSTALL_FLAGS =
|
||||
install-no-strip: install
|
||||
|
||||
uninstall:
|
||||
@echo ' UNINSTALLING/REMOVE...'
|
||||
$(QUIET)rm -f $(addprefix $(DESTDIR)$(prefix)/bin$(suffix)/,$(MDBX_TOOLS)) \
|
||||
$(addprefix $(DESTDIR)$(prefix)/lib$(suffix)/,$(LIBRARIES)) \
|
||||
$(addprefix $(DESTDIR)$(prefix)/include/,$(HEADERS)) \
|
||||
$(addprefix $(DESTDIR)$(mandir)/man1/,$(MANPAGES))
|
||||
|
||||
################################################################################
|
||||
# Benchmarking by ioarena
|
||||
|
||||
ifeq ($(origin IOARENA),undefined)
|
||||
IOARENA := $(shell \
|
||||
(test -x ../ioarena/@BUILD/src/ioarena && echo ../ioarena/@BUILD/src/ioarena) || \
|
||||
(test -x ../../@BUILD/src/ioarena && echo ../../@BUILD/src/ioarena) || \
|
||||
(test -x ../../src/ioarena && echo ../../src/ioarena) || which ioarena 2>&- || \
|
||||
(echo false && echo '$(TIP) Clone and build the https://abf.io/erthink/ioarena.git within a neighbouring directory for availability of benchmarking.' >&2))
|
||||
endif
|
||||
NN ?= 25000000
|
||||
BENCH_CRUD_MODE ?= nosync
|
||||
|
||||
bench-clean:
|
||||
@echo ' REMOVE bench-*.txt _ioarena/*'
|
||||
$(QUIET)rm -rf bench-*.txt _ioarena/*
|
||||
|
||||
re-bench: bench-clean bench
|
||||
|
||||
ifeq ($(or $(IOARENA),false),false)
|
||||
bench bench-quartet bench-triplet bench-couple:
|
||||
$(QUIET)echo 'The `ioarena` benchmark is required.' >&2 && \
|
||||
echo 'Please clone and build the https://abf.io/erthink/ioarena.git within a neighbouring `ioarena` directory.' >&2 && \
|
||||
false
|
||||
|
||||
else
|
||||
|
||||
.PHONY: bench bench-clean bench-couple re-bench bench-quartet bench-triplet
|
||||
|
||||
define bench-rule
|
||||
bench-$(1)_$(2).txt: $(3) $(IOARENA) $(lastword $(MAKEFILE_LIST))
|
||||
@echo ' RUNNING ioarena for $1/$2...'
|
||||
$(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/' && \
|
||||
$(IOARENA) -D $(1) -B crud -m $(BENCH_CRUD_MODE) -n $(2) \
|
||||
| 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/' && \
|
||||
$(IOARENA) -D $(1) -B delete -m $(BENCH_CRUD_MODE) -n $(2) \
|
||||
| tee -a $$@ | grep throughput | $(SED) 's/throughput/ delete/' && \
|
||||
true) || mv -f $$@ $$@.error
|
||||
|
||||
endef
|
||||
|
||||
|
||||
$(eval $(call bench-rule,mdbx,$(NN),libmdbx.$(SO_SUFFIX)))
|
||||
|
||||
$(eval $(call bench-rule,sophia,$(NN)))
|
||||
$(eval $(call bench-rule,leveldb,$(NN)))
|
||||
$(eval $(call bench-rule,rocksdb,$(NN)))
|
||||
$(eval $(call bench-rule,wiredtiger,$(NN)))
|
||||
$(eval $(call bench-rule,forestdb,$(NN)))
|
||||
$(eval $(call bench-rule,lmdb,$(NN)))
|
||||
$(eval $(call bench-rule,nessdb,$(NN)))
|
||||
$(eval $(call bench-rule,sqlite3,$(NN)))
|
||||
$(eval $(call bench-rule,ejdb,$(NN)))
|
||||
$(eval $(call bench-rule,vedisdb,$(NN)))
|
||||
$(eval $(call bench-rule,dummy,$(NN)))
|
||||
bench: bench-mdbx_$(NN).txt
|
||||
bench-quartet: bench-mdbx_$(NN).txt bench-lmdb_$(NN).txt bench-rocksdb_$(NN).txt bench-wiredtiger_$(NN).txt
|
||||
bench-triplet: bench-mdbx_$(NN).txt bench-lmdb_$(NN).txt bench-sqlite3_$(NN).txt
|
||||
bench-couple: bench-mdbx_$(NN).txt bench-lmdb_$(NN).txt
|
||||
|
||||
# $(eval $(call bench-rule,debug,10))
|
||||
# .PHONY: bench-debug
|
||||
# bench-debug: bench-debug_10.txt
|
||||
|
||||
endif
|
206
LICENSE
206
LICENSE
@ -1,47 +1,177 @@
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
1. Definitions.
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
231
Makefile
231
Makefile
@ -1,217 +1,16 @@
|
||||
# GNU Makefile for libmdbx, https://github.com/leo-yuriev/libmdbx
|
||||
# This is thunk-Makefile for calling GNU Make 3.80 or above
|
||||
|
||||
########################################################################
|
||||
# Configuration. The compiler options must enable threaded compilation.
|
||||
#
|
||||
# Preprocessor macros (for XCFLAGS) of interest...
|
||||
# Note that the defaults should already be correct for most
|
||||
# platforms; you should not need to change any of these.
|
||||
# Read their descriptions in mdb.c if you do. There may be
|
||||
# other macros of interest. You should read mdb.c
|
||||
# before changing any of them.
|
||||
#
|
||||
|
||||
# install sandbox
|
||||
SANDBOX ?=
|
||||
|
||||
# install prefixes (inside sandbox)
|
||||
prefix ?= /usr/local
|
||||
mandir ?= $(prefix)/man
|
||||
|
||||
# lib/bin suffix for multiarch/biarch, e.g. '.x86_64'
|
||||
suffix ?=
|
||||
|
||||
CC ?= gcc
|
||||
CXX ?= g++
|
||||
CFLAGS ?= -O2 -g3 -Wall -Werror -Wextra -ffunction-sections -fPIC -fvisibility=hidden
|
||||
|
||||
XCFLAGS ?= -DNDEBUG=1 -DMDBX_DEBUG=0 -DLIBMDBX_EXPORTS=1
|
||||
CFLAGS += -D_GNU_SOURCE=1 -std=gnu11 -pthread $(XCFLAGS)
|
||||
CXXFLAGS = -std=c++11 $(filter-out -std=gnu11,$(CFLAGS))
|
||||
TESTDB ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.db
|
||||
TESTLOG ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.log
|
||||
|
||||
# LY: '--no-as-needed,-lrt' for ability to built with modern glibc, but then run with the old
|
||||
LDFLAGS ?= -Wl,--gc-sections,-z,relro,-O,--no-as-needed,-lrt
|
||||
EXE_LDFLAGS ?= -pthread -lrt
|
||||
|
||||
# LY: just for benchmarking
|
||||
IOARENA ?= $(shell \
|
||||
(test -x ../ioarena/@BUILD/src/ioarena && echo ../ioarena/@BUILD/src/ioarena) || \
|
||||
(test -x ../../@BUILD/src/ioarena && echo ../../@BUILD/src/ioarena) || \
|
||||
(test -x ../../src/ioarena && echo ../../src/ioarena) || which ioarena)
|
||||
NN ?= 25000000
|
||||
|
||||
########################################################################
|
||||
|
||||
HEADERS := mdbx.h
|
||||
LIBRARIES := libmdbx.a libmdbx.so
|
||||
TOOLS := mdbx_stat mdbx_copy mdbx_dump mdbx_load mdbx_chk
|
||||
MANPAGES := mdbx_stat.1 mdbx_copy.1 mdbx_dump.1 mdbx_load.1
|
||||
SHELL := /bin/bash
|
||||
|
||||
CORE_SRC := $(filter-out src/lck-windows.c, $(wildcard src/*.c))
|
||||
CORE_INC := $(wildcard src/*.h)
|
||||
CORE_OBJ := $(patsubst %.c,%.o,$(CORE_SRC))
|
||||
TEST_SRC := $(filter-out test/osal-windows.cc, $(wildcard test/*.cc))
|
||||
TEST_INC := $(wildcard test/*.h)
|
||||
TEST_OBJ := $(patsubst %.cc,%.o,$(TEST_SRC))
|
||||
|
||||
.PHONY: mdbx all install clean check coverage
|
||||
|
||||
all: $(LIBRARIES) $(TOOLS) mdbx_test example
|
||||
|
||||
mdbx: libmdbx.a libmdbx.so
|
||||
|
||||
example: mdbx.h tutorial/sample-mdbx.c libmdbx.so
|
||||
$(CC) $(CFLAGS) -I. tutorial/sample-mdbx.c ./libmdbx.so -o example
|
||||
|
||||
tools: $(TOOLS)
|
||||
|
||||
install: $(LIBRARIES) $(TOOLS) $(HEADERS)
|
||||
mkdir -p $(SANDBOX)$(prefix)/bin$(suffix) \
|
||||
&& cp -t $(SANDBOX)$(prefix)/bin$(suffix) $(TOOLS) && \
|
||||
mkdir -p $(SANDBOX)$(prefix)/lib$(suffix) \
|
||||
&& cp -t $(SANDBOX)$(prefix)/lib$(suffix) $(LIBRARIES) && \
|
||||
mkdir -p $(SANDBOX)$(prefix)/include \
|
||||
&& cp -t $(SANDBOX)$(prefix)/include $(HEADERS) && \
|
||||
mkdir -p $(SANDBOX)$(mandir)/man1 \
|
||||
&& cp -t $(SANDBOX)$(mandir)/man1 $(MANPAGES)
|
||||
|
||||
clean:
|
||||
rm -rf $(TOOLS) mdbx_test @* *.[ao] *.[ls]o *~ tmp.db/* *.gcov *.log *.err src/*.o test/*.o
|
||||
|
||||
check: all
|
||||
rm -f $(TESTDB) $(TESTLOG) && (set -o pipefail; ./mdbx_test --pathname=$(TESTDB) --dont-cleanup-after basic | tee -a $(TESTLOG) | tail -n 42) && ./mdbx_chk -vvn $(TESTDB)
|
||||
|
||||
check-singleprocess: all
|
||||
rm -f $(TESTDB) $(TESTLOG) && (set -o pipefail; ./mdbx_test --pathname=$(TESTDB) --dont-cleanup-after --hill | tee -a $(TESTLOG) | tail -n 42) && ./mdbx_chk -vvn $(TESTDB)
|
||||
|
||||
check-fault: all
|
||||
rm -f $(TESTDB) $(TESTLOG) && (set -o pipefail; ./mdbx_test --pathname=$(TESTDB) --inject-writefault=42 --dump-config --dont-cleanup-after basic | tee -a $(TESTLOG) | tail -n 42) && ./mdbx_chk -vvn $(TESTDB)
|
||||
|
||||
define core-rule
|
||||
$(patsubst %.c,%.o,$(1)): $(1) $(CORE_INC) mdbx.h Makefile
|
||||
$(CC) $(CFLAGS) -c $(1) -o $$@
|
||||
|
||||
endef
|
||||
$(foreach file,$(CORE_SRC),$(eval $(call core-rule,$(file))))
|
||||
|
||||
define test-rule
|
||||
$(patsubst %.cc,%.o,$(1)): $(1) $(TEST_INC) mdbx.h Makefile
|
||||
$(CXX) $(CXXFLAGS) -c $(1) -o $$@
|
||||
|
||||
endef
|
||||
$(foreach file,$(TEST_SRC),$(eval $(call test-rule,$(file))))
|
||||
|
||||
libmdbx.a: $(CORE_OBJ)
|
||||
$(AR) rs $@ $?
|
||||
|
||||
libmdbx.so: $(CORE_OBJ)
|
||||
$(CC) $(CFLAGS) -save-temps $^ -pthread -shared $(LDFLAGS) -o $@
|
||||
|
||||
mdbx_%: src/tools/mdbx_%.c libmdbx.a
|
||||
$(CC) $(CFLAGS) $^ $(EXE_LDFLAGS) -o $@
|
||||
|
||||
mdbx_test: $(TEST_OBJ) libmdbx.so
|
||||
$(CXX) $(CXXFLAGS) $(TEST_OBJ) -Wl,-rpath . -L . -l mdbx $(EXE_LDFLAGS) -o $@
|
||||
|
||||
###############################################################################
|
||||
|
||||
ifneq ($(wildcard $(IOARENA)),)
|
||||
|
||||
.PHONY: bench clean-bench re-bench
|
||||
|
||||
clean-bench:
|
||||
rm -rf bench-*.txt _ioarena/*
|
||||
|
||||
re-bench: clean-bench bench
|
||||
|
||||
define bench-rule
|
||||
bench-$(1)_$(2).txt: $(3) $(IOARENA) Makefile
|
||||
LD_LIBRARY_PATH="./:$$$${LD_LIBRARY_PATH}" \
|
||||
$(IOARENA) -D $(1) -B crud -m nosync -n $(2) \
|
||||
| tee $$@ | grep throughput && \
|
||||
LD_LIBRARY_PATH="./:$$$${LD_LIBRARY_PATH}" \
|
||||
$(IOARENA) -D $(1) -B get,iterate -m sync -r 4 -n $(2) \
|
||||
| tee -a $$@ | grep throughput \
|
||||
|| mv -f $$@ $$@.error
|
||||
|
||||
endef
|
||||
|
||||
$(eval $(call bench-rule,mdbx,$(NN),libmdbx.so))
|
||||
|
||||
$(eval $(call bench-rule,sophia,$(NN)))
|
||||
$(eval $(call bench-rule,leveldb,$(NN)))
|
||||
$(eval $(call bench-rule,rocksdb,$(NN)))
|
||||
$(eval $(call bench-rule,wiredtiger,$(NN)))
|
||||
$(eval $(call bench-rule,forestdb,$(NN)))
|
||||
$(eval $(call bench-rule,lmdb,$(NN)))
|
||||
$(eval $(call bench-rule,nessdb,$(NN)))
|
||||
$(eval $(call bench-rule,sqlite3,$(NN)))
|
||||
$(eval $(call bench-rule,ejdb,$(NN)))
|
||||
$(eval $(call bench-rule,vedisdb,$(NN)))
|
||||
$(eval $(call bench-rule,dummy,$(NN)))
|
||||
|
||||
$(eval $(call bench-rule,debug,10))
|
||||
|
||||
bench: bench-mdbx_$(NN).txt
|
||||
|
||||
.PHONY: bench-debug
|
||||
|
||||
bench-debug: bench-debug_10.txt
|
||||
|
||||
bench-quartet: bench-mdbx_$(NN).txt bench-lmdb_$(NN).txt bench-rocksdb_$(NN).txt bench-wiredtiger_$(NN).txt
|
||||
|
||||
endif
|
||||
|
||||
###############################################################################
|
||||
|
||||
ci-rule = ( CC=$$(which $1); if [ -n "$$CC" ]; then \
|
||||
echo -n "probe by $2 ($$(readlink -f $$(which $$CC))): " && \
|
||||
$(MAKE) clean >$1.log 2>$1.err && \
|
||||
$(MAKE) CC=$$(readlink -f $$CC) XCFLAGS="-UNDEBUG -DMDBX_DEBUG=2 -DLIBMDBX_EXPORTS=1" check 1>$1.log 2>$1.err && echo "OK" \
|
||||
|| ( echo "FAILED"; cat $1.err >&2; exit 1 ); \
|
||||
else echo "no $2 ($1) for probe"; fi; )
|
||||
ci:
|
||||
@if [ "$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which gcc || echo /bin/false))" -a \
|
||||
"$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which clang || echo /bin/false))" -a \
|
||||
"$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which icc || echo /bin/false))" ]; then \
|
||||
$(call ci-rule,$(CC),default C compiler); \
|
||||
fi
|
||||
@$(call ci-rule,gcc,GCC)
|
||||
@$(call ci-rule,clang,clang LLVM)
|
||||
@$(call ci-rule,icc,Intel C)
|
||||
|
||||
###############################################################################
|
||||
|
||||
CROSS_LIST = alpha-linux-gnu-gcc mips-linux-gnu-gcc \
|
||||
powerpc64-linux-gnu-gcc powerpc-linux-gnu-gcc \
|
||||
arm-linux-gnueabihf-gcc aarch64-linux-gnu-gcc
|
||||
|
||||
# hppa-linux-gnu-gcc - don't supported by current qemu release
|
||||
# s390x-linux-gnu-gcc - qemu troubles (hang/abort)
|
||||
# sh4-linux-gnu-gcc - qemu troubles (pread syscall, etc)
|
||||
# mips64-linux-gnuabi64-gcc - qemu troubles (pread syscall, etc)
|
||||
# sparc64-linux-gnu-gcc - qemu troubles (fcntl for F_SETLK/F_GETLK)
|
||||
CROSS_LIST_NOQEMU = hppa-linux-gnu-gcc s390x-linux-gnu-gcc \
|
||||
sh4-linux-gnu-gcc mips64-linux-gnuabi64-gcc sparc64-linux-gnu-gcc
|
||||
|
||||
cross-gcc:
|
||||
@echo "CORRESPONDING CROSS-COMPILERs ARE REQUIRED."
|
||||
@echo "FOR INSTANCE: apt install g++-aarch64-linux-gnu g++-alpha-linux-gnu g++-arm-linux-gnueabihf g++-hppa-linux-gnu g++-mips-linux-gnu g++-mips64-linux-gnuabi64 g++-powerpc-linux-gnu g++-powerpc64-linux-gnu g++-s390x-linux-gnu g++-sh4-linux-gnu"
|
||||
@for CC in $(CROSS_LIST_NOQEMU) $(CROSS_LIST); do \
|
||||
echo "===================== $$CC"; \
|
||||
$(MAKE) clean && CC=$$CC CXX=$$(echo $$CC | sed 's/-gcc/-g++/') EXE_LDFLAGS=-static $(MAKE) all || exit $$?; \
|
||||
done
|
||||
|
||||
#
|
||||
# Unfortunately qemu don't provide robust support for futexes.
|
||||
# Therefore it is impossible to run full multi-process tests.
|
||||
cross-qemu:
|
||||
@echo "CORRESPONDING CROSS-COMPILERs AND QEMUs ARE REQUIRED."
|
||||
@echo "FOR INSTANCE: apt install binfmt-support qemu-user-static qemu-user qemu-system-arm qemu-system-mips qemu-system-misc qemu-system-ppc qemu-system-sparc g++-aarch64-linux-gnu g++-alpha-linux-gnu g++-arm-linux-gnueabihf g++-hppa-linux-gnu g++-mips-linux-gnu g++-mips64-linux-gnuabi64 g++-powerpc-linux-gnu g++-powerpc64-linux-gnu g++-s390x-linux-gnu g++-sh4-linux-gnu"
|
||||
@for CC in $(CROSS_LIST); do \
|
||||
echo "===================== $$CC + qemu"; \
|
||||
$(MAKE) clean && CC=$$CC CXX=$$(echo $$CC | sed 's/-gcc/-g++/') EXE_LDFLAGS=-static $(MAKE) check-singleprocess || exit $$?; \
|
||||
done
|
||||
all help options cmake-build ninja \
|
||||
clean install install-no-strip install-strip strip tools uninstall \
|
||||
bench bench-clean bench-couple bench-quartet bench-triplet re-bench \
|
||||
lib libs lib-static lib-shared tools-static \
|
||||
libmdbx mdbx mdbx_chk mdbx_copy mdbx_drop mdbx_dump mdbx_load mdbx_stat \
|
||||
check dist memcheck cross-gcc cross-qemu doxygen gcc-analyzer reformat \
|
||||
release-assets tags build-test mdbx_test \
|
||||
smoke smoke-fault smoke-singleprocess smoke-assertion smoke-memcheck \
|
||||
test test-assertion test-long test-long-assertion test-ci test-ci-extra \
|
||||
test-asan test-leak test-singleprocess test-ubsan test-memcheck:
|
||||
@CC=$(CC) \
|
||||
CXX=`if test -n "$(CXX)" && which "$(CXX)" > /dev/null; then echo "$(CXX)"; elif test -n "$(CCC)" && which "$(CCC)" > /dev/null; then echo "$(CCC)"; else echo "c++"; fi` \
|
||||
`which gmake || which gnumake || echo 'echo "GNU Make 3.80 or above is required"; exit 2;'` \
|
||||
$(MAKEFLAGS) -f GNUmakefile $@
|
||||
|
39
NOTICE
Normal file
39
NOTICE
Normal file
@ -0,0 +1,39 @@
|
||||
libmdbx (aka MDBX) is an extremely fast, compact, powerful, embeddedable,
|
||||
transactional key-value storage engine with open-source code. MDBX has a
|
||||
specific set of properties and capabilities, focused on creating unique
|
||||
lightweight solutions.
|
||||
|
||||
Please visit https://libmdbx.dqdkfa.ru for more information, changelog,
|
||||
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 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 libmdbx source.
|
||||
|
||||
---
|
||||
|
||||
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.
|
692
README-RU.md
692
README-RU.md
@ -1,692 +0,0 @@
|
||||
libmdbx
|
||||
======================================
|
||||
**The revised and extended descendant of [Symas LMDB](https://symas.com/lmdb/).**
|
||||
|
||||
*The Future will Positive. Всё будет хорошо.*
|
||||
[](https://travis-ci.org/leo-yuriev/libmdbx)
|
||||
[](https://ci.appveyor.com/project/leo-yuriev/libmdbx/branch/master)
|
||||
[](https://scan.coverity.com/projects/reopen-libmdbx)
|
||||
|
||||
English version [by Google](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/leo-yuriev/libmdbx/tree/master)
|
||||
and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.com%2FReOpen%2Flibmdbx%2Ftree%2Fmaster&lang=ru-en).
|
||||
|
||||
### Project Status
|
||||
|
||||
**Сейчас MDBX _активно перерабатывается_** и к середине 2018 ожидается
|
||||
большое изменение как API, так и формата базы данных. К сожалению,
|
||||
обновление приведет к потере совместимости с предыдущими версиями.
|
||||
|
||||
Цель этой революции - обеспечение более четкого надежного API и
|
||||
добавление новых функции, а также наделение базы данных новыми
|
||||
свойствами.
|
||||
|
||||
В настоящее время MDBX предназначена для Linux, а также поддерживает
|
||||
Windows (начиная с Windows Server 2008) в качестве дополнительной
|
||||
платформы. Поддержка других ОС может быть обеспечена на коммерческой
|
||||
основе. Однако такие усовершенствования (т. е. pull-requests) могут быть
|
||||
приняты в мейнстрим только в том случае, если будет доступен
|
||||
соответствующий публичный и бесплатный сервис непрерывной интеграции
|
||||
(aka Continuous Integration).
|
||||
|
||||
## Содержание
|
||||
- [Обзор](#Обзор)
|
||||
- [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД)
|
||||
- [История & Acknowledgments](#История)
|
||||
- [Основные свойства](#Основные-свойства)
|
||||
- [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb)
|
||||
- [Недостатки и Компромиссы](#Недостатки-и-Компромиссы)
|
||||
- [Проблема долгих чтений](#Проблема-долгих-чтений)
|
||||
- [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации)
|
||||
- [Сравнение производительности](#Сравнение-производительности)
|
||||
- [Интегральная производительность](#Интегральная-производительность)
|
||||
- [Масштабируемость чтения](#Масштабируемость-чтения)
|
||||
- [Синхронная фиксация](#Синхронная-фиксация)
|
||||
- [Отложенная фиксация](#Отложенная-фиксация)
|
||||
- [Асинхронная фиксация](#Асинхронная-фиксация)
|
||||
- [Потребление ресурсов](#Потребление-ресурсов)
|
||||
|
||||
|
||||
## Обзор
|
||||
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
|
||||
набором свойств и возможностей, ориентированный на создание уникальных
|
||||
легковесных решений с предельной производительностью под Linux и
|
||||
Windows.
|
||||
|
||||
_libmdbx_ позволяет множеству процессов совместно читать и обновлять
|
||||
несколько key-value таблиц с соблюдением
|
||||
[ACID](https://ru.wikipedia.org/wiki/ACID), при минимальных накладных
|
||||
расходах и амортизационной стоимости любых операций Olog(N).
|
||||
|
||||
_libmdbx_ обеспечивает
|
||||
[serializability](https://en.wikipedia.org/wiki/Serializability)
|
||||
изменений и согласованность данных после аварий. При этом транзакции,
|
||||
изменяющие данные, никак не мешают операциям чтения и выполняются строго
|
||||
последовательно с использованием единственного
|
||||
[мьютекса](https://en.wikipedia.org/wiki/Mutual_exclusion).
|
||||
|
||||
_libmdbx_ позволяет выполнять операции чтения с гарантиями
|
||||
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom),
|
||||
параллельно на каждом ядре CPU, без использования атомарных операций
|
||||
и/или примитивов синхронизации.
|
||||
|
||||
_libmdbx_ не использует
|
||||
[LSM](https://en.wikipedia.org/wiki/Log-structured_merge-tree), а
|
||||
основан на [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree) с
|
||||
[отображением](https://en.wikipedia.org/wiki/Memory-mapped_file) всех
|
||||
данных в память, при этом текущая версия не использует
|
||||
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). Это
|
||||
предопределяет многие свойства, в том числе удачные и противопоказанные
|
||||
сценарии использования.
|
||||
|
||||
|
||||
### Сравнение с другими СУБД
|
||||
Ввиду того, что в _libmdbx_ сейчас происходит революция, я посчитал
|
||||
лучшим решением ограничится здесь ссылкой на [главу Comparison with
|
||||
other databases](https://github.com/coreos/bbolt#comparison-with-other-databases)
|
||||
в описании _BoltDB_.
|
||||
|
||||
|
||||
### История
|
||||
_libmdbx_ является результатом переработки и развития "Lightning
|
||||
Memory-Mapped Database", известной под аббревиатурой
|
||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
||||
Изначально доработка производилась в составе проекта
|
||||
[ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP). Примерно за год
|
||||
работы внесенные изменения приобрели самостоятельную ценность. Осенью
|
||||
2015 доработанный движок был выделен в отдельный проект, который был
|
||||
[представлен на конференции Highload++
|
||||
2015](http://www.highload.ru/2015/abstracts/1831.html).
|
||||
|
||||
В начале 2017 года движок _libmdbx_ получил новый импульс развития,
|
||||
благодаря использованию в [Fast Positive
|
||||
Tables](https://github.com/leo-yuriev/libfpta), aka ["Позитивные
|
||||
Таблицы"](https://github.com/leo-yuriev/libfpta) by [Positive
|
||||
Technologies](https://www.ptsecurity.ru).
|
||||
|
||||
|
||||
#### Acknowledgments
|
||||
Howard Chu (Symas Corporation) - the author of LMDB, from which
|
||||
originated the MDBX in 2015.
|
||||
|
||||
Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, which
|
||||
was used for begin development of LMDB.
|
||||
|
||||
|
||||
Основные свойства
|
||||
=================
|
||||
|
||||
_libmdbx_ наследует все ключевые возможности и особенности своего
|
||||
прародителя
|
||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
|
||||
но с устранением ряда описываемых далее проблем и архитектурных
|
||||
недочетов.
|
||||
|
||||
1. Данные хранятся в упорядоченном отображении (ordered map), ключи
|
||||
всегда отсортированы, поддерживается выборка диапазонов (range lookups).
|
||||
|
||||
2. Данные отображается в память каждого работающего с БД процесса. К
|
||||
данным и ключам обеспечивается прямой доступ в памяти без необходимости
|
||||
их копирования.
|
||||
|
||||
3. Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID),
|
||||
посредством [MVCC](https://ru.wikipedia.org/wiki/MVCC) и
|
||||
[COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8).
|
||||
Изменения строго последовательны и не блокируются чтением, конфликты
|
||||
между транзакциями невозможны. При этом гарантируется чтение только
|
||||
зафиксированных данных, см [relaxing
|
||||
serializability](https://en.wikipedia.org/wiki/Serializability).
|
||||
|
||||
4. Чтение и поиск [без
|
||||
блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F),
|
||||
без [атомарных
|
||||
операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F).
|
||||
Читатели не блокируются операциями записи и не конкурируют между собой,
|
||||
чтение масштабируется линейно по ядрам CPU.
|
||||
> Для точности следует отметить, что "подключение к БД" (старт первой
|
||||
> читающей транзакции в потоке) и "отключение от БД" (закрытие БД или
|
||||
> завершение потока) требуют краткосрочного захвата блокировки для
|
||||
> регистрации/дерегистрации текущего потока в "таблице читателей".
|
||||
|
||||
5. Эффективное хранение дубликатов (ключей с несколькими значениями),
|
||||
без дублирования ключей, с сортировкой значений, в том числе
|
||||
целочисленных (для вторичных индексов).
|
||||
|
||||
6. Эффективная поддержка коротких ключей фиксированной длины, в том
|
||||
числе целочисленных.
|
||||
|
||||
7. Амортизационная стоимость любой операции Olog(N),
|
||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
|
||||
Amplification Factor) и RAF (Read Amplification Factor) также Olog(N).
|
||||
|
||||
8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и
|
||||
журнала транзакций, после сбоев не требуется восстановление. Не
|
||||
требуется компактификация или какое-либо периодическое обслуживание.
|
||||
Поддерживается резервное копирование "по горячему", на работающей БД без
|
||||
приостановки изменения данных.
|
||||
|
||||
9. Отсутствует какое-либо внутреннее управление памятью или
|
||||
кэшированием. Всё необходимое штатно выполняет ядро ОС.
|
||||
|
||||
|
||||
Доработки и усовершенствования относительно LMDB
|
||||
================================================
|
||||
|
||||
1. Утилита `mdbx_chk` для проверки целостности структуры БД.
|
||||
|
||||
2. Автоматическое динамическое управление размером БД согласно
|
||||
параметрам задаваемым функцией `mdbx_env_set_geometry()`, включая шаг
|
||||
приращения и порог уменьшения размера БД, а также выбор размера
|
||||
страницы. Соответственно, это позволяет снизить фрагментированность
|
||||
файла БД на диске и освободить место, в том числе в **Windows**.
|
||||
|
||||
3. Автоматическая без-затратная компактификация БД путем возврата
|
||||
освобождающихся страниц в область нераспределенного резерва в конце
|
||||
файла данных. При этом уменьшается количество страниц находящихся в
|
||||
памяти и участвующих в в обмене с диском.
|
||||
|
||||
4. Поддержка ключей и значений нулевой длины, включая сортированные
|
||||
дубликаты.
|
||||
|
||||
5. Возможность связать с каждой завершаемой транзакцией до 3
|
||||
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
|
||||
в транзакции чтения посредством `mdbx_canary_get()`.
|
||||
|
||||
6. Возможность посредством `mdbx_replace()` обновить или удалить запись
|
||||
с получением предыдущего значения данных, а также адресно изменить
|
||||
конкретное multi-значение.
|
||||
|
||||
7. Режим `LIFO RECLAIM`.
|
||||
|
||||
Для повторного использования выбираются не самые старые, а
|
||||
самые новые страницы из доступных. За счет этого цикл
|
||||
использования страниц всегда имеет минимальную длину и не
|
||||
зависит от общего числа выделенных страниц.
|
||||
|
||||
В результате механизмы кэширования и обратной записи работают с
|
||||
максимально возможной эффективностью. В случае использования
|
||||
контроллера дисков или системы хранения с
|
||||
[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
|
||||
многократное увеличение производительности по записи
|
||||
(обновлению данных).
|
||||
|
||||
8. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
|
||||
|
||||
9. Обработчик `OOM-KICK`.
|
||||
|
||||
Посредством `mdbx_env_set_oomfunc()` может быть установлен
|
||||
внешний обработчик (callback), который будет вызван при
|
||||
исчерпании свободных страниц по причине долгой операцией чтения
|
||||
на фоне интенсивного изменения данных.
|
||||
Обработчику будет передан PID и pthread_id виновника.
|
||||
В свою очередь обработчик может предпринять одно из действий:
|
||||
|
||||
* нейтрализовать виновника (отправить сигнал kill #9), если
|
||||
долгое чтение выполняется сторонним процессом;
|
||||
|
||||
* отменить или перезапустить проблемную операцию чтения, если
|
||||
операция выполняется одним из потоков текущего процесса;
|
||||
|
||||
* подождать некоторое время, в расчете на то, что проблемная операция
|
||||
чтения будет штатно завершена;
|
||||
|
||||
* прервать текущую операцию изменения данных с возвратом кода
|
||||
ошибки.
|
||||
|
||||
10. Возможность открыть БД в эксклюзивном режиме посредством флага
|
||||
`MDBX_EXCLUSIVE`.
|
||||
|
||||
11. Возможность получить отставание текущей транзакции чтения от
|
||||
последней версии данных в БД посредством `mdbx_txn_straggler()`.
|
||||
|
||||
12. Возможность явно запросить обновление существующей записи, без
|
||||
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
|
||||
|
||||
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
|
||||
количество дубликатов для всех типов таблиц и любого положения курсора.
|
||||
|
||||
14. Возможность получить посредством `mdbx_env_info()` дополнительную
|
||||
информацию, включая номер самой старой версии БД (снимка данных),
|
||||
который используется одним из читателей.
|
||||
|
||||
15. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
|
||||
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
|
||||
при его ненулевом значении всегда использует его для сверки с удаляемой
|
||||
записью.
|
||||
|
||||
16. Возможность открыть dbi-таблицу, одновременно с установкой
|
||||
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
|
||||
|
||||
17. Возможность посредством `mdbx_is_dirty()` определить находятся ли
|
||||
некоторый ключ или данные в "грязной" странице БД. Таким образом,
|
||||
избегая лишнего копирования данных перед выполнением модифицирующих
|
||||
операций (значения, размещенные в "грязных" страницах, могут быть
|
||||
перезаписаны при изменениях, иначе они будут неизменны).
|
||||
|
||||
18. Корректное обновление текущей записи, в том числе сортированного
|
||||
дубликата, при использовании режима `MDBX_CURRENT` в
|
||||
`mdbx_cursor_put()`.
|
||||
|
||||
19. Возможность узнать есть ли за текущей позицией курсора строка данных
|
||||
посредством `mdbx_cursor_eof()`.
|
||||
|
||||
20. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
|
||||
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
|
||||
обновление или удаления одного из нескольких значений с одним ключом.
|
||||
|
||||
21. Возможность посредством `mdbx_get_ex()` получить значение по
|
||||
заданному ключу, одновременно с количеством дубликатов.
|
||||
|
||||
22. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`,
|
||||
которые позволяют быстро выяснить стоит ли курсор на первой/последней
|
||||
позиции.
|
||||
|
||||
23. Возможность автоматического формирования контрольных точек (сброса
|
||||
данных на диск) при накоплении заданного объёма изменений,
|
||||
устанавливаемого функцией `mdbx_env_set_syncbytes()`.
|
||||
|
||||
24. Управление отладкой и получение отладочных сообщений посредством
|
||||
`mdbx_setup_debug()`.
|
||||
|
||||
25. Функция `mdbx_env_pgwalk()` для обхода всех страниц БД.
|
||||
|
||||
26. Три мета-страницы вместо двух, что позволяет гарантированно
|
||||
консистентно обновлять слабые контрольные точки фиксации без риска
|
||||
повредить крайнюю сильную точку фиксации.
|
||||
|
||||
27. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
|
||||
> В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
|
||||
> режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
|
||||
> когда при системной аварии есть шанс полного разрушения БД как в LMDB.
|
||||
> Для подробностей смотрите раздел
|
||||
> [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации).
|
||||
|
||||
28. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
|
||||
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
|
||||
|
||||
29. При завершении читающих транзакций, открытые в них DBI-хендлы не
|
||||
закрываются и не теряются при завершении таких транзакций посредством
|
||||
`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда
|
||||
сложно обнаруживаемых ошибок.
|
||||
|
||||
30. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
|
||||
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
|
||||
ОСВОБОЖДАТЬСЯ ЯВНО.
|
||||
>
|
||||
> ## _ВАЖНО_, Обратите внимание!
|
||||
>
|
||||
> Это единственное изменение в API, которое значимо меняет
|
||||
> семантику управления курсорами и может приводить к утечкам
|
||||
> памяти. Следует отметить, что это изменение вынужденно.
|
||||
> Так устраняется неоднозначность с массой тяжких последствий:
|
||||
>
|
||||
> - обращение к уже освобожденной памяти;
|
||||
> - попытки повторного освобождения памяти;
|
||||
> - повреждение памяти и ошибки сегментации.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
## Недостатки и Компромиссы
|
||||
|
||||
1. Единовременно может выполняться не более одной транзакция изменения данных
|
||||
(один писатель). Зато все изменения всегда последовательны, не может быть
|
||||
конфликтов или логических ошибок при откате транзакций.
|
||||
|
||||
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
|
||||
обуславливает относительно большой
|
||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
|
||||
Amplification Factor). Поэтому фиксация изменений на диске может быть
|
||||
достаточно дорогой и являться главным ограничением производительности
|
||||
при интенсивном изменении данных.
|
||||
> В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой
|
||||
> и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором
|
||||
> изменения происходят только в памяти и асинхронно фиксируются на диске
|
||||
> ядром ОС.
|
||||
>
|
||||
> Однако, следует воспринимать это свойство аккуратно и взвешенно.
|
||||
> Например, полная фиксация транзакции в БД с журналом потребует минимум 2
|
||||
> IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В
|
||||
> _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
|
||||
> журналом кол-во IOPS будет меняться в зависимости от файловой системы,
|
||||
> но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
|
||||
> будет расти логарифмически от кол-ва записей/строк в БД (по высоте
|
||||
> b+tree).
|
||||
|
||||
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
|
||||
для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
|
||||
уровне страниц в [B+
|
||||
дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
|
||||
Поэтому изменение данных амортизационно требует копирования Olog(N)
|
||||
страниц, что расходует [пропускную способность оперативной
|
||||
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
|
||||
основным ограничителем производительности в режиме `MAPASYNC`.
|
||||
> Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
|
||||
> Дело в том, что фиксация изменений на диске потребует гораздо более
|
||||
> значительного копирования данных в памяти и массы других затратных операций.
|
||||
> Поэтому обусловленное этим недостатком падение производительности становится
|
||||
> заметным только при отказе от фиксации изменений на диске.
|
||||
> Соответственно, корректнее сказать, что _libmdbx_ позволяет
|
||||
> получить персистентность ценой минимального падения производительности.
|
||||
> Если же нет необходимости оперативно сохранять данные, то логичнее
|
||||
> использовать `std::map`.
|
||||
|
||||
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
|
||||
которая приводит к деградации производительности и переполнению БД.
|
||||
> В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
|
||||
> некомфортной ситуации и устранения её последствий. Подробности ниже.
|
||||
|
||||
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
|
||||
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
|
||||
так и согласованность данных.
|
||||
> Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
|
||||
> Подробности ниже.
|
||||
|
||||
|
||||
#### Проблема долгих чтений
|
||||
*Следует отметить*, что проблема "сборки мусора" так или иначе
|
||||
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
|
||||
и LMDB она проявляется более остро, прежде всего из-за высокой
|
||||
производительности, а также из-за намеренного упрощения внутренних
|
||||
механизмов ради производительности.
|
||||
|
||||
Понимание проблемы требует некоторых пояснений, которые
|
||||
изложены ниже, но могут быть сложны для быстрого восприятия.
|
||||
Поэтому, тезисно:
|
||||
|
||||
* Изменение данных на фоне долгой операции чтения может
|
||||
приводить к исчерпанию места в БД.
|
||||
|
||||
* После чего любая попытка обновить данные будет приводить к
|
||||
ошибке `MAP_FULL` до завершения долгой операции чтения.
|
||||
|
||||
* Характерными примерами долгих чтений являются горячее
|
||||
резервное копирования и отладка клиентского приложения при
|
||||
активной транзакции чтения.
|
||||
|
||||
* В оригинальной _LMDB_ после этого будет наблюдаться
|
||||
устойчивая деградация производительности всех механизмов
|
||||
обратной записи на диск (в I/O контроллере, в гипервизоре,
|
||||
в ядре ОС).
|
||||
|
||||
* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
|
||||
операций, а также режим `LIFO RECLAIM` устраняющий последующую
|
||||
деградацию производительности.
|
||||
|
||||
Операции чтения выполняются в контексте снимка данных (версии
|
||||
БД), который был актуальным на момент старта транзакции чтения. Такой
|
||||
читаемый снимок поддерживается неизменным до завершения операции. В свою
|
||||
очередь, это не позволяет повторно использовать страницы БД в
|
||||
последующих версиях (снимках БД).
|
||||
|
||||
Другими словами, если обновление данных выполняется на фоне долгой
|
||||
операции чтения, то вместо повторного использования "старых" ненужных
|
||||
страниц будут выделяться новые, так как "старые" страницы составляют
|
||||
снимок БД, который еще используется долгой операцией чтения.
|
||||
|
||||
В результате, при интенсивном изменении данных и достаточно длительной
|
||||
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
|
||||
позволит создавать новые снимки/версии БД. Такая ситуация будет
|
||||
сохраняться до завершения операции чтения, которая использует старый
|
||||
снимок данных и препятствует повторному использованию страниц БД.
|
||||
|
||||
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
|
||||
дополнительные страницы, которые были выделены пока переработка старых
|
||||
была невозможна, будут участвовать в цикле выделения/освобождения до
|
||||
конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
|
||||
страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
|
||||
Поэтому увеличение количества циркулирующий страниц, с точки зрения
|
||||
механизмов кэширования и/или обратной записи, выглядит как увеличение
|
||||
рабочего набор данных. Проще говоря, однократное попадание в ситуацию
|
||||
"уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
|
||||
при всех последующих изменениях данных.
|
||||
|
||||
Для устранения описанных проблемы в _libmdbx_ сделаны существенные
|
||||
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
|
||||
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
|
||||
|
||||
Там же приведен пример количественной оценки прироста производительности
|
||||
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
|
||||
при включении `LIFO RECLAIM` в _libmdbx_.
|
||||
|
||||
#### Сохранность данных в режиме асинхронной фиксации
|
||||
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
|
||||
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
|
||||
приложения, ядро ОС сохранит все изменения.
|
||||
|
||||
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
|
||||
может быть сохранена только часть измененных страниц БД. При этом с
|
||||
большой вероятностью может оказаться, что будут сохранены мета-страницы
|
||||
со ссылками на страницы с новыми версиями данных, но не сами новые
|
||||
данные. В этом случае БД будет безвозвратна разрушена, даже если до
|
||||
аварии производилась полная синхронизация данных (посредством
|
||||
`mdbx_env_sync()`).
|
||||
|
||||
В _libmdbx_ эта проблема устранена путем полной переработки
|
||||
пути записи данных:
|
||||
|
||||
* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
|
||||
мета-страницы непосредственно, а поддерживает их теневые копии
|
||||
с переносом изменений после фиксации данных.
|
||||
|
||||
* При завершении транзакций, в зависимости от состояния
|
||||
синхронности данных между диском и оперативной памятью,
|
||||
_libmdbx_ помечает точки фиксации либо как сильные (strong),
|
||||
либо как слабые (weak). Так например, в режиме
|
||||
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
|
||||
слабые, а при явной синхронизации данных - как сильные.
|
||||
|
||||
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
|
||||
Это позволяет выполнять фиксацию транзакций с формированием как
|
||||
сильной, так и слабой точки фиксации, без потери двух предыдущих
|
||||
точек фиксации (из которых одна может быть сильной, а вторая слабой).
|
||||
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
|
||||
сильные и слабые точки фиксации без нарушения соответствующих
|
||||
гарантий в случае неожиданной системной аварии во время фиксации.
|
||||
|
||||
* При открытии БД выполняется автоматический откат к последней
|
||||
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
|
||||
|
||||
Такая гарантия надежности не дается бесплатно. Для сохранности данных,
|
||||
страницы, формирующие крайний снимок с сильной фиксацией, не должны
|
||||
повторно использоваться (перезаписываться) до формирования следующей
|
||||
сильной точки фиксации. Таким образом, крайняя точка фиксации создает
|
||||
описанный выше эффект "долгого чтения". Разница же здесь в том, что при
|
||||
исчерпании свободных страниц ситуация будет автоматически исправлена,
|
||||
посредством записи изменений на диск и формирования новой сильной точки
|
||||
фиксации.
|
||||
|
||||
Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
|
||||
всегда использовать новые страницы до исчерпания места в БД или до
|
||||
явного формирования сильной точки фиксации посредством
|
||||
`mdbx_env_sync()`. При этом суммарный трафик записи на диск будет
|
||||
примерно такой же, как если бы отдельно фиксировалась каждая транзакция.
|
||||
|
||||
В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
|
||||
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
|
||||
когда при системной аварии есть шанс полного разрушения БД как в LMDB.
|
||||
|
||||
В последующих версиях _libmdbx_ будут предусмотрены средства для
|
||||
асинхронной записи данных на диск с автоматическим формированием сильных
|
||||
точек фиксации.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Сравнение производительности
|
||||
============================
|
||||
|
||||
Все представленные ниже данные получены многократным прогоном тестов на
|
||||
ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском
|
||||
SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб.
|
||||
|
||||
Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и
|
||||
сценарии тестирования [доступны на
|
||||
github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Интегральная производительность
|
||||
|
||||
Показана соотнесенная сумма ключевых показателей производительности в трёх
|
||||
бенчмарках:
|
||||
|
||||
- Чтение/Поиск на машине с 4-мя процессорами;
|
||||
|
||||
- Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями
|
||||
(вставка, чтение, обновление, удаление) в режиме **синхронной фиксации**
|
||||
данных (fdatasync при завершении каждой транзакции или аналог);
|
||||
|
||||
- Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями
|
||||
(вставка, чтение, обновление, удаление) в режиме **отложенной фиксации**
|
||||
данных (отложенная запись посредством файловой систем или аналог);
|
||||
|
||||
*Бенчмарк в режиме асинхронной записи не включен по двум причинам:*
|
||||
|
||||
1. Такое сравнение не совсем правомочно, его следует делать с движками
|
||||
ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)).
|
||||
|
||||
2. Превосходство libmdbx становится еще более подавляющим, что мешает
|
||||
восприятию информации.
|
||||
|
||||

|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Масштабируемость чтения
|
||||
|
||||
Для каждого движка показана суммарная производительность при
|
||||
одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на
|
||||
машине с 4-мя физическими процессорами.
|
||||
|
||||

|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Синхронная фиксация
|
||||
|
||||
- Линейная шкала слева и темные прямоугольники соответствуют количеству
|
||||
транзакций в секунду, усредненному за всё время теста.
|
||||
|
||||
- Логарифмическая шкала справа и желтые интервальные отрезки
|
||||
соответствуют времени выполнения транзакций. При этом каждый отрезок
|
||||
показывает минимальное и максимальное время, затраченное на выполнение
|
||||
транзакций, а крестиком отмечено среднеквадратичное значение.
|
||||
|
||||
Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на
|
||||
диске. При этом требуется гарантия, что при аварийном выключении питания
|
||||
(или другом подобном сбое) все данные будут консистентны и полностью
|
||||
соответствовать последней завершенной транзакции. В _libmdbx_ в этом
|
||||
режиме при фиксации каждой транзакции выполняется системный вызов
|
||||
[fdatasync](https://linux.die.net/man/2/fdatasync).
|
||||
|
||||
В каждой транзакции выполняется комбинированная CRUD-операция (две
|
||||
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
|
||||
на пустой базе, а при завершении, в результате выполняемых действий, в
|
||||
базе насчитывается 10.000 небольших key-value записей.
|
||||
|
||||

|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Отложенная фиксация
|
||||
|
||||
- Линейная шкала слева и темные прямоугольники соответствуют количеству
|
||||
транзакций в секунду, усредненному за всё время теста.
|
||||
|
||||
- Логарифмическая шкала справа и желтые интервальные отрезки
|
||||
соответствуют времени выполнения транзакций. При этом каждый отрезок
|
||||
показывает минимальное и максимальное время, затраченное на выполнение
|
||||
транзакций, а крестиком отмечено среднеквадратичное значение.
|
||||
|
||||
Выполняется **100.000 транзакций в режиме отложенной фиксации данных**
|
||||
на диске. При этом требуется гарантия, что при аварийном выключении
|
||||
питания (или другом подобном сбое) все данные будут консистентны на
|
||||
момент завершения одной из транзакций, но допускается потеря изменений
|
||||
из некоторого количества последних транзакций, что для многих движков
|
||||
предполагает включение
|
||||
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead
|
||||
logging) либо журнала транзакций, который в свою очередь опирается на
|
||||
гарантию упорядоченности данных в журналируемой файловой системе.
|
||||
_libmdbx_ при этом не ведет WAL, а передает весь контроль файловой
|
||||
системе и ядру ОС.
|
||||
|
||||
В каждой транзакции выполняется комбинированная CRUD-операция (две
|
||||
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
|
||||
на пустой базе, а при завершении, в результате выполняемых действий, в
|
||||
базе насчитывается 100.000 небольших key-value записей.
|
||||
|
||||

|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Асинхронная фиксация
|
||||
|
||||
- Линейная шкала слева и темные прямоугольники соответствуют количеству
|
||||
транзакций в секунду, усредненному за всё время теста.
|
||||
|
||||
- Логарифмическая шкала справа и желтые интервальные отрезки
|
||||
соответствуют времени выполнения транзакций. При этом каждый отрезок
|
||||
показывает минимальное и максимальное время, затраченное на выполнение
|
||||
транзакций, а крестиком отмечено среднеквадратичное значение.
|
||||
|
||||
Выполняется **1.000.000 транзакций в режиме асинхронной фиксации
|
||||
данных** на диске. При этом требуется гарантия, что при аварийном
|
||||
выключении питания (или другом подобном сбое) все данные будут
|
||||
консистентны на момент завершения одной из транзакций, но допускается
|
||||
потеря изменений из значительного количества последних транзакций. Во
|
||||
всех движках при этом включался режим предполагающий минимальную
|
||||
нагрузку на диск по записи, и соответственно минимальную гарантию
|
||||
сохранности данных. В _libmdbx_ при этом используется режим асинхронной
|
||||
записи измененных страниц на диск посредством ядра ОС и системного
|
||||
вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync).
|
||||
|
||||
В каждой транзакции выполняется комбинированная CRUD-операция (две
|
||||
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
|
||||
на пустой базе, а при завершении, в результате выполняемых действий, в
|
||||
базе насчитывается 10.000 небольших key-value записей.
|
||||
|
||||

|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Потребление ресурсов
|
||||
|
||||
Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в
|
||||
режиме отложенной фиксации:
|
||||
|
||||
- суммарное количество операций ввода-вывода (IOPS), как записи, так и
|
||||
чтения.
|
||||
|
||||
- суммарное затраченное время процессора, как в режиме пользовательских
|
||||
процессов, так и в режиме ядра ОС.
|
||||
|
||||
- использованное место на диске при завершении теста, после закрытия БД
|
||||
из тестирующего процесса, но без ожидания всех внутренних операций
|
||||
обслуживания (компактификации LSM и т.п.).
|
||||
|
||||
Движок _ForestDB_ был исключен при оформлении результатов, так как
|
||||
относительно конкурентов многократно превысил потребление каждого из
|
||||
ресурсов (потратил процессорное время на генерацию IOPS для заполнения
|
||||
диска), что не позволяло наглядно сравнить показатели остальных движков
|
||||
на одной диаграмме.
|
||||
|
||||
Все данные собирались посредством системного вызова
|
||||
[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и
|
||||
сканированием директорий с данными.
|
||||
|
||||

|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
```
|
||||
$ objdump -f -h -j .text libmdbx.so
|
||||
|
||||
libmdbx.so: file format elf64-x86-64
|
||||
architecture: i386:x86-64, flags 0x00000150:
|
||||
HAS_SYMS, DYNAMIC, D_PAGED
|
||||
start address 0x000030e0
|
||||
|
||||
Sections:
|
||||
Idx Name Size VMA LMA File off Algn
|
||||
11 .text 00014d84 00000000000030e0 00000000000030e0 000030e0 2**4
|
||||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||||
|
||||
```
|
44
TODO.md
Normal file
44
TODO.md
Normal file
@ -0,0 +1,44 @@
|
||||
TODO
|
||||
----
|
||||
|
||||
- [SWIG](https://www.swig.org/).
|
||||
- Параллельная lto-сборка с устранением предупреждений.
|
||||
- Интеграция c DTrace и аналогами.
|
||||
- Новый стиль обработки ошибок с записью "трассы" и причин.
|
||||
- Формирование отладочной информации посредством gdb.
|
||||
- Поддержка WASM.
|
||||
- Ранняя/не-отложенная очистка GC.
|
||||
- Явная и автоматические уплотнение/дефрагментация.
|
||||
- Нелинейная обработка GC.
|
||||
- Перевести курсоры на двусвязный список вместо односвязного.
|
||||
- Внутри `txn_renew()` вынести проверку когерентности mmap за/после изменение размера.
|
||||
- [Migration guide from LMDB to MDBX](https://libmdbx.dqdkfa.ru/dead-github/issues/199).
|
||||
- [Support for RAW devices](https://libmdbx.dqdkfa.ru/dead-github/issues/124).
|
||||
- [Support MessagePack for Keys & Values](https://libmdbx.dqdkfa.ru/dead-github/issues/115).
|
||||
- Packages for [Astra Linux](https://astralinux.ru/), [ALT Linux](https://www.altlinux.org/), [ROSA Linux](https://www.rosalinux.ru/), etc.
|
||||
|
||||
Done
|
||||
----
|
||||
|
||||
- Рефакторинг gc-get/gc-put c переходом на "интервальные" списки.
|
||||
- [Engage new terminology](https://libmdbx.dqdkfa.ru/dead-github/issues/137).
|
||||
- [More flexible support of asynchronous runtime/framework(s)](https://libmdbx.dqdkfa.ru/dead-github/issues/200).
|
||||
- [Move most of `mdbx_chk` functional to the library API](https://libmdbx.dqdkfa.ru/dead-github/issues/204).
|
||||
- [Simple careful mode for working with corrupted DB](https://libmdbx.dqdkfa.ru/dead-github/issues/223).
|
||||
- [Engage an "overlapped I/O" on Windows](https://libmdbx.dqdkfa.ru/dead-github/issues/224).
|
||||
- [Large/Overflow pages accounting for dirty-room](https://libmdbx.dqdkfa.ru/dead-github/issues/192).
|
||||
- [Get rid of dirty-pages list in MDBX_WRITEMAP mode](https://libmdbx.dqdkfa.ru/dead-github/issues/193).
|
||||
|
||||
Cancelled
|
||||
--------
|
||||
|
||||
- [Replace SRW-lock on Windows to allow shrink DB with `MDBX_NOSTICKYTHREADS` option](https://libmdbx.dqdkfa.ru/dead-github/issues/210).
|
||||
Доработка не может быть реализована, так как замена SRW-блокировки
|
||||
лишает лишь предварительную проблему, но не главную. На Windows
|
||||
уменьшение размера отображенного в память файла не поддерживается ядром
|
||||
ОС. Для этого необходимо снять отображение, изменить размер файла и
|
||||
затем отобразить обратно. В свою очередь, для это необходимо
|
||||
приостановить работающие с БД потоки выполняющие транзакции чтения, либо
|
||||
готовые к такому выполнению. Но в режиме MDBX_NOSTICKYTHREADS нет
|
||||
возможности отслеживать работающие с БД потоки, а приостановка всех
|
||||
потоков неприемлема для большинства приложений.
|
54
appveyor.yml
54
appveyor.yml
@ -1,54 +0,0 @@
|
||||
version: 0.2.0.{build}
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
TOOLSET: v141
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
TOOLSET: v140
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013
|
||||
TOOLSET: v120
|
||||
|
||||
branches:
|
||||
except:
|
||||
- coverity_scan
|
||||
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
#- ARM
|
||||
|
||||
build_script:
|
||||
- ps: >
|
||||
msbuild "C:\projects\libmdbx\mdbx.sln" /verbosity:minimal
|
||||
/logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
/property:PlatformToolset=$env:TOOLSET
|
||||
/property:Configuration=$env:CONFIGURATION
|
||||
/property:Platform=$env:PLATFORM
|
||||
|
||||
test_script:
|
||||
- ps: |
|
||||
if (($env:PLATFORM -eq "x86") -and (Test-Path "C:\projects\libmdbx\Win32\$env:CONFIGURATION\mdbx_test.exe" -PathType Leaf)) {
|
||||
$mdbx_test = "C:\projects\libmdbx\Win32\$env:CONFIGURATION\mdbx_test.exe"
|
||||
$mdbx_chk = "C:\projects\libmdbx\Win32\$env:CONFIGURATION\mdbx_chk.exe"
|
||||
} elseif (($env:PLATFORM -ne "ARM") -and ($env:PLATFORM -ne "ARM64")) {
|
||||
$mdbx_test = "C:\projects\libmdbx\$env:PLATFORM\$env:CONFIGURATION\mdbx_test.exe"
|
||||
$mdbx_chk = "C:\projects\libmdbx\$env:PLATFORM\$env:CONFIGURATION\mdbx_chk.exe"
|
||||
} else {
|
||||
$mdbx_test = ""
|
||||
$mdbx_chk = ""
|
||||
}
|
||||
|
||||
if ($mdbx_test -ne "") {
|
||||
& "$mdbx_test" --pathname=test.db --dont-cleanup-after basic | Tee-Object -file test.log | Select-Object -last 42
|
||||
& "$mdbx_chk" -nvv test.db | Tee-Object -file chk.log | Select-Object -last 42
|
||||
}
|
||||
|
||||
on_failure:
|
||||
- ps: Push-AppveyorArtifact test.log
|
||||
- ps: Push-AppveyorArtifact test.db
|
||||
- ps: Push-AppveyorArtifact chk.log
|
1221
cmake/compiler.cmake
Normal file
1221
cmake/compiler.cmake
Normal file
File diff suppressed because it is too large
Load Diff
58
cmake/profile.cmake
Normal file
58
cmake/profile.cmake
Normal file
@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2012-2025 Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> ###############################################
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.8.2)
|
||||
cmake_minimum_required(VERSION 3.0.2)
|
||||
elseif(CMAKE_VERSION VERSION_LESS 3.12)
|
||||
cmake_minimum_required(VERSION 3.8.2)
|
||||
else()
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
endif()
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION ${CMAKE_MINIMUM_REQUIRED_VERSION})
|
||||
|
||||
unset(MEMCHECK_OPTION_NAME)
|
||||
if(NOT DEFINED ENABLE_MEMCHECK)
|
||||
if(DEFINED MDBX_USE_VALGRIND)
|
||||
set(MEMCHECK_OPTION_NAME "MDBX_USE_VALGRIND")
|
||||
elseif(DEFINED ENABLE_VALGRIND)
|
||||
set(MEMCHECK_OPTION_NAME "ENABLE_VALGRIND")
|
||||
else()
|
||||
set(MEMCHECK_OPTION_NAME "ENABLE_MEMCHECK")
|
||||
endif()
|
||||
if(MEMCHECK_OPTION_NAME STREQUAL "ENABLE_MEMCHECK")
|
||||
option(ENABLE_MEMCHECK "Enable integration with valgrind, a memory analyzing tool" OFF)
|
||||
elseif(${MEMCHECK_OPTION_NAME})
|
||||
set(ENABLE_MEMCHECK ON)
|
||||
else()
|
||||
set(ENABLE_MEMCHECK OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(gcov __gcov_flush "" HAVE_GCOV)
|
||||
|
||||
option(ENABLE_GCOV "Enable integration with gcov, a code coverage program" OFF)
|
||||
|
||||
option(ENABLE_GPROF "Enable integration with gprof, a performance analyzing tool" OFF)
|
||||
|
||||
option(ENABLE_ASAN "Enable AddressSanitizer, a fast memory error detector based on compiler instrumentation" OFF)
|
||||
|
||||
option(ENABLE_UBSAN
|
||||
"Enable UndefinedBehaviorSanitizer, a fast undefined behavior detector based on compiler instrumentation" OFF)
|
||||
|
||||
if(ENABLE_MEMCHECK)
|
||||
if(CMAKE_CXX_COMPILER_LOADED)
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx(valgrind/memcheck.h HAVE_VALGRIND_MEMCHECK_H)
|
||||
else()
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(valgrind/memcheck.h HAVE_VALGRIND_MEMCHECK_H)
|
||||
endif()
|
||||
if(NOT HAVE_VALGRIND_MEMCHECK_H)
|
||||
message(FATAL_ERROR "${MEMCHECK_OPTION_NAME} option is set but valgrind/memcheck.h is not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
cmake_policy(POP)
|
524
cmake/utils.cmake
Normal file
524
cmake/utils.cmake
Normal file
@ -0,0 +1,524 @@
|
||||
# Copyright (c) 2012-2025 Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> ###############################################
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.8.2)
|
||||
cmake_minimum_required(VERSION 3.0.2)
|
||||
elseif(CMAKE_VERSION VERSION_LESS 3.12)
|
||||
cmake_minimum_required(VERSION 3.8.2)
|
||||
else()
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
endif()
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(VERSION ${CMAKE_MINIMUM_REQUIRED_VERSION})
|
||||
|
||||
macro(add_option HIVE NAME DESCRIPTION DEFAULT)
|
||||
list(APPEND ${HIVE}_BUILD_OPTIONS ${HIVE}_${NAME})
|
||||
if(NOT ${DEFAULT} STREQUAL "AUTO")
|
||||
option(${HIVE}_${NAME} "${DESCRIPTION}" ${DEFAULT})
|
||||
elseif(NOT DEFINED ${HIVE}_${NAME})
|
||||
set(${HIVE}_${NAME}_AUTO ON)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(set_if_undefined VARNAME)
|
||||
if(NOT DEFINED "${VARNAME}")
|
||||
set("${VARNAME}" ${ARGN})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(add_compile_flags languages)
|
||||
foreach(_lang ${languages})
|
||||
string(REPLACE ";" " " _flags "${ARGN}")
|
||||
if(CMAKE_CXX_COMPILER_LOADED AND _lang STREQUAL "CXX")
|
||||
set("${_lang}_FLAGS" "${${_lang}_FLAGS} ${_flags}")
|
||||
endif()
|
||||
if(CMAKE_C_COMPILER_LOADED AND _lang STREQUAL "C")
|
||||
set("${_lang}_FLAGS" "${${_lang}_FLAGS} ${_flags}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_lang)
|
||||
unset(_flags)
|
||||
endmacro(add_compile_flags)
|
||||
|
||||
macro(remove_flag varname flag)
|
||||
string(REGEX REPLACE "^(.*)( ${flag} )(.*)$" "\\1 \\3" ${varname} ${${varname}})
|
||||
string(REGEX REPLACE "^((.+ )*)(${flag})(( .+)*)$" "\\1\\4" ${varname} ${${varname}})
|
||||
endmacro(remove_flag)
|
||||
|
||||
macro(remove_compile_flag languages flag)
|
||||
foreach(_lang ${languages})
|
||||
if(CMAKE_CXX_COMPILER_LOADED AND _lang STREQUAL "CXX")
|
||||
remove_flag(${_lang}_FLAGS ${flag})
|
||||
endif()
|
||||
if(CMAKE_C_COMPILER_LOADED AND _lang STREQUAL "C")
|
||||
remove_flag(${_lang}_FLAGS ${flag})
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_lang)
|
||||
endmacro(remove_compile_flag)
|
||||
|
||||
macro(set_source_files_compile_flags)
|
||||
foreach(file ${ARGN})
|
||||
get_filename_component(_file_ext ${file} EXT)
|
||||
set(_lang "")
|
||||
if("${_file_ext}" STREQUAL ".m")
|
||||
set(_lang OBJC)
|
||||
# CMake believes that Objective C is a flavor of C++, not C, and uses g++ compiler for .m files. LANGUAGE property
|
||||
# forces CMake to use CC for ${file}
|
||||
set_source_files_properties(${file} PROPERTIES LANGUAGE C)
|
||||
elseif("${_file_ext}" STREQUAL ".mm")
|
||||
set(_lang OBJCXX)
|
||||
endif()
|
||||
|
||||
if(_lang)
|
||||
get_source_file_property(_flags ${file} COMPILE_FLAGS)
|
||||
if("${_flags}" STREQUAL "NOTFOUND")
|
||||
set(_flags "${CMAKE_${_lang}_FLAGS}")
|
||||
else()
|
||||
set(_flags "${_flags} ${CMAKE_${_lang}_FLAGS}")
|
||||
endif()
|
||||
# message(STATUS "Set (${file} ${_flags}")
|
||||
set_source_files_properties(${file} PROPERTIES COMPILE_FLAGS "${_flags}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_file_ext)
|
||||
unset(_lang)
|
||||
endmacro(set_source_files_compile_flags)
|
||||
|
||||
macro(semver_parse str)
|
||||
set(_semver_ok FALSE)
|
||||
set(_semver_err "")
|
||||
set(_semver_major 0)
|
||||
set(_semver_minor 0)
|
||||
set(_semver_patch 0)
|
||||
set(_semver_tweak_withdot "")
|
||||
set(_semver_tweak "")
|
||||
set(_semver_extra "")
|
||||
set(_semver_prerelease_withdash "")
|
||||
set(_semver_prerelease "")
|
||||
set(_semver_buildmetadata_withplus "")
|
||||
set(_semver_buildmetadata "")
|
||||
if("${str}" MATCHES
|
||||
"^v?(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))?([-+]-*[0-9a-zA-Z]+.*)?$")
|
||||
set(_semver_major ${CMAKE_MATCH_1})
|
||||
set(_semver_minor ${CMAKE_MATCH_2})
|
||||
set(_semver_patch ${CMAKE_MATCH_3})
|
||||
set(_semver_tweak_withdot ${CMAKE_MATCH_4})
|
||||
set(_semver_tweak ${CMAKE_MATCH_5})
|
||||
set(_semver_extra "${CMAKE_MATCH_6}")
|
||||
if("${_semver_extra}" STREQUAL "")
|
||||
set(_semver_ok TRUE)
|
||||
elseif("${_semver_extra}" MATCHES "^([.-][a-zA-Z0-9-]+)*(\\+[^+]+)?$")
|
||||
set(_semver_prerelease_withdash "${CMAKE_MATCH_1}")
|
||||
if(NOT "${_semver_prerelease_withdash}" STREQUAL "")
|
||||
string(SUBSTRING "${_semver_prerelease_withdash}" 1 -1 _semver_prerelease)
|
||||
endif()
|
||||
set(_semver_buildmetadata_withplus "${CMAKE_MATCH_2}")
|
||||
if(NOT "${_semver_buildmetadata_withplus}" STREQUAL "")
|
||||
string(SUBSTRING "${_semver_buildmetadata_withplus}" 1 -1 _semver_buildmetadata)
|
||||
endif()
|
||||
set(_semver_ok TRUE)
|
||||
else()
|
||||
set(_semver_err
|
||||
"Поля prerelease и/или buildmetadata (строка `-foo+bar` в составе `0.0.0[.0][-foo][+bar]`) не соответствуют SemVer-спецификации"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
set(_semver_err "Версионная отметка в целом не соответствует шаблону `0.0.0[.0][-foo][+bar]` SemVer-спецификации")
|
||||
endif()
|
||||
endmacro(semver_parse)
|
||||
|
||||
function(_semver_parse_probe str expect)
|
||||
semver_parse(${str})
|
||||
if(expect AND NOT _semver_ok)
|
||||
message(FATAL_ERROR "semver_parse(${str}) expect SUCCESS, got ${_semver_ok}: ${_semver_err}")
|
||||
elseif(NOT expect AND _semver_ok)
|
||||
message(FATAL_ERROR "semver_parse(${str}) expect FAIL, got ${_semver_ok}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(semver_parse_selfcheck)
|
||||
_semver_parse_probe("0.0.4" TRUE)
|
||||
_semver_parse_probe("v1.2.3" TRUE)
|
||||
_semver_parse_probe("10.20.30" TRUE)
|
||||
_semver_parse_probe("10.20.30.42" TRUE)
|
||||
_semver_parse_probe("1.1.2-prerelease+meta" TRUE)
|
||||
_semver_parse_probe("1.1.2+meta" TRUE)
|
||||
_semver_parse_probe("1.1.2+meta-valid" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha" TRUE)
|
||||
_semver_parse_probe("1.0.0-beta" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha.beta" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha.beta.1" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha.1" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha0.valid" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha.0valid" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay" TRUE)
|
||||
_semver_parse_probe("1.0.0-rc.1+build.1" TRUE)
|
||||
_semver_parse_probe("2.0.0-rc.1+build.123" TRUE)
|
||||
_semver_parse_probe("1.2.3-beta" TRUE)
|
||||
_semver_parse_probe("10.2.3-DEV-SNAPSHOT" TRUE)
|
||||
_semver_parse_probe("1.2.3-SNAPSHOT-123" TRUE)
|
||||
_semver_parse_probe("1.0.0" TRUE)
|
||||
_semver_parse_probe("2.0.0" TRUE)
|
||||
_semver_parse_probe("1.1.7" TRUE)
|
||||
_semver_parse_probe("2.0.0+build.1848" TRUE)
|
||||
_semver_parse_probe("2.0.1-alpha.1227" TRUE)
|
||||
_semver_parse_probe("1.0.0-alpha+beta" TRUE)
|
||||
_semver_parse_probe("1.2.3----RC-SNAPSHOT.12.9.1--.12+788" TRUE)
|
||||
_semver_parse_probe("1.2.3----R-S.12.9.1--.12+meta" TRUE)
|
||||
_semver_parse_probe("1.2.3----RC-SNAPSHOT.12.9.1--.12" TRUE)
|
||||
_semver_parse_probe("1.0.0+0.build.1-rc.10000aaa-kk-0.1" TRUE)
|
||||
_semver_parse_probe("99999999999999999999999.999999999999999999.99999999999999999" TRUE)
|
||||
_semver_parse_probe("v1.0.0-0A.is.legal" TRUE)
|
||||
|
||||
_semver_parse_probe("1" FALSE)
|
||||
_semver_parse_probe("1.2" FALSE)
|
||||
# _semver_parse_probe("1.2.3-0123" FALSE) _semver_parse_probe("1.2.3-0123.0123" FALSE)
|
||||
_semver_parse_probe("1.1.2+.123" FALSE)
|
||||
_semver_parse_probe("+invalid" FALSE)
|
||||
_semver_parse_probe("-invalid" FALSE)
|
||||
_semver_parse_probe("-invalid+invalid" FALSE)
|
||||
_semver_parse_probe("-invalid.01" FALSE)
|
||||
_semver_parse_probe("alpha" FALSE)
|
||||
_semver_parse_probe("alpha.beta" FALSE)
|
||||
_semver_parse_probe("alpha.beta.1" FALSE)
|
||||
_semver_parse_probe("alpha.1" FALSE)
|
||||
_semver_parse_probe("alpha+beta" FALSE)
|
||||
_semver_parse_probe("alpha_beta" FALSE)
|
||||
_semver_parse_probe("alpha." FALSE)
|
||||
_semver_parse_probe("alpha.." FALSE)
|
||||
_semver_parse_probe("beta" FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha_beta" FALSE)
|
||||
_semver_parse_probe("-alpha." FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha.." FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha..1" FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha...1" FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha....1" FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha.....1" FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha......1" FALSE)
|
||||
_semver_parse_probe("1.0.0-alpha.......1" FALSE)
|
||||
_semver_parse_probe("01.1.1" FALSE)
|
||||
_semver_parse_probe("1.01.1" FALSE)
|
||||
_semver_parse_probe("1.1.01" FALSE)
|
||||
_semver_parse_probe("1.2" FALSE)
|
||||
_semver_parse_probe("1.2.3.DEV" FALSE)
|
||||
_semver_parse_probe("1.2-SNAPSHOT" FALSE)
|
||||
_semver_parse_probe("1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788" FALSE)
|
||||
_semver_parse_probe("1.2-RC-SNAPSHOT" FALSE)
|
||||
_semver_parse_probe("-1.0.3-gamma+b7718" FALSE)
|
||||
_semver_parse_probe("+justmeta" FALSE)
|
||||
_semver_parse_probe("9.8.7+meta+meta" FALSE)
|
||||
_semver_parse_probe("9.8.7-whatever+meta+meta" FALSE)
|
||||
_semver_parse_probe(
|
||||
"99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12"
|
||||
FALSE)
|
||||
endfunction()
|
||||
|
||||
macro(git_get_versioninfo source_root_directory)
|
||||
set(_git_describe "")
|
||||
set(_git_timestamp "")
|
||||
set(_git_tree "")
|
||||
set(_git_commit "")
|
||||
set(_git_last_vtag "")
|
||||
set(_git_trailing_commits 0)
|
||||
set(_git_is_dirty FALSE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT} show --no-patch --format=%cI HEAD
|
||||
OUTPUT_VARIABLE _git_timestamp
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_timestamp}" STREQUAL "%cI")
|
||||
execute_process(
|
||||
COMMAND ${GIT} show --no-patch --format=%ci HEAD
|
||||
OUTPUT_VARIABLE _git_timestamp
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_timestamp}" STREQUAL "%ci")
|
||||
message(FATAL_ERROR "Please install latest version of git (`show --no-patch --format=%cI HEAD` failed)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT} show --no-patch --format=%T HEAD
|
||||
OUTPUT_VARIABLE _git_tree
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_tree}" STREQUAL "")
|
||||
message(FATAL_ERROR "Please install latest version of git (`show --no-patch --format=%T HEAD` failed)")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT} show --no-patch --format=%H HEAD
|
||||
OUTPUT_VARIABLE _git_commit
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_commit}" STREQUAL "")
|
||||
message(FATAL_ERROR "Please install latest version of git (`show --no-patch --format=%H HEAD` failed)")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT} status --untracked-files=no --porcelain
|
||||
OUTPUT_VARIABLE _git_status
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc)
|
||||
message(FATAL_ERROR "Please install latest version of git (`status --untracked-files=no --porcelain` failed)")
|
||||
endif()
|
||||
if(NOT "${_git_status}" STREQUAL "")
|
||||
set(_git_commit "DIRTY-${_git_commit}")
|
||||
set(_git_is_dirty TRUE)
|
||||
endif()
|
||||
unset(_git_status)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT} describe --tags --abbrev=0 "--match=v[0-9]*"
|
||||
OUTPUT_VARIABLE _git_last_vtag
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_last_vtag}" STREQUAL "")
|
||||
execute_process(
|
||||
COMMAND ${GIT} tag
|
||||
OUTPUT_VARIABLE _git_tags_dump
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
execute_process(
|
||||
COMMAND ${GIT} rev-list --count --no-merges --remove-empty HEAD
|
||||
OUTPUT_VARIABLE _git_whole_count
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Please install latest version of git (`git rev-list --count --no-merges --remove-empty HEAD` failed)")
|
||||
endif()
|
||||
if(_git_whole_count GREATER 42 AND "${_git_tags_dump}" STREQUAL "")
|
||||
message(FATAL_ERROR "Please fetch tags (`describe --tags --abbrev=0 --match=v[0-9]*` failed)")
|
||||
else()
|
||||
message(NOTICE "Falling back to version `0.0.0` (have you made an initial release?")
|
||||
endif()
|
||||
set(_git_last_vtag "0.0.0")
|
||||
set(_git_trailing_commits ${_git_whole_count})
|
||||
execute_process(
|
||||
COMMAND ${GIT} describe --tags --dirty --long --always
|
||||
OUTPUT_VARIABLE _git_describe
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_describe}" STREQUAL "")
|
||||
execute_process(
|
||||
COMMAND ${GIT} describe --tags --all --dirty --long --always
|
||||
OUTPUT_VARIABLE _git_describe
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_describe}" STREQUAL "")
|
||||
message(FATAL_ERROR "Please install latest version of git (`describe --tags --all --long` failed)")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND ${GIT} describe --tags --dirty --long "--match=v[0-9]*"
|
||||
OUTPUT_VARIABLE _git_describe
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_describe}" STREQUAL "")
|
||||
message(FATAL_ERROR "Please install latest version of git (`describe --tags --long --match=v[0-9]*`)")
|
||||
endif()
|
||||
execute_process(
|
||||
COMMAND ${GIT} rev-list --count "${_git_last_vtag}..HEAD"
|
||||
OUTPUT_VARIABLE _git_trailing_commits
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_trailing_commits}" STREQUAL "")
|
||||
message(FATAL_ERROR "Please install latest version of git (`rev-list --count ${_git_last_vtag}..HEAD` failed)")
|
||||
endif()
|
||||
endif()
|
||||
endmacro(git_get_versioninfo)
|
||||
|
||||
macro(semver_provide name source_root_directory build_directory_for_json_output build_metadata parent_scope)
|
||||
set(_semver "")
|
||||
set(_git_describe "")
|
||||
set(_git_timestamp "")
|
||||
set(_git_tree "")
|
||||
set(_git_commit "")
|
||||
set(_version_from "")
|
||||
set(_git_root FALSE)
|
||||
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(
|
||||
COMMAND ${GIT} rev-parse --show-toplevel
|
||||
OUTPUT_VARIABLE _git_root
|
||||
ERROR_VARIABLE _git_root_error
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
WORKING_DIRECTORY ${source_root_directory}
|
||||
RESULT_VARIABLE _rc)
|
||||
if(_rc OR "${_git_root}" STREQUAL "")
|
||||
if(EXISTS "${source_root_directory}/.git")
|
||||
message(ERROR "`git rev-parse --show-toplevel` failed '${_git_root_error}'")
|
||||
else()
|
||||
message(VERBOSE "`git rev-parse --show-toplevel` failed '${_git_root_error}'")
|
||||
endif()
|
||||
else()
|
||||
set(_source_root "${source_root_directory}")
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.19)
|
||||
file(REAL_PATH "${_git_root}" _git_root)
|
||||
file(REAL_PATH "${_source_root}" _source_root)
|
||||
endif()
|
||||
if(_source_root STREQUAL _git_root AND EXISTS "${_git_root}/VERSION.json")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Несколько источников информации о версии, допустим только один из: репозиторий git, либо файл VERSION.json"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EXISTS "${source_root_directory}/VERSION.json")
|
||||
set(_version_from "${source_root_directory}/VERSION.json")
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.19)
|
||||
message(FATAL_ERROR "Требуется CMake версии >= 3.19 для чтения VERSION.json")
|
||||
endif()
|
||||
file(
|
||||
STRINGS "${_version_from}" _versioninfo_json NEWLINE_CONSUME
|
||||
LIMIT_COUNT 9
|
||||
LIMIT_INPUT 999
|
||||
ENCODING UTF-8)
|
||||
string(JSON _git_describe GET ${_versioninfo_json} git_describe)
|
||||
string(JSON _git_timestamp GET "${_versioninfo_json}" "git_timestamp")
|
||||
string(JSON _git_tree GET "${_versioninfo_json}" "git_tree")
|
||||
string(JSON _git_commit GET "${_versioninfo_json}" "git_commit")
|
||||
string(JSON _semver GET "${_versioninfo_json}" "semver")
|
||||
unset(_json_object)
|
||||
if(NOT _semver)
|
||||
message(FATAL_ERROR "Unable to retrieve ${name} version from \"${_version_from}\" file.")
|
||||
endif()
|
||||
semver_parse("${_semver}")
|
||||
if(NOT _semver_ok)
|
||||
message(FATAL_ERROR "SemVer `${_semver}` from ${_version_from}: ${_semver_err}")
|
||||
endif()
|
||||
elseif(_git_root AND _source_root STREQUAL _git_root)
|
||||
set(_version_from git)
|
||||
git_get_versioninfo(${source_root_directory})
|
||||
semver_parse(${_git_last_vtag})
|
||||
if(NOT _semver_ok)
|
||||
message(FATAL_ERROR "Git tag `${_git_last_vtag}`: ${_semver_err}")
|
||||
endif()
|
||||
if(_git_trailing_commits GREATER 0 AND "${_semver_tweak}" STREQUAL "")
|
||||
set(_semver_tweak ${_git_trailing_commits})
|
||||
endif()
|
||||
|
||||
elseif(GIT)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Нет источника информации о версии (${source_root_directory}), требуется один из: репозиторий git, либо VERSION.json"
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR "Требуется git для получения информации о версии")
|
||||
endif()
|
||||
|
||||
if(NOT _git_describe
|
||||
OR NOT _git_timestamp
|
||||
OR NOT _git_tree
|
||||
OR NOT _git_commit
|
||||
OR "${_semver_major}" STREQUAL ""
|
||||
OR "${_semver_minor}" STREQUAL ""
|
||||
OR "${_semver_patch}" STREQUAL "")
|
||||
message(ERROR "Unable to retrieve ${name} version from ${_version_from}.")
|
||||
endif()
|
||||
|
||||
set(_semver "${_semver_major}.${_semver_minor}.${_semver_patch}")
|
||||
if("${_semver_tweak}" STREQUAL "")
|
||||
set(_semver_tweak 0)
|
||||
elseif(_semver_tweak GREATER 0)
|
||||
string(APPEND _semver ".${_semver_tweak}")
|
||||
endif()
|
||||
if(NOT "${_semver_prerelease}" STREQUAL "")
|
||||
string(APPEND _semver "-${_semver_prerelease}")
|
||||
endif()
|
||||
if(_git_is_dirty)
|
||||
string(APPEND _semver "-DIRTY")
|
||||
endif()
|
||||
|
||||
set(_semver_complete "${_semver}")
|
||||
if(NOT "${build_metadata}" STREQUAL "")
|
||||
string(APPEND _semver_complete "+${build_metadata}")
|
||||
endif()
|
||||
|
||||
set(${name}_VERSION "${_semver_complete}")
|
||||
set(${name}_VERSION_PURE "${_semver}")
|
||||
set(${name}_VERSION_MAJOR ${_semver_major})
|
||||
set(${name}_VERSION_MINOR ${_semver_minor})
|
||||
set(${name}_VERSION_PATCH ${_semver_patch})
|
||||
set(${name}_VERSION_TWEAK ${_semver_tweak})
|
||||
set(${name}_VERSION_PRERELEASE "${_semver_prerelease}")
|
||||
set(${name}_GIT_DESCRIBE "${_git_describe}")
|
||||
set(${name}_GIT_TIMESTAMP "${_git_timestamp}")
|
||||
set(${name}_GIT_TREE "${_git_tree}")
|
||||
set(${name}_GIT_COMMIT "${_git_commit}")
|
||||
|
||||
if(${parent_scope})
|
||||
set(${name}_VERSION
|
||||
"${_semver_complete}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_VERSION_PURE
|
||||
"${_semver}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_VERSION_MAJOR
|
||||
${_semver_major}
|
||||
PARENT_SCOPE)
|
||||
set(${name}_VERSION_MINOR
|
||||
${_semver_minor}
|
||||
PARENT_SCOPE)
|
||||
set(${name}_VERSION_PATCH
|
||||
${_semver_patch}
|
||||
PARENT_SCOPE)
|
||||
set(${name}_VERSION_TWEAK
|
||||
"${_semver_tweak}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_VERSION_PRERELEASE
|
||||
"${_semver_prerelease}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_GIT_DESCRIBE
|
||||
"${_git_describe}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_GIT_TIMESTAMP
|
||||
"${_git_timestamp}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_GIT_TREE
|
||||
"${_git_tree}"
|
||||
PARENT_SCOPE)
|
||||
set(${name}_GIT_COMMIT
|
||||
"${_git_commit}"
|
||||
PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if(_version_from STREQUAL "git")
|
||||
string(
|
||||
CONFIGURE
|
||||
"{
|
||||
\"git_describe\" : \"@_git_describe@\",
|
||||
\"git_timestamp\" : \"@_git_timestamp@\",
|
||||
\"git_tree\" : \"@_git_tree@\",
|
||||
\"git_commit\" : \"@_git_commit@\",
|
||||
\"semver\" : \"@_semver@\"\n}"
|
||||
_versioninfo_json
|
||||
@ONLY ESCAPE_QUOTES)
|
||||
file(WRITE "${build_directory_for_json_output}/VERSION.json" "${_versioninfo_json}")
|
||||
endif()
|
||||
endmacro(semver_provide)
|
||||
|
||||
cmake_policy(POP)
|
323
conanfile.py
Normal file
323
conanfile.py
Normal file
@ -0,0 +1,323 @@
|
||||
import shutil
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from conan.tools.files import rm
|
||||
from conan.tools.scm import Git
|
||||
from conan.tools.apple import is_apple_os
|
||||
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps
|
||||
from conan import ConanFile
|
||||
required_conan_version = '>=2.7'
|
||||
|
||||
|
||||
def semver_parse(s):
|
||||
m = re.match('^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(\\.(?P<tweak>0|[1-9]\d*))?(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$', s)
|
||||
return m.groupdict() if m else None
|
||||
|
||||
|
||||
def semver_string(semver):
|
||||
s = str(semver['major']) + '.' + \
|
||||
str(semver['minor']) + '.' + str(semver['patch'])
|
||||
if not semver['tweak'] is None and semver['tweak'] != 0:
|
||||
s += '.' + str(semver['tweak'])
|
||||
if not semver['prerelease'] is None and semver['prerelease'] != '':
|
||||
s += '-' + semver['prerelease']
|
||||
return s
|
||||
|
||||
|
||||
def semver_string_with_buildmetadata(semver):
|
||||
s = semver_string(semver)
|
||||
if not semver['buildmetadata'] is None and semver['buildmetadata'] != '':
|
||||
s += '+' + semver['buildmetadata']
|
||||
return s
|
||||
|
||||
|
||||
class libmdbx(ConanFile):
|
||||
name = 'mdbx'
|
||||
package_type = 'library'
|
||||
description = 'One of the fastest embeddable key-value ACID database without WAL. libmdbx surpasses the legendary LMDB in terms of reliability, features and performance.'
|
||||
license = 'Apache-2.0'
|
||||
author = 'Leo Yuriev <leo@yuriev.ru>'
|
||||
homepage = 'https://libmdbx.dqdkfa.ru'
|
||||
url = 'https://gitflic.ru/project/erthink/libmdbx.git'
|
||||
topics = ('embedded-database', 'key-value', 'btree', 'LMDB', 'storage-engine',
|
||||
'data-storage', 'nosql', 'ACID', 'MVCC', 'MDBX')
|
||||
no_copy_source = True
|
||||
test_type = 'explicit'
|
||||
build_policy = 'missing'
|
||||
revision_mode = 'scm'
|
||||
languages = 'C', 'C++'
|
||||
provides = 'libmdbx'
|
||||
implements = ['auto_shared_fpic']
|
||||
# upload_policy = 'skip'
|
||||
# exports_sources = 'LICENSE', 'NOTICE', 'CMakeLists.txt', '*.h', '*.h++', '*.c', '*.c++', 'ntdll.def', 'man1/*', 'cmake/*', 'config.h.in'
|
||||
|
||||
settings = 'os', 'compiler', 'build_type', 'arch'
|
||||
options = {
|
||||
'mdbx.64bit_atomic': ['Auto', True, False],
|
||||
'mdbx.64bit_cas': ['Auto', True, False],
|
||||
'mdbx.apple.speed_insteadof_durability': ['Default', True, False],
|
||||
'mdbx.avoid_msync': ['Auto', True, False],
|
||||
'mdbx.build_cxx': ['Default', True, False],
|
||||
'mdbx.build_tools': ['Default', True, False],
|
||||
'mdbx.cacheline_size': ['Auto', 16, 32, 64, 128, 256],
|
||||
'mdbx.disable_validation': ['Default', True, False],
|
||||
'mdbx.enable_bigfoot': ['Default', True, False],
|
||||
'mdbx.enable_dbi_lockfree': ['Default', True, False],
|
||||
'mdbx.enable_dbi_sparse': ['Default', True, False],
|
||||
'mdbx.enable_pgop_stat': ['Default', True, False],
|
||||
'mdbx.enable_profgc': ['Default', True, False],
|
||||
'mdbx.enable_refund': ['Default', True, False],
|
||||
'mdbx.env_checkpid': ['Default', True, False],
|
||||
'mdbx.force_assertions': ['Default', True, False],
|
||||
'mdbx.have_builtin_cpu_supports': ['Auto', True, False],
|
||||
'mdbx.locking': ['Auto', 'WindowsFileLocking', 'SystemV', 'POSIX1988', 'POSIX2001', 'POSIX2008'],
|
||||
'mdbx.mmap_incoherent_file_write': ['Auto', True, False],
|
||||
'mdbx.mmap_needs_jolt': ['Auto', True, False],
|
||||
'mdbx.trust_rtc': ['Default', True, False],
|
||||
'mdbx.txn_checkowner': ['Default', True, False],
|
||||
'mdbx.unaligned_ok': ['Auto', True, False],
|
||||
'mdbx.use_copyfilerange': ['Auto', True, False],
|
||||
'mdbx.use_mincore': ['Auto', True, False],
|
||||
'mdbx.use_ofdlocks': ['Auto', True, False],
|
||||
'mdbx.use_sendfile': ['Auto', True, False],
|
||||
'mdbx.without_msvc_crt': ['Default', True, False],
|
||||
'shared': [True, False],
|
||||
}
|
||||
default_options = {
|
||||
'mdbx.64bit_atomic': 'Auto',
|
||||
'mdbx.64bit_cas': 'Auto',
|
||||
'mdbx.apple.speed_insteadof_durability': 'Default',
|
||||
'mdbx.avoid_msync': 'Auto',
|
||||
'mdbx.build_cxx': 'Default',
|
||||
'mdbx.build_tools': 'Default',
|
||||
'mdbx.cacheline_size': 'Auto',
|
||||
'mdbx.disable_validation': 'Default',
|
||||
'mdbx.enable_bigfoot': 'Default',
|
||||
'mdbx.enable_dbi_lockfree': 'Default',
|
||||
'mdbx.enable_dbi_sparse': 'Default',
|
||||
'mdbx.enable_pgop_stat': 'Default',
|
||||
'mdbx.enable_profgc': 'Default',
|
||||
'mdbx.enable_refund': 'Default',
|
||||
'mdbx.env_checkpid': 'Default',
|
||||
'mdbx.force_assertions': 'Default',
|
||||
'mdbx.have_builtin_cpu_supports': 'Auto',
|
||||
'mdbx.locking': 'Auto',
|
||||
'mdbx.mmap_incoherent_file_write': 'Auto',
|
||||
'mdbx.mmap_needs_jolt': 'Auto',
|
||||
'mdbx.trust_rtc': 'Default',
|
||||
'mdbx.txn_checkowner': 'Default',
|
||||
'mdbx.unaligned_ok': 'Auto',
|
||||
'mdbx.use_copyfilerange': 'Auto',
|
||||
'mdbx.use_mincore': 'Auto',
|
||||
'mdbx.use_ofdlocks': 'Auto',
|
||||
'mdbx.use_sendfile': 'Auto',
|
||||
'mdbx.without_msvc_crt': 'Default',
|
||||
'shared': True,
|
||||
}
|
||||
options_description = {
|
||||
'mdbx.64bit_atomic': 'Advanced: Assume 64-bit operations are atomic and not splitted to 32-bit halves. ',
|
||||
'mdbx.64bit_cas': 'Advanced: Assume 64-bit atomic compare-and-swap operation is available. ',
|
||||
'mdbx.apple.speed_insteadof_durability': 'Disable using `fcntl(F_FULLFSYNC)` for a performance reasons at the cost of durability on power failure. ',
|
||||
'mdbx.avoid_msync': 'Disable in-memory database updating with consequent flush-to-disk/msync syscall in `MDBX_WRITEMAP` mode. ',
|
||||
'mdbx.build_cxx': 'Build C++ portion. ',
|
||||
'mdbx.build_tools': 'Build CLI tools (mdbx_chk/stat/dump/load/copy/drop). ',
|
||||
'mdbx.cacheline_size': 'Advanced: CPU cache line size for data alignment to avoid cache line false-sharing. ',
|
||||
'mdbx.disable_validation': 'Disable some checks to reduce an overhead and detection probability of database corruption to a values closer to the LMDB. ',
|
||||
'mdbx.enable_bigfoot': 'Chunking long list of retired pages during huge transactions commit to avoid use sequences of pages. ',
|
||||
'mdbx.enable_dbi_lockfree': 'Support for deferred releasing and a lockfree path to quickly open DBI handles. ',
|
||||
'mdbx.enable_dbi_sparse': 'Support for sparse sets of DBI handles to reduce overhead when starting and processing transactions. ',
|
||||
'mdbx.enable_pgop_stat': 'Gathering statistics for page operations. ',
|
||||
'mdbx.enable_profgc': 'Profiling of GC search and updates. ',
|
||||
'mdbx.enable_refund': 'Online database zero-cost auto-compactification during write-transactions. ',
|
||||
'mdbx.env_checkpid': "Checking PID inside libmdbx's API against reuse database environment after the `fork()`. ",
|
||||
'mdbx.force_assertions': 'Forces assertion checking even for release builds. ',
|
||||
'mdbx.have_builtin_cpu_supports': 'Advanced: Assume the compiler and target system has `__builtin_cpu_supports()`. ',
|
||||
'mdbx.locking': 'Advanced: Choices the locking implementation. ',
|
||||
'mdbx.mmap_incoherent_file_write': "Advanced: Assume system don't have unified page cache and/or file write operations incoherent with memory-mapped files. ",
|
||||
'mdbx.mmap_needs_jolt': 'Advanced: Assume system needs explicit syscall to sync/flush/write modified mapped memory. ',
|
||||
'mdbx.trust_rtc': 'Advanced: Does a system have battery-backed Real-Time Clock or just a fake. ',
|
||||
'mdbx.txn_checkowner': 'Checking transaction owner thread against misuse transactions from other threads. ',
|
||||
'mdbx.unaligned_ok': 'Advanced: Assume a target CPU and/or the compiler support unaligned access. ',
|
||||
'mdbx.use_copyfilerange': 'Advanced: Use `copy_file_range()` syscall. ',
|
||||
'mdbx.use_mincore': "Use Unix' `mincore()` to determine whether database pages are resident in memory. ",
|
||||
'mdbx.use_ofdlocks': 'Advanced: Use POSIX OFD-locks. ',
|
||||
'mdbx.use_sendfile': 'Advancedc: Use `sendfile()` syscall. ',
|
||||
'mdbx.without_msvc_crt': 'Avoid dependence from MSVC CRT and use ntdll.dll instead. ',
|
||||
}
|
||||
|
||||
build_metadata = None
|
||||
|
||||
def config_options(self):
|
||||
if self.settings.get_safe('os') != 'Linux':
|
||||
self.options.rm_safe('mdbx.use_copyfilerange')
|
||||
self.options.rm_safe('mdbx.use_sendfile')
|
||||
if self.settings.get_safe('os') == 'Windows':
|
||||
self.default_options['mdbx.avoid_msync'] = True
|
||||
self.options.rm_safe('mdbx.env_checkpid')
|
||||
self.options.rm_safe('mdbx.locking')
|
||||
self.options.rm_safe('mdbx.mmap_incoherent_file_write')
|
||||
self.options.rm_safe('mdbx.use_mincore')
|
||||
self.options.rm_safe('mdbx.use_ofdlocks')
|
||||
else:
|
||||
self.options.rm_safe('mdbx.without_msvc_crt')
|
||||
if is_apple_os(self):
|
||||
self.options.rm_safe('mdbx.mmap_incoherent_file_write')
|
||||
else:
|
||||
self.options.rm_safe('mdbx.apple.speed_insteadof_durability')
|
||||
|
||||
def fetch_versioninfo_from_git(self):
|
||||
git = Git(self, folder=self.recipe_folder)
|
||||
git_timestamp = git.run('show --no-patch --format=%cI HEAD')
|
||||
git_tree = git.run('show --no-patch --format=%T HEAD')
|
||||
git_commit = git.run('show --no-patch --format=%H HEAD')
|
||||
if git.run('rev-list --tags --count') == 0:
|
||||
git.run('fetch --tags')
|
||||
git_last_vtag = git.run('describe --tags --abbrev=0 --match=v[0-9]*')
|
||||
if git_last_vtag == '':
|
||||
git_describe = git.run('describe --all --long --always')
|
||||
git_semver = semver_parse(
|
||||
'0.0.0.' + git.run('rev-list --count --remove-empty --no-merges HEAD'))
|
||||
else:
|
||||
git_describe = git.run('describe --tags --long --match=v[0-9]*')
|
||||
git_version = '.'.join(
|
||||
map(str, re.split('[-v.]+', git.run('describe --tags --match=v[0-9]*'))[1:5]))
|
||||
git_semver = semver_parse(git_last_vtag)
|
||||
if git_semver['prerelease'] is None or git_semver['prerelease'] == '':
|
||||
git_since_vtag = git.run(
|
||||
'rev-list ' + git_last_vtag + '.. --count')
|
||||
if int(git_since_vtag) > 0:
|
||||
git_semver['tweak'] = int(git_since_vtag)
|
||||
else:
|
||||
git_semver['tweak'] = None
|
||||
info = {'git_describe': git_describe, 'git_timestamp': git_timestamp,
|
||||
'git_tree': git_tree, 'git_commit': git_commit, 'semver': semver_string(git_semver)}
|
||||
return info
|
||||
|
||||
def export_sources(self):
|
||||
subprocess.run(['make', '-C', self.recipe_folder, 'DIST_DIR=' +
|
||||
self.export_sources_folder, '@dist-checked.tag'], check=True)
|
||||
rm(self, 'Makefile', self.export_sources_folder)
|
||||
rm(self, 'GNUmakefile', self.export_sources_folder)
|
||||
# json.dump(self.fetch_versioninfo_from_git(), open(os.path.join(
|
||||
# self.export_sources_folder, 'VERSION.json'), 'w', encoding='utf-8'))
|
||||
|
||||
def source(self):
|
||||
version_json_pathname = os.path.join(
|
||||
self.export_sources_folder, 'VERSION.json')
|
||||
version_json = json.load(
|
||||
open(os.path.join(version_json_pathname), encoding='utf-8'))['semver']
|
||||
if version_json != semver_string(semver_parse(self.version)):
|
||||
self.output.error('Package/Recipe version "' + self.version +
|
||||
'" mismatch VERSION.json "' + version_json + '"')
|
||||
|
||||
def set_version(self):
|
||||
if self.build_metadata is None and not self.version is None:
|
||||
self.build_metadata = self.version
|
||||
semver = semver_parse(self.build_metadata)
|
||||
if semver:
|
||||
self.build_metadata = semver['buildmetadata']
|
||||
else:
|
||||
self.build_metadata = re.match(
|
||||
'^[^0-9a-zA-Z]*([0-9a-zA-Z]+[-.0-9a-zA-Z]*)', self.build_metadata).group(1)
|
||||
if self.build_metadata is None:
|
||||
self.build_metadata = ''
|
||||
version_json_pathname = os.path.join(
|
||||
self.recipe_folder, 'VERSION.json')
|
||||
if os.path.exists(version_json_pathname):
|
||||
self.version = json.load(
|
||||
open(version_json_pathname, encoding='utf-8'))['semver']
|
||||
version_from = "'" + version_json_pathname + "'"
|
||||
else:
|
||||
self.version = self.fetch_versioninfo_from_git()['semver']
|
||||
version_from = 'Git'
|
||||
self.output.verbose('Fetch version from ' +
|
||||
version_from + ': ' + self.version)
|
||||
if self.build_metadata != '':
|
||||
self.version += '+' + self.build_metadata
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
|
||||
def handle_option(self, tc, name, define=False):
|
||||
opt = self.options.get_safe(name)
|
||||
if not opt is None:
|
||||
value = str(opt).lower()
|
||||
if value != 'auto' and value != 'default':
|
||||
name = name.upper().replace('.', '_')
|
||||
if define:
|
||||
if value == 'false' or value == 'no' or value == 'off':
|
||||
tc.preprocessor_definitions[name] = 0
|
||||
elif value == 'true' or value == 'yes' or value == 'on':
|
||||
tc.preprocessor_definitions[name] = 1
|
||||
else:
|
||||
tc.preprocessor_definitions[name] = int(opt)
|
||||
self.output.highlight(
|
||||
name + '=' + str(tc.preprocessor_definitions[name]) + ' (' + str(opt) + ')')
|
||||
else:
|
||||
tc.cache_variables[name] = opt
|
||||
self.output.highlight(
|
||||
name + '=' + str(tc.cache_variables[name]) + ' (' + str(opt) + ')')
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
if self.build_metadata is None:
|
||||
self.build_metadata = semver_parse(self.version)['buildmetadata']
|
||||
if not self.build_metadata is None and self.build_metadata != '':
|
||||
tc.variables['MDBX_BUILD_METADATA'] = self.build_metadata
|
||||
self.output.highlight('MDBX_BUILD_METADATA is ' +
|
||||
str(tc.variables['MDBX_BUILD_METADATA']))
|
||||
self.handle_option(tc, 'mdbx.64bit_atomic', True)
|
||||
self.handle_option(tc, 'mdbx.64bit_cas', True)
|
||||
self.handle_option(tc, 'mdbx.apple.speed_insteadof_durability')
|
||||
self.handle_option(tc, 'mdbx.avoid_msync')
|
||||
self.handle_option(tc, 'mdbx.build_tools')
|
||||
self.handle_option(tc, 'mdbx.build_cxx')
|
||||
self.handle_option(tc, 'mdbx.cacheline_size', True)
|
||||
self.handle_option(tc, 'mdbx.disable_validation')
|
||||
self.handle_option(tc, 'mdbx.enable_bigfoot')
|
||||
self.handle_option(tc, 'mdbx.enable_dbi_lockfree')
|
||||
self.handle_option(tc, 'mdbx.enable_dbi_sparse')
|
||||
self.handle_option(tc, 'mdbx.enable_pgop_stat')
|
||||
self.handle_option(tc, 'mdbx.enable_profgc')
|
||||
self.handle_option(tc, 'mdbx.enable_refund')
|
||||
self.handle_option(tc, 'mdbx.env_checkpid')
|
||||
self.handle_option(tc, 'mdbx.force_assertions')
|
||||
self.handle_option(tc, 'mdbx.have_builtin_cpu_supports', True)
|
||||
self.handle_option(tc, 'mdbx.mmap_incoherent_file_write', True)
|
||||
self.handle_option(tc, 'mdbx.mmap_needs_jolt')
|
||||
self.handle_option(tc, 'mdbx.trust_rtc')
|
||||
self.handle_option(tc, 'mdbx.txn_checkowner')
|
||||
self.handle_option(tc, 'mdbx.unaligned_ok', True)
|
||||
self.handle_option(tc, 'mdbx.use_copyfilerange', True)
|
||||
self.handle_option(tc, 'mdbx.use_mincore')
|
||||
self.handle_option(tc, 'mdbx.use_ofdlocks')
|
||||
self.handle_option(tc, 'mdbx.use_sendfile', True)
|
||||
self.handle_option(tc, 'mdbx.without_msvc_crt')
|
||||
opt = self.options.get_safe('mdbx.locking', 'auto')
|
||||
if not opt is None:
|
||||
value = str(opt).lower()
|
||||
if value != 'auto' and value != 'default':
|
||||
map = {'windowsfilelocking': -1, 'systemv': 5, 'posix1988': 1988,
|
||||
'posix2001': 2001, 'posix2008': 2008}
|
||||
value = map[value]
|
||||
tc.cache_variables['MDBX_LOCKING'] = value
|
||||
self.output.highlight('MDBX_LOCKING=' +
|
||||
str(tc.cache_variables['MDBX_LOCKING']))
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def package(self):
|
||||
cmake = CMake(self)
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
if self.options.shared:
|
||||
self.cpp_info.libs = ['mdbx']
|
||||
else:
|
||||
self.cpp_info.libs = ['mdbx-static']
|
181
dll.vcxproj
181
dll.vcxproj
@ -1,181 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{6D19209B-ECE7-4B9C-941C-0AA2B484F199}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<TargetName>mdbx</TargetName>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<TargetName>mdbx</TargetName>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<TargetName>mdbx</TargetName>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<TargetName>mdbx</TargetName>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBMDBX_EXPORTS;%(PreprocessorDefinitions);MDBX_DEBUG=1</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<WarningLevel>EnableAllWarnings</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<StringPooling>true</StringPooling>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBMDBX_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<WarningLevel>EnableAllWarnings</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<StringPooling>true</StringPooling>
|
||||
<Optimization>Full</Optimization>
|
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>EnableAllWarnings</WarningLevel>
|
||||
<PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;LIBMDBX_EXPORTS;%(PreprocessorDefinitions);MDBX_DEBUG=1</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<StringPooling>true</StringPooling>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;LIBMDBX_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<StringPooling>true</StringPooling>
|
||||
<Optimization>Full</Optimization>
|
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<WarningLevel>EnableAllWarnings</WarningLevel>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\lck-windows.c" />
|
||||
<ClCompile Include="src\mdbx.c" />
|
||||
<ClCompile Include="src\osal.c" />
|
||||
<ClCompile Include="src\version.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="mdbx.h" />
|
||||
<ClInclude Include="src\bits.h" />
|
||||
<ClInclude Include="src\defs.h" />
|
||||
<ClInclude Include="src\osal.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
2940
docs/Doxyfile.in
Normal file
2940
docs/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load Diff
47
docs/_preface.md
Normal file
47
docs/_preface.md
Normal file
@ -0,0 +1,47 @@
|
||||
\page intro Introduction
|
||||
\section characteristics Characteristics
|
||||
|
||||
Preface {#preface}
|
||||
------------------
|
||||
|
||||
> For the most part, this section is a copy of the corresponding text
|
||||
> from LMDB description, but with some edits reflecting the improvements
|
||||
> and enhancements were made in MDBX.
|
||||
|
||||
MDBX is a Btree-based database management library modeled loosely on the
|
||||
BerkeleyDB API, but much simplified. The entire database (aka "environment")
|
||||
is exposed in a memory map, and all data fetches return data directly from
|
||||
the mapped memory, so no malloc's or memcpy's occur during data fetches.
|
||||
As such, the library is extremely simple because it requires no page caching
|
||||
layer of its own, and it is extremely high performance and memory-efficient.
|
||||
It is also fully transactional with full ACID semantics, and when the memory
|
||||
map is read-only, the database integrity cannot be corrupted by stray pointer
|
||||
writes from application code.
|
||||
|
||||
The library is fully thread-aware and supports concurrent read/write access
|
||||
from multiple processes and threads. Data pages use a copy-on-write strategy
|
||||
so no active data pages are ever overwritten, which also provides resistance
|
||||
to corruption and eliminates the need of any special recovery procedures
|
||||
after a system crash. Writes are fully serialized; only one write transaction
|
||||
may be active at a time, which guarantees that writers can never deadlock.
|
||||
The database structure is multi-versioned so readers run with no locks;
|
||||
writers cannot block readers, and readers don't block writers.
|
||||
|
||||
Unlike other well-known database mechanisms which use either write-ahead
|
||||
transaction logs or append-only data writes, MDBX requires no maintenance
|
||||
during operation. Both write-ahead loggers and append-only databases require
|
||||
periodic checkpointing and/or compaction of their log or database files
|
||||
otherwise they grow without bound. MDBX tracks retired/freed pages within the
|
||||
database and re-uses them for new write operations, so the database size does
|
||||
not grow without bound in normal use. It is worth noting that the "next"
|
||||
version libmdbx (\ref MithrilDB) will solve this problem.
|
||||
|
||||
The memory map can be used as a read-only or read-write map. It is read-only
|
||||
by default as this provides total immunity to corruption. Using read-write
|
||||
mode offers much higher write performance, but adds the possibility for stray
|
||||
application writes thru pointers to silently corrupt the database.
|
||||
Of course if your application code is known to be bug-free (...) then this is
|
||||
not an issue.
|
||||
|
||||
If this is your first time using a transactional embedded key-value store,
|
||||
you may find the \ref starting section below to be helpful.
|
248
docs/_restrictions.md
Normal file
248
docs/_restrictions.md
Normal file
@ -0,0 +1,248 @@
|
||||
Restrictions & Caveats {#restrictions}
|
||||
======================
|
||||
In addition to those listed for some functions.
|
||||
|
||||
|
||||
## Long-lived read transactions {#long-lived-read}
|
||||
Avoid long-lived read transactions, especially in the scenarios with a
|
||||
high rate of write transactions. Long-lived read transactions prevents
|
||||
recycling pages retired/freed by newer write transactions, thus the
|
||||
database can grow quickly.
|
||||
|
||||
Understanding the problem of long-lived read transactions requires some
|
||||
explanation, but can be difficult for quick perception. So is is
|
||||
reasonable to simplify this as follows:
|
||||
1. Garbage collection problem exists in all databases one way or
|
||||
another, e.g. VACUUM in PostgreSQL. But in MDBX it's even more
|
||||
discernible because of high transaction rate and intentional
|
||||
internals simplification in favor of performance.
|
||||
|
||||
2. MDBX employs [Multiversion concurrency control](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
|
||||
on the [Copy-on-Write](https://en.wikipedia.org/wiki/Copy-on-write)
|
||||
basis, that allows multiple readers runs in parallel with a write
|
||||
transaction without blocking. An each write transaction needs free
|
||||
pages to put the changed data, that pages will be placed in the new
|
||||
b-tree snapshot at commit. MDBX efficiently recycling pages from
|
||||
previous created unused snapshots, BUT this is impossible if anyone
|
||||
a read transaction use such snapshot.
|
||||
|
||||
3. Thus massive altering of data during a parallel long read operation
|
||||
will increase the process's work set and may exhaust entire free
|
||||
database space.
|
||||
|
||||
A good example of long readers is a hot backup to the slow destination
|
||||
or debugging of a client application while retaining an active read
|
||||
transaction. LMDB this results in `MDB_MAP_FULL` error and subsequent write
|
||||
performance degradation.
|
||||
|
||||
MDBX mostly solve "long-lived" readers issue by offering to use a
|
||||
transaction parking-and-ousting approach by \ref mdbx_txn_park(),
|
||||
Handle-Slow-Readers \ref MDBX_hsr_func callback which allows to abort
|
||||
long-lived read transactions, and using the \ref MDBX_LIFORECLAIM mode
|
||||
which addresses subsequent performance degradation. The "next" version
|
||||
of libmdbx (aka \ref MithrilDB) will completely solve this.
|
||||
|
||||
Nonetheless, situations that encourage lengthy read transactions while
|
||||
intensively updating data should be avoided. For example, you should
|
||||
avoid suspending/blocking processes/threads performing read
|
||||
transactions, including during debugging, and use transaction parking if
|
||||
necessary.
|
||||
|
||||
You should also beware of aborting processes that perform reading
|
||||
transactions. Despite the fact that libmdbx automatically checks and
|
||||
cleans readers, as an a process aborting (especially with core dump) can
|
||||
take a long time, and checking readers cannot be performed too often due
|
||||
to performance degradation.
|
||||
|
||||
This issue will be addressed in MithrilDB and one of libmdbx releases,
|
||||
presumably in 2025. To do this, nonlinear GC recycling will be
|
||||
implemented, without stopping garbage recycling on the old MVCC snapshot
|
||||
used by a long read transaction.
|
||||
|
||||
After the planned implementation, any long-term reading transaction will
|
||||
still keep the used MVCC-snapshot (all the database pages forming it)
|
||||
from being recycled, but it will allow all unused MVCC snapshots to be
|
||||
recycled, both before and after the readable one. This will eliminate
|
||||
one of the main architectural flaws inherited from LMDB and caused the
|
||||
growth of a database in proportion to a volume of data changes made
|
||||
concurrently with a long-running read transaction.
|
||||
|
||||
|
||||
## Large data items
|
||||
|
||||
MDBX allows you to store values up to 1 gigabyte in size, but this is
|
||||
not the main functionality for a key-value storage, but an additional
|
||||
feature that should not be abused. Such long values are stored in
|
||||
consecutive/adjacent DB pages, which has both pros and cons. This allows
|
||||
you to read long values directly without copying and without any
|
||||
overhead from a linear section of memory.
|
||||
|
||||
On the other hand, when putting such values in the database, it is
|
||||
required to find a sufficient number of free consecutive/adjacent
|
||||
database pages, which can be very difficult and expensive, moreover
|
||||
sometimes impossible since b-tree tends to fragmentation. So, when
|
||||
placing very long values, the engine may need to process the entire GC,
|
||||
and in the absence of a sufficient sequence of free pages, increase the
|
||||
DB file. Thus, for long values, MDBX provides maximum read performance
|
||||
at the expense of write performance.
|
||||
|
||||
Some aspects related to GC have been refined and improved in 2022 within
|
||||
the first releases of the 0.12.x series. In particular the search for
|
||||
free consecutive/adjacent pages through GC has been significantly
|
||||
speeded, including acceleration using NOEN/SSE2/AVX2/AVX512
|
||||
instructions.
|
||||
|
||||
This issue will be addressed in MithrilDB and refined within one of
|
||||
0.15.x libmdbx releases, presumably at end of 2025.
|
||||
|
||||
|
||||
### Huge transactions
|
||||
|
||||
A similar situation can be with huge transactions, in which a lot of
|
||||
database pages are retired. The retired pages should be put into GC as a
|
||||
list of page numbers for future reuse. But in huge transactions, such a
|
||||
list of retired page numbers can also be huge, i.e. it is a very long
|
||||
value and requires a long sequence of free pages to be saved. Thus, if
|
||||
you delete large amounts of information from the database in a single
|
||||
transaction, MDBX may need to increase the database file to save the
|
||||
list of pages to be retired.
|
||||
|
||||
This issue was fixed in 2022 within the first releases of the 0.12.x
|
||||
series by `Big Foot` feature, which now is enabled by default.
|
||||
See \ref MDBX_ENABLE_BIGFOOT build-time option.
|
||||
|
||||
The `Big Foot` feature which significantly reduces GC overhead for
|
||||
processing large lists of retired pages from huge transactions. Now
|
||||
libmdbx avoid creating large chunks of PNLs (page number lists) which
|
||||
required a long sequences of free pages, aka large/overflow pages. Thus
|
||||
avoiding searching, allocating and storing such sequences inside GC.
|
||||
|
||||
|
||||
## Space reservation
|
||||
An MDBX database configuration will often reserve considerable unused
|
||||
memory address space and maybe file size for future growth. This does
|
||||
not use actual memory or disk space, but users may need to understand
|
||||
the difference so they won't be scared off.
|
||||
|
||||
However, on 64-bit systems with a relative small amount of RAM, such
|
||||
reservation can deplete system resources (trigger ENOMEM error, etc)
|
||||
when setting an inadequately large upper DB size using \ref
|
||||
mdbx_env_set_geometry() or \ref mdbx::env::geometry. So just avoid this.
|
||||
|
||||
|
||||
## Remote filesystems
|
||||
Do not use MDBX databases on remote filesystems, even between processes
|
||||
on the same host. This breaks file locks on some platforms, possibly
|
||||
memory map sync, and certainly sync between programs on different hosts.
|
||||
|
||||
On the other hand, MDBX support the exclusive database operation over
|
||||
a network, and cooperative read-only access to the database placed on
|
||||
a read-only network shares.
|
||||
|
||||
|
||||
## Child processes
|
||||
Do not use opened \ref MDBX_env instance(s) in a child processes after `fork()`.
|
||||
It would be insane to call fork() and any MDBX-functions simultaneously
|
||||
from multiple threads. The best way is to prevent the presence of open
|
||||
MDBX-instances during `fork()`.
|
||||
|
||||
The \ref MDBX_ENV_CHECKPID build-time option, which is ON by default on
|
||||
non-Windows platforms (i.e. where `fork()` is available), enables PID
|
||||
checking at a few critical points. But this does not give any guarantees,
|
||||
but only allows you to detect such errors a little sooner. Depending on
|
||||
the platform, you should expect an application crash and/or database
|
||||
corruption in such cases.
|
||||
|
||||
On the other hand, MDBX allow calling \ref mdbx_env_close() in such cases to
|
||||
release resources, but no more and in general this is a wrong way.
|
||||
|
||||
#### Since v0.13.1 and later
|
||||
|
||||
Starting from the v0.13.1 release, the \ref mdbx_env_resurrect_after_fork()
|
||||
is available, which allows you to reuse an already open database
|
||||
environment in child processes, but strictly without inheriting any
|
||||
transactions from a parent process.
|
||||
|
||||
|
||||
## Read-only mode
|
||||
There is no pure read-only mode in a normal explicitly way, since
|
||||
readers need write access to LCK-file to be ones visible for writer.
|
||||
|
||||
So MDBX always tries to open/create LCK-file for read-write, but switches
|
||||
to without-LCK mode on appropriate errors (`EROFS`, `EACCESS`, `EPERM`)
|
||||
if the read-only mode was requested by the \ref MDBX_RDONLY flag which is
|
||||
described below.
|
||||
|
||||
|
||||
## Troubleshooting the LCK-file
|
||||
1. A broken LCK-file can cause sync issues, including appearance of
|
||||
wrong/inconsistent data for readers. When database opened in the
|
||||
cooperative read-write mode the LCK-file requires to be mapped to
|
||||
memory in read-write access. In this case it is always possible for
|
||||
stray/malfunctioned application could writes thru pointers to
|
||||
silently corrupt the LCK-file.
|
||||
|
||||
Unfortunately, there is no any portable way to prevent such
|
||||
corruption, since the LCK-file is updated concurrently by
|
||||
multiple processes in a lock-free manner and any locking is
|
||||
unwise due to a large overhead.
|
||||
|
||||
\note Workaround: Just make all programs using the database close it;
|
||||
the LCK-file is always reset on first open.
|
||||
|
||||
2. Stale reader transactions left behind by an aborted program cause
|
||||
further writes to grow the database quickly, and stale locks can
|
||||
block further operation.
|
||||
MDBX checks for stale readers while opening environment and before
|
||||
growth the database. But in some cases, this may not be enough.
|
||||
|
||||
\note Workaround: Check for stale readers periodically, using the
|
||||
\ref mdbx_reader_check() function or the mdbx_stat tool.
|
||||
|
||||
3. Stale writers will be cleared automatically by MDBX on supported
|
||||
platforms. But this is platform-specific, especially of
|
||||
implementation of shared POSIX-mutexes and support for robust
|
||||
mutexes. For instance there are no known issues on Linux, OSX,
|
||||
Windows and FreeBSD.
|
||||
|
||||
\note Workaround: Otherwise just make all programs using the database
|
||||
close it; the LCK-file is always reset on first open of the environment.
|
||||
|
||||
|
||||
## One thread - One transaction
|
||||
A thread can only use one transaction at a time, plus any nested
|
||||
read-write transactions in the non-writemap mode. Each transaction
|
||||
belongs to one thread. The \ref MDBX_NOSTICKYTHREADS flag changes this,
|
||||
see below.
|
||||
|
||||
Do not start more than one transaction for a one thread. If you think
|
||||
about this, it's really strange to do something with two data snapshots
|
||||
at once, which may be different. MDBX checks and preventing this by
|
||||
returning corresponding error code (\ref MDBX_TXN_OVERLAPPING,
|
||||
\ref MDBX_BAD_RSLOT, \ref MDBX_BUSY) unless you using
|
||||
\ref MDBX_NOSTICKYTHREADS option on the environment.
|
||||
Nonetheless, with the `MDBX_NOSTICKYTHREADS` option, you must know
|
||||
exactly what you are doing, otherwise you will get deadlocks or reading
|
||||
an alien data.
|
||||
|
||||
|
||||
## Do not open twice
|
||||
Do not have open an MDBX database twice in the same process at the same
|
||||
time. By default MDBX prevent this in most cases by tracking databases
|
||||
opening and return \ref MDBX_BUSY if anyone LCK-file is already open.
|
||||
|
||||
The reason for this is that when the "Open file description" locks (aka
|
||||
OFD-locks) are not available, MDBX uses POSIX locks on files, and these
|
||||
locks have issues if one process opens a file multiple times. If a single
|
||||
process opens the same environment multiple times, closing it once will
|
||||
remove all the locks held on it, and the other instances will be
|
||||
vulnerable to corruption from other processes.
|
||||
|
||||
For compatibility with LMDB which allows multi-opening, MDBX can be
|
||||
configured at runtime by `mdbx_setup_debug(MDBX_DBG_LEGACY_MULTIOPEN, ...)`
|
||||
prior to calling other MDBX functions. In this way MDBX will track
|
||||
databases opening, detect multi-opening cases and then recover POSIX file
|
||||
locks as necessary. However, lock recovery can cause unexpected pauses,
|
||||
such as when another process opened the database in exclusive mode before
|
||||
the lock was restored - we have to wait until such a process releases the
|
||||
database, and so on.
|
245
docs/_starting.md
Normal file
245
docs/_starting.md
Normal file
@ -0,0 +1,245 @@
|
||||
Getting started {#starting}
|
||||
===============
|
||||
|
||||
> This section is based on Bert Hubert's intro "LMDB Semantics", with
|
||||
> edits reflecting the improvements and enhancements were made in MDBX.
|
||||
> See Bert Hubert's [original](https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md).
|
||||
|
||||
Everything starts with an environment, created by \ref mdbx_env_create().
|
||||
Once created, this environment must also be opened with \ref mdbx_env_open(),
|
||||
and after use be closed by \ref mdbx_env_close(). At that a non-zero value
|
||||
of the last argument "mode" supposes MDBX will create database and directory
|
||||
if ones does not exist. In this case the non-zero "mode" argument specifies
|
||||
the file mode bits be applied when a new files are created by `open()` function.
|
||||
|
||||
Within that directory, a lock file (aka LCK-file) and a storage file (aka
|
||||
DXB-file) will be generated. If you don't want to use a directory, you can
|
||||
pass the \ref MDBX_NOSUBDIR option, in which case the path you provided is used
|
||||
directly as the DXB-file, and another file with a "-lck" suffix added
|
||||
will be used for the LCK-file.
|
||||
|
||||
Once the environment is open, a transaction can be created within it using
|
||||
\ref mdbx_txn_begin(). Transactions may be read-write or read-only, and read-write
|
||||
transactions may be nested. A transaction must only be used by one thread at
|
||||
a time. Transactions are always required, even for read-only access. The
|
||||
transaction provides a consistent view of the data.
|
||||
|
||||
Once a transaction has been created, a database (i.e. key-value space inside
|
||||
the environment) can be opened within it using \ref mdbx_dbi_open(). If only one
|
||||
database will ever be used in the environment, a `NULL` can be passed as the
|
||||
database name. For named databases, the \ref MDBX_CREATE flag must be used to
|
||||
create the database if it doesn't already exist. Also, \ref mdbx_env_set_maxdbs()
|
||||
must be called after \ref mdbx_env_create() and before \ref mdbx_env_open() to set
|
||||
the maximum number of named databases you want to support.
|
||||
|
||||
\note A single transaction can open multiple databases. Generally databases
|
||||
should only be opened once, by the first transaction in the process.
|
||||
|
||||
Within a transaction, \ref mdbx_get() and \ref mdbx_put() can store single key-value
|
||||
pairs if that is all you need to do (but see \ref Cursors below if you want to do
|
||||
more).
|
||||
|
||||
A key-value pair is expressed as two \ref MDBX_val structures. This struct that is
|
||||
exactly similar to POSIX's `struct iovec` and has two fields, `iov_len` and
|
||||
`iov_base`. The data is a `void` pointer to an array of `iov_len` bytes.
|
||||
\note The notable difference between MDBX and LMDB is that MDBX support zero
|
||||
length keys.
|
||||
|
||||
Because MDBX is very efficient (and usually zero-copy), the data returned in
|
||||
an \ref MDBX_val structure may be memory-mapped straight from disk. In other words
|
||||
look but do not touch (or `free()` for that matter). Once a transaction is
|
||||
closed, the values can no longer be used, so make a copy if you need to keep
|
||||
them after that.
|
||||
|
||||
## Cursors {#Cursors}
|
||||
To do more powerful things, we must use a cursor.
|
||||
|
||||
Within the transaction, a cursor can be created with \ref mdbx_cursor_open().
|
||||
With this cursor we can store/retrieve/delete (multiple) values using
|
||||
\ref mdbx_cursor_get(), \ref mdbx_cursor_put() and \ref mdbx_cursor_del().
|
||||
|
||||
The \ref mdbx_cursor_get() positions itself depending on the cursor operation
|
||||
requested, and for some operations, on the supplied key. For example, to list
|
||||
all key-value pairs in a database, use operation \ref MDBX_FIRST for the first
|
||||
call to \ref mdbx_cursor_get(), and \ref MDBX_NEXT on subsequent calls, until
|
||||
the end is hit.
|
||||
|
||||
To retrieve all keys starting from a specified key value, use \ref MDBX_SET. For
|
||||
more cursor operations, see the \ref c_api reference.
|
||||
|
||||
When using \ref mdbx_cursor_put(), either the function will position the cursor
|
||||
for you based on the key, or you can use operation \ref MDBX_CURRENT to use the
|
||||
current position of the cursor. \note Note that key must then match the current
|
||||
position's key.
|
||||
|
||||
|
||||
## Summarizing the opening
|
||||
|
||||
So we have a cursor in a transaction which opened a database in an
|
||||
environment which is opened from a filesystem after it was separately
|
||||
created.
|
||||
|
||||
Or, we create an environment, open it from a filesystem, create a transaction
|
||||
within it, open a database within that transaction, and create a cursor
|
||||
within all of the above.
|
||||
|
||||
Got it?
|
||||
|
||||
|
||||
## Threads and processes
|
||||
|
||||
Do not have open an database twice in the same process at the same time, MDBX
|
||||
will track and prevent this. Instead, share the MDBX environment that has
|
||||
opened the file across all threads. The reason for this is:
|
||||
- When the "Open file description" locks (aka OFD-locks) are not available,
|
||||
MDBX uses POSIX locks on files, and these locks have issues if one process
|
||||
opens a file multiple times.
|
||||
- If a single process opens the same environment multiple times, closing it
|
||||
once will remove all the locks held on it, and the other instances will be
|
||||
vulnerable to corruption from other processes.
|
||||
+ For compatibility with LMDB which allows multi-opening, MDBX can be
|
||||
configured at runtime by \ref mdbx_setup_debug() with \ref MDBX_DBG_LEGACY_MULTIOPEN` option
|
||||
prior to calling other MDBX functions. In this way MDBX will track
|
||||
databases opening, detect multi-opening cases and then recover POSIX file
|
||||
locks as necessary. However, lock recovery can cause unexpected pauses,
|
||||
such as when another process opened the database in exclusive mode before
|
||||
the lock was restored - we have to wait until such a process releases the
|
||||
database, and so on.
|
||||
|
||||
Do not use opened MDBX environment(s) after `fork()` in a child process(es),
|
||||
MDBX will check and prevent this at critical points. Instead, ensure there is
|
||||
no open MDBX-instance(s) during fork(), or at least close it immediately after
|
||||
`fork()` in the child process and reopen if required - for instance by using
|
||||
`pthread_atfork()`. The reason for this is:
|
||||
- For competitive consistent reading, MDBX assigns a slot in the shared
|
||||
table for each process that interacts with the database. This slot is
|
||||
populated with process attributes, including the PID.
|
||||
- After `fork()`, in order to remain connected to a database, the child
|
||||
process must have its own such "slot", which can't be assigned in any
|
||||
simple and robust way another than the regular.
|
||||
- A write transaction from a parent process cannot continue in a child
|
||||
process for obvious reasons.
|
||||
- Moreover, in a multithreaded process at the fork() moment any number of
|
||||
threads could run in critical and/or intermediate sections of MDBX code
|
||||
with interaction and/or racing conditions with threads from other
|
||||
process(es). For instance: shrinking a database or copying it to a pipe,
|
||||
opening or closing environment, beginning or finishing a transaction,
|
||||
and so on.
|
||||
= Therefore, any solution other than simply close database (and reopen if
|
||||
necessary) in a child process would be both extreme complicated and so
|
||||
fragile.
|
||||
|
||||
Do not start more than one transaction for a one thread. If you think
|
||||
about this, it's really strange to do something with two data snapshots
|
||||
at once, which may be different. MDBX checks and preventing this by
|
||||
returning corresponding error code (\ref MDBX_TXN_OVERLAPPING,
|
||||
\ref MDBX_BAD_RSLOT, \ref MDBX_BUSY) unless you using
|
||||
\ref MDBX_NOSTICKYTHREADS option on the environment. Nonetheless,
|
||||
with the \ref MDBX_NOSTICKYTHREADS option, you must know exactly what
|
||||
you are doing, otherwise you will get deadlocks or reading an alien
|
||||
data.
|
||||
|
||||
Also note that a transaction is tied to one thread by default using
|
||||
Thread Local Storage. If you want to pass transactions across threads,
|
||||
you can use the \ref MDBX_NOSTICKYTHREADS option on the environment.
|
||||
Nevertheless, a write transaction must be committed or aborted in the
|
||||
same thread which it was started. MDBX checks this in a reasonable
|
||||
manner and return the \ref MDBX_THREAD_MISMATCH error in rules
|
||||
violation.
|
||||
|
||||
|
||||
## Transactions, rollbacks etc
|
||||
|
||||
To actually get anything done, a transaction must be committed using
|
||||
\ref mdbx_txn_commit(). Alternatively, all of a transaction's operations
|
||||
can be discarded using \ref mdbx_txn_abort().
|
||||
|
||||
\attention An important difference between MDBX and LMDB is that 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.
|
||||
|
||||
For read-only transactions, obviously there is nothing to commit to storage.
|
||||
\attention An another notable difference between MDBX and LMDB is that MDBX make
|
||||
handles opened for existing databases immediately available for other
|
||||
transactions, regardless this transaction will be aborted or reset. The
|
||||
REASON for this is to avoiding the requirement for multiple opening a same
|
||||
handles in concurrent read transactions, and tracking of such open but hidden
|
||||
handles until the completion of read transactions which opened them.
|
||||
|
||||
In addition, as long as a transaction is open, a consistent view of the
|
||||
database is kept alive, which requires storage. A read-only transaction that
|
||||
no longer requires this consistent view should be terminated (committed or
|
||||
aborted) when the view is no longer needed (but see below for an
|
||||
optimization).
|
||||
|
||||
There can be multiple simultaneously active read-only transactions but only
|
||||
one that can write. Once a single read-write transaction is opened, all
|
||||
further attempts to begin one will block until the first one is committed or
|
||||
aborted. This has no effect on read-only transactions, however, and they may
|
||||
continue to be opened at any time.
|
||||
|
||||
|
||||
## Duplicate keys aka Multi-values
|
||||
|
||||
\ref mdbx_get() and \ref mdbx_put() respectively have no and only some support or
|
||||
multiple key-value pairs with identical keys. If there are multiple values
|
||||
for a key, \ref mdbx_get() will only return the first value.
|
||||
|
||||
When multiple values for one key are required, pass the \ref MDBX_DUPSORT flag to
|
||||
\ref mdbx_dbi_open(). In an \ref MDBX_DUPSORT database, by default \ref mdbx_put() will
|
||||
not replace the value for a key if the key existed already. Instead it will add
|
||||
the new value to the key. In addition, \ref mdbx_del() will pay attention to the
|
||||
value field too, allowing for specific values of a key to be deleted.
|
||||
|
||||
Finally, additional cursor operations become available for traversing through
|
||||
and retrieving duplicate values.
|
||||
|
||||
|
||||
## Some optimization
|
||||
|
||||
If you frequently begin and abort read-only transactions, as an optimization,
|
||||
it is possible to only reset and renew a transaction.
|
||||
|
||||
\ref mdbx_txn_reset() releases any old copies of data kept around for a read-only
|
||||
transaction. To reuse this reset transaction, call \ref mdbx_txn_renew() on it.
|
||||
Any cursors in this transaction can also be renewed using \ref mdbx_cursor_renew()
|
||||
or freed by \ref mdbx_cursor_close().
|
||||
|
||||
To permanently free a transaction, reset or not, use \ref mdbx_txn_abort().
|
||||
|
||||
|
||||
## Cleaning up
|
||||
|
||||
Any created cursors must be closed using \ref mdbx_cursor_close(). It is advisable
|
||||
to repeat:
|
||||
\note An important difference between MDBX and LMDB is that 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.
|
||||
|
||||
It is very rarely necessary to close a database handle, and in general they
|
||||
should just be left open. When you close a handle, it immediately becomes
|
||||
unavailable for all transactions in the environment. Therefore, you should
|
||||
avoid closing the handle while at least one transaction is using it.
|
||||
|
||||
|
||||
## Now read up on the full API!
|
||||
|
||||
The full \ref c_api documentation lists further details below, like how to:
|
||||
|
||||
- Configure database size and automatic size management: \ref mdbx_env_set_geometry().
|
||||
- Drop and clean a database: \ref mdbx_drop().
|
||||
- Detect and report errors: \ref c_err.
|
||||
- Optimize (bulk) loading speed: \ref MDBX_MULTIPLE, \ref MDBX_APPEND.
|
||||
- Reduce (temporarily) robustness to gain even more speed: \ref sync_modes.
|
||||
- Gather statistics about the database: \ref c_statinfo.
|
||||
- Sstimate size of range query result: \ref c_rqest.
|
||||
- Double performance by LIFO reclaiming on storages with write-back: \ref MDBX_LIFORECLAIM.
|
||||
- Use sequences and canary markers: \ref mdbx_dbi_sequence(), \ref MDBX_canary.
|
||||
- Use Handle-Slow-Readers callback to resolve a database full/overflow issues
|
||||
due to long-lived read transactions: \ref mdbx_env_set_hsr().
|
||||
- Use exclusive mode: \ref MDBX_EXCLUSIVE.
|
||||
- Define custom sort orders (but this is recommended to be avoided).
|
37
docs/_toc.md
Normal file
37
docs/_toc.md
Normal file
@ -0,0 +1,37 @@
|
||||
The source code is availale on [Gitflic](https://gitflic.ru/project/erthink/libmdbx).
|
||||
Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`.
|
||||
Всё будет хорошо!
|
||||
|
||||
> Questions, feedback and suggestions are welcome to the [Telegram' group](https://t.me/libmdbx) (archive [1](https://libmdbx.dqdkfa.ru/tg-archive/messages1.html),
|
||||
> [2](https://libmdbx.dqdkfa.ru/tg-archive/messages2.html), [3](https://libmdbx.dqdkfa.ru/tg-archive/messages3.html), [4](https://libmdbx.dqdkfa.ru/tg-archive/messages4.html),
|
||||
> [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.
|
||||
|
||||
\section toc Table of Contents
|
||||
|
||||
This manual is divided into parts,
|
||||
each of which is divided into several sections.
|
||||
|
||||
1. The \ref intro
|
||||
- \ref characteristics
|
||||
- \ref improvements
|
||||
- \ref restrictions
|
||||
- \ref performance
|
||||
2. \ref usage
|
||||
- \ref getting
|
||||
- \ref starting
|
||||
- \ref bindings
|
||||
|
||||
3. The `C/C++` API manual:
|
||||
- The \ref c_api reference
|
||||
- \ref c_crud_hints "Quick reference for Insert/Update/Delete operations"
|
||||
- The \ref mdbx.h header file reference
|
||||
- The \ref cxx_api reference
|
||||
- The \ref mdbx.h++ header file reference
|
||||
|
||||
Please do not hesitate to point out errors in the documentation,
|
||||
including creating [merge-request](https://gitflic.ru/project/erthink/libmdbx/merge-request) with corrections and improvements.
|
||||
|
||||
---
|
||||
|
||||
\section MithrilDB MithrilDB and Future
|
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
103
docs/header.html
Normal file
103
docs/header.html
Normal file
@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<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-->
|
||||
<link rel="icon" href="$relpath^$projecticon" type="image/x-icon" />
|
||||
<!--END PROJECT_ICON-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN FULL_SIDEBAR-->
|
||||
<script type="text/javascript">var page_layout=1;</script>
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
<!--BEGIN COPY_CLIPBOARD-->
|
||||
<script type="text/javascript" src="$relpath^clipboard.js"></script>
|
||||
<!--END COPY_CLIPBOARD-->
|
||||
$treeview
|
||||
$search
|
||||
$mathjax
|
||||
$darkmode
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
$extrastylesheet
|
||||
</head>
|
||||
<body>
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN FULL_SIDEBAR-->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr id="projectrow">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"$logosize/></td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign">
|
||||
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber"> $projectnumber</span><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td>
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<!--BEGIN !FULL_SIDEBAR-->
|
||||
<td>$searchbox</td>
|
||||
<!--END !FULL_SIDEBAR-->
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<!--BEGIN FULL_SIDEBAR-->
|
||||
<tr><td colspan="2">$searchbox</td></tr>
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END SEARCHENGINE-->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript" >
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||
|
||||
ym(99261645, "init", {
|
||||
clickmap:true,
|
||||
trackLinks:true,
|
||||
accurateTrackBounce:true,
|
||||
webvisor:true
|
||||
});
|
||||
</script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/99261645" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
<!-- end header part -->
|
27
docs/ld+json
Normal file
27
docs/ld+json
Normal 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>
|
6
docs/manifest.webmanifest
Normal file
6
docs/manifest.webmanifest
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"icons": [
|
||||
{ "src": "favicon.ico", "type": "image/ico", "sizes": "32x32" },
|
||||
{ "src": "img/bear.png", "type": "image/png", "sizes": "256x256" }
|
||||
]
|
||||
}
|
2
docs/title
Normal file
2
docs/title
Normal 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.">
|
6
example/CMakeLists.txt
Normal file
6
example/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
set(TARGET mdbx_example)
|
||||
project(${TARGET})
|
||||
|
||||
add_executable(${TARGET} example-mdbx.c)
|
||||
|
||||
target_link_libraries(${TARGET} mdbx)
|
1
example/README.md
Normal file
1
example/README.md
Normal file
@ -0,0 +1 @@
|
||||
See [example-mdbx.c](example-mdbx.c) as an example of using _libmdbx_, and do a line-by-line comparison of it with the [sample-bdb.txt](sample-bdb.txt) file.
|
154
example/example-mdbx.c
Normal file
154
example/example-mdbx.c
Normal file
@ -0,0 +1,154 @@
|
||||
/* MDBX usage example
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-bdb.txt
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2015-2025 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2017 Ilya Shipitsin <chipitsine@gmail.com>.
|
||||
* Copyright 2012-2015 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#if (defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__)) && !defined(__USE_MINGW_ANSI_STDIO)
|
||||
#define __USE_MINGW_ANSI_STDIO 1
|
||||
#endif /* MinGW */
|
||||
|
||||
#include "mdbx.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
int rc;
|
||||
MDBX_env *env = NULL;
|
||||
MDBX_dbi dbi = 0;
|
||||
MDBX_val key, data;
|
||||
MDBX_txn *txn = NULL;
|
||||
MDBX_cursor *cursor = NULL;
|
||||
char sval[32];
|
||||
|
||||
printf("MDBX limits:\n");
|
||||
#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
|
||||
const double scale_factor = 1099511627776.0;
|
||||
const char *const scale_unit = "TiB";
|
||||
#else
|
||||
const double scale_factor = 1073741824.0;
|
||||
const char *const scale_unit = "GiB";
|
||||
#endif
|
||||
const size_t pagesize_min = mdbx_limits_pgsize_min();
|
||||
const size_t pagesize_max = mdbx_limits_pgsize_max();
|
||||
const size_t pagesize_default = mdbx_default_pagesize();
|
||||
|
||||
printf("\tPage size: a power of 2, minimum %zu, maximum %zu bytes,"
|
||||
" default %zu bytes.\n",
|
||||
pagesize_min, pagesize_max, pagesize_default);
|
||||
printf("\tKey size: minimum %zu, maximum ≈¼ pagesize (%zu bytes for default"
|
||||
" %zuK pagesize, %zu bytes for %zuK pagesize).\n",
|
||||
(size_t)0, mdbx_limits_keysize_max(-1, MDBX_DB_DEFAULTS), pagesize_default / 1024,
|
||||
mdbx_limits_keysize_max(pagesize_max, MDBX_DB_DEFAULTS), pagesize_max / 1024);
|
||||
printf("\tValue size: minimum %zu, maximum %zu (0x%08zX) bytes for maps,"
|
||||
" ≈¼ pagesize for multimaps (%zu bytes for default %zuK pagesize,"
|
||||
" %zu bytes for %zuK pagesize).\n",
|
||||
(size_t)0, mdbx_limits_valsize_max(pagesize_min, MDBX_DB_DEFAULTS),
|
||||
mdbx_limits_valsize_max(pagesize_min, MDBX_DB_DEFAULTS), mdbx_limits_valsize_max(-1, MDBX_DUPSORT),
|
||||
pagesize_default / 1024, mdbx_limits_valsize_max(pagesize_max, MDBX_DUPSORT), pagesize_max / 1024);
|
||||
printf("\tWrite transaction size: up to %zu (0x%zX) pages (%f %s for default "
|
||||
"%zuK pagesize, %f %s for %zuK pagesize).\n",
|
||||
mdbx_limits_txnsize_max(pagesize_min) / pagesize_min, mdbx_limits_txnsize_max(pagesize_min) / pagesize_min,
|
||||
mdbx_limits_txnsize_max(-1) / scale_factor, scale_unit, pagesize_default / 1024,
|
||||
mdbx_limits_txnsize_max(pagesize_max) / scale_factor, scale_unit, pagesize_max / 1024);
|
||||
printf("\tDatabase size: up to %zu pages (%f %s for default %zuK "
|
||||
"pagesize, %f %s for %zuK pagesize).\n",
|
||||
mdbx_limits_dbsize_max(pagesize_min) / pagesize_min, mdbx_limits_dbsize_max(-1) / scale_factor, scale_unit,
|
||||
pagesize_default / 1024, mdbx_limits_dbsize_max(pagesize_max) / scale_factor, scale_unit, pagesize_max / 1024);
|
||||
printf("\tMaximum sub-databases: %u.\n", MDBX_MAX_DBI);
|
||||
printf("-----\n");
|
||||
|
||||
rc = mdbx_env_create(&env);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_env_create: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
rc = mdbx_env_open(env, "./example-db", MDBX_NOSUBDIR | MDBX_LIFORECLAIM, 0664);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_env_open: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
rc = mdbx_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_txn_begin: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
rc = mdbx_dbi_open(txn, NULL, 0, &dbi);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_dbi_open: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
key.iov_len = sizeof(int);
|
||||
key.iov_base = sval;
|
||||
data.iov_len = sizeof(sval);
|
||||
data.iov_base = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = mdbx_put(txn, dbi, &key, &data, 0);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_put: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
rc = mdbx_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdbx_txn_commit: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
txn = NULL;
|
||||
|
||||
rc = mdbx_txn_begin(env, NULL, MDBX_TXN_RDONLY, &txn);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_txn_begin: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
rc = mdbx_cursor_open(txn, dbi, &cursor);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
fprintf(stderr, "mdbx_cursor_open: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
while ((rc = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n", key.iov_base, (int)key.iov_len, (char *)key.iov_base, data.iov_base,
|
||||
(int)data.iov_len, (char *)data.iov_base);
|
||||
found += 1;
|
||||
}
|
||||
if (rc != MDBX_NOTFOUND || found == 0) {
|
||||
fprintf(stderr, "mdbx_cursor_get: (%d) %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
} else {
|
||||
rc = MDBX_SUCCESS;
|
||||
}
|
||||
bailout:
|
||||
if (cursor)
|
||||
mdbx_cursor_close(cursor);
|
||||
if (txn)
|
||||
mdbx_txn_abort(txn);
|
||||
if (dbi)
|
||||
mdbx_dbi_close(env, dbi);
|
||||
if (env)
|
||||
mdbx_env_close(env);
|
||||
return (rc != MDBX_SUCCESS) ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample
|
||||
/* BerkeleyDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-mdb.txt
|
||||
* Do a line-by-line comparison of this and example-mdbx.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2015-2018 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015-2025 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2012-2015 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
@ -1,2 +0,0 @@
|
||||
// Add predefined macros for your project here. For example:
|
||||
// #define THE_ANSWER 42
|
@ -1 +0,0 @@
|
||||
[General]
|
@ -1,58 +0,0 @@
|
||||
CMakeLists.txt
|
||||
README-RU.md
|
||||
pcrf_test/CMakeLists.txt
|
||||
src/tools/CMakeLists.txt
|
||||
test/CMakeLists.txt
|
||||
tutorial/CMakeLists.txt
|
||||
tutorial/sample-mdbx.c
|
||||
AUTHORS
|
||||
LICENSE
|
||||
Makefile
|
||||
README.md
|
||||
TODO.md
|
||||
mdbx.h
|
||||
src/bits.h
|
||||
src/defs.h
|
||||
src/lck-posix.c
|
||||
src/lck-windows.c
|
||||
src/mdbx.c
|
||||
src/osal.c
|
||||
src/osal.h
|
||||
src/tools/mdbx_chk.c
|
||||
src/tools/mdbx_copy.1
|
||||
src/tools/mdbx_copy.c
|
||||
src/tools/mdbx_dump.1
|
||||
src/tools/mdbx_dump.c
|
||||
src/tools/mdbx_load.1
|
||||
src/tools/mdbx_load.c
|
||||
src/tools/mdbx_stat.1
|
||||
src/tools/mdbx_stat.c
|
||||
src/tools/wingetopt.c
|
||||
src/tools/wingetopt.h
|
||||
src/version.c
|
||||
test/actor.cc
|
||||
test/base.h
|
||||
test/chrono.cc
|
||||
test/chrono.h
|
||||
test/config.h
|
||||
test/dead.cc
|
||||
test/hill.cc
|
||||
test/jitter.cc
|
||||
test/keygen.cc
|
||||
test/keygen.h
|
||||
test/log.cc
|
||||
test/log.h
|
||||
test/main.cc
|
||||
test/config.cc
|
||||
test/cases.cc
|
||||
test/osal-unix.cc
|
||||
test/osal-windows.cc
|
||||
test/osal.h
|
||||
test/test.cc
|
||||
test/test.h
|
||||
test/try.cc
|
||||
test/utils.cc
|
||||
test/utils.h
|
||||
tutorial/README.md
|
||||
tutorial/sample-bdb.txt
|
||||
tutorial/sample-mdb.txt
|
@ -1,6 +0,0 @@
|
||||
.
|
||||
src
|
||||
src/tools
|
||||
test
|
||||
pcrf_test
|
||||
tutorial
|
97
mdbx.sln
97
mdbx.sln
@ -1,97 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dll", "dll.vcxproj", "{6D19209B-ECE7-4B9C-941C-0AA2B484F199}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0A147F9F-22D5-44E6-B389-218CFFB0C524}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mdbx_load", "src\tools\mdbx_load.vcxproj", "{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mdbx_dump", "src\tools\mdbx_dump.vcxproj", "{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mdbx_copy", "src\tools\mdbx_copy.vcxproj", "{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mdbx_chk", "src\tools\mdbx_chk.vcxproj", "{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mdbx_stat", "src\tools\mdbx_stat.vcxproj", "{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Debug|x64.Build.0 = Debug|x64
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Debug|x86.Build.0 = Debug|Win32
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Release|x64.ActiveCfg = Release|x64
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Release|x64.Build.0 = Release|x64
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Release|x86.ActiveCfg = Release|Win32
|
||||
{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}.Release|x86.Build.0 = Release|Win32
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Debug|x64.Build.0 = Debug|x64
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Debug|x86.Build.0 = Debug|Win32
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Release|x64.ActiveCfg = Release|x64
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Release|x64.Build.0 = Release|x64
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Release|x86.ActiveCfg = Release|Win32
|
||||
{6D19209B-ECE7-4B9C-941C-0AA2B484F199}.Release|x86.Build.0 = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Debug|x64.Build.0 = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Debug|x86.Build.0 = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Release|x64.ActiveCfg = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Release|x64.Build.0 = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Release|x86.ActiveCfg = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB}.Release|x86.Build.0 = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Debug|x64.Build.0 = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Debug|x86.Build.0 = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Release|x64.ActiveCfg = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Release|x64.Build.0 = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Release|x86.ActiveCfg = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC}.Release|x86.Build.0 = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Debug|x64.Build.0 = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Debug|x86.Build.0 = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Release|x64.ActiveCfg = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Release|x64.Build.0 = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Release|x86.ActiveCfg = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD}.Release|x86.Build.0 = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Debug|x64.Build.0 = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Debug|x86.Build.0 = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Release|x64.ActiveCfg = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Release|x64.Build.0 = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Release|x86.ActiveCfg = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE}.Release|x86.Build.0 = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Debug|x64.Build.0 = Debug|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Debug|x86.Build.0 = Debug|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Release|x64.ActiveCfg = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Release|x64.Build.0 = Release|x64
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Release|x86.ActiveCfg = Release|Win32
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BB} = {0A147F9F-22D5-44E6-B389-218CFFB0C524}
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BC} = {0A147F9F-22D5-44E6-B389-218CFFB0C524}
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BD} = {0A147F9F-22D5-44E6-B389-218CFFB0C524}
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BE} = {0A147F9F-22D5-44E6-B389-218CFFB0C524}
|
||||
{15030120-5F7F-48F9-ABE5-DFC814F2A4BF} = {0A147F9F-22D5-44E6-B389-218CFFB0C524}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -0,0 +1,173 @@
|
||||
From 349c08cf21b66ecea851340133a1b845c25675f7 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, 22 Apr 2025 14:38:49 +0300
|
||||
Subject: [PATCH] package/libmdbx: new package (library/database).
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This patch adds libmdbx:
|
||||
- libmdbx is one of the fastest compact embeddable key-value ACID database.
|
||||
- libmdbx has a specific set of properties and capabilities,
|
||||
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://libmdbx.dqdkfa.ru
|
||||
|
||||
The 0.13.6 "Бузина" (Elderberry) is stable release of _libmdbx_ branch with new superior features.
|
||||
|
||||
The complete ChangeLog: https://gitflic.ru/project/erthink/libmdbx/blob?file=ChangeLog.md
|
||||
|
||||
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
|
||||
---
|
||||
DEVELOPERS | 3 +++
|
||||
package/Config.in | 1 +
|
||||
package/libmdbx/Config.in | 45 ++++++++++++++++++++++++++++++++++++
|
||||
package/libmdbx/libmdbx.hash | 6 +++++
|
||||
package/libmdbx/libmdbx.mk | 42 +++++++++++++++++++++++++++++++++
|
||||
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
|
||||
|
||||
diff --git a/DEVELOPERS b/DEVELOPERS
|
||||
index 9ab1e125f4..758ff6a2d5 100644
|
||||
--- a/DEVELOPERS
|
||||
+++ b/DEVELOPERS
|
||||
@@ -1482,6 +1482,9 @@ N: Leon Anavi <leon.anavi@konsulko.com>
|
||||
F: board/olimex/a10_olinuxino
|
||||
F: configs/olimex_a10_olinuxino_lime_defconfig
|
||||
|
||||
+N: Leonid Yuriev <leo@yuriev.ru>
|
||||
+F: package/libmdbx/
|
||||
+
|
||||
N: Lionel Flandrin <lionel@svkt.org>
|
||||
F: package/python-babel/
|
||||
F: package/python-daemonize/
|
||||
diff --git a/package/Config.in b/package/Config.in
|
||||
index 016a99ed1a..a6f95bfaa9 100644
|
||||
--- a/package/Config.in
|
||||
+++ b/package/Config.in
|
||||
@@ -1372,6 +1372,7 @@ menu "Database"
|
||||
source "package/kompexsqlite/Config.in"
|
||||
source "package/leveldb/Config.in"
|
||||
source "package/libgit2/Config.in"
|
||||
+ source "package/libmdbx/Config.in"
|
||||
source "package/libodb/Config.in"
|
||||
source "package/libodb-boost/Config.in"
|
||||
source "package/libodb-mysql/Config.in"
|
||||
diff --git a/package/libmdbx/Config.in b/package/libmdbx/Config.in
|
||||
new file mode 100644
|
||||
index 0000000000..a9a4ac45c5
|
||||
--- /dev/null
|
||||
+++ b/package/libmdbx/Config.in
|
||||
@@ -0,0 +1,45 @@
|
||||
+config BR2_PACKAGE_LIBMDBX
|
||||
+ bool "libmdbx"
|
||||
+ depends on BR2_USE_MMU
|
||||
+ depends on BR2_TOOLCHAIN_HAS_SYNC_4
|
||||
+ depends on BR2_TOOLCHAIN_HAS_THREADS
|
||||
+ depends on BR2_TOOLCHAIN_GCC_AT_LEAST_4_4
|
||||
+ help
|
||||
+ One of the fastest compact key-value ACID database
|
||||
+ without WAL. libmdbx has a specific set of properties
|
||||
+ and capabilities, focused on creating unique lightweight
|
||||
+ solutions.
|
||||
+
|
||||
+ libmdbx surpasses the legendary LMDB in terms of
|
||||
+ reliability, features and performance.
|
||||
+
|
||||
+ https://libmdbx.dqdkfa.ru
|
||||
+
|
||||
+if BR2_PACKAGE_LIBMDBX
|
||||
+
|
||||
+config BR2_PACKAGE_LIBMDBX_TOOLS
|
||||
+ bool "install tools"
|
||||
+ help
|
||||
+ Install libmdbx tools for checking, dump, restore
|
||||
+ and show statistics of databases.
|
||||
+
|
||||
+config BR2_PACKAGE_LIBMDBX_CXX
|
||||
+ bool "C++ API"
|
||||
+ depends on BR2_INSTALL_LIBSTDCPP
|
||||
+ depends on BR2_TOOLCHAIN_GCC_AT_LEAST_4_8
|
||||
+ depends on !BR2_TOOLCHAIN_HAS_GCC_BUG_64735
|
||||
+ help
|
||||
+ Enable modern C++11/14/17/20 API for libmdbx.
|
||||
+
|
||||
+comment "libmdbx C++ support needs a toolchain w/ C++11, gcc >= 4.8 w/o bug#64735"
|
||||
+ depends on !BR2_INSTALL_LIBSTDCPP || \
|
||||
+ !BR2_TOOLCHAIN_GCC_AT_LEAST_4_8 || \
|
||||
+ BR2_TOOLCHAIN_HAS_GCC_BUG_64735
|
||||
+
|
||||
+endif
|
||||
+
|
||||
+comment "libmdbx needs MMU, a toolchain w/ threads, gcc >= 4.4 w/ 4-byte atomics"
|
||||
+ depends on BR2_USE_MMU
|
||||
+ depends on !BR2_TOOLCHAIN_HAS_THREADS || \
|
||||
+ !BR2_TOOLCHAIN_HAS_SYNC_4 || \
|
||||
+ !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..ae5266716b
|
||||
--- /dev/null
|
||||
+++ b/package/libmdbx/libmdbx.hash
|
||||
@@ -0,0 +1,6 @@
|
||||
+# Hashes from: https://libmdbx.dqdkfa.ru/release/SHA256SUMS
|
||||
+sha256 57db987de6f7ccc66a66ae28a7bda9f9fbb48ac5fb9279bcca92fd5de13075d1 libmdbx-amalgamated-0.13.6.tar.xz
|
||||
+
|
||||
+# Locally calculated
|
||||
+sha256 0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594 LICENSE
|
||||
+sha256 651f71b46c6bb0046d2122df7f9def9cb24f4dc28c5b11cef059f66565cda30f NOTICE
|
||||
diff --git a/package/libmdbx/libmdbx.mk b/package/libmdbx/libmdbx.mk
|
||||
new file mode 100644
|
||||
index 0000000000..571757262e
|
||||
--- /dev/null
|
||||
+++ b/package/libmdbx/libmdbx.mk
|
||||
@@ -0,0 +1,42 @@
|
||||
+################################################################################
|
||||
+#
|
||||
+# libmdbx
|
||||
+#
|
||||
+################################################################################
|
||||
+
|
||||
+LIBMDBX_VERSION = 0.13.6
|
||||
+LIBMDBX_SOURCE = libmdbx-amalgamated-$(LIBMDBX_VERSION).tar.xz
|
||||
+LIBMDBX_SITE = https://libmdbx.dqdkfa.ru/release
|
||||
+LIBMDBX_SUPPORTS_IN_SOURCE_BUILD = NO
|
||||
+LIBMDBX_LICENSE = Apache-2.0
|
||||
+LIBMDBX_LICENSE_FILES = LICENSE NOTICE
|
||||
+LIBMDBX_REDISTRIBUTE = YES
|
||||
+LIBMDBX_STRIP_COMPONENTS = 0
|
||||
+LIBMDBX_INSTALL_STAGING = YES
|
||||
+
|
||||
+# Set CMAKE_BUILD_TYPE to Release to remove -Werror and avoid a build failure
|
||||
+# with glibc < 2.12
|
||||
+LIBMDBX_CONF_OPTS = \
|
||||
+ -DCMAKE_BUILD_TYPE=Release \
|
||||
+ -DMDBX_INSTALL_MANPAGES=OFF \
|
||||
+ -DBUILD_FOR_NATIVE_CPU=OFF \
|
||||
+ -DMDBX_BUILD_CXX=$(if $(BR2_PACKAGE_LIBMDBX_CXX),ON,OFF) \
|
||||
+ -DMDBX_BUILD_TOOLS=$(if $(BR2_PACKAGE_LIBMDBX_TOOLS),ON,OFF)
|
||||
+
|
||||
+ifeq ($(BR2_STATIC_LIBS)$(BR2_SHARED_STATIC_LIBS),y)
|
||||
+LIBMDBX_CONF_OPTS += -DMDBX_INSTALL_STATIC=ON
|
||||
+else
|
||||
+LIBMDBX_CONF_OPTS += -DMDBX_INSTALL_STATIC=OFF
|
||||
+endif
|
||||
+
|
||||
+ifeq ($(BR2_SHARED_LIBS)$(BR2_SHARED_STATIC_LIBS),y)
|
||||
+LIBMDBX_CONF_OPTS += \
|
||||
+ -DMDBX_BUILD_SHARED_LIBRARY=ON \
|
||||
+ -DMDBX_LINK_TOOLS_NONSTATIC=ON
|
||||
+else
|
||||
+LIBMDBX_CONF_OPTS += \
|
||||
+ -DMDBX_BUILD_SHARED_LIBRARY=OFF \
|
||||
+ -DMDBX_LINK_TOOLS_NONSTATIC=OFF
|
||||
+endif
|
||||
+
|
||||
+$(eval $(cmake-package))
|
||||
--
|
||||
2.49.0
|
||||
|
@ -1,184 +0,0 @@
|
||||
cmake_minimum_required(VERSION 2.8.7)
|
||||
set(TARGET mdbx)
|
||||
project(${TARGET})
|
||||
|
||||
set(MDBX_VERSION_MAJOR 0)
|
||||
set(MDBX_VERSION_MINOR 2)
|
||||
set(MDBX_VERSION_RELEASE 0)
|
||||
set(MDBX_VERSION_REVISION 0)
|
||||
|
||||
set(MDBX_VERSION_STRING ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR}.${MDBX_VERSION_RELEASE})
|
||||
|
||||
enable_language(C)
|
||||
enable_language(CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED on)
|
||||
|
||||
add_definitions(-DNDEBUG=1 -DMDBX_DEBUG=0 -DLIBMDBX_EXPORTS=1 -D_GNU_SOURCE=1)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
get_directory_property(hasParent PARENT_DIRECTORY)
|
||||
if(hasParent)
|
||||
set(STANDALONE_BUILD 0)
|
||||
else()
|
||||
set(STANDALONE_BUILD 1)
|
||||
enable_testing()
|
||||
|
||||
if (CMAKE_C_COMPILER_ID MATCHES GNU)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu11")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES GNU)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpointer-arith")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-compare")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat-security")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wwrite-strings")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=20")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wunused-function -Wunused-variable -Wunused-value -Wmissing-declarations")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-qual")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -finline-functions-called-once")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-packed-bitfield-compat")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g3")
|
||||
endif()
|
||||
|
||||
if (COVERAGE)
|
||||
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
message(FATAL_ERROR "Coverage requires -DCMAKE_BUILD_TYPE=Debug Current value=${CMAKE_BUILD_TYPE}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Setting coverage compiler flags")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ggdb3 -O0 --coverage -fprofile-arcs -ftest-coverage")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -ggdb3 -O0 --coverage -fprofile-arcs -ftest-coverage")
|
||||
add_definitions(-DCOVERAGE_TEST)
|
||||
endif()
|
||||
|
||||
if (NOT TRAVIS)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fstack-protector-strong -static-libasan")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(${TARGET}_SRC
|
||||
mdbx.h
|
||||
src/bits.h
|
||||
src/defs.h
|
||||
src/lck-posix.c
|
||||
src/mdbx.c
|
||||
src/osal.c
|
||||
src/osal.h
|
||||
src/version.c
|
||||
)
|
||||
|
||||
add_library(${TARGET}_STATIC STATIC
|
||||
${${TARGET}_SRC}
|
||||
)
|
||||
|
||||
add_library(${TARGET} ALIAS ${TARGET}_STATIC)
|
||||
|
||||
add_library(${TARGET}_SHARED SHARED
|
||||
${${TARGET}_SRC}
|
||||
)
|
||||
|
||||
set_target_properties(${TARGET}_SHARED PROPERTIES
|
||||
VERSION ${MDBX_VERSION_STRING}
|
||||
SOVERSION ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR}
|
||||
OUTPUT_NAME ${TARGET}
|
||||
CLEAN_DIRECT_OUTPUT 1
|
||||
)
|
||||
|
||||
set_target_properties(${TARGET}_STATIC PROPERTIES
|
||||
VERSION ${MDBX_VERSION_STRING}
|
||||
SOVERSION ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR}
|
||||
OUTPUT_NAME ${TARGET}
|
||||
CLEAN_DIRECT_OUTPUT 1
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET}_STATIC PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(${TARGET}_SHARED PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(${TARGET}_STATIC ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_libraries(${TARGET}_SHARED ${CMAKE_THREAD_LIBS_INIT})
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(${TARGET}_STATIC rt)
|
||||
target_link_libraries(${TARGET}_SHARED rt)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${TARGET}_STATIC DESTINATION ${CMAKE_INSTALL_PREFIX}/lib64 COMPONENT mdbx)
|
||||
install(TARGETS ${TARGET}_SHARED DESTINATION ${CMAKE_INSTALL_PREFIX}/lib64 COMPONENT mdbx)
|
||||
install(FILES mdbx.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include COMPONENT mdbx-devel)
|
||||
|
||||
add_subdirectory(src/tools)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(test/pcrf)
|
||||
add_subdirectory(tutorial)
|
||||
|
||||
##############################################################################
|
||||
|
||||
set(CPACK_GENERATOR "RPM")
|
||||
set(CPACK_RPM_COMPONENT_INSTALL ON)
|
||||
|
||||
# Version
|
||||
if (NOT "$ENV{BUILD_NUMBER}" STREQUAL "")
|
||||
set(CPACK_PACKAGE_RELEASE $ENV{BUILD_NUMBER})
|
||||
else()
|
||||
if (NOT "$ENV{CI_PIPELINE_ID}" STREQUAL "")
|
||||
set(CPACK_PACKAGE_RELEASE $ENV{CI_PIPELINE_ID})
|
||||
else()
|
||||
set(CPACK_PACKAGE_RELEASE 1)
|
||||
endif()
|
||||
endif()
|
||||
set(CPACK_RPM_PACKAGE_RELEASE ${CPACK_PACKAGE_RELEASE})
|
||||
|
||||
set(CPACK_PACKAGE_VERSION ${MDBX_VERSION_STRING})
|
||||
set(CPACK_PACKAGE_VERSION_FULL ${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE})
|
||||
|
||||
set(CPACK_RPM_mdbx-devel_PACKAGE_REQUIRES "mdbx = ${CPACK_PACKAGE_VERSION}")
|
||||
|
||||
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true")
|
||||
set(CPACK_RPM_mdbx_PACKAGE_NAME mdbx)
|
||||
set(CPACK_RPM_mdbx-devel_PACKAGE_NAME mdbx-devel)
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The revised and extended descendant of Symas LMDB")
|
||||
|
||||
set(CPACK_PACKAGE_VENDOR "???")
|
||||
set(CPACK_PACKAGE_CONTACT "Vladimir Romanov")
|
||||
set(CPACK_PACKAGE_RELOCATABLE false)
|
||||
set(CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Applications/Database")
|
||||
|
||||
set(CPACK_RPM_mdbx_FILE_NAME "${CPACK_RPM_mdbx_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_FULL}.${CPACK_RPM_PACKAGE_ARCHITECTURE}.rpm")
|
||||
set(CPACK_RPM_mdbx-devel_FILE_NAME "${CPACK_RPM_mdbx-devel_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_FULL}.${CPACK_RPM_PACKAGE_ARCHITECTURE}.rpm")
|
||||
|
||||
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
|
||||
/usr/local
|
||||
/usr/local/bin
|
||||
/usr/local/lib64
|
||||
/usr/local/include
|
||||
/usr/local/man
|
||||
/usr/local/man/man1
|
||||
)
|
||||
|
||||
include(CPack)
|
@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
CONFIG=$1
|
||||
|
||||
if [[ -z "${CONFIG}" ]]; then
|
||||
CONFIG=Debug
|
||||
fi
|
||||
if [[ -r /opt/rh/devtoolset-6/enable ]]; then
|
||||
source /opt/rh/devtoolset-6/enable
|
||||
fi
|
||||
#rm -f -r build || true
|
||||
mkdir -p cmake-build-${CONFIG}
|
||||
pushd cmake-build-${CONFIG} &> /dev/null
|
||||
if [[ ! -r Makefile ]]; then
|
||||
cmake .. -DCMAKE_BUILD_TYPE=${CONFIG}
|
||||
fi
|
||||
make -j8 || exit 1
|
||||
popd &> /dev/null
|
@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG=$1
|
||||
|
||||
if [[ -z "${CONFIG}" ]]; then
|
||||
CONFIG=Debug
|
||||
fi
|
||||
|
||||
DIRNAME=`dirname ${BASH_SOURCE[0]}`
|
||||
DIRNAME=`readlink --canonicalize ${DIRNAME}`
|
||||
|
||||
if [[ -r /opt/rh/devtoolset-6/enable ]]; then
|
||||
source /opt/rh/devtoolset-6/enable
|
||||
fi
|
||||
|
||||
mkdir -p cmake-build-${CONFIG}
|
||||
pushd cmake-build-${CONFIG} &> /dev/null
|
||||
if [[ ! -r Makefile ]]; then
|
||||
cmake .. -DCMAKE_BUILD_TYPE=${CONFIG}
|
||||
fi
|
||||
rm -f *.rpm
|
||||
make -j8 package || exit 1
|
||||
rm -f *-Unspecified.rpm
|
||||
popd &> /dev/null
|
58
src/alloy.c
Normal file
58
src/alloy.c
Normal file
@ -0,0 +1,58 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#define xMDBX_ALLOY 1 /* alloyed build */
|
||||
#include "internals.h" /* must be included first */
|
||||
|
||||
#include "api-cold.c"
|
||||
#include "api-copy.c"
|
||||
#include "api-cursor.c"
|
||||
#include "api-dbi.c"
|
||||
#include "api-env.c"
|
||||
#include "api-extra.c"
|
||||
#include "api-key-transform.c"
|
||||
#include "api-misc.c"
|
||||
#include "api-opts.c"
|
||||
#include "api-range-estimate.c"
|
||||
#include "api-txn-data.c"
|
||||
#include "api-txn.c"
|
||||
#include "audit.c"
|
||||
#include "chk.c"
|
||||
#include "cogs.c"
|
||||
#include "coherency.c"
|
||||
#include "cursor.c"
|
||||
#include "dbi.c"
|
||||
#include "dpl.c"
|
||||
#include "dxb.c"
|
||||
#include "env.c"
|
||||
#include "gc-get.c"
|
||||
#include "gc-put.c"
|
||||
#include "global.c"
|
||||
#include "lck-posix.c"
|
||||
#include "lck-windows.c"
|
||||
#include "lck.c"
|
||||
#include "logging_and_debug.c"
|
||||
#include "meta.c"
|
||||
#include "mvcc-readers.c"
|
||||
#include "node.c"
|
||||
#include "osal.c"
|
||||
#include "page-get.c"
|
||||
#include "page-iov.c"
|
||||
#include "page-ops.c"
|
||||
#include "pnl.c"
|
||||
#include "refund.c"
|
||||
#include "rkl.c"
|
||||
#include "spill.c"
|
||||
#include "table.c"
|
||||
#include "tls.c"
|
||||
#include "tree-ops.c"
|
||||
#include "tree-search.c"
|
||||
#include "txl.c"
|
||||
#include "txn-basal.c"
|
||||
#include "txn-nested.c"
|
||||
#include "txn-ro.c"
|
||||
#include "txn.c"
|
||||
#include "utils.c"
|
||||
#include "version.c"
|
||||
#include "walk.c"
|
||||
#include "windows-import.c"
|
543
src/api-cold.c
Normal file
543
src/api-cold.c
Normal file
@ -0,0 +1,543 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
__cold size_t mdbx_default_pagesize(void) {
|
||||
size_t pagesize = globals.sys_pagesize;
|
||||
ENSURE(nullptr, is_powerof2(pagesize));
|
||||
pagesize = (pagesize >= MDBX_MIN_PAGESIZE) ? pagesize : MDBX_MIN_PAGESIZE;
|
||||
pagesize = (pagesize <= MDBX_MAX_PAGESIZE) ? pagesize : MDBX_MAX_PAGESIZE;
|
||||
return pagesize;
|
||||
}
|
||||
|
||||
__cold intptr_t mdbx_limits_dbsize_min(intptr_t pagesize) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
else if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
return MIN_PAGENO * pagesize;
|
||||
}
|
||||
|
||||
__cold intptr_t mdbx_limits_dbsize_max(intptr_t pagesize) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
else if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
STATIC_ASSERT(MAX_MAPSIZE < INTPTR_MAX);
|
||||
const uint64_t limit = (1 + (uint64_t)MAX_PAGENO) * pagesize;
|
||||
return (limit < MAX_MAPSIZE) ? (intptr_t)limit : (intptr_t)MAX_MAPSIZE;
|
||||
}
|
||||
|
||||
__cold intptr_t mdbx_limits_txnsize_max(intptr_t pagesize) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
else if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
STATIC_ASSERT(MAX_MAPSIZE < INTPTR_MAX);
|
||||
const uint64_t pgl_limit = pagesize * (uint64_t)(PAGELIST_LIMIT / MDBX_GOLD_RATIO_DBL);
|
||||
const uint64_t map_limit = (uint64_t)(MAX_MAPSIZE / MDBX_GOLD_RATIO_DBL);
|
||||
return (pgl_limit < map_limit) ? (intptr_t)pgl_limit : (intptr_t)map_limit;
|
||||
}
|
||||
|
||||
__cold intptr_t mdbx_limits_keysize_max(intptr_t pagesize, MDBX_db_flags_t flags) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
return keysize_max(pagesize, flags);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_maxkeysize_ex(const MDBX_env *env, MDBX_db_flags_t flags) {
|
||||
if (unlikely(!env || env->signature.weak != env_signature))
|
||||
return -1;
|
||||
|
||||
return (int)mdbx_limits_keysize_max((intptr_t)env->ps, flags);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_maxkeysize(const MDBX_env *env) { return mdbx_env_get_maxkeysize_ex(env, MDBX_DUPSORT); }
|
||||
|
||||
__cold intptr_t mdbx_limits_keysize_min(MDBX_db_flags_t flags) { return keysize_min(flags); }
|
||||
|
||||
__cold intptr_t mdbx_limits_valsize_max(intptr_t pagesize, MDBX_db_flags_t flags) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
return valsize_max(pagesize, flags);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_maxvalsize_ex(const MDBX_env *env, MDBX_db_flags_t flags) {
|
||||
if (unlikely(!env || env->signature.weak != env_signature))
|
||||
return -1;
|
||||
|
||||
return (int)mdbx_limits_valsize_max((intptr_t)env->ps, flags);
|
||||
}
|
||||
|
||||
__cold intptr_t mdbx_limits_valsize_min(MDBX_db_flags_t flags) { return valsize_min(flags); }
|
||||
|
||||
__cold intptr_t mdbx_limits_pairsize4page_max(intptr_t pagesize, MDBX_db_flags_t flags) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
if (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP))
|
||||
return BRANCH_NODE_MAX(pagesize) - NODESIZE;
|
||||
|
||||
return LEAF_NODE_MAX(pagesize) - NODESIZE;
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_pairsize4page_max(const MDBX_env *env, MDBX_db_flags_t flags) {
|
||||
if (unlikely(!env || env->signature.weak != env_signature))
|
||||
return -1;
|
||||
|
||||
return (int)mdbx_limits_pairsize4page_max((intptr_t)env->ps, flags);
|
||||
}
|
||||
|
||||
__cold intptr_t mdbx_limits_valsize4page_max(intptr_t pagesize, MDBX_db_flags_t flags) {
|
||||
if (pagesize < 1)
|
||||
pagesize = (intptr_t)mdbx_default_pagesize();
|
||||
if (unlikely(pagesize < (intptr_t)MDBX_MIN_PAGESIZE || pagesize > (intptr_t)MDBX_MAX_PAGESIZE ||
|
||||
!is_powerof2((size_t)pagesize)))
|
||||
return -1;
|
||||
|
||||
if (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP))
|
||||
return valsize_max(pagesize, flags);
|
||||
|
||||
return PAGESPACE(pagesize);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_valsize4page_max(const MDBX_env *env, MDBX_db_flags_t flags) {
|
||||
if (unlikely(!env || env->signature.weak != env_signature))
|
||||
return -1;
|
||||
|
||||
return (int)mdbx_limits_valsize4page_max((intptr_t)env->ps, flags);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
static size_t estimate_rss(size_t database_bytes) {
|
||||
return database_bytes + database_bytes / 64 + (512 + MDBX_WORDBITS * 16) * MEGABYTE;
|
||||
}
|
||||
|
||||
__cold int mdbx_env_warmup(const MDBX_env *env, const MDBX_txn *txn, MDBX_warmup_flags_t flags,
|
||||
unsigned timeout_seconds_16dot16) {
|
||||
if (unlikely(env == nullptr && txn == nullptr))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
if (unlikely(flags > (MDBX_warmup_force | MDBX_warmup_oomsafe | MDBX_warmup_lock | MDBX_warmup_touchlimit |
|
||||
MDBX_warmup_release)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (txn) {
|
||||
int err = check_txn(txn, MDBX_TXN_FINISHED | MDBX_TXN_ERROR);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
}
|
||||
if (env) {
|
||||
int err = check_env(env, false);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
if (txn && unlikely(txn->env != env))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
} else {
|
||||
env = txn->env;
|
||||
}
|
||||
|
||||
const uint64_t timeout_monotime = (timeout_seconds_16dot16 && (flags & MDBX_warmup_force))
|
||||
? osal_monotime() + osal_16dot16_to_monotime(timeout_seconds_16dot16)
|
||||
: 0;
|
||||
|
||||
if (flags & MDBX_warmup_release)
|
||||
munlock_all(env);
|
||||
|
||||
pgno_t used_pgno;
|
||||
if (txn) {
|
||||
used_pgno = txn->geo.first_unallocated;
|
||||
} else {
|
||||
const troika_t troika = meta_tap(env);
|
||||
used_pgno = meta_recent(env, &troika).ptr_v->geometry.first_unallocated;
|
||||
}
|
||||
const size_t used_range = pgno_align2os_bytes(env, used_pgno);
|
||||
const pgno_t mlock_pgno = bytes2pgno(env, used_range);
|
||||
|
||||
int rc = MDBX_SUCCESS;
|
||||
if (flags & MDBX_warmup_touchlimit) {
|
||||
const size_t estimated_rss = estimate_rss(used_range);
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
SIZE_T current_ws_lower, current_ws_upper;
|
||||
if (GetProcessWorkingSetSize(GetCurrentProcess(), ¤t_ws_lower, ¤t_ws_upper) &&
|
||||
current_ws_lower < estimated_rss) {
|
||||
const SIZE_T ws_lower = estimated_rss;
|
||||
const SIZE_T ws_upper =
|
||||
(MDBX_WORDBITS == 32 && ws_lower > MEGABYTE * 2048) ? ws_lower : ws_lower + MDBX_WORDBITS * MEGABYTE * 32;
|
||||
if (!SetProcessWorkingSetSize(GetCurrentProcess(), ws_lower, ws_upper)) {
|
||||
rc = (int)GetLastError();
|
||||
WARNING("SetProcessWorkingSetSize(%zu, %zu) error %d", ws_lower, ws_upper, rc);
|
||||
}
|
||||
}
|
||||
#endif /* Windows */
|
||||
#ifdef RLIMIT_RSS
|
||||
struct rlimit rss;
|
||||
if (getrlimit(RLIMIT_RSS, &rss) == 0 && rss.rlim_cur < estimated_rss) {
|
||||
rss.rlim_cur = estimated_rss;
|
||||
if (rss.rlim_max < estimated_rss)
|
||||
rss.rlim_max = estimated_rss;
|
||||
if (setrlimit(RLIMIT_RSS, &rss)) {
|
||||
rc = errno;
|
||||
WARNING("setrlimit(%s, {%zu, %zu}) error %d", "RLIMIT_RSS", (size_t)rss.rlim_cur, (size_t)rss.rlim_max, rc);
|
||||
}
|
||||
}
|
||||
#endif /* RLIMIT_RSS */
|
||||
#ifdef RLIMIT_MEMLOCK
|
||||
if (flags & MDBX_warmup_lock) {
|
||||
struct rlimit memlock;
|
||||
if (getrlimit(RLIMIT_MEMLOCK, &memlock) == 0 && memlock.rlim_cur < estimated_rss) {
|
||||
memlock.rlim_cur = estimated_rss;
|
||||
if (memlock.rlim_max < estimated_rss)
|
||||
memlock.rlim_max = estimated_rss;
|
||||
if (setrlimit(RLIMIT_MEMLOCK, &memlock)) {
|
||||
rc = errno;
|
||||
WARNING("setrlimit(%s, {%zu, %zu}) error %d", "RLIMIT_MEMLOCK", (size_t)memlock.rlim_cur,
|
||||
(size_t)memlock.rlim_max, rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* RLIMIT_MEMLOCK */
|
||||
(void)estimated_rss;
|
||||
}
|
||||
|
||||
#if defined(MLOCK_ONFAULT) && \
|
||||
((defined(_GNU_SOURCE) && __GLIBC_PREREQ(2, 27)) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 30)) && \
|
||||
(defined(__linux__) || defined(__gnu_linux__))
|
||||
if ((flags & MDBX_warmup_lock) != 0 && globals.linux_kernel_version >= 0x04040000 &&
|
||||
atomic_load32(&env->mlocked_pgno, mo_AcquireRelease) < mlock_pgno) {
|
||||
if (mlock2(env->dxb_mmap.base, used_range, MLOCK_ONFAULT)) {
|
||||
rc = errno;
|
||||
WARNING("mlock2(%zu, %s) error %d", used_range, "MLOCK_ONFAULT", rc);
|
||||
} else {
|
||||
update_mlcnt(env, mlock_pgno, true);
|
||||
rc = MDBX_SUCCESS;
|
||||
}
|
||||
if (rc != EINVAL)
|
||||
flags -= MDBX_warmup_lock;
|
||||
}
|
||||
#endif /* MLOCK_ONFAULT */
|
||||
|
||||
int err = MDBX_ENOSYS;
|
||||
err = dxb_set_readahead(env, used_pgno, true, true);
|
||||
if (err != MDBX_SUCCESS && rc == MDBX_SUCCESS)
|
||||
rc = err;
|
||||
|
||||
if ((flags & MDBX_warmup_force) != 0 && (rc == MDBX_SUCCESS || rc == MDBX_ENOSYS)) {
|
||||
const volatile uint8_t *ptr = env->dxb_mmap.base;
|
||||
size_t offset = 0, unused = 42;
|
||||
#if !(defined(_WIN32) || defined(_WIN64))
|
||||
if (flags & MDBX_warmup_oomsafe) {
|
||||
const int null_fd = open("/dev/null", O_WRONLY);
|
||||
if (unlikely(null_fd < 0))
|
||||
rc = errno;
|
||||
else {
|
||||
struct iovec iov[MDBX_AUXILARY_IOV_MAX];
|
||||
for (;;) {
|
||||
unsigned i;
|
||||
for (i = 0; i < MDBX_AUXILARY_IOV_MAX && offset < used_range; ++i) {
|
||||
iov[i].iov_base = (void *)(ptr + offset);
|
||||
iov[i].iov_len = 1;
|
||||
offset += globals.sys_pagesize;
|
||||
}
|
||||
if (unlikely(writev(null_fd, iov, i) < 0)) {
|
||||
rc = errno;
|
||||
if (rc == EFAULT)
|
||||
rc = ENOMEM;
|
||||
break;
|
||||
}
|
||||
if (offset >= used_range) {
|
||||
rc = MDBX_SUCCESS;
|
||||
break;
|
||||
}
|
||||
if (timeout_seconds_16dot16 && osal_monotime() > timeout_monotime) {
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(null_fd);
|
||||
}
|
||||
} else
|
||||
#endif /* Windows */
|
||||
for (;;) {
|
||||
unused += ptr[offset];
|
||||
offset += globals.sys_pagesize;
|
||||
if (offset >= used_range) {
|
||||
rc = MDBX_SUCCESS;
|
||||
break;
|
||||
}
|
||||
if (timeout_seconds_16dot16 && osal_monotime() > timeout_monotime) {
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
(void)unused;
|
||||
}
|
||||
|
||||
if ((flags & MDBX_warmup_lock) != 0 && (rc == MDBX_SUCCESS || rc == MDBX_ENOSYS) &&
|
||||
atomic_load32(&env->mlocked_pgno, mo_AcquireRelease) < mlock_pgno) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if (VirtualLock(env->dxb_mmap.base, used_range)) {
|
||||
update_mlcnt(env, mlock_pgno, true);
|
||||
rc = MDBX_SUCCESS;
|
||||
} else {
|
||||
rc = (int)GetLastError();
|
||||
WARNING("%s(%zu) error %d", "VirtualLock", used_range, rc);
|
||||
}
|
||||
#elif defined(_POSIX_MEMLOCK_RANGE)
|
||||
if (mlock(env->dxb_mmap.base, used_range) == 0) {
|
||||
update_mlcnt(env, mlock_pgno, true);
|
||||
rc = MDBX_SUCCESS;
|
||||
} else {
|
||||
rc = errno;
|
||||
WARNING("%s(%zu) error %d", "mlock", used_range, rc);
|
||||
}
|
||||
#else
|
||||
rc = MDBX_ENOSYS;
|
||||
#endif
|
||||
}
|
||||
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
__cold int mdbx_env_get_fd(const MDBX_env *env, mdbx_filehandle_t *arg) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!arg))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
*arg = env->lazy_fd;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold int mdbx_env_set_flags(MDBX_env *env, MDBX_env_flags_t flags, bool onoff) {
|
||||
int rc = check_env(env, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(flags & ((env->flags & ENV_ACTIVE) ? ~ENV_CHANGEABLE_FLAGS : ~ENV_USABLE_FLAGS)))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
|
||||
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);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
should_unlock = true;
|
||||
}
|
||||
|
||||
if (onoff)
|
||||
env->flags = combine_durability_flags(env->flags, flags);
|
||||
else
|
||||
env->flags &= ~flags;
|
||||
|
||||
if (should_unlock)
|
||||
lck_txn_unlock(env);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_flags(const MDBX_env *env, unsigned *flags) {
|
||||
int rc = check_env(env, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!flags))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
*flags = env->flags & ENV_USABLE_FLAGS;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold int mdbx_env_set_userctx(MDBX_env *env, void *ctx) {
|
||||
int rc = check_env(env, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
env->userctx = ctx;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold void *mdbx_env_get_userctx(const MDBX_env *env) { return env ? env->userctx : nullptr; }
|
||||
|
||||
__cold int mdbx_env_set_assert(MDBX_env *env, MDBX_assert_func *func) {
|
||||
int rc = check_env(env, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
#if MDBX_DEBUG
|
||||
env->assert_func = func;
|
||||
return MDBX_SUCCESS;
|
||||
#else
|
||||
(void)func;
|
||||
return LOG_IFERR(MDBX_ENOSYS);
|
||||
#endif
|
||||
}
|
||||
|
||||
__cold int mdbx_env_set_hsr(MDBX_env *env, MDBX_hsr_func *hsr) {
|
||||
int rc = check_env(env, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
env->hsr_callback = hsr;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold MDBX_hsr_func *mdbx_env_get_hsr(const MDBX_env *env) {
|
||||
return likely(env && env->signature.weak == env_signature) ? env->hsr_callback : nullptr;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
__cold int mdbx_env_get_pathW(const MDBX_env *env, const wchar_t **arg) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!arg))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
*arg = env->pathname.specified;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
#endif /* Windows */
|
||||
|
||||
__cold int mdbx_env_get_path(const MDBX_env *env, const char **arg) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!arg))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if (!env->pathname_char) {
|
||||
*arg = nullptr;
|
||||
DWORD flags = /* WC_ERR_INVALID_CHARS */ 0x80;
|
||||
size_t mb_len =
|
||||
WideCharToMultiByte(CP_THREAD_ACP, flags, env->pathname.specified, -1, nullptr, 0, nullptr, nullptr);
|
||||
rc = mb_len ? MDBX_SUCCESS : (int)GetLastError();
|
||||
if (rc == ERROR_INVALID_FLAGS) {
|
||||
mb_len = WideCharToMultiByte(CP_THREAD_ACP, flags = 0, env->pathname.specified, -1, nullptr, 0, nullptr, nullptr);
|
||||
rc = mb_len ? MDBX_SUCCESS : (int)GetLastError();
|
||||
}
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
char *const mb_pathname = osal_malloc(mb_len);
|
||||
if (!mb_pathname)
|
||||
return LOG_IFERR(MDBX_ENOMEM);
|
||||
if (mb_len != (size_t)WideCharToMultiByte(CP_THREAD_ACP, flags, env->pathname.specified, -1, mb_pathname,
|
||||
(int)mb_len, nullptr, nullptr)) {
|
||||
rc = (int)GetLastError();
|
||||
osal_free(mb_pathname);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
if (env->pathname_char ||
|
||||
InterlockedCompareExchangePointer((PVOID volatile *)&env->pathname_char, mb_pathname, nullptr))
|
||||
osal_free(mb_pathname);
|
||||
}
|
||||
*arg = env->pathname_char;
|
||||
#else
|
||||
*arg = env->pathname.specified;
|
||||
#endif /* Windows */
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Legacy API */
|
||||
|
||||
#ifndef LIBMDBX_NO_EXPORTS_LEGACY_API
|
||||
|
||||
LIBMDBX_API int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, MDBX_txn_flags_t flags, MDBX_txn **ret) {
|
||||
return __inline_mdbx_txn_begin(env, parent, flags, ret);
|
||||
}
|
||||
|
||||
LIBMDBX_API int mdbx_txn_commit(MDBX_txn *txn) { return __inline_mdbx_txn_commit(txn); }
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_stat(const MDBX_env *env, MDBX_stat *stat, size_t bytes) {
|
||||
return __inline_mdbx_env_stat(env, stat, bytes);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_info(const MDBX_env *env, MDBX_envinfo *info, size_t bytes) {
|
||||
return __inline_mdbx_env_info(env, info, bytes);
|
||||
}
|
||||
|
||||
LIBMDBX_API int mdbx_dbi_flags(const MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags) {
|
||||
return __inline_mdbx_dbi_flags(txn, dbi, flags);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_sync(MDBX_env *env) { return __inline_mdbx_env_sync(env); }
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_sync_poll(MDBX_env *env) { return __inline_mdbx_env_sync_poll(env); }
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_close(MDBX_env *env) { return __inline_mdbx_env_close(env); }
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_set_mapsize(MDBX_env *env, size_t size) {
|
||||
return __inline_mdbx_env_set_mapsize(env, size);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_set_maxdbs(MDBX_env *env, MDBX_dbi dbs) {
|
||||
return __inline_mdbx_env_set_maxdbs(env, dbs);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_get_maxdbs(const MDBX_env *env, MDBX_dbi *dbs) {
|
||||
return __inline_mdbx_env_get_maxdbs(env, dbs);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_set_maxreaders(MDBX_env *env, unsigned readers) {
|
||||
return __inline_mdbx_env_set_maxreaders(env, readers);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_get_maxreaders(const MDBX_env *env, unsigned *readers) {
|
||||
return __inline_mdbx_env_get_maxreaders(env, readers);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_set_syncbytes(MDBX_env *env, size_t threshold) {
|
||||
return __inline_mdbx_env_set_syncbytes(env, threshold);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_get_syncbytes(const MDBX_env *env, size_t *threshold) {
|
||||
return __inline_mdbx_env_get_syncbytes(env, threshold);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_set_syncperiod(MDBX_env *env, unsigned seconds_16dot16) {
|
||||
return __inline_mdbx_env_set_syncperiod(env, seconds_16dot16);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold int mdbx_env_get_syncperiod(const MDBX_env *env, unsigned *seconds_16dot16) {
|
||||
return __inline_mdbx_env_get_syncperiod(env, seconds_16dot16);
|
||||
}
|
||||
|
||||
LIBMDBX_API __cold uint64_t mdbx_key_from_int64(const int64_t i64) { return __inline_mdbx_key_from_int64(i64); }
|
||||
|
||||
LIBMDBX_API __cold uint32_t mdbx_key_from_int32(const int32_t i32) { return __inline_mdbx_key_from_int32(i32); }
|
||||
|
||||
LIBMDBX_API __cold intptr_t mdbx_limits_pgsize_min(void) { return __inline_mdbx_limits_pgsize_min(); }
|
||||
|
||||
LIBMDBX_API __cold intptr_t mdbx_limits_pgsize_max(void) { return __inline_mdbx_limits_pgsize_max(); }
|
||||
|
||||
#endif /* LIBMDBX_NO_EXPORTS_LEGACY_API */
|
880
src/api-copy.c
Normal file
880
src/api-copy.c
Normal file
@ -0,0 +1,880 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \note Please refer to the COPYRIGHT file for explanations license change,
|
||||
/// credits and acknowledgments.
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
typedef struct compacting_context {
|
||||
MDBX_env *env;
|
||||
MDBX_txn *txn;
|
||||
MDBX_copy_flags_t flags;
|
||||
pgno_t first_unallocated;
|
||||
osal_condpair_t condpair;
|
||||
volatile unsigned head;
|
||||
volatile unsigned tail;
|
||||
uint8_t *write_buf[2];
|
||||
size_t write_len[2];
|
||||
/* Error code. Never cleared if set. Both threads can set nonzero
|
||||
* to fail the copy. Not mutex-protected, expects atomic int. */
|
||||
volatile int error;
|
||||
mdbx_filehandle_t fd;
|
||||
} ctx_t;
|
||||
|
||||
__cold static int compacting_walk_tree(ctx_t *ctx, tree_t *tree);
|
||||
|
||||
/* Dedicated writer thread for compacting copy. */
|
||||
__cold static THREAD_RESULT THREAD_CALL compacting_write_thread(void *arg) {
|
||||
ctx_t *const ctx = arg;
|
||||
|
||||
#if defined(EPIPE) && !(defined(_WIN32) || defined(_WIN64))
|
||||
sigset_t sigset;
|
||||
sigemptyset(&sigset);
|
||||
sigaddset(&sigset, SIGPIPE);
|
||||
ctx->error = pthread_sigmask(SIG_BLOCK, &sigset, nullptr);
|
||||
#endif /* EPIPE */
|
||||
|
||||
osal_condpair_lock(&ctx->condpair);
|
||||
while (!ctx->error) {
|
||||
while (ctx->tail == ctx->head && !ctx->error) {
|
||||
int err = osal_condpair_wait(&ctx->condpair, true);
|
||||
if (err != MDBX_SUCCESS) {
|
||||
ctx->error = err;
|
||||
goto bailout;
|
||||
}
|
||||
}
|
||||
const unsigned toggle = ctx->tail & 1;
|
||||
size_t wsize = ctx->write_len[toggle];
|
||||
if (wsize == 0) {
|
||||
ctx->tail += 1;
|
||||
break /* EOF */;
|
||||
}
|
||||
ctx->write_len[toggle] = 0;
|
||||
uint8_t *ptr = ctx->write_buf[toggle];
|
||||
if (!ctx->error) {
|
||||
int err = osal_write(ctx->fd, ptr, wsize);
|
||||
if (err != MDBX_SUCCESS) {
|
||||
#if defined(EPIPE) && !(defined(_WIN32) || defined(_WIN64))
|
||||
if (err == EPIPE) {
|
||||
/* Collect the pending SIGPIPE,
|
||||
* otherwise at least OS X gives it to the process on thread-exit. */
|
||||
int unused;
|
||||
sigwait(&sigset, &unused);
|
||||
}
|
||||
#endif /* EPIPE */
|
||||
ctx->error = err;
|
||||
goto bailout;
|
||||
}
|
||||
}
|
||||
ctx->tail += 1;
|
||||
osal_condpair_signal(&ctx->condpair, false);
|
||||
}
|
||||
bailout:
|
||||
osal_condpair_unlock(&ctx->condpair);
|
||||
return (THREAD_RESULT)0;
|
||||
}
|
||||
|
||||
/* Give buffer and/or MDBX_EOF to writer thread, await unused buffer. */
|
||||
__cold static int compacting_toggle_write_buffers(ctx_t *ctx) {
|
||||
osal_condpair_lock(&ctx->condpair);
|
||||
eASSERT(ctx->env, ctx->head - ctx->tail < 2 || ctx->error);
|
||||
ctx->head += 1;
|
||||
osal_condpair_signal(&ctx->condpair, true);
|
||||
while (!ctx->error && ctx->head - ctx->tail == 2 /* both buffers in use */) {
|
||||
if (ctx->flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(ctx->txn, false);
|
||||
int err = osal_condpair_wait(&ctx->condpair, false);
|
||||
if (err == MDBX_SUCCESS && (ctx->flags & MDBX_CP_THROTTLE_MVCC) != 0)
|
||||
err = mdbx_txn_unpark(ctx->txn, false);
|
||||
if (err != MDBX_SUCCESS)
|
||||
ctx->error = err;
|
||||
}
|
||||
osal_condpair_unlock(&ctx->condpair);
|
||||
return ctx->error;
|
||||
}
|
||||
|
||||
static int compacting_put_bytes(ctx_t *ctx, const void *src, size_t bytes, pgno_t pgno, pgno_t npages) {
|
||||
assert(pgno == 0 || bytes > PAGEHDRSZ);
|
||||
while (bytes > 0) {
|
||||
const size_t side = ctx->head & 1;
|
||||
const size_t left = MDBX_ENVCOPY_WRITEBUF - ctx->write_len[side];
|
||||
if (left < (pgno ? PAGEHDRSZ : 1)) {
|
||||
int err = compacting_toggle_write_buffers(ctx);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
const size_t chunk = (bytes < left) ? bytes : left;
|
||||
void *const dst = ctx->write_buf[side] + ctx->write_len[side];
|
||||
if (src) {
|
||||
memcpy(dst, src, chunk);
|
||||
if (pgno) {
|
||||
assert(chunk > PAGEHDRSZ);
|
||||
page_t *mp = dst;
|
||||
mp->pgno = pgno;
|
||||
if (mp->txnid == 0)
|
||||
mp->txnid = ctx->txn->txnid;
|
||||
if (mp->flags == P_LARGE) {
|
||||
assert(bytes <= pgno2bytes(ctx->env, npages));
|
||||
mp->pages = npages;
|
||||
}
|
||||
pgno = 0;
|
||||
}
|
||||
src = ptr_disp(src, chunk);
|
||||
} else
|
||||
memset(dst, 0, chunk);
|
||||
bytes -= chunk;
|
||||
ctx->write_len[side] += chunk;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static int compacting_put_page(ctx_t *ctx, const page_t *mp, const size_t head_bytes, const size_t tail_bytes,
|
||||
const pgno_t npages) {
|
||||
if (tail_bytes) {
|
||||
assert(head_bytes + tail_bytes <= ctx->env->ps);
|
||||
assert(npages == 1 && (page_type(mp) == P_BRANCH || page_type(mp) == P_LEAF));
|
||||
} else {
|
||||
assert(head_bytes <= pgno2bytes(ctx->env, npages));
|
||||
assert((npages == 1 && page_type(mp) == (P_LEAF | P_DUPFIX)) || page_type(mp) == P_LARGE);
|
||||
}
|
||||
|
||||
const pgno_t pgno = ctx->first_unallocated;
|
||||
ctx->first_unallocated += npages;
|
||||
int err = compacting_put_bytes(ctx, mp, head_bytes, pgno, npages);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
err = compacting_put_bytes(ctx, nullptr, pgno2bytes(ctx->env, npages) - (head_bytes + tail_bytes), 0, 0);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
return compacting_put_bytes(ctx, ptr_disp(mp, ctx->env->ps - tail_bytes), tail_bytes, 0, 0);
|
||||
}
|
||||
|
||||
__cold static int compacting_walk(ctx_t *ctx, MDBX_cursor *mc, pgno_t *const parent_pgno, txnid_t parent_txnid) {
|
||||
mc->top = 0;
|
||||
mc->ki[0] = 0;
|
||||
int rc = page_get(mc, *parent_pgno, &mc->pg[0], parent_txnid);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
rc = tree_search_finalize(mc, nullptr, Z_FIRST);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
/* Make cursor pages writable */
|
||||
const intptr_t deep_limit = mc->top + 1;
|
||||
void *const buf = osal_malloc(pgno2bytes(ctx->env, deep_limit + 1));
|
||||
if (buf == nullptr)
|
||||
return MDBX_ENOMEM;
|
||||
|
||||
void *ptr = buf;
|
||||
for (intptr_t i = 0; i <= mc->top; i++) {
|
||||
page_copy(ptr, mc->pg[i], ctx->env->ps);
|
||||
mc->pg[i] = ptr;
|
||||
ptr = ptr_disp(ptr, ctx->env->ps);
|
||||
}
|
||||
/* This is writable space for a leaf page. Usually not needed. */
|
||||
page_t *const leaf = ptr;
|
||||
|
||||
while (mc->top >= 0) {
|
||||
page_t *mp = mc->pg[mc->top];
|
||||
const size_t nkeys = page_numkeys(mp);
|
||||
if (is_leaf(mp)) {
|
||||
if (!(mc->flags & z_inner) /* may have nested N_TREE or N_BIG nodes */) {
|
||||
for (size_t i = 0; i < nkeys; i++) {
|
||||
node_t *node = page_node(mp, i);
|
||||
if (node_flags(node) == N_BIG) {
|
||||
/* Need writable leaf */
|
||||
if (mp != leaf) {
|
||||
mc->pg[mc->top] = leaf;
|
||||
page_copy(leaf, mp, ctx->env->ps);
|
||||
mp = leaf;
|
||||
node = page_node(mp, i);
|
||||
}
|
||||
|
||||
const pgr_t lp = page_get_large(mc, node_largedata_pgno(node), mp->txnid);
|
||||
if (unlikely((rc = lp.err) != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
const size_t datasize = node_ds(node);
|
||||
const pgno_t npages = largechunk_npages(ctx->env, datasize);
|
||||
poke_pgno(node_data(node), ctx->first_unallocated);
|
||||
rc = compacting_put_page(ctx, lp.page, PAGEHDRSZ + datasize, 0, npages);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
} else if (node_flags(node) & N_TREE) {
|
||||
if (!MDBX_DISABLE_VALIDATION && unlikely(node_ds(node) != sizeof(tree_t))) {
|
||||
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid dupsort sub-tree node size",
|
||||
(unsigned)node_ds(node));
|
||||
rc = MDBX_CORRUPTED;
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
/* Need writable leaf */
|
||||
if (mp != leaf) {
|
||||
mc->pg[mc->top] = leaf;
|
||||
page_copy(leaf, mp, ctx->env->ps);
|
||||
mp = leaf;
|
||||
node = page_node(mp, i);
|
||||
}
|
||||
|
||||
tree_t *nested = nullptr;
|
||||
if (node_flags(node) & N_DUP) {
|
||||
rc = cursor_dupsort_setup(mc, node, mp);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
nested = &mc->subcur->nested_tree;
|
||||
rc = compacting_walk(ctx, &mc->subcur->cursor, &nested->root, mp->txnid);
|
||||
}
|
||||
} else {
|
||||
cASSERT(mc, (mc->flags & z_inner) == 0 && mc->subcur == 0);
|
||||
cursor_couple_t *couple = container_of(mc, cursor_couple_t, outer);
|
||||
nested = &couple->inner.nested_tree;
|
||||
memcpy(nested, node_data(node), sizeof(tree_t));
|
||||
rc = compacting_walk_tree(ctx, nested);
|
||||
}
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
memcpy(node_data(node), nested, sizeof(tree_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mc->ki[mc->top]++;
|
||||
if (mc->ki[mc->top] < nkeys) {
|
||||
for (;;) {
|
||||
const node_t *node = page_node(mp, mc->ki[mc->top]);
|
||||
rc = page_get(mc, node_pgno(node), &mp, mp->txnid);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
mc->top += 1;
|
||||
if (unlikely(mc->top >= deep_limit)) {
|
||||
rc = MDBX_CURSOR_FULL;
|
||||
goto bailout;
|
||||
}
|
||||
mc->ki[mc->top] = 0;
|
||||
if (!is_branch(mp)) {
|
||||
mc->pg[mc->top] = mp;
|
||||
break;
|
||||
}
|
||||
/* Whenever we advance to a sibling branch page,
|
||||
* we must proceed all the way down to its first leaf. */
|
||||
page_copy(mc->pg[mc->top], mp, ctx->env->ps);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const pgno_t pgno = ctx->first_unallocated;
|
||||
if (likely(!is_dupfix_leaf(mp))) {
|
||||
rc = compacting_put_page(ctx, mp, PAGEHDRSZ + mp->lower, ctx->env->ps - (PAGEHDRSZ + mp->upper), 1);
|
||||
} else {
|
||||
rc = compacting_put_page(ctx, mp, PAGEHDRSZ + page_numkeys(mp) * mp->dupfix_ksize, 0, 1);
|
||||
}
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
|
||||
if (mc->top) {
|
||||
/* Update parent if there is one */
|
||||
node_set_pgno(page_node(mc->pg[mc->top - 1], mc->ki[mc->top - 1]), pgno);
|
||||
cursor_pop(mc);
|
||||
} else {
|
||||
/* Otherwise we're done */
|
||||
*parent_pgno = pgno;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bailout:
|
||||
osal_free(buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold static int compacting_walk_tree(ctx_t *ctx, tree_t *tree) {
|
||||
if (unlikely(tree->root == P_INVALID))
|
||||
return MDBX_SUCCESS; /* empty db */
|
||||
|
||||
cursor_couple_t couple;
|
||||
memset(&couple, 0, sizeof(couple));
|
||||
couple.inner.cursor.signature = ~cur_signature_live;
|
||||
kvx_t kvx = {.clc = {.k = {.lmin = INT_MAX}, .v = {.lmin = INT_MAX}}};
|
||||
int rc = cursor_init4walk(&couple, ctx->txn, tree, &kvx);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
couple.outer.checking |= z_ignord | z_pagecheck;
|
||||
couple.inner.cursor.checking |= z_ignord | z_pagecheck;
|
||||
if (!tree->mod_txnid)
|
||||
tree->mod_txnid = ctx->txn->txnid;
|
||||
return compacting_walk(ctx, &couple.outer, &tree->root, tree->mod_txnid);
|
||||
}
|
||||
|
||||
__cold static void compacting_fixup_meta(MDBX_env *env, meta_t *meta) {
|
||||
eASSERT(env, meta->trees.gc.mod_txnid || meta->trees.gc.root == P_INVALID);
|
||||
eASSERT(env, meta->trees.main.mod_txnid || meta->trees.main.root == P_INVALID);
|
||||
|
||||
/* Calculate filesize taking in account shrink/growing thresholds */
|
||||
if (meta->geometry.first_unallocated != meta->geometry.now) {
|
||||
meta->geometry.now = meta->geometry.first_unallocated;
|
||||
const size_t aligner = pv2pages(meta->geometry.grow_pv ? meta->geometry.grow_pv : meta->geometry.shrink_pv);
|
||||
if (aligner) {
|
||||
const pgno_t aligned = pgno_align2os_pgno(env, meta->geometry.first_unallocated + aligner -
|
||||
meta->geometry.first_unallocated % aligner);
|
||||
meta->geometry.now = aligned;
|
||||
}
|
||||
}
|
||||
|
||||
if (meta->geometry.now < meta->geometry.lower)
|
||||
meta->geometry.now = meta->geometry.lower;
|
||||
if (meta->geometry.now > meta->geometry.upper)
|
||||
meta->geometry.now = meta->geometry.upper;
|
||||
|
||||
/* Update signature */
|
||||
assert(meta->geometry.now >= meta->geometry.first_unallocated);
|
||||
meta_sign_as_steady(meta);
|
||||
}
|
||||
|
||||
/* Make resizable */
|
||||
__cold static void meta_make_sizeable(meta_t *meta) {
|
||||
meta->geometry.lower = MIN_PAGENO;
|
||||
if (meta->geometry.grow_pv == 0) {
|
||||
const pgno_t step = 1 + (meta->geometry.upper - meta->geometry.lower) / 42;
|
||||
meta->geometry.grow_pv = pages2pv(step);
|
||||
}
|
||||
if (meta->geometry.shrink_pv == 0) {
|
||||
const pgno_t step = pv2pages(meta->geometry.grow_pv) << 1;
|
||||
meta->geometry.shrink_pv = pages2pv(step);
|
||||
}
|
||||
}
|
||||
|
||||
__cold static int copy_with_compacting(MDBX_env *env, MDBX_txn *txn, mdbx_filehandle_t fd, uint8_t *buffer,
|
||||
const bool dest_is_pipe, const MDBX_copy_flags_t flags) {
|
||||
const size_t meta_bytes = pgno2bytes(env, NUM_METAS);
|
||||
uint8_t *const data_buffer = buffer + ceil_powerof2(meta_bytes, globals.sys_pagesize);
|
||||
meta_t *const meta = meta_init_triplet(env, buffer);
|
||||
meta_set_txnid(env, meta, txn->txnid);
|
||||
|
||||
if (flags & MDBX_CP_FORCE_DYNAMIC_SIZE)
|
||||
meta_make_sizeable(meta);
|
||||
|
||||
/* copy canary sequences if present */
|
||||
if (txn->canary.v) {
|
||||
meta->canary = txn->canary;
|
||||
meta->canary.v = constmeta_txnid(meta);
|
||||
}
|
||||
|
||||
if (txn->dbs[MAIN_DBI].root == P_INVALID) {
|
||||
/* When the DB is empty, handle it specially to
|
||||
* fix any breakage like page leaks from ITS#8174. */
|
||||
meta->trees.main.flags = txn->dbs[MAIN_DBI].flags;
|
||||
compacting_fixup_meta(env, meta);
|
||||
if (dest_is_pipe) {
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(txn, false);
|
||||
int rc = osal_write(fd, buffer, meta_bytes);
|
||||
if (likely(rc == MDBX_SUCCESS) && (flags & MDBX_CP_THROTTLE_MVCC) != 0)
|
||||
rc = mdbx_txn_unpark(txn, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
/* Count free pages + GC pages. */
|
||||
cursor_couple_t couple;
|
||||
int rc = cursor_init(&couple.outer, txn, FREE_DBI);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
pgno_t gc_npages = txn->dbs[FREE_DBI].branch_pages + txn->dbs[FREE_DBI].leaf_pages + txn->dbs[FREE_DBI].large_pages;
|
||||
MDBX_val key, data;
|
||||
rc = outer_first(&couple.outer, &key, &data);
|
||||
while (rc == MDBX_SUCCESS) {
|
||||
const pnl_t pnl = data.iov_base;
|
||||
if (unlikely(data.iov_len % sizeof(pgno_t) || data.iov_len < MDBX_PNL_SIZEOF(pnl))) {
|
||||
ERROR("%s/%d: %s %zu", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-record length", data.iov_len);
|
||||
return MDBX_CORRUPTED;
|
||||
}
|
||||
if (unlikely(!pnl_check(pnl, txn->geo.first_unallocated))) {
|
||||
ERROR("%s/%d: %s", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-record content");
|
||||
return MDBX_CORRUPTED;
|
||||
}
|
||||
gc_npages += MDBX_PNL_GETSIZE(pnl);
|
||||
rc = outer_next(&couple.outer, &key, &data, MDBX_NEXT);
|
||||
}
|
||||
if (unlikely(rc != MDBX_NOTFOUND))
|
||||
return rc;
|
||||
|
||||
meta->geometry.first_unallocated = txn->geo.first_unallocated - gc_npages;
|
||||
meta->trees.main = txn->dbs[MAIN_DBI];
|
||||
|
||||
ctx_t ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
rc = osal_condpair_init(&ctx.condpair);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
memset(data_buffer, 0, 2 * (size_t)MDBX_ENVCOPY_WRITEBUF);
|
||||
ctx.write_buf[0] = data_buffer;
|
||||
ctx.write_buf[1] = data_buffer + (size_t)MDBX_ENVCOPY_WRITEBUF;
|
||||
ctx.first_unallocated = NUM_METAS;
|
||||
ctx.env = env;
|
||||
ctx.fd = fd;
|
||||
ctx.txn = txn;
|
||||
ctx.flags = flags;
|
||||
|
||||
osal_thread_t thread;
|
||||
int thread_err = osal_thread_create(&thread, compacting_write_thread, &ctx);
|
||||
if (likely(thread_err == MDBX_SUCCESS)) {
|
||||
if (dest_is_pipe) {
|
||||
if (!meta->trees.main.mod_txnid)
|
||||
meta->trees.main.mod_txnid = txn->txnid;
|
||||
compacting_fixup_meta(env, meta);
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(txn, false);
|
||||
rc = osal_write(fd, buffer, meta_bytes);
|
||||
if (likely(rc == MDBX_SUCCESS) && (flags & MDBX_CP_THROTTLE_MVCC) != 0)
|
||||
rc = mdbx_txn_unpark(txn, false);
|
||||
}
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = compacting_walk_tree(&ctx, &meta->trees.main);
|
||||
if (ctx.write_len[ctx.head & 1])
|
||||
/* toggle to flush non-empty buffers */
|
||||
compacting_toggle_write_buffers(&ctx);
|
||||
|
||||
if (likely(rc == MDBX_SUCCESS) && unlikely(meta->geometry.first_unallocated != ctx.first_unallocated)) {
|
||||
if (ctx.first_unallocated > meta->geometry.first_unallocated) {
|
||||
ERROR("the source DB %s: post-compactification used pages %" PRIaPGNO " %c expected %" PRIaPGNO,
|
||||
"has double-used pages or other corruption", ctx.first_unallocated, '>',
|
||||
meta->geometry.first_unallocated);
|
||||
rc = MDBX_CORRUPTED; /* corrupted DB */
|
||||
}
|
||||
if (ctx.first_unallocated < meta->geometry.first_unallocated) {
|
||||
WARNING("the source DB %s: post-compactification used pages %" PRIaPGNO " %c expected %" PRIaPGNO,
|
||||
"has page leak(s)", ctx.first_unallocated, '<', meta->geometry.first_unallocated);
|
||||
if (dest_is_pipe)
|
||||
/* the root within already written meta-pages is wrong */
|
||||
rc = MDBX_CORRUPTED;
|
||||
}
|
||||
/* fixup meta */
|
||||
meta->geometry.first_unallocated = ctx.first_unallocated;
|
||||
}
|
||||
|
||||
/* toggle with empty buffers to exit thread's loop */
|
||||
eASSERT(env, (ctx.write_len[ctx.head & 1]) == 0);
|
||||
compacting_toggle_write_buffers(&ctx);
|
||||
thread_err = osal_thread_join(thread);
|
||||
eASSERT(env, (ctx.tail == ctx.head && ctx.write_len[ctx.head & 1] == 0) || ctx.error);
|
||||
osal_condpair_destroy(&ctx.condpair);
|
||||
}
|
||||
if (unlikely(thread_err != MDBX_SUCCESS))
|
||||
return thread_err;
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
if (unlikely(ctx.error != MDBX_SUCCESS))
|
||||
return ctx.error;
|
||||
if (!dest_is_pipe)
|
||||
compacting_fixup_meta(env, meta);
|
||||
}
|
||||
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(txn, false);
|
||||
|
||||
/* Extend file if required */
|
||||
if (meta->geometry.now != meta->geometry.first_unallocated) {
|
||||
const size_t whole_size = pgno2bytes(env, meta->geometry.now);
|
||||
if (!dest_is_pipe)
|
||||
return osal_ftruncate(fd, whole_size);
|
||||
|
||||
const size_t used_size = pgno2bytes(env, meta->geometry.first_unallocated);
|
||||
memset(data_buffer, 0, (size_t)MDBX_ENVCOPY_WRITEBUF);
|
||||
for (size_t offset = used_size; offset < whole_size;) {
|
||||
const size_t chunk =
|
||||
((size_t)MDBX_ENVCOPY_WRITEBUF < whole_size - offset) ? (size_t)MDBX_ENVCOPY_WRITEBUF : whole_size - offset;
|
||||
int rc = osal_write(fd, data_buffer, chunk);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
offset += chunk;
|
||||
}
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
__cold static int copy_asis(MDBX_env *env, MDBX_txn *txn, mdbx_filehandle_t fd, uint8_t *buffer,
|
||||
const bool dest_is_pipe, const MDBX_copy_flags_t flags) {
|
||||
bool should_unlock = false;
|
||||
if ((txn->flags & MDBX_TXN_RDONLY) != 0 && (flags & MDBX_CP_RENEW_TXN) != 0) {
|
||||
/* Try temporarily block writers until we snapshot the meta pages */
|
||||
int err = lck_txn_lock(env, true);
|
||||
if (likely(err == MDBX_SUCCESS))
|
||||
should_unlock = true;
|
||||
else if (unlikely(err != MDBX_BUSY))
|
||||
return err;
|
||||
}
|
||||
|
||||
jitter4testing(false);
|
||||
int rc = MDBX_SUCCESS;
|
||||
const size_t meta_bytes = pgno2bytes(env, NUM_METAS);
|
||||
troika_t troika = meta_tap(env);
|
||||
/* Make a snapshot of meta-pages,
|
||||
* but writing ones after the data was flushed */
|
||||
retry_snap_meta:
|
||||
memcpy(buffer, env->dxb_mmap.base, meta_bytes);
|
||||
const meta_ptr_t recent = meta_recent(env, &troika);
|
||||
meta_t *headcopy = /* LY: get pointer to the snapshot copy */
|
||||
ptr_disp(buffer, ptr_dist(recent.ptr_c, env->dxb_mmap.base));
|
||||
jitter4testing(false);
|
||||
if (txn->flags & MDBX_TXN_RDONLY) {
|
||||
if (recent.txnid != txn->txnid) {
|
||||
if (flags & MDBX_CP_RENEW_TXN)
|
||||
rc = mdbx_txn_renew(txn);
|
||||
else {
|
||||
rc = MDBX_MVCC_RETARDED;
|
||||
for (size_t n = 0; n < NUM_METAS; ++n) {
|
||||
meta_t *const meta = page_meta(ptr_disp(buffer, pgno2bytes(env, n)));
|
||||
if (troika.txnid[n] == txn->txnid && ((/* is_steady */ (troika.fsm >> n) & 1) || rc != MDBX_SUCCESS)) {
|
||||
rc = MDBX_SUCCESS;
|
||||
headcopy = meta;
|
||||
} else if (troika.txnid[n] > txn->txnid)
|
||||
meta_set_txnid(env, meta, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should_unlock)
|
||||
lck_txn_unlock(env);
|
||||
else {
|
||||
troika_t snap = meta_tap(env);
|
||||
if (memcmp(&troika, &snap, sizeof(troika_t)) && rc == MDBX_SUCCESS) {
|
||||
troika = snap;
|
||||
goto retry_snap_meta;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
if (txn->flags & MDBX_TXN_RDONLY)
|
||||
eASSERT(env, meta_txnid(headcopy) == txn->txnid);
|
||||
if (flags & MDBX_CP_FORCE_DYNAMIC_SIZE)
|
||||
meta_make_sizeable(headcopy);
|
||||
/* Update signature to steady */
|
||||
meta_sign_as_steady(headcopy);
|
||||
|
||||
/* Copy the data */
|
||||
const size_t whole_size = pgno_align2os_bytes(env, txn->geo.end_pgno);
|
||||
const size_t used_size = pgno2bytes(env, txn->geo.first_unallocated);
|
||||
jitter4testing(false);
|
||||
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(txn, false);
|
||||
|
||||
if (dest_is_pipe)
|
||||
rc = osal_write(fd, buffer, meta_bytes);
|
||||
|
||||
uint8_t *const data_buffer = buffer + ceil_powerof2(meta_bytes, globals.sys_pagesize);
|
||||
#if MDBX_USE_COPYFILERANGE
|
||||
static bool copyfilerange_unavailable;
|
||||
#if (defined(__linux__) || defined(__gnu_linux__))
|
||||
if (globals.linux_kernel_version >= 0x05030000 && globals.linux_kernel_version < 0x05130000)
|
||||
copyfilerange_unavailable = true;
|
||||
#endif /* linux */
|
||||
bool not_the_same_filesystem = false;
|
||||
if (!copyfilerange_unavailable) {
|
||||
struct statfs statfs_info;
|
||||
if (fstatfs(fd, &statfs_info) || statfs_info.f_type == /* ECRYPTFS_SUPER_MAGIC */ 0xf15f)
|
||||
/* avoid use copyfilerange_unavailable() to ecryptfs due bugs */
|
||||
not_the_same_filesystem = true;
|
||||
}
|
||||
#endif /* MDBX_USE_COPYFILERANGE */
|
||||
|
||||
for (size_t offset = meta_bytes; rc == MDBX_SUCCESS && offset < used_size;) {
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC) {
|
||||
rc = mdbx_txn_unpark(txn, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
break;
|
||||
}
|
||||
|
||||
#if MDBX_USE_SENDFILE
|
||||
static bool sendfile_unavailable;
|
||||
if (dest_is_pipe && likely(!sendfile_unavailable)) {
|
||||
off_t in_offset = offset;
|
||||
const ssize_t written = sendfile(fd, env->lazy_fd, &in_offset, used_size - offset);
|
||||
if (likely(written > 0)) {
|
||||
offset = in_offset;
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
rc = mdbx_txn_park(txn, false);
|
||||
continue;
|
||||
}
|
||||
rc = MDBX_ENODATA;
|
||||
if (written == 0 || ignore_enosys_and_eagain(rc = errno) != MDBX_RESULT_TRUE)
|
||||
break;
|
||||
sendfile_unavailable = true;
|
||||
}
|
||||
#endif /* MDBX_USE_SENDFILE */
|
||||
|
||||
#if MDBX_USE_COPYFILERANGE
|
||||
if (!dest_is_pipe && !not_the_same_filesystem && likely(!copyfilerange_unavailable)) {
|
||||
off_t in_offset = offset, out_offset = offset;
|
||||
ssize_t bytes_copied = copy_file_range(env->lazy_fd, &in_offset, fd, &out_offset, used_size - offset, 0);
|
||||
if (likely(bytes_copied > 0)) {
|
||||
offset = in_offset;
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
rc = mdbx_txn_park(txn, false);
|
||||
continue;
|
||||
}
|
||||
rc = MDBX_ENODATA;
|
||||
if (bytes_copied == 0)
|
||||
break;
|
||||
rc = errno;
|
||||
if (rc == EXDEV || rc == /* workaround for ecryptfs bug(s),
|
||||
maybe useful for others FS */
|
||||
EINVAL)
|
||||
not_the_same_filesystem = true;
|
||||
else if (ignore_enosys_and_eagain(rc) == MDBX_RESULT_TRUE)
|
||||
copyfilerange_unavailable = true;
|
||||
else
|
||||
break;
|
||||
}
|
||||
#endif /* MDBX_USE_COPYFILERANGE */
|
||||
|
||||
/* fallback to portable */
|
||||
const size_t chunk =
|
||||
((size_t)MDBX_ENVCOPY_WRITEBUF < used_size - offset) ? (size_t)MDBX_ENVCOPY_WRITEBUF : used_size - offset;
|
||||
/* copy to avoid EFAULT in case swapped-out */
|
||||
memcpy(data_buffer, ptr_disp(env->dxb_mmap.base, offset), chunk);
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(txn, false);
|
||||
rc = osal_write(fd, data_buffer, chunk);
|
||||
offset += chunk;
|
||||
}
|
||||
|
||||
/* Extend file if required */
|
||||
if (likely(rc == MDBX_SUCCESS) && whole_size != used_size) {
|
||||
if (!dest_is_pipe)
|
||||
rc = osal_ftruncate(fd, whole_size);
|
||||
else {
|
||||
memset(data_buffer, 0, (size_t)MDBX_ENVCOPY_WRITEBUF);
|
||||
for (size_t offset = used_size; rc == MDBX_SUCCESS && offset < whole_size;) {
|
||||
const size_t chunk =
|
||||
((size_t)MDBX_ENVCOPY_WRITEBUF < whole_size - offset) ? (size_t)MDBX_ENVCOPY_WRITEBUF : whole_size - offset;
|
||||
rc = osal_write(fd, data_buffer, chunk);
|
||||
offset += chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
__cold static int copy2fd(MDBX_txn *txn, mdbx_filehandle_t fd, MDBX_copy_flags_t flags) {
|
||||
if (unlikely(txn->flags & MDBX_TXN_DIRTY))
|
||||
return MDBX_BAD_TXN;
|
||||
|
||||
int rc = MDBX_SUCCESS;
|
||||
if (txn->flags & MDBX_TXN_RDONLY) {
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC) {
|
||||
rc = mdbx_txn_park(txn, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
}
|
||||
} else if (unlikely(flags & (MDBX_CP_THROTTLE_MVCC | MDBX_CP_RENEW_TXN)))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
const int dest_is_pipe = osal_is_pipe(fd);
|
||||
if (MDBX_IS_ERROR(dest_is_pipe))
|
||||
return dest_is_pipe;
|
||||
|
||||
if (!dest_is_pipe) {
|
||||
rc = osal_fseek(fd, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
}
|
||||
|
||||
MDBX_env *const env = txn->env;
|
||||
const size_t buffer_size =
|
||||
pgno_align2os_bytes(env, NUM_METAS) +
|
||||
ceil_powerof2(((flags & MDBX_CP_COMPACT) ? 2 * (size_t)MDBX_ENVCOPY_WRITEBUF : (size_t)MDBX_ENVCOPY_WRITEBUF),
|
||||
globals.sys_pagesize);
|
||||
|
||||
uint8_t *buffer = nullptr;
|
||||
rc = osal_memalign_alloc(globals.sys_pagesize, buffer_size, (void **)&buffer);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
if (!dest_is_pipe) {
|
||||
/* Firstly write a stub to meta-pages.
|
||||
* Now we sure to incomplete copy will not be used. */
|
||||
memset(buffer, -1, pgno2bytes(env, NUM_METAS));
|
||||
rc = osal_write(fd, buffer, pgno2bytes(env, NUM_METAS));
|
||||
}
|
||||
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = mdbx_txn_unpark(txn, false);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
memset(buffer, 0, pgno2bytes(env, NUM_METAS));
|
||||
rc = ((flags & MDBX_CP_COMPACT) ? copy_with_compacting : copy_asis)(env, txn, fd, buffer, dest_is_pipe, flags);
|
||||
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = mdbx_txn_unpark(txn, false);
|
||||
}
|
||||
|
||||
if (txn->flags & MDBX_TXN_RDONLY) {
|
||||
if (flags & MDBX_CP_THROTTLE_MVCC)
|
||||
mdbx_txn_park(txn, true);
|
||||
else if (flags & MDBX_CP_DISPOSE_TXN)
|
||||
mdbx_txn_reset(txn);
|
||||
}
|
||||
|
||||
if (!dest_is_pipe) {
|
||||
if (likely(rc == MDBX_SUCCESS) && (flags & MDBX_CP_DONT_FLUSH) == 0)
|
||||
rc = osal_fsync(fd, MDBX_SYNC_DATA | MDBX_SYNC_SIZE);
|
||||
|
||||
/* Write actual meta */
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = osal_pwrite(fd, buffer, pgno2bytes(env, NUM_METAS), 0);
|
||||
|
||||
if (likely(rc == MDBX_SUCCESS) && (flags & MDBX_CP_DONT_FLUSH) == 0)
|
||||
rc = osal_fsync(fd, MDBX_SYNC_DATA | MDBX_SYNC_IODQ);
|
||||
}
|
||||
|
||||
osal_memalign_free(buffer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold static int copy2pathname(MDBX_txn *txn, const pathchar_t *dest_path, MDBX_copy_flags_t flags) {
|
||||
if (unlikely(!dest_path || *dest_path == '\0'))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
/* The destination path must exist, but the destination file must not.
|
||||
* We don't want the OS to cache the writes, since the source data is
|
||||
* already in the OS cache. */
|
||||
mdbx_filehandle_t newfd = INVALID_HANDLE_VALUE;
|
||||
int rc = osal_openfile(MDBX_OPEN_COPY, txn->env, dest_path, &newfd,
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
(mdbx_mode_t)-1
|
||||
#else
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
|
||||
#endif
|
||||
);
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* no locking required since the file opened with ShareMode == 0 */
|
||||
#else
|
||||
if (rc == MDBX_SUCCESS) {
|
||||
MDBX_STRUCT_FLOCK lock_op;
|
||||
memset(&lock_op, 0, sizeof(lock_op));
|
||||
lock_op.l_type = F_WRLCK;
|
||||
lock_op.l_whence = SEEK_SET;
|
||||
lock_op.l_start = 0;
|
||||
lock_op.l_len = OFF_T_MAX;
|
||||
if (MDBX_FCNTL(newfd, MDBX_F_SETLK, &lock_op))
|
||||
rc = errno;
|
||||
}
|
||||
|
||||
#if defined(LOCK_EX) && (!defined(__ANDROID_API__) || __ANDROID_API__ >= 24)
|
||||
if (rc == MDBX_SUCCESS && flock(newfd, LOCK_EX | LOCK_NB)) {
|
||||
const int err_flock = errno, err_fs = osal_check_fs_local(newfd, 0);
|
||||
if (err_flock != EAGAIN || err_fs != MDBX_EREMOTE) {
|
||||
ERROR("%s flock(%" MDBX_PRIsPATH ") error %d, remote-fs check status %d", "unexpected", dest_path, err_flock,
|
||||
err_fs);
|
||||
rc = err_flock;
|
||||
} else {
|
||||
WARNING("%s flock(%" MDBX_PRIsPATH ") error %d, remote-fs check status %d", "ignore", dest_path, err_flock,
|
||||
err_fs);
|
||||
}
|
||||
}
|
||||
#endif /* LOCK_EX && ANDROID_API >= 24 */
|
||||
|
||||
#endif /* Windows / POSIX */
|
||||
|
||||
if (rc == MDBX_SUCCESS)
|
||||
rc = copy2fd(txn, newfd, flags);
|
||||
|
||||
if (newfd != INVALID_HANDLE_VALUE) {
|
||||
int err = osal_closefile(newfd);
|
||||
if (rc == MDBX_SUCCESS && err != rc)
|
||||
rc = err;
|
||||
if (rc != MDBX_SUCCESS)
|
||||
(void)osal_removefile(dest_path);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
__cold int mdbx_txn_copy2fd(MDBX_txn *txn, mdbx_filehandle_t fd, MDBX_copy_flags_t flags) {
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = copy2fd(txn, fd, flags);
|
||||
if (flags & MDBX_CP_DISPOSE_TXN)
|
||||
mdbx_txn_abort(txn);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_copy2fd(MDBX_env *env, mdbx_filehandle_t fd, MDBX_copy_flags_t flags) {
|
||||
if (unlikely(flags & (MDBX_CP_DISPOSE_TXN | MDBX_CP_RENEW_TXN)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
MDBX_txn *txn = nullptr;
|
||||
rc = mdbx_txn_begin(env, nullptr, MDBX_TXN_RDONLY, &txn);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = copy2fd(txn, fd, flags | MDBX_CP_DISPOSE_TXN | MDBX_CP_RENEW_TXN);
|
||||
mdbx_txn_abort(txn);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_txn_copy2pathname(MDBX_txn *txn, const char *dest_path, MDBX_copy_flags_t flags) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
wchar_t *dest_pathW = nullptr;
|
||||
int rc = osal_mb2w(dest_path, &dest_pathW);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
rc = mdbx_txn_copy2pathnameW(txn, dest_pathW, flags);
|
||||
osal_free(dest_pathW);
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_txn_copy2pathnameW(MDBX_txn *txn, const wchar_t *dest_path, MDBX_copy_flags_t flags) {
|
||||
#endif /* Windows */
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = copy2pathname(txn, dest_path, flags);
|
||||
if (flags & MDBX_CP_DISPOSE_TXN)
|
||||
mdbx_txn_abort(txn);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_copy(MDBX_env *env, const char *dest_path, MDBX_copy_flags_t flags) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
wchar_t *dest_pathW = nullptr;
|
||||
int rc = osal_mb2w(dest_path, &dest_pathW);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
rc = mdbx_env_copyW(env, dest_pathW, flags);
|
||||
osal_free(dest_pathW);
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_copyW(MDBX_env *env, const wchar_t *dest_path, MDBX_copy_flags_t flags) {
|
||||
#endif /* Windows */
|
||||
if (unlikely(flags & (MDBX_CP_DISPOSE_TXN | MDBX_CP_RENEW_TXN)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
MDBX_txn *txn = nullptr;
|
||||
rc = mdbx_txn_begin(env, nullptr, MDBX_TXN_RDONLY, &txn);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = copy2pathname(txn, dest_path, flags | MDBX_CP_DISPOSE_TXN | MDBX_CP_RENEW_TXN);
|
||||
mdbx_txn_abort(txn);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
755
src/api-cursor.c
Normal file
755
src/api-cursor.c
Normal file
@ -0,0 +1,755 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
MDBX_cursor *mdbx_cursor_create(void *context) {
|
||||
cursor_couple_t *couple = osal_calloc(1, sizeof(cursor_couple_t));
|
||||
if (unlikely(!couple))
|
||||
return nullptr;
|
||||
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(couple, sizeof(cursor_couple_t));
|
||||
couple->outer.signature = cur_signature_ready4dispose;
|
||||
couple->outer.next = &couple->outer;
|
||||
couple->userctx = context;
|
||||
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));
|
||||
VALGRIND_MAKE_MEM_DEFINED(&couple->outer.dbi_state, sizeof(couple->outer.dbi_state));
|
||||
VALGRIND_MAKE_MEM_DEFINED(&couple->outer.subcur, sizeof(couple->outer.subcur));
|
||||
VALGRIND_MAKE_MEM_DEFINED(&couple->outer.txn, sizeof(couple->outer.txn));
|
||||
return &couple->outer;
|
||||
}
|
||||
|
||||
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) {
|
||||
int rc = cursor_check(mc, MDBX_TXN_FINISHED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
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);
|
||||
|
||||
rc = dbi_check(txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
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 == MDBX_BAD_TXN) ? MDBX_EINVAL : rc;
|
||||
}
|
||||
cASSERT(mc, mc->next == mc);
|
||||
|
||||
rc = cursor_init(mc, txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
mc->next = txn->cursors[dbi];
|
||||
txn->cursors[dbi] = mc;
|
||||
txn->flags |= txn_may_have_cursors;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_cursor_unbind(MDBX_cursor *mc) {
|
||||
if (unlikely(!mc))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(mc->signature != cur_signature_live))
|
||||
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);
|
||||
|
||||
int rc = check_txn(mc->txn, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
for (const MDBX_txn *txn = mc->txn; rc == MDBX_BAD_TXN && check_txn(txn, MDBX_TXN_FINISHED) == MDBX_SUCCESS;
|
||||
txn = txn->nested)
|
||||
if (dbi_state(txn, cursor_dbi(mc)) == 0)
|
||||
/* специальный случай: курсор прикреплён к родительской транзакции, но соответствующий dbi-дескриптор ещё
|
||||
* не использовался во вложенной транзакции, т.е. курсор ещё не импортирован в дочернюю транзакцию и не имеет
|
||||
* связанного сохранённого состояния (поэтому mc→backup равен nullptr). */
|
||||
rc = MDBX_EINVAL;
|
||||
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 = 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) {
|
||||
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;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
|
||||
if (unlikely(!ret))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
*ret = nullptr;
|
||||
|
||||
MDBX_cursor *const mc = mdbx_cursor_create(nullptr);
|
||||
if (unlikely(!mc))
|
||||
return LOG_IFERR(MDBX_ENOMEM);
|
||||
|
||||
int rc = mdbx_cursor_bind(txn, mc, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
mdbx_cursor_close(mc);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
*ret = mc;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_cursor_copy(const MDBX_cursor *src, MDBX_cursor *dest) {
|
||||
int rc = cursor_check(src, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = mdbx_cursor_bind(src->txn, dest, cursor_dbi(src));
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
assert(dest->tree == src->tree);
|
||||
assert(cursor_dbi(dest) == cursor_dbi(src));
|
||||
again:
|
||||
assert(dest->clc == src->clc);
|
||||
assert(dest->txn == src->txn);
|
||||
dest->top_and_flags = src->top_and_flags;
|
||||
for (intptr_t i = 0; i <= src->top; ++i) {
|
||||
dest->ki[i] = src->ki[i];
|
||||
dest->pg[i] = src->pg[i];
|
||||
}
|
||||
|
||||
if (src->subcur) {
|
||||
dest->subcur->nested_tree = src->subcur->nested_tree;
|
||||
src = &src->subcur->cursor;
|
||||
dest = &dest->subcur->cursor;
|
||||
goto again;
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
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 (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
size_t n = 0;
|
||||
do {
|
||||
TXN_FOREACH_DBI_FROM(txn, i, MAIN_DBI) {
|
||||
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;
|
||||
bk->backup = nullptr;
|
||||
bk->signature = 0;
|
||||
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);
|
||||
}
|
||||
}
|
||||
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(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)
|
||||
return (l->txn->env > r->txn->env) ? incomparable * 7 : -incomparable * 7;
|
||||
if (l->txn->txnid != r->txn->txnid)
|
||||
return (l->txn->txnid > r->txn->txnid) ? incomparable * 6 : -incomparable * 6;
|
||||
return (l->clc > r->clc) ? incomparable * 5 : -incomparable * 5;
|
||||
}
|
||||
assert(cursor_dbi(l) == cursor_dbi(r));
|
||||
|
||||
int diff = is_pointed(l) - is_pointed(r);
|
||||
if (unlikely(diff))
|
||||
return (diff > 0) ? incomparable * 4 : -incomparable * 4;
|
||||
if (unlikely(!is_pointed(l)))
|
||||
return 0;
|
||||
|
||||
intptr_t detent = (l->top <= r->top) ? l->top : r->top;
|
||||
for (intptr_t i = 0; i <= detent; ++i) {
|
||||
diff = l->ki[i] - r->ki[i];
|
||||
if (diff)
|
||||
return diff;
|
||||
}
|
||||
if (unlikely(l->top != r->top))
|
||||
return (l->top > r->top) ? incomparable * 3 : -incomparable * 3;
|
||||
|
||||
assert((l->subcur != nullptr) == (r->subcur != nullptr));
|
||||
if (unlikely((l->subcur != nullptr) != (r->subcur != nullptr)))
|
||||
return l->subcur ? incomparable * 2 : -incomparable * 2;
|
||||
if (ignore_multival || !l->subcur)
|
||||
return 0;
|
||||
|
||||
#if MDBX_DEBUG
|
||||
if (is_pointed(&l->subcur->cursor)) {
|
||||
const page_t *mp = l->pg[l->top];
|
||||
const node_t *node = page_node(mp, l->ki[l->top]);
|
||||
assert(node_flags(node) & N_DUP);
|
||||
}
|
||||
if (is_pointed(&r->subcur->cursor)) {
|
||||
const page_t *mp = r->pg[r->top];
|
||||
const node_t *node = page_node(mp, r->ki[r->top]);
|
||||
assert(node_flags(node) & N_DUP);
|
||||
}
|
||||
#endif /* MDBX_DEBUG */
|
||||
|
||||
l = &l->subcur->cursor;
|
||||
r = &r->subcur->cursor;
|
||||
diff = is_pointed(l) - is_pointed(r);
|
||||
if (unlikely(diff))
|
||||
return (diff > 0) ? incomparable * 2 : -incomparable * 2;
|
||||
if (unlikely(!is_pointed(l)))
|
||||
return 0;
|
||||
|
||||
detent = (l->top <= r->top) ? l->top : r->top;
|
||||
for (intptr_t i = 0; i <= detent; ++i) {
|
||||
diff = l->ki[i] - r->ki[i];
|
||||
if (diff)
|
||||
return diff;
|
||||
}
|
||||
if (unlikely(l->top != r->top))
|
||||
return (l->top > r->top) ? incomparable : -incomparable;
|
||||
|
||||
return (l->flags & z_eof_hard) - (r->flags & z_eof_hard);
|
||||
}
|
||||
|
||||
int mdbx_cursor_count_ex(const MDBX_cursor *mc, size_t *count, MDBX_stat *ns, size_t bytes) {
|
||||
int rc = cursor_check_ro(mc);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (ns) {
|
||||
const size_t size_before_modtxnid = offsetof(MDBX_stat, ms_mod_txnid);
|
||||
if (unlikely(bytes != sizeof(MDBX_stat)) && bytes != size_before_modtxnid)
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
memset(ns, 0, sizeof(*ns));
|
||||
}
|
||||
|
||||
size_t nvals = 0;
|
||||
if (is_filled(mc)) {
|
||||
nvals = 1;
|
||||
if (!inner_hollow(mc)) {
|
||||
const page_t *mp = mc->pg[mc->top];
|
||||
const node_t *node = page_node(mp, mc->ki[mc->top]);
|
||||
cASSERT(mc, node_flags(node) & N_DUP);
|
||||
const tree_t *nt = &mc->subcur->nested_tree;
|
||||
nvals = unlikely(nt->items > PTRDIFF_MAX) ? PTRDIFF_MAX : (size_t)nt->items;
|
||||
if (ns) {
|
||||
ns->ms_psize = (unsigned)node_ds(node);
|
||||
if (node_flags(node) & N_TREE) {
|
||||
ns->ms_psize = mc->txn->env->ps;
|
||||
ns->ms_depth = nt->height;
|
||||
ns->ms_branch_pages = nt->branch_pages;
|
||||
}
|
||||
cASSERT(mc, nt->large_pages == 0);
|
||||
ns->ms_leaf_pages = nt->leaf_pages;
|
||||
ns->ms_entries = nt->items;
|
||||
if (likely(bytes >= offsetof(MDBX_stat, ms_mod_txnid) + sizeof(ns->ms_mod_txnid)))
|
||||
ns->ms_mod_txnid = nt->mod_txnid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (likely(count))
|
||||
*count = nvals;
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_cursor_count(const MDBX_cursor *mc, size_t *count) {
|
||||
if (unlikely(count == nullptr))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
return mdbx_cursor_count_ex(mc, count, nullptr, 0);
|
||||
}
|
||||
|
||||
int mdbx_cursor_on_first(const MDBX_cursor *mc) {
|
||||
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])
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_cursor_on_first_dup(const MDBX_cursor *mc) {
|
||||
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;
|
||||
for (intptr_t i = 0; i <= mc->top; ++i) {
|
||||
if (mc->ki[i])
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_cursor_on_last(const MDBX_cursor *mc) {
|
||||
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]);
|
||||
if (mc->ki[i] < nkeys - 1)
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_cursor_on_last_dup(const MDBX_cursor *mc) {
|
||||
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;
|
||||
for (intptr_t i = 0; i <= mc->top; ++i) {
|
||||
size_t nkeys = page_numkeys(mc->pg[i]);
|
||||
if (mc->ki[i] < nkeys - 1)
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_cursor_eof(const MDBX_cursor *mc) {
|
||||
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) {
|
||||
int rc = cursor_check_ro(mc);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
return LOG_IFERR(cursor_ops(mc, key, data, op));
|
||||
}
|
||||
|
||||
__hot static int scan_confinue(MDBX_cursor *mc, MDBX_predicate_func *predicate, void *context, void *arg, MDBX_val *key,
|
||||
MDBX_val *value, MDBX_cursor_op turn_op) {
|
||||
int rc;
|
||||
switch (turn_op) {
|
||||
case MDBX_NEXT:
|
||||
case MDBX_NEXT_NODUP:
|
||||
for (;;) {
|
||||
rc = predicate(context, key, value, arg);
|
||||
if (rc != MDBX_RESULT_FALSE)
|
||||
return rc;
|
||||
rc = outer_next(mc, key, value, turn_op);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return (rc == MDBX_NOTFOUND) ? MDBX_RESULT_FALSE : rc;
|
||||
}
|
||||
|
||||
case MDBX_PREV:
|
||||
case MDBX_PREV_NODUP:
|
||||
for (;;) {
|
||||
rc = predicate(context, key, value, arg);
|
||||
if (rc != MDBX_RESULT_FALSE)
|
||||
return rc;
|
||||
rc = outer_prev(mc, key, value, turn_op);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return (rc == MDBX_NOTFOUND) ? MDBX_RESULT_FALSE : rc;
|
||||
}
|
||||
|
||||
case MDBX_NEXT_DUP:
|
||||
if (mc->subcur)
|
||||
for (;;) {
|
||||
rc = predicate(context, key, value, arg);
|
||||
if (rc != MDBX_RESULT_FALSE)
|
||||
return rc;
|
||||
rc = inner_next(&mc->subcur->cursor, value);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return (rc == MDBX_NOTFOUND) ? MDBX_RESULT_FALSE : rc;
|
||||
}
|
||||
return MDBX_NOTFOUND;
|
||||
|
||||
case MDBX_PREV_DUP:
|
||||
if (mc->subcur)
|
||||
for (;;) {
|
||||
rc = predicate(context, key, value, arg);
|
||||
if (rc != MDBX_RESULT_FALSE)
|
||||
return rc;
|
||||
rc = inner_prev(&mc->subcur->cursor, value);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return (rc == MDBX_NOTFOUND) ? MDBX_RESULT_FALSE : rc;
|
||||
}
|
||||
return MDBX_NOTFOUND;
|
||||
|
||||
default:
|
||||
for (;;) {
|
||||
rc = predicate(context, key, value, arg);
|
||||
if (rc != MDBX_RESULT_FALSE)
|
||||
return rc;
|
||||
rc = cursor_ops(mc, key, value, turn_op);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return (rc == MDBX_NOTFOUND) ? MDBX_RESULT_FALSE : rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mdbx_cursor_scan(MDBX_cursor *mc, MDBX_predicate_func *predicate, void *context, MDBX_cursor_op start_op,
|
||||
MDBX_cursor_op turn_op, void *arg) {
|
||||
if (unlikely(!predicate))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
const unsigned valid_start_mask = 1 << MDBX_FIRST | 1 << MDBX_FIRST_DUP | 1 << MDBX_LAST | 1 << MDBX_LAST_DUP |
|
||||
1 << MDBX_GET_CURRENT | 1 << MDBX_GET_MULTIPLE;
|
||||
if (unlikely(start_op > 30 || ((1 << start_op) & valid_start_mask) == 0))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
const unsigned valid_turn_mask = 1 << MDBX_NEXT | 1 << MDBX_NEXT_DUP | 1 << MDBX_NEXT_NODUP | 1 << MDBX_PREV |
|
||||
1 << MDBX_PREV_DUP | 1 << MDBX_PREV_NODUP | 1 << MDBX_NEXT_MULTIPLE |
|
||||
1 << MDBX_PREV_MULTIPLE;
|
||||
if (unlikely(turn_op > 30 || ((1 << turn_op) & valid_turn_mask) == 0))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
MDBX_val key = {nullptr, 0}, value = {nullptr, 0};
|
||||
int rc = mdbx_cursor_get(mc, &key, &value, start_op);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
return LOG_IFERR(scan_confinue(mc, predicate, context, arg, &key, &value, turn_op));
|
||||
}
|
||||
|
||||
int mdbx_cursor_scan_from(MDBX_cursor *mc, MDBX_predicate_func *predicate, void *context, MDBX_cursor_op from_op,
|
||||
MDBX_val *key, MDBX_val *value, MDBX_cursor_op turn_op, void *arg) {
|
||||
if (unlikely(!predicate || !key))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
const unsigned valid_start_mask = 1 << MDBX_GET_BOTH | 1 << MDBX_GET_BOTH_RANGE | 1 << MDBX_SET_KEY |
|
||||
1 << MDBX_GET_MULTIPLE | 1 << MDBX_SET_LOWERBOUND | 1 << MDBX_SET_UPPERBOUND;
|
||||
if (unlikely(from_op < MDBX_TO_KEY_LESSER_THAN && ((1 << from_op) & valid_start_mask) == 0))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
const unsigned valid_turn_mask = 1 << MDBX_NEXT | 1 << MDBX_NEXT_DUP | 1 << MDBX_NEXT_NODUP | 1 << MDBX_PREV |
|
||||
1 << MDBX_PREV_DUP | 1 << MDBX_PREV_NODUP | 1 << MDBX_NEXT_MULTIPLE |
|
||||
1 << MDBX_PREV_MULTIPLE;
|
||||
if (unlikely(turn_op > 30 || ((1 << turn_op) & valid_turn_mask) == 0))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = mdbx_cursor_get(mc, key, value, from_op);
|
||||
if (unlikely(MDBX_IS_ERROR(rc)))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
cASSERT(mc, key != nullptr);
|
||||
MDBX_val stub;
|
||||
if (!value) {
|
||||
value = &stub;
|
||||
rc = cursor_ops(mc, key, value, MDBX_GET_CURRENT);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
return LOG_IFERR(scan_confinue(mc, predicate, context, arg, key, value, turn_op));
|
||||
}
|
||||
|
||||
int mdbx_cursor_get_batch(MDBX_cursor *mc, size_t *count, MDBX_val *pairs, size_t limit, MDBX_cursor_op op) {
|
||||
if (unlikely(!count))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
*count = 0;
|
||||
if (unlikely(limit < 4 || limit > INTPTR_MAX - 2))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = cursor_check_ro(mc);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(mc->subcur))
|
||||
return LOG_IFERR(MDBX_INCOMPATIBLE) /* must be a non-dupsort table */;
|
||||
|
||||
switch (op) {
|
||||
case MDBX_NEXT:
|
||||
if (unlikely(is_eof(mc)))
|
||||
return LOG_IFERR(is_pointed(mc) ? MDBX_NOTFOUND : MDBX_ENODATA);
|
||||
break;
|
||||
|
||||
case MDBX_FIRST:
|
||||
if (!is_filled(mc)) {
|
||||
rc = outer_first(mc, nullptr, nullptr);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG("unhandled/unimplemented cursor operation %u", op);
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
}
|
||||
|
||||
const page_t *mp = mc->pg[mc->top];
|
||||
size_t nkeys = page_numkeys(mp);
|
||||
size_t ki = mc->ki[mc->top];
|
||||
size_t n = 0;
|
||||
while (n + 2 <= limit) {
|
||||
cASSERT(mc, ki < nkeys);
|
||||
if (unlikely(ki >= nkeys))
|
||||
goto sibling;
|
||||
|
||||
const node_t *leaf = page_node(mp, ki);
|
||||
pairs[n] = get_key(leaf);
|
||||
rc = node_read(mc, leaf, &pairs[n + 1], mp);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
|
||||
n += 2;
|
||||
if (++ki == nkeys) {
|
||||
sibling:
|
||||
rc = cursor_sibling_right(mc);
|
||||
if (rc != MDBX_SUCCESS) {
|
||||
if (rc == MDBX_NOTFOUND)
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
mp = mc->pg[mc->top];
|
||||
DEBUG("next page is %" PRIaPGNO ", key index %u", mp->pgno, mc->ki[mc->top]);
|
||||
if (!MDBX_DISABLE_VALIDATION && unlikely(!check_leaf_type(mc, mp))) {
|
||||
ERROR("unexpected leaf-page #%" PRIaPGNO " type 0x%x seen by cursor", mp->pgno, mp->flags);
|
||||
rc = MDBX_CORRUPTED;
|
||||
goto bailout;
|
||||
}
|
||||
nkeys = page_numkeys(mp);
|
||||
ki = 0;
|
||||
}
|
||||
}
|
||||
mc->ki[mc->top] = (indx_t)ki;
|
||||
|
||||
bailout:
|
||||
*count = n;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
int mdbx_cursor_set_userctx(MDBX_cursor *mc, void *ctx) {
|
||||
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;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
void *mdbx_cursor_get_userctx(const MDBX_cursor *mc) {
|
||||
if (unlikely(!mc))
|
||||
return nullptr;
|
||||
|
||||
if (unlikely(mc->signature != cur_signature_ready4dispose && mc->signature != cur_signature_live))
|
||||
return nullptr;
|
||||
|
||||
cursor_couple_t *couple = container_of(mc, cursor_couple_t, outer);
|
||||
return couple->userctx;
|
||||
}
|
||||
|
||||
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 || (txn->flags & MDBX_TXN_FINISHED)))
|
||||
return nullptr;
|
||||
return (txn->flags & MDBX_TXN_HAS_CHILD) ? txn->env->txn : txn;
|
||||
}
|
||||
|
||||
MDBX_dbi mdbx_cursor_dbi(const MDBX_cursor *mc) {
|
||||
if (unlikely(!mc || mc->signature != cur_signature_live))
|
||||
return UINT_MAX;
|
||||
return cursor_dbi(mc);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags) {
|
||||
if (unlikely(key == nullptr || data == nullptr))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = cursor_check_rw(mc);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(flags & MDBX_MULTIPLE)) {
|
||||
rc = cursor_check_multiple(mc, key, data, flags);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
if (flags & MDBX_RESERVE) {
|
||||
if (unlikely(mc->tree->flags & (MDBX_DUPSORT | MDBX_REVERSEDUP | MDBX_INTEGERDUP | MDBX_DUPFIXED)))
|
||||
return LOG_IFERR(MDBX_INCOMPATIBLE);
|
||||
data->iov_base = nullptr;
|
||||
}
|
||||
|
||||
return LOG_IFERR(cursor_put_checklen(mc, key, data, flags));
|
||||
}
|
||||
|
||||
int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) {
|
||||
int rc = cursor_check_rw(mc);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
return LOG_IFERR(cursor_del(mc, flags));
|
||||
}
|
||||
|
||||
__cold int mdbx_cursor_ignord(MDBX_cursor *mc) {
|
||||
int rc = cursor_check(mc, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
mc->checking |= z_ignord;
|
||||
if (mc->subcur)
|
||||
mc->subcur->cursor.checking |= z_ignord;
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
315
src/api-dbi.c
Normal file
315
src/api-dbi.c
Normal file
@ -0,0 +1,315 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
int mdbx_dbi_open2(MDBX_txn *txn, const MDBX_val *name, MDBX_db_flags_t flags, MDBX_dbi *dbi) {
|
||||
return LOG_IFERR(dbi_open(txn, name, flags, dbi, nullptr, nullptr));
|
||||
}
|
||||
|
||||
int mdbx_dbi_open_ex2(MDBX_txn *txn, const MDBX_val *name, MDBX_db_flags_t flags, MDBX_dbi *dbi, MDBX_cmp_func *keycmp,
|
||||
MDBX_cmp_func *datacmp) {
|
||||
return LOG_IFERR(dbi_open(txn, name, flags, dbi, keycmp, datacmp));
|
||||
}
|
||||
|
||||
static int dbi_open_cstr(MDBX_txn *txn, const char *name_cstr, MDBX_db_flags_t flags, MDBX_dbi *dbi,
|
||||
MDBX_cmp_func *keycmp, MDBX_cmp_func *datacmp) {
|
||||
MDBX_val thunk, *name;
|
||||
if (name_cstr == MDBX_CHK_MAIN || name_cstr == MDBX_CHK_GC || name_cstr == MDBX_CHK_META)
|
||||
name = (void *)name_cstr;
|
||||
else {
|
||||
thunk.iov_len = strlen(name_cstr);
|
||||
thunk.iov_base = (void *)name_cstr;
|
||||
name = &thunk;
|
||||
}
|
||||
return dbi_open(txn, name, flags, dbi, keycmp, datacmp);
|
||||
}
|
||||
|
||||
int mdbx_dbi_open(MDBX_txn *txn, const char *name, MDBX_db_flags_t flags, MDBX_dbi *dbi) {
|
||||
return LOG_IFERR(dbi_open_cstr(txn, name, flags, dbi, nullptr, nullptr));
|
||||
}
|
||||
|
||||
int mdbx_dbi_open_ex(MDBX_txn *txn, const char *name, MDBX_db_flags_t flags, MDBX_dbi *dbi, MDBX_cmp_func *keycmp,
|
||||
MDBX_cmp_func *datacmp) {
|
||||
return LOG_IFERR(dbi_open_cstr(txn, name, flags, dbi, keycmp, datacmp));
|
||||
}
|
||||
|
||||
__cold int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, bool del) {
|
||||
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 (txn->dbs[dbi].height) {
|
||||
cx.outer.next = txn->cursors[dbi];
|
||||
txn->cursors[dbi] = &cx.outer;
|
||||
rc = tree_drop(&cx.outer, dbi == MAIN_DBI || (cx.outer.tree->flags & MDBX_DUPSORT));
|
||||
txn->cursors[dbi] = cx.outer.next;
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
/* Invalidate the dropped DB's cursors */
|
||||
for (MDBX_cursor *mc = txn->cursors[dbi]; mc; mc = mc->next)
|
||||
be_poor(mc);
|
||||
|
||||
if (!del || dbi < CORE_DBS) {
|
||||
/* reset the DB record, mark it dirty */
|
||||
txn->dbi_state[dbi] |= DBI_DIRTY;
|
||||
txn->dbs[dbi].height = 0;
|
||||
txn->dbs[dbi].branch_pages = 0;
|
||||
txn->dbs[dbi].leaf_pages = 0;
|
||||
txn->dbs[dbi].large_pages = 0;
|
||||
txn->dbs[dbi].items = 0;
|
||||
txn->dbs[dbi].root = P_INVALID;
|
||||
txn->dbs[dbi].sequence = 0;
|
||||
/* txn->dbs[dbi].mod_txnid = txn->txnid; */
|
||||
txn->flags |= MDBX_TXN_DIRTY;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
MDBX_env *const env = txn->env;
|
||||
MDBX_val name = env->kvs[dbi].name;
|
||||
rc = cursor_init(&cx.outer, txn, MAIN_DBI);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
rc = cursor_seek(&cx.outer, &name, nullptr, MDBX_SET).err;
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
cx.outer.next = txn->cursors[MAIN_DBI];
|
||||
txn->cursors[MAIN_DBI] = &cx.outer;
|
||||
rc = cursor_del(&cx.outer, N_TREE);
|
||||
txn->cursors[MAIN_DBI] = cx.outer.next;
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
tASSERT(txn, txn->dbi_state[MAIN_DBI] & DBI_DIRTY);
|
||||
tASSERT(txn, txn->flags & MDBX_TXN_DIRTY);
|
||||
txn->dbi_state[dbi] = DBI_LINDO | DBI_OLDEN;
|
||||
rc = osal_fastmutex_acquire(&env->dbi_lock);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
return LOG_IFERR(dbi_close_release(env, dbi));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txn->flags |= MDBX_TXN_ERROR;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_dbi_rename(MDBX_txn *txn, MDBX_dbi dbi, const char *name_cstr) {
|
||||
MDBX_val thunk, *name;
|
||||
if (name_cstr == MDBX_CHK_MAIN || name_cstr == MDBX_CHK_GC || name_cstr == MDBX_CHK_META)
|
||||
name = (void *)name_cstr;
|
||||
else {
|
||||
thunk.iov_len = strlen(name_cstr);
|
||||
thunk.iov_base = (void *)name_cstr;
|
||||
name = &thunk;
|
||||
}
|
||||
return mdbx_dbi_rename2(txn, dbi, name);
|
||||
}
|
||||
|
||||
__cold int mdbx_dbi_rename2(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *new_name) {
|
||||
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(new_name == MDBX_CHK_MAIN || new_name->iov_base == MDBX_CHK_MAIN || new_name == MDBX_CHK_GC ||
|
||||
new_name->iov_base == MDBX_CHK_GC || new_name == MDBX_CHK_META || new_name->iov_base == MDBX_CHK_META))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(dbi < CORE_DBS))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
rc = dbi_check(txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = osal_fastmutex_acquire(&txn->env->dbi_lock);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
struct dbi_rename_result pair = dbi_rename_locked(txn, dbi, *new_name);
|
||||
if (pair.defer)
|
||||
pair.defer->next = nullptr;
|
||||
dbi_defer_release(txn->env, pair.defer);
|
||||
rc = pair.err;
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_dbi_close(MDBX_env *env, MDBX_dbi dbi) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(dbi < CORE_DBS))
|
||||
return (dbi == MAIN_DBI) ? MDBX_SUCCESS : LOG_IFERR(MDBX_BAD_DBI);
|
||||
|
||||
if (unlikely(dbi >= env->max_dbi))
|
||||
return LOG_IFERR(MDBX_BAD_DBI);
|
||||
|
||||
rc = osal_fastmutex_acquire(&env->dbi_lock);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(dbi >= env->n_dbi)) {
|
||||
rc = MDBX_BAD_DBI;
|
||||
bailout:
|
||||
osal_fastmutex_release(&env->dbi_lock);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
while (env->basal_txn && (env->dbs_flags[dbi] & DB_VALID) && (env->basal_txn->flags & MDBX_TXN_FINISHED) == 0) {
|
||||
/* LY: Опасный код, так как env->txn может быть изменено в другом потоке.
|
||||
* К сожалению тут нет надежного решения и может быть падение при неверном
|
||||
* использовании API (вызове mdbx_dbi_close конкурентно с завершением
|
||||
* пишущей транзакции).
|
||||
*
|
||||
* Для минимизации вероятности падения сначала проверяем dbi-флаги
|
||||
* в basal_txn, а уже после в env->txn. Таким образом, падение может быть
|
||||
* только при коллизии с завершением вложенной транзакции.
|
||||
*
|
||||
* Альтернативно можно попробовать выполнять обновление/put записи в
|
||||
* mainDb соответствующей таблице закрываемого хендла. Семантически это
|
||||
* верный путь, но проблема в текущем API, в котором исторически dbi-хендл
|
||||
* живет и закрывается вне транзакции. Причем проблема не только в том,
|
||||
* что нет указателя на текущую пишущую транзакцию, а в том что
|
||||
* пользователь точно не ожидает что закрытие хендла приведет к
|
||||
* скрытой/непрозрачной активности внутри транзакции потенциально
|
||||
* выполняемой в другом потоке. Другими словами, проблема может быть
|
||||
* только при неверном использовании API и если пользователь это
|
||||
* допускает, то точно не будет ожидать скрытых действий внутри
|
||||
* транзакции, и поэтому этот путь потенциально более опасен. */
|
||||
const MDBX_txn *const hazard = env->txn;
|
||||
osal_compiler_barrier();
|
||||
if ((dbi_state(env->basal_txn, dbi) & (DBI_LINDO | DBI_DIRTY | DBI_CREAT)) > DBI_LINDO) {
|
||||
rc = MDBX_DANGLING_DBI;
|
||||
goto bailout;
|
||||
}
|
||||
osal_memory_barrier();
|
||||
if (unlikely(hazard != env->txn))
|
||||
continue;
|
||||
if (hazard != env->basal_txn && hazard && (hazard->flags & MDBX_TXN_FINISHED) == 0 &&
|
||||
hazard->signature == txn_signature &&
|
||||
(dbi_state(hazard, dbi) & (DBI_LINDO | DBI_DIRTY | DBI_CREAT)) > DBI_LINDO) {
|
||||
rc = MDBX_DANGLING_DBI;
|
||||
goto bailout;
|
||||
}
|
||||
osal_compiler_barrier();
|
||||
if (likely(hazard == env->txn))
|
||||
break;
|
||||
}
|
||||
rc = dbi_close_release(env, dbi);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_dbi_flags_ex(const MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags, unsigned *state) {
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_ERROR - MDBX_TXN_PARKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = dbi_check(txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!flags || !state))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
*flags = txn->dbs[dbi].flags & DB_PERSISTENT_FLAGS;
|
||||
*state = txn->dbi_state[dbi] & (DBI_FRESH | DBI_CREAT | DBI_DIRTY | DBI_STALE);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static void stat_get(const tree_t *db, MDBX_stat *st, size_t bytes) {
|
||||
st->ms_depth = db->height;
|
||||
st->ms_branch_pages = db->branch_pages;
|
||||
st->ms_leaf_pages = db->leaf_pages;
|
||||
st->ms_overflow_pages = db->large_pages;
|
||||
st->ms_entries = db->items;
|
||||
if (likely(bytes >= offsetof(MDBX_stat, ms_mod_txnid) + sizeof(st->ms_mod_txnid)))
|
||||
st->ms_mod_txnid = db->mod_txnid;
|
||||
}
|
||||
|
||||
__cold int mdbx_dbi_stat(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_stat *dest, size_t bytes) {
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = dbi_check(txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(txn->flags & MDBX_TXN_BLOCKED))
|
||||
return LOG_IFERR(MDBX_BAD_TXN);
|
||||
|
||||
if (unlikely(txn->dbi_state[dbi] & DBI_STALE)) {
|
||||
rc = tbl_fetch((MDBX_txn *)txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
if (unlikely(!dest))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
const size_t size_before_modtxnid = offsetof(MDBX_stat, ms_mod_txnid);
|
||||
if (unlikely(bytes != sizeof(MDBX_stat)) && bytes != size_before_modtxnid)
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
dest->ms_psize = txn->env->ps;
|
||||
stat_get(&txn->dbs[dbi], dest, bytes);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold int mdbx_enumerate_tables(const MDBX_txn *txn, MDBX_table_enum_func *func, void *ctx) {
|
||||
if (unlikely(!func))
|
||||
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 cx;
|
||||
rc = cursor_init(&cx.outer, txn, MAIN_DBI);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
cx.outer.next = txn->cursors[MAIN_DBI];
|
||||
txn->cursors[MAIN_DBI] = &cx.outer;
|
||||
for (rc = outer_first(&cx.outer, nullptr, nullptr); rc == MDBX_SUCCESS;
|
||||
rc = outer_next(&cx.outer, nullptr, nullptr, MDBX_NEXT_NODUP)) {
|
||||
node_t *node = page_node(cx.outer.pg[cx.outer.top], cx.outer.ki[cx.outer.top]);
|
||||
if (node_flags(node) != N_TREE)
|
||||
continue;
|
||||
if (unlikely(node_ds(node) != sizeof(tree_t))) {
|
||||
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid dupsort sub-tree node size",
|
||||
(unsigned)node_ds(node));
|
||||
rc = MDBX_CORRUPTED;
|
||||
break;
|
||||
}
|
||||
|
||||
tree_t reside;
|
||||
const tree_t *tree = memcpy(&reside, node_data(node), sizeof(reside));
|
||||
const MDBX_val name = {node_key(node), node_ks(node)};
|
||||
const MDBX_env *const env = txn->env;
|
||||
MDBX_dbi dbi = 0;
|
||||
for (size_t i = CORE_DBS; i < env->n_dbi; ++i) {
|
||||
if (i >= txn->n_dbi || !(env->dbs_flags[i] & DB_VALID))
|
||||
continue;
|
||||
if (env->kvs[MAIN_DBI].clc.k.cmp(&name, &env->kvs[i].name))
|
||||
continue;
|
||||
|
||||
tree = dbi_dig(txn, i, &reside);
|
||||
dbi = (MDBX_dbi)i;
|
||||
break;
|
||||
}
|
||||
|
||||
MDBX_stat stat;
|
||||
stat_get(tree, &stat, sizeof(stat));
|
||||
rc = func(ctx, txn, &name, tree->flags, &stat, dbi);
|
||||
if (rc != MDBX_SUCCESS)
|
||||
goto bailout;
|
||||
}
|
||||
rc = (rc == MDBX_NOTFOUND) ? MDBX_SUCCESS : rc;
|
||||
|
||||
bailout:
|
||||
txn->cursors[MAIN_DBI] = cx.outer.next;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
1407
src/api-env.c
Normal file
1407
src/api-env.c
Normal file
File diff suppressed because it is too large
Load Diff
165
src/api-extra.c
Normal file
165
src/api-extra.c
Normal file
@ -0,0 +1,165 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Readers API */
|
||||
|
||||
__cold int mdbx_reader_list(const MDBX_env *env, MDBX_reader_list_func *func, void *ctx) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!func))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
int serial = 0;
|
||||
lck_t *const lck = env->lck_mmap.lck;
|
||||
if (likely(lck)) {
|
||||
const size_t snap_nreaders = atomic_load32(&lck->rdt_length, mo_AcquireRelease);
|
||||
for (size_t i = 0; i < snap_nreaders; i++) {
|
||||
const reader_slot_t *r = lck->rdt + i;
|
||||
retry_reader:;
|
||||
const uint32_t pid = atomic_load32(&r->pid, mo_AcquireRelease);
|
||||
if (!pid)
|
||||
continue;
|
||||
txnid_t txnid = safe64_read(&r->txnid);
|
||||
const uint64_t tid = atomic_load64(&r->tid, mo_Relaxed);
|
||||
const pgno_t pages_used = atomic_load32(&r->snapshot_pages_used, mo_Relaxed);
|
||||
const uint64_t reader_pages_retired = atomic_load64(&r->snapshot_pages_retired, mo_Relaxed);
|
||||
if (unlikely(txnid != safe64_read(&r->txnid) || pid != atomic_load32(&r->pid, mo_AcquireRelease) ||
|
||||
tid != atomic_load64(&r->tid, mo_Relaxed) ||
|
||||
pages_used != atomic_load32(&r->snapshot_pages_used, mo_Relaxed) ||
|
||||
reader_pages_retired != atomic_load64(&r->snapshot_pages_retired, mo_Relaxed)))
|
||||
goto retry_reader;
|
||||
|
||||
eASSERT(env, txnid > 0);
|
||||
if (txnid >= SAFE64_INVALID_THRESHOLD)
|
||||
txnid = 0;
|
||||
|
||||
size_t bytes_used = 0;
|
||||
size_t bytes_retained = 0;
|
||||
uint64_t lag = 0;
|
||||
if (txnid) {
|
||||
troika_t troika = meta_tap(env);
|
||||
retry_header:;
|
||||
const meta_ptr_t head = meta_recent(env, &troika);
|
||||
const uint64_t head_pages_retired = unaligned_peek_u64_volatile(4, head.ptr_v->pages_retired);
|
||||
if (unlikely(meta_should_retry(env, &troika) ||
|
||||
head_pages_retired != unaligned_peek_u64_volatile(4, head.ptr_v->pages_retired)))
|
||||
goto retry_header;
|
||||
|
||||
lag = (head.txnid - txnid) / xMDBX_TXNID_STEP;
|
||||
bytes_used = pgno2bytes(env, pages_used);
|
||||
bytes_retained = (head_pages_retired > reader_pages_retired)
|
||||
? pgno2bytes(env, (pgno_t)(head_pages_retired - reader_pages_retired))
|
||||
: 0;
|
||||
}
|
||||
rc = func(ctx, ++serial, (unsigned)i, pid, (mdbx_tid_t)((intptr_t)tid), txnid, lag, bytes_used, bytes_retained);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
__cold int mdbx_reader_check(MDBX_env *env, int *dead) {
|
||||
if (dead)
|
||||
*dead = 0;
|
||||
return LOG_IFERR(mvcc_cleanup_dead(env, false, dead));
|
||||
}
|
||||
|
||||
__cold int mdbx_thread_register(const MDBX_env *env) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!env->lck_mmap.lck))
|
||||
return LOG_IFERR((env->flags & MDBX_EXCLUSIVE) ? MDBX_EINVAL : MDBX_EPERM);
|
||||
|
||||
if (unlikely((env->flags & ENV_TXKEY) == 0)) {
|
||||
eASSERT(env, env->flags & MDBX_NOSTICKYTHREADS);
|
||||
return LOG_IFERR(MDBX_EINVAL) /* MDBX_NOSTICKYTHREADS mode */;
|
||||
}
|
||||
|
||||
eASSERT(env, (env->flags & (MDBX_NOSTICKYTHREADS | ENV_TXKEY)) == ENV_TXKEY);
|
||||
reader_slot_t *r = thread_rthc_get(env->me_txkey);
|
||||
if (unlikely(r != nullptr)) {
|
||||
eASSERT(env, r->pid.weak == env->pid);
|
||||
eASSERT(env, r->tid.weak == osal_thread_self());
|
||||
if (unlikely(r->pid.weak != env->pid))
|
||||
return LOG_IFERR(MDBX_BAD_RSLOT);
|
||||
return MDBX_RESULT_TRUE /* already registered */;
|
||||
}
|
||||
|
||||
return LOG_IFERR(mvcc_bind_slot((MDBX_env *)env).err);
|
||||
}
|
||||
|
||||
__cold int mdbx_thread_unregister(const MDBX_env *env) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!env->lck_mmap.lck))
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
if (unlikely((env->flags & ENV_TXKEY) == 0)) {
|
||||
eASSERT(env, env->flags & MDBX_NOSTICKYTHREADS);
|
||||
return MDBX_RESULT_TRUE /* MDBX_NOSTICKYTHREADS mode */;
|
||||
}
|
||||
|
||||
eASSERT(env, (env->flags & (MDBX_NOSTICKYTHREADS | ENV_TXKEY)) == ENV_TXKEY);
|
||||
reader_slot_t *r = thread_rthc_get(env->me_txkey);
|
||||
if (unlikely(r == nullptr))
|
||||
return MDBX_RESULT_TRUE /* not registered */;
|
||||
|
||||
eASSERT(env, r->pid.weak == env->pid);
|
||||
if (unlikely(r->pid.weak != env->pid || r->tid.weak != osal_thread_self()))
|
||||
return LOG_IFERR(MDBX_BAD_RSLOT);
|
||||
|
||||
eASSERT(env, r->txnid.weak >= SAFE64_INVALID_THRESHOLD);
|
||||
if (unlikely(r->txnid.weak < SAFE64_INVALID_THRESHOLD))
|
||||
return LOG_IFERR(MDBX_BUSY) /* transaction is still active */;
|
||||
|
||||
atomic_store32(&r->pid, 0, mo_Relaxed);
|
||||
atomic_store32(&env->lck->rdt_refresh_flag, true, mo_AcquireRelease);
|
||||
thread_rthc_set(env->me_txkey, nullptr);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Locking API */
|
||||
|
||||
int mdbx_txn_lock(MDBX_env *env, bool dont_wait) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
if (dont_wait && unlikely(env->basal_txn->owner || (env->basal_txn->flags & MDBX_TXN_FINISHED) == 0))
|
||||
return LOG_IFERR(MDBX_BUSY);
|
||||
|
||||
return LOG_IFERR(lck_txn_lock(env, dont_wait));
|
||||
}
|
||||
|
||||
int mdbx_txn_unlock(MDBX_env *env) {
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
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);
|
||||
|
||||
lck_txn_unlock(env);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
197
src/api-key-transform.c
Normal file
197
src/api-key-transform.c
Normal file
@ -0,0 +1,197 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static inline double key2double(const int64_t key) {
|
||||
union {
|
||||
uint64_t u;
|
||||
double f;
|
||||
} casting;
|
||||
|
||||
casting.u = (key < 0) ? key + UINT64_C(0x8000000000000000) : UINT64_C(0xffffFFFFffffFFFF) - key;
|
||||
return casting.f;
|
||||
}
|
||||
|
||||
static inline uint64_t double2key(const double *const ptr) {
|
||||
STATIC_ASSERT(sizeof(double) == sizeof(int64_t));
|
||||
const int64_t i = *(const int64_t *)ptr;
|
||||
const uint64_t u = (i < 0) ? UINT64_C(0xffffFFFFffffFFFF) - i : i + UINT64_C(0x8000000000000000);
|
||||
if (ASSERT_ENABLED()) {
|
||||
const double f = key2double(u);
|
||||
assert(memcmp(&f, ptr, sizeof(double)) == 0);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
static inline float key2float(const int32_t key) {
|
||||
union {
|
||||
uint32_t u;
|
||||
float f;
|
||||
} casting;
|
||||
|
||||
casting.u = (key < 0) ? key + UINT32_C(0x80000000) : UINT32_C(0xffffFFFF) - key;
|
||||
return casting.f;
|
||||
}
|
||||
|
||||
static inline uint32_t float2key(const float *const ptr) {
|
||||
STATIC_ASSERT(sizeof(float) == sizeof(int32_t));
|
||||
const int32_t i = *(const int32_t *)ptr;
|
||||
const uint32_t u = (i < 0) ? UINT32_C(0xffffFFFF) - i : i + UINT32_C(0x80000000);
|
||||
if (ASSERT_ENABLED()) {
|
||||
const float f = key2float(u);
|
||||
assert(memcmp(&f, ptr, sizeof(float)) == 0);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
uint64_t mdbx_key_from_double(const double ieee754_64bit) { return double2key(&ieee754_64bit); }
|
||||
|
||||
uint64_t mdbx_key_from_ptrdouble(const double *const ieee754_64bit) { return double2key(ieee754_64bit); }
|
||||
|
||||
uint32_t mdbx_key_from_float(const float ieee754_32bit) { return float2key(&ieee754_32bit); }
|
||||
|
||||
uint32_t mdbx_key_from_ptrfloat(const float *const ieee754_32bit) { return float2key(ieee754_32bit); }
|
||||
|
||||
#define IEEE754_DOUBLE_MANTISSA_SIZE 52
|
||||
#define IEEE754_DOUBLE_EXPONENTA_BIAS 0x3FF
|
||||
#define IEEE754_DOUBLE_EXPONENTA_MAX 0x7FF
|
||||
#define IEEE754_DOUBLE_IMPLICIT_LEAD UINT64_C(0x0010000000000000)
|
||||
#define IEEE754_DOUBLE_MANTISSA_MASK UINT64_C(0x000FFFFFFFFFFFFF)
|
||||
#define IEEE754_DOUBLE_MANTISSA_AMAX UINT64_C(0x001FFFFFFFFFFFFF)
|
||||
|
||||
static inline int clz64(uint64_t value) {
|
||||
#if __GNUC_PREREQ(4, 1) || __has_builtin(__builtin_clzl)
|
||||
if (sizeof(value) == sizeof(int))
|
||||
return __builtin_clz(value);
|
||||
if (sizeof(value) == sizeof(long))
|
||||
return __builtin_clzl(value);
|
||||
#if (defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ == 8) || __has_builtin(__builtin_clzll)
|
||||
return __builtin_clzll(value);
|
||||
#endif /* have(long long) && long long == uint64_t */
|
||||
#endif /* GNU C */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long index;
|
||||
#if defined(_M_AMD64) || defined(_M_ARM64) || defined(_M_X64)
|
||||
_BitScanReverse64(&index, value);
|
||||
return 63 - index;
|
||||
#else
|
||||
if (value > UINT32_MAX) {
|
||||
_BitScanReverse(&index, (uint32_t)(value >> 32));
|
||||
return 31 - index;
|
||||
}
|
||||
_BitScanReverse(&index, (uint32_t)value);
|
||||
return 63 - index;
|
||||
#endif
|
||||
#endif /* MSVC */
|
||||
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value |= value >> 32;
|
||||
static const uint8_t debruijn_clz64[64] = {63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2,
|
||||
9, 5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59, 1,
|
||||
17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18,
|
||||
38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0};
|
||||
return debruijn_clz64[value * UINT64_C(0x03F79D71B4CB0A89) >> 58];
|
||||
}
|
||||
|
||||
static inline uint64_t round_mantissa(const uint64_t u64, int shift) {
|
||||
assert(shift < 0 && u64 > 0);
|
||||
shift = -shift;
|
||||
const unsigned half = 1 << (shift - 1);
|
||||
const unsigned lsb = 1 & (unsigned)(u64 >> shift);
|
||||
const unsigned tie2even = 1 ^ lsb;
|
||||
return (u64 + half - tie2even) >> shift;
|
||||
}
|
||||
|
||||
uint64_t mdbx_key_from_jsonInteger(const int64_t json_integer) {
|
||||
const uint64_t bias = UINT64_C(0x8000000000000000);
|
||||
if (json_integer > 0) {
|
||||
const uint64_t u64 = json_integer;
|
||||
int shift = clz64(u64) - (64 - IEEE754_DOUBLE_MANTISSA_SIZE - 1);
|
||||
uint64_t mantissa = u64 << shift;
|
||||
if (unlikely(shift < 0)) {
|
||||
mantissa = round_mantissa(u64, shift);
|
||||
if (mantissa > IEEE754_DOUBLE_MANTISSA_AMAX)
|
||||
mantissa = round_mantissa(u64, --shift);
|
||||
}
|
||||
|
||||
assert(mantissa >= IEEE754_DOUBLE_IMPLICIT_LEAD && mantissa <= IEEE754_DOUBLE_MANTISSA_AMAX);
|
||||
const uint64_t exponent = (uint64_t)IEEE754_DOUBLE_EXPONENTA_BIAS + IEEE754_DOUBLE_MANTISSA_SIZE - shift;
|
||||
assert(exponent > 0 && exponent <= IEEE754_DOUBLE_EXPONENTA_MAX);
|
||||
const uint64_t key = bias + (exponent << IEEE754_DOUBLE_MANTISSA_SIZE) + (mantissa - IEEE754_DOUBLE_IMPLICIT_LEAD);
|
||||
#if !defined(_MSC_VER) || defined(_DEBUG) /* Workaround for MSVC error LNK2019: unresolved external \
|
||||
symbol __except1 referenced in function __ftol3_except */
|
||||
assert(key == mdbx_key_from_double((double)json_integer));
|
||||
#endif /* Workaround for MSVC */
|
||||
return key;
|
||||
}
|
||||
|
||||
if (json_integer < 0) {
|
||||
const uint64_t u64 = -json_integer;
|
||||
int shift = clz64(u64) - (64 - IEEE754_DOUBLE_MANTISSA_SIZE - 1);
|
||||
uint64_t mantissa = u64 << shift;
|
||||
if (unlikely(shift < 0)) {
|
||||
mantissa = round_mantissa(u64, shift);
|
||||
if (mantissa > IEEE754_DOUBLE_MANTISSA_AMAX)
|
||||
mantissa = round_mantissa(u64, --shift);
|
||||
}
|
||||
|
||||
assert(mantissa >= IEEE754_DOUBLE_IMPLICIT_LEAD && mantissa <= IEEE754_DOUBLE_MANTISSA_AMAX);
|
||||
const uint64_t exponent = (uint64_t)IEEE754_DOUBLE_EXPONENTA_BIAS + IEEE754_DOUBLE_MANTISSA_SIZE - shift;
|
||||
assert(exponent > 0 && exponent <= IEEE754_DOUBLE_EXPONENTA_MAX);
|
||||
const uint64_t key =
|
||||
bias - 1 - (exponent << IEEE754_DOUBLE_MANTISSA_SIZE) - (mantissa - IEEE754_DOUBLE_IMPLICIT_LEAD);
|
||||
#if !defined(_MSC_VER) || defined(_DEBUG) /* Workaround for MSVC error LNK2019: unresolved external \
|
||||
symbol __except1 referenced in function __ftol3_except */
|
||||
assert(key == mdbx_key_from_double((double)json_integer));
|
||||
#endif /* Workaround for MSVC */
|
||||
return key;
|
||||
}
|
||||
|
||||
return bias;
|
||||
}
|
||||
|
||||
int64_t mdbx_jsonInteger_from_key(const MDBX_val v) {
|
||||
assert(v.iov_len == 8);
|
||||
const uint64_t key = unaligned_peek_u64(2, v.iov_base);
|
||||
const uint64_t bias = UINT64_C(0x8000000000000000);
|
||||
const uint64_t covalent = (key > bias) ? key - bias : bias - key - 1;
|
||||
const int shift = IEEE754_DOUBLE_EXPONENTA_BIAS + 63 -
|
||||
(IEEE754_DOUBLE_EXPONENTA_MAX & (int)(covalent >> IEEE754_DOUBLE_MANTISSA_SIZE));
|
||||
if (unlikely(shift < 1))
|
||||
return (key < bias) ? INT64_MIN : INT64_MAX;
|
||||
if (unlikely(shift > 63))
|
||||
return 0;
|
||||
|
||||
const uint64_t unscaled = ((covalent & IEEE754_DOUBLE_MANTISSA_MASK) << (63 - IEEE754_DOUBLE_MANTISSA_SIZE)) + bias;
|
||||
const int64_t absolute = unscaled >> shift;
|
||||
const int64_t value = (key < bias) ? -absolute : absolute;
|
||||
assert(key == mdbx_key_from_jsonInteger(value) ||
|
||||
(mdbx_key_from_jsonInteger(value - 1) < key && key < mdbx_key_from_jsonInteger(value + 1)));
|
||||
return value;
|
||||
}
|
||||
|
||||
double mdbx_double_from_key(const MDBX_val v) {
|
||||
assert(v.iov_len == 8);
|
||||
return key2double(unaligned_peek_u64(2, v.iov_base));
|
||||
}
|
||||
|
||||
float mdbx_float_from_key(const MDBX_val v) {
|
||||
assert(v.iov_len == 4);
|
||||
return key2float(unaligned_peek_u32(2, v.iov_base));
|
||||
}
|
||||
|
||||
int32_t mdbx_int32_from_key(const MDBX_val v) {
|
||||
assert(v.iov_len == 4);
|
||||
return (int32_t)(unaligned_peek_u32(2, v.iov_base) - UINT32_C(0x80000000));
|
||||
}
|
||||
|
||||
int64_t mdbx_int64_from_key(const MDBX_val v) {
|
||||
assert(v.iov_len == 8);
|
||||
return (int64_t)(unaligned_peek_u64(2, v.iov_base) - UINT64_C(0x8000000000000000));
|
||||
}
|
286
src/api-misc.c
Normal file
286
src/api-misc.c
Normal file
@ -0,0 +1,286 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
__cold int mdbx_is_readahead_reasonable(size_t volume, intptr_t redundancy) {
|
||||
if (volume <= 1024 * 1024 * 4ul)
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
intptr_t pagesize, total_ram_pages;
|
||||
int err = mdbx_get_sysraminfo(&pagesize, &total_ram_pages, nullptr);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
|
||||
const int log2page = log2n_powerof2(pagesize);
|
||||
const intptr_t volume_pages = (volume + pagesize - 1) >> log2page;
|
||||
const intptr_t redundancy_pages = (redundancy < 0) ? -(intptr_t)((-redundancy + pagesize - 1) >> log2page)
|
||||
: (intptr_t)(redundancy + pagesize - 1) >> log2page;
|
||||
if (volume_pages >= total_ram_pages || volume_pages + redundancy_pages >= total_ram_pages)
|
||||
return MDBX_RESULT_FALSE;
|
||||
|
||||
intptr_t avail_ram_pages;
|
||||
err = mdbx_get_sysraminfo(nullptr, nullptr, &avail_ram_pages);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
|
||||
return (volume_pages + redundancy_pages >= avail_ram_pages) ? MDBX_RESULT_FALSE : MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result, uint64_t increment) {
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = dbi_check(txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(txn->dbi_state[dbi] & DBI_STALE)) {
|
||||
rc = tbl_fetch(txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
tree_t *dbs = &txn->dbs[dbi];
|
||||
if (likely(result))
|
||||
*result = dbs->sequence;
|
||||
|
||||
if (likely(increment > 0)) {
|
||||
if (unlikely(dbi == FREE_DBI || (txn->flags & MDBX_TXN_RDONLY) != 0))
|
||||
return MDBX_EACCESS;
|
||||
|
||||
uint64_t new = dbs->sequence + increment;
|
||||
if (unlikely(new < increment))
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
tASSERT(txn, new > dbs->sequence);
|
||||
if ((txn->dbi_state[dbi] & DBI_DIRTY) == 0) {
|
||||
txn->flags |= MDBX_TXN_DIRTY;
|
||||
txn->dbi_state[dbi] |= DBI_DIRTY;
|
||||
if (unlikely(dbi == MAIN_DBI) && txn->dbs[MAIN_DBI].root != P_INVALID) {
|
||||
/* LY: Временная подпорка для coherency_check(), которую в перспективе
|
||||
* следует заменить вместе с переделкой установки mod_txnid.
|
||||
*
|
||||
* Суть проблемы:
|
||||
* - coherency_check() в качестве одного из критериев "когерентности"
|
||||
* проверяет условие meta.maindb.mod_txnid == maindb.root->txnid;
|
||||
* - при обновлении maindb.sequence высталяется DBI_DIRTY, что приведет
|
||||
* к обновлению meta.maindb.mod_txnid = current_txnid;
|
||||
* - однако, если в само дерево maindb обновление не вносились и оно
|
||||
* не пустое, то корневая страницы останеться с прежним txnid и из-за
|
||||
* этого ложно сработает coherency_check().
|
||||
*
|
||||
* Временное (текущее) решение: Принудительно обновляем корневую
|
||||
* страницу в описанной выше ситуации. Это устраняет проблему, но и
|
||||
* не создает рисков регресса.
|
||||
*
|
||||
* FIXME: Итоговое решение, которое предстоит реализовать:
|
||||
* - изменить семантику установки/обновления mod_txnid, привязав его
|
||||
* строго к изменению b-tree, но не атрибутов;
|
||||
* - обновлять mod_txnid при фиксации вложенных транзакций;
|
||||
* - для dbi-хендлов пользовательских table (видимо) можно оставить
|
||||
* DBI_DIRTY в качестве признака необходимости обновления записи
|
||||
* table в MainDB, при этом взводить DBI_DIRTY вместе с обновлением
|
||||
* mod_txnid, в том числе при обновлении sequence.
|
||||
* - для MAIN_DBI при обновлении sequence не следует взводить DBI_DIRTY
|
||||
* и/или обновлять mod_txnid, а только взводить MDBX_TXN_DIRTY.
|
||||
* - альтернативно, можно перераспределить флажки-признаки dbi_state,
|
||||
* чтобы различать состояние dirty-tree и dirty-attributes. */
|
||||
cursor_couple_t cx;
|
||||
rc = cursor_init(&cx.outer, txn, MAIN_DBI);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
rc = tree_search(&cx.outer, nullptr, Z_MODIFY | Z_ROOTONLY);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
}
|
||||
dbs->sequence = new;
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_cmp(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, const MDBX_val *b) {
|
||||
eASSERT(nullptr, txn->signature == txn_signature);
|
||||
tASSERT(txn, (dbi_state(txn, dbi) & DBI_VALID) && !dbi_changed(txn, dbi));
|
||||
tASSERT(txn, dbi < txn->env->n_dbi && (txn->env->dbs_flags[dbi] & DB_VALID) != 0);
|
||||
return txn->env->kvs[dbi].clc.k.cmp(a, b);
|
||||
}
|
||||
|
||||
int mdbx_dcmp(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, const MDBX_val *b) {
|
||||
eASSERT(nullptr, txn->signature == txn_signature);
|
||||
tASSERT(txn, (dbi_state(txn, dbi) & DBI_VALID) && !dbi_changed(txn, dbi));
|
||||
tASSERT(txn, dbi < txn->env->n_dbi && (txn->env->dbs_flags[dbi] & DB_VALID));
|
||||
return txn->env->kvs[dbi].clc.v.cmp(a, b);
|
||||
}
|
||||
|
||||
__cold MDBX_cmp_func *mdbx_get_keycmp(MDBX_db_flags_t flags) { return builtin_keycmp(flags); }
|
||||
|
||||
__cold MDBX_cmp_func *mdbx_get_datacmp(MDBX_db_flags_t flags) { return builtin_datacmp(flags); }
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
__cold const char *mdbx_liberr2str(int errnum) {
|
||||
/* Table of descriptions for MDBX errors */
|
||||
static const char *const tbl[] = {
|
||||
"MDBX_KEYEXIST: Key/data pair already exists",
|
||||
"MDBX_NOTFOUND: No matching key/data pair found",
|
||||
"MDBX_PAGE_NOTFOUND: Requested page not found",
|
||||
"MDBX_CORRUPTED: Database is corrupted",
|
||||
"MDBX_PANIC: Environment had fatal error",
|
||||
"MDBX_VERSION_MISMATCH: DB version mismatch libmdbx",
|
||||
"MDBX_INVALID: File is not an MDBX file",
|
||||
"MDBX_MAP_FULL: Environment mapsize limit reached",
|
||||
"MDBX_DBS_FULL: Too many DBI-handles (maxdbs reached)",
|
||||
"MDBX_READERS_FULL: Too many readers (maxreaders reached)",
|
||||
nullptr /* MDBX_TLS_FULL (-30789): unused in MDBX */,
|
||||
"MDBX_TXN_FULL: Transaction has too many dirty pages,"
|
||||
" i.e transaction is too big",
|
||||
"MDBX_CURSOR_FULL: Cursor stack limit reachedn - this usually indicates"
|
||||
" corruption, i.e branch-pages loop",
|
||||
"MDBX_PAGE_FULL: Internal error - Page has no more space",
|
||||
"MDBX_UNABLE_EXTEND_MAPSIZE: Database engine was unable to extend"
|
||||
" mapping, e.g. since address space is unavailable or busy,"
|
||||
" or Operation system not supported such operations",
|
||||
"MDBX_INCOMPATIBLE: Environment or database is not compatible"
|
||||
" with the requested operation or the specified flags",
|
||||
"MDBX_BAD_RSLOT: Invalid reuse of reader locktable slot,"
|
||||
" e.g. read-transaction already run for current thread",
|
||||
"MDBX_BAD_TXN: Transaction is not valid for requested operation,"
|
||||
" e.g. had errored and be must aborted, has a child, or is invalid",
|
||||
"MDBX_BAD_VALSIZE: Invalid size or alignment of key or data"
|
||||
" for target database, either invalid table name",
|
||||
"MDBX_BAD_DBI: The specified DBI-handle is invalid"
|
||||
" or changed by another thread/transaction",
|
||||
"MDBX_PROBLEM: Unexpected internal error, transaction should be aborted",
|
||||
"MDBX_BUSY: Another write transaction is running,"
|
||||
" or environment is already used while opening with MDBX_EXCLUSIVE flag",
|
||||
};
|
||||
|
||||
if (errnum >= MDBX_KEYEXIST && errnum <= MDBX_BUSY) {
|
||||
int i = errnum - MDBX_KEYEXIST;
|
||||
return tbl[i];
|
||||
}
|
||||
|
||||
switch (errnum) {
|
||||
case MDBX_SUCCESS:
|
||||
return "MDBX_SUCCESS: Successful";
|
||||
case MDBX_EMULTIVAL:
|
||||
return "MDBX_EMULTIVAL: The specified key has"
|
||||
" more than one associated value";
|
||||
case MDBX_EBADSIGN:
|
||||
return "MDBX_EBADSIGN: Wrong signature of a runtime object(s),"
|
||||
" e.g. memory corruption or double-free";
|
||||
case MDBX_WANNA_RECOVERY:
|
||||
return "MDBX_WANNA_RECOVERY: Database should be recovered,"
|
||||
" but this could NOT be done automatically for now"
|
||||
" since it opened in read-only mode";
|
||||
case MDBX_EKEYMISMATCH:
|
||||
return "MDBX_EKEYMISMATCH: The given key value is mismatched to the"
|
||||
" current cursor position";
|
||||
case MDBX_TOO_LARGE:
|
||||
return "MDBX_TOO_LARGE: Database is too large for current system,"
|
||||
" e.g. could NOT be mapped into RAM";
|
||||
case MDBX_THREAD_MISMATCH:
|
||||
return "MDBX_THREAD_MISMATCH: A thread has attempted to use a not"
|
||||
" owned object, e.g. a transaction that started by another thread";
|
||||
case MDBX_TXN_OVERLAPPING:
|
||||
return "MDBX_TXN_OVERLAPPING: Overlapping read and write transactions for"
|
||||
" the current thread";
|
||||
case MDBX_DUPLICATED_CLK:
|
||||
return "MDBX_DUPLICATED_CLK: Alternative/Duplicate LCK-file is exists,"
|
||||
" please keep one and remove unused other";
|
||||
case MDBX_DANGLING_DBI:
|
||||
return "MDBX_DANGLING_DBI: Some cursors and/or other resources should be"
|
||||
" closed before table or corresponding DBI-handle could be (re)used";
|
||||
case MDBX_OUSTED:
|
||||
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 parked transaction was bygone";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
__cold const char *mdbx_strerror_r(int errnum, char *buf, size_t buflen) {
|
||||
const char *msg = mdbx_liberr2str(errnum);
|
||||
if (!msg && buflen > 0 && buflen < INT_MAX) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
DWORD size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errnum,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (DWORD)buflen, nullptr);
|
||||
while (size && buf[size - 1] <= ' ')
|
||||
--size;
|
||||
buf[size] = 0;
|
||||
return size ? buf : "FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM) failed";
|
||||
#elif defined(_GNU_SOURCE) && defined(__GLIBC__)
|
||||
/* GNU-specific */
|
||||
if (errnum > 0)
|
||||
msg = strerror_r(errnum, buf, buflen);
|
||||
#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600)
|
||||
/* XSI-compliant */
|
||||
if (errnum > 0 && strerror_r(errnum, buf, buflen) == 0)
|
||||
msg = buf;
|
||||
#else
|
||||
if (errnum > 0) {
|
||||
msg = strerror(errnum);
|
||||
if (msg) {
|
||||
strncpy(buf, msg, buflen);
|
||||
msg = buf;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!msg) {
|
||||
(void)snprintf(buf, buflen, "error %d", errnum);
|
||||
msg = buf;
|
||||
}
|
||||
buf[buflen - 1] = '\0';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
__cold const char *mdbx_strerror(int errnum) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
static char buf[1024];
|
||||
return mdbx_strerror_r(errnum, buf, sizeof(buf));
|
||||
#else
|
||||
const char *msg = mdbx_liberr2str(errnum);
|
||||
if (!msg) {
|
||||
if (errnum > 0)
|
||||
msg = strerror(errnum);
|
||||
if (!msg) {
|
||||
static char buf[32];
|
||||
(void)snprintf(buf, sizeof(buf) - 1, "error %d", errnum);
|
||||
msg = buf;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64) /* Bit of madness for Windows */
|
||||
const char *mdbx_strerror_r_ANSI2OEM(int errnum, char *buf, size_t buflen) {
|
||||
const char *msg = mdbx_liberr2str(errnum);
|
||||
if (!msg && buflen > 0 && buflen < INT_MAX) {
|
||||
DWORD size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errnum,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (DWORD)buflen, nullptr);
|
||||
while (size && buf[size - 1] <= ' ')
|
||||
--size;
|
||||
buf[size] = 0;
|
||||
if (!size)
|
||||
msg = "FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM) failed";
|
||||
else if (!CharToOemBuffA(buf, buf, size))
|
||||
msg = "CharToOemBuffA() failed";
|
||||
else
|
||||
msg = buf;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
const char *mdbx_strerror_ANSI2OEM(int errnum) {
|
||||
static char buf[1024];
|
||||
return mdbx_strerror_r_ANSI2OEM(errnum, buf, sizeof(buf));
|
||||
}
|
||||
#endif /* Bit of madness for Windows */
|
578
src/api-opts.c
Normal file
578
src/api-opts.c
Normal file
@ -0,0 +1,578 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static pgno_t env_max_pgno(const MDBX_env *env) {
|
||||
return env->ps ? bytes2pgno(env, env->geo_in_bytes.upper ? env->geo_in_bytes.upper : MAX_MAPSIZE) : PAGELIST_LIMIT;
|
||||
}
|
||||
|
||||
__cold pgno_t default_dp_limit(const MDBX_env *env) {
|
||||
/* auto-setup dp_limit by "The42" ;-) */
|
||||
intptr_t total_ram_pages, avail_ram_pages;
|
||||
int err = mdbx_get_sysraminfo(nullptr, &total_ram_pages, &avail_ram_pages);
|
||||
pgno_t dp_limit = 1024;
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
ERROR("mdbx_get_sysraminfo(), rc %d", err);
|
||||
else {
|
||||
size_t estimate = (size_t)(total_ram_pages + avail_ram_pages) / 42;
|
||||
if (env->ps) {
|
||||
if (env->ps > globals.sys_pagesize)
|
||||
estimate /= env->ps / globals.sys_pagesize;
|
||||
else if (env->ps < globals.sys_pagesize)
|
||||
estimate *= globals.sys_pagesize / env->ps;
|
||||
}
|
||||
dp_limit = (pgno_t)estimate;
|
||||
}
|
||||
|
||||
dp_limit = (dp_limit < PAGELIST_LIMIT) ? dp_limit : PAGELIST_LIMIT;
|
||||
const pgno_t max_pgno = env_max_pgno(env);
|
||||
if (dp_limit > max_pgno - NUM_METAS)
|
||||
dp_limit = max_pgno - NUM_METAS;
|
||||
dp_limit = (dp_limit > CURSOR_STACK_SIZE * 4) ? dp_limit : CURSOR_STACK_SIZE * 4;
|
||||
return dp_limit;
|
||||
}
|
||||
|
||||
__cold static pgno_t default_rp_augment_limit(const MDBX_env *env) {
|
||||
const size_t timeframe = /* 16 секунд */ 16 << 16;
|
||||
const size_t remain_1sec =
|
||||
(env->options.gc_time_limit < timeframe) ? timeframe - (size_t)env->options.gc_time_limit : 0;
|
||||
const size_t minimum = (env->maxgc_large1page * 2 > MDBX_PNL_INITIAL) ? env->maxgc_large1page * 2 : MDBX_PNL_INITIAL;
|
||||
const size_t one_third = env->geo_in_bytes.now / 3 >> env->ps2ln;
|
||||
const size_t augment_limit =
|
||||
(one_third > minimum) ? minimum + (one_third - minimum) / timeframe * remain_1sec : minimum;
|
||||
eASSERT(env, augment_limit < PAGELIST_LIMIT);
|
||||
return pnl_bytes2size(pnl_size2bytes(augment_limit));
|
||||
}
|
||||
|
||||
static bool default_prefault_write(const MDBX_env *env) {
|
||||
return !MDBX_MMAP_INCOHERENT_FILE_WRITE && !env->incore &&
|
||||
(env->flags & (MDBX_WRITEMAP | MDBX_RDONLY)) == MDBX_WRITEMAP;
|
||||
}
|
||||
|
||||
static bool default_prefer_waf_insteadof_balance(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint16_t default_subpage_limit(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 65535 /* 100% */;
|
||||
}
|
||||
|
||||
static uint16_t default_subpage_room_threshold(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 0 /* 0% */;
|
||||
}
|
||||
|
||||
static uint16_t default_subpage_reserve_prereq(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 27525 /* 42% */;
|
||||
}
|
||||
|
||||
static uint16_t default_subpage_reserve_limit(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 2753 /* 4.2% */;
|
||||
}
|
||||
|
||||
static uint16_t default_merge_threshold_16dot16_percent(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 65536 / 4 /* 25% */;
|
||||
}
|
||||
|
||||
static pgno_t default_dp_reserve_limit(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return MDBX_PNL_INITIAL;
|
||||
}
|
||||
|
||||
static pgno_t default_dp_initial(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return MDBX_PNL_INITIAL;
|
||||
}
|
||||
|
||||
static uint8_t default_spill_max_denominator(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 8;
|
||||
}
|
||||
|
||||
static uint8_t default_spill_min_denominator(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 8;
|
||||
}
|
||||
|
||||
static uint8_t default_spill_parent4child_denominator(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t default_dp_loose_limit(const MDBX_env *env) {
|
||||
(void)env;
|
||||
return 64;
|
||||
}
|
||||
|
||||
void env_options_init(MDBX_env *env) {
|
||||
env->options.rp_augment_limit = default_rp_augment_limit(env);
|
||||
env->options.dp_reserve_limit = default_dp_reserve_limit(env);
|
||||
env->options.dp_initial = default_dp_initial(env);
|
||||
env->options.dp_limit = default_dp_limit(env);
|
||||
env->options.spill_max_denominator = default_spill_max_denominator(env);
|
||||
env->options.spill_min_denominator = default_spill_min_denominator(env);
|
||||
env->options.spill_parent4child_denominator = default_spill_parent4child_denominator(env);
|
||||
env->options.dp_loose_limit = default_dp_loose_limit(env);
|
||||
env->options.merge_threshold_16dot16_percent = default_merge_threshold_16dot16_percent(env);
|
||||
if (default_prefer_waf_insteadof_balance(env))
|
||||
env->options.prefer_waf_insteadof_balance = true;
|
||||
|
||||
#if !(defined(_WIN32) || defined(_WIN64))
|
||||
env->options.writethrough_threshold =
|
||||
#if defined(__linux__) || defined(__gnu_linux__)
|
||||
globals.running_on_WSL1 ? MAX_PAGENO :
|
||||
#endif /* Linux */
|
||||
MDBX_WRITETHROUGH_THRESHOLD_DEFAULT;
|
||||
#endif /* Windows */
|
||||
|
||||
env->options.subpage.limit = default_subpage_limit(env);
|
||||
env->options.subpage.room_threshold = default_subpage_room_threshold(env);
|
||||
env->options.subpage.reserve_prereq = default_subpage_reserve_prereq(env);
|
||||
env->options.subpage.reserve_limit = default_subpage_reserve_limit(env);
|
||||
}
|
||||
|
||||
void env_options_adjust_dp_limit(MDBX_env *env) {
|
||||
if (!env->options.flags.non_auto.dp_limit)
|
||||
env->options.dp_limit = default_dp_limit(env);
|
||||
else {
|
||||
const pgno_t max_pgno = env_max_pgno(env);
|
||||
if (env->options.dp_limit > max_pgno - NUM_METAS)
|
||||
env->options.dp_limit = max_pgno - NUM_METAS;
|
||||
if (env->options.dp_limit < CURSOR_STACK_SIZE * 4)
|
||||
env->options.dp_limit = CURSOR_STACK_SIZE * 4;
|
||||
}
|
||||
#ifdef MDBX_DEBUG_DPL_LIMIT
|
||||
env->options.dp_limit = MDBX_DEBUG_DPL_LIMIT;
|
||||
#endif /* MDBX_DEBUG_DPL_LIMIT */
|
||||
if (env->options.dp_initial > env->options.dp_limit && env->options.dp_initial > default_dp_initial(env))
|
||||
env->options.dp_initial = env->options.dp_limit;
|
||||
env->options.need_dp_limit_adjust = false;
|
||||
}
|
||||
|
||||
void env_options_adjust_defaults(MDBX_env *env) {
|
||||
if (!env->options.flags.non_auto.rp_augment_limit)
|
||||
env->options.rp_augment_limit = default_rp_augment_limit(env);
|
||||
if (!env->options.flags.non_auto.prefault_write)
|
||||
env->options.prefault_write = default_prefault_write(env);
|
||||
|
||||
env->options.need_dp_limit_adjust = true;
|
||||
if (!env->txn)
|
||||
env_options_adjust_dp_limit(env);
|
||||
|
||||
const size_t basis = env->geo_in_bytes.now;
|
||||
/* TODO: use options? */
|
||||
const unsigned factor = 9;
|
||||
size_t threshold = (basis < ((size_t)65536 << factor)) ? 65536 /* minimal threshold */
|
||||
: (basis > (MEGABYTE * 4 << factor)) ? MEGABYTE * 4 /* maximal threshold */
|
||||
: basis >> factor;
|
||||
threshold =
|
||||
(threshold < env->geo_in_bytes.shrink || !env->geo_in_bytes.shrink) ? threshold : env->geo_in_bytes.shrink;
|
||||
env->madv_threshold = bytes2pgno(env, bytes_align2os_bytes(env, threshold));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
__cold int mdbx_env_set_option(MDBX_env *env, const MDBX_option_t option, uint64_t value) {
|
||||
int err = check_env(env, false);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
|
||||
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:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = MAX_WRITE;
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
if (unlikely(!(env->flags & ENV_ACTIVE)))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
if (unlikely(value > SIZE_MAX - 65536))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
value = bytes2pgno(env, (size_t)value + env->ps - 1);
|
||||
if ((uint32_t)value != atomic_load32(&env->lck->autosync_threshold, mo_AcquireRelease) &&
|
||||
atomic_store32(&env->lck->autosync_threshold, (uint32_t)value, mo_Relaxed)
|
||||
/* Дергаем sync(force=off) только если задано новое не-нулевое значение
|
||||
* и мы вне транзакции */
|
||||
&& lock_needed) {
|
||||
err = env_sync(env, false, false);
|
||||
if (err == /* нечего сбрасывать на диск */ MDBX_RESULT_TRUE)
|
||||
err = MDBX_SUCCESS;
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_sync_period:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = 2780315 /* 42.42424 секунды */;
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
if (unlikely(!(env->flags & ENV_ACTIVE)))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
if (unlikely(value > UINT32_MAX))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
value = osal_16dot16_to_monotime((uint32_t)value);
|
||||
if (value != atomic_load64(&env->lck->autosync_period, mo_AcquireRelease) &&
|
||||
atomic_store64(&env->lck->autosync_period, value, mo_Relaxed)
|
||||
/* Дергаем sync(force=off) только если задано новое не-нулевое значение
|
||||
* и мы вне транзакции */
|
||||
&& lock_needed) {
|
||||
err = env_sync(env, false, false);
|
||||
if (err == /* нечего сбрасывать на диск */ MDBX_RESULT_TRUE)
|
||||
err = MDBX_SUCCESS;
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_max_db:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = 42;
|
||||
if (unlikely(value > MDBX_MAX_DBI))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
if (unlikely(env->dxb_mmap.base))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
env->max_dbi = (unsigned)value + CORE_DBS;
|
||||
break;
|
||||
|
||||
case MDBX_opt_max_readers:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = MDBX_READERS_LIMIT;
|
||||
if (unlikely(value < 1 || value > MDBX_READERS_LIMIT))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
if (unlikely(env->dxb_mmap.base))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
env->max_readers = (unsigned)value;
|
||||
break;
|
||||
|
||||
case MDBX_opt_dp_reserve_limit:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = default_dp_reserve_limit(env);
|
||||
if (unlikely(value > INT_MAX))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
if (env->options.dp_reserve_limit != (unsigned)value) {
|
||||
if (lock_needed) {
|
||||
err = lck_txn_lock(env, false);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
should_unlock = true;
|
||||
}
|
||||
env->options.dp_reserve_limit = (unsigned)value;
|
||||
while (env->shadow_reserve_len > env->options.dp_reserve_limit) {
|
||||
eASSERT(env, env->shadow_reserve != nullptr);
|
||||
page_t *dp = env->shadow_reserve;
|
||||
MDBX_ASAN_UNPOISON_MEMORY_REGION(dp, env->ps);
|
||||
VALGRIND_MAKE_MEM_DEFINED(&page_next(dp), sizeof(page_t *));
|
||||
env->shadow_reserve = page_next(dp);
|
||||
void *const ptr = ptr_disp(dp, -(ptrdiff_t)sizeof(size_t));
|
||||
osal_free(ptr);
|
||||
env->shadow_reserve_len -= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_rp_augment_limit:
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.flags.non_auto.rp_augment_limit = 0;
|
||||
env->options.rp_augment_limit = default_rp_augment_limit(env);
|
||||
} else if (unlikely(value > PAGELIST_LIMIT))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
else {
|
||||
env->options.flags.non_auto.rp_augment_limit = 1;
|
||||
env->options.rp_augment_limit = (unsigned)value;
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_gc_time_limit:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = 0;
|
||||
if (unlikely(value > UINT32_MAX))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
value = osal_16dot16_to_monotime((uint32_t)value);
|
||||
if (value != env->options.gc_time_limit) {
|
||||
if (env->txn && lock_needed)
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
env->options.gc_time_limit = value;
|
||||
if (!env->options.flags.non_auto.rp_augment_limit)
|
||||
env->options.rp_augment_limit = default_rp_augment_limit(env);
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_txn_dp_limit:
|
||||
case MDBX_opt_txn_dp_initial:
|
||||
if (value != /* default */ UINT64_MAX && unlikely(value > PAGELIST_LIMIT || value < CURSOR_STACK_SIZE * 4))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
if (lock_needed) {
|
||||
err = lck_txn_lock(env, false);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
should_unlock = true;
|
||||
}
|
||||
if (env->txn)
|
||||
err = MDBX_EPERM /* unable change during transaction */;
|
||||
else {
|
||||
const pgno_t max_pgno = env_max_pgno(env);
|
||||
if (option == MDBX_opt_txn_dp_initial) {
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
env->options.dp_initial = default_dp_initial(env);
|
||||
else {
|
||||
env->options.dp_initial = (pgno_t)value;
|
||||
if (env->options.dp_initial > max_pgno)
|
||||
env->options.dp_initial = (max_pgno > CURSOR_STACK_SIZE * 4) ? max_pgno : CURSOR_STACK_SIZE * 4;
|
||||
}
|
||||
}
|
||||
if (option == MDBX_opt_txn_dp_limit) {
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.flags.non_auto.dp_limit = 0;
|
||||
} else {
|
||||
env->options.flags.non_auto.dp_limit = 1;
|
||||
env->options.dp_limit = (pgno_t)value;
|
||||
}
|
||||
env_options_adjust_dp_limit(env);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_spill_max_denominator:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = default_spill_max_denominator(env);
|
||||
if (unlikely(value > 255))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
env->options.spill_max_denominator = (uint8_t)value;
|
||||
break;
|
||||
case MDBX_opt_spill_min_denominator:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = default_spill_min_denominator(env);
|
||||
if (unlikely(value > 255))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
env->options.spill_min_denominator = (uint8_t)value;
|
||||
break;
|
||||
case MDBX_opt_spill_parent4child_denominator:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = default_spill_parent4child_denominator(env);
|
||||
if (unlikely(value > 255))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
env->options.spill_parent4child_denominator = (uint8_t)value;
|
||||
break;
|
||||
|
||||
case MDBX_opt_loose_limit:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = default_dp_loose_limit(env);
|
||||
if (unlikely(value > 255))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
env->options.dp_loose_limit = (uint8_t)value;
|
||||
break;
|
||||
|
||||
case MDBX_opt_merge_threshold_16dot16_percent:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = default_merge_threshold_16dot16_percent(env);
|
||||
if (unlikely(value < 8192 || value > 32768))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
env->options.merge_threshold_16dot16_percent = (unsigned)value;
|
||||
recalculate_merge_thresholds(env);
|
||||
break;
|
||||
|
||||
case MDBX_opt_writethrough_threshold:
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* позволяем "установить" значение по-умолчанию и совпадающее
|
||||
* с поведением соответствующим текущей установке MDBX_NOMETASYNC */
|
||||
if (value == /* default */ UINT64_MAX && value != ((env->flags & MDBX_NOMETASYNC) ? 0 : UINT_MAX))
|
||||
err = MDBX_EINVAL;
|
||||
#else
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
value = MDBX_WRITETHROUGH_THRESHOLD_DEFAULT;
|
||||
if (value != (unsigned)value)
|
||||
err = MDBX_EINVAL;
|
||||
else
|
||||
env->options.writethrough_threshold = (unsigned)value;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case MDBX_opt_prefault_write_enable:
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.prefault_write = default_prefault_write(env);
|
||||
env->options.flags.non_auto.prefault_write = false;
|
||||
} else if (value > 1)
|
||||
err = MDBX_EINVAL;
|
||||
else {
|
||||
env->options.prefault_write = value != 0;
|
||||
env->options.flags.non_auto.prefault_write = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_prefer_waf_insteadof_balance:
|
||||
if (value == /* default */ UINT64_MAX)
|
||||
env->options.prefer_waf_insteadof_balance = default_prefer_waf_insteadof_balance(env);
|
||||
else if (value > 1)
|
||||
err = MDBX_EINVAL;
|
||||
else
|
||||
env->options.prefer_waf_insteadof_balance = value != 0;
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_limit:
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.subpage.limit = default_subpage_limit(env);
|
||||
recalculate_subpage_thresholds(env);
|
||||
} else if (value > 65535)
|
||||
err = MDBX_EINVAL;
|
||||
else {
|
||||
env->options.subpage.limit = (uint16_t)value;
|
||||
recalculate_subpage_thresholds(env);
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_room_threshold:
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.subpage.room_threshold = default_subpage_room_threshold(env);
|
||||
recalculate_subpage_thresholds(env);
|
||||
} else if (value > 65535)
|
||||
err = MDBX_EINVAL;
|
||||
else {
|
||||
env->options.subpage.room_threshold = (uint16_t)value;
|
||||
recalculate_subpage_thresholds(env);
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_reserve_prereq:
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.subpage.reserve_prereq = default_subpage_reserve_prereq(env);
|
||||
recalculate_subpage_thresholds(env);
|
||||
} else if (value > 65535)
|
||||
err = MDBX_EINVAL;
|
||||
else {
|
||||
env->options.subpage.reserve_prereq = (uint16_t)value;
|
||||
recalculate_subpage_thresholds(env);
|
||||
}
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_reserve_limit:
|
||||
if (value == /* default */ UINT64_MAX) {
|
||||
env->options.subpage.reserve_limit = default_subpage_reserve_limit(env);
|
||||
recalculate_subpage_thresholds(env);
|
||||
} else if (value > 65535)
|
||||
err = MDBX_EINVAL;
|
||||
else {
|
||||
env->options.subpage.reserve_limit = (uint16_t)value;
|
||||
recalculate_subpage_thresholds(env);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
}
|
||||
|
||||
if (should_unlock)
|
||||
lck_txn_unlock(env);
|
||||
return LOG_IFERR(err);
|
||||
}
|
||||
|
||||
__cold int mdbx_env_get_option(const MDBX_env *env, const MDBX_option_t option, uint64_t *pvalue) {
|
||||
int err = check_env(env, false);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return LOG_IFERR(err);
|
||||
if (unlikely(!pvalue))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
switch (option) {
|
||||
case MDBX_opt_sync_bytes:
|
||||
if (unlikely(!(env->flags & ENV_ACTIVE)))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
*pvalue = pgno2bytes(env, atomic_load32(&env->lck->autosync_threshold, mo_Relaxed));
|
||||
break;
|
||||
|
||||
case MDBX_opt_sync_period:
|
||||
if (unlikely(!(env->flags & ENV_ACTIVE)))
|
||||
return LOG_IFERR(MDBX_EPERM);
|
||||
*pvalue = osal_monotime_to_16dot16(atomic_load64(&env->lck->autosync_period, mo_Relaxed));
|
||||
break;
|
||||
|
||||
case MDBX_opt_max_db:
|
||||
*pvalue = env->max_dbi - CORE_DBS;
|
||||
break;
|
||||
|
||||
case MDBX_opt_max_readers:
|
||||
*pvalue = env->max_readers;
|
||||
break;
|
||||
|
||||
case MDBX_opt_dp_reserve_limit:
|
||||
*pvalue = env->options.dp_reserve_limit;
|
||||
break;
|
||||
|
||||
case MDBX_opt_rp_augment_limit:
|
||||
*pvalue = env->options.rp_augment_limit;
|
||||
break;
|
||||
|
||||
case MDBX_opt_gc_time_limit:
|
||||
*pvalue = osal_monotime_to_16dot16(env->options.gc_time_limit);
|
||||
break;
|
||||
|
||||
case MDBX_opt_txn_dp_limit:
|
||||
*pvalue = env->options.dp_limit;
|
||||
break;
|
||||
case MDBX_opt_txn_dp_initial:
|
||||
*pvalue = env->options.dp_initial;
|
||||
break;
|
||||
|
||||
case MDBX_opt_spill_max_denominator:
|
||||
*pvalue = env->options.spill_max_denominator;
|
||||
break;
|
||||
case MDBX_opt_spill_min_denominator:
|
||||
*pvalue = env->options.spill_min_denominator;
|
||||
break;
|
||||
case MDBX_opt_spill_parent4child_denominator:
|
||||
*pvalue = env->options.spill_parent4child_denominator;
|
||||
break;
|
||||
|
||||
case MDBX_opt_loose_limit:
|
||||
*pvalue = env->options.dp_loose_limit;
|
||||
break;
|
||||
|
||||
case MDBX_opt_merge_threshold_16dot16_percent:
|
||||
*pvalue = env->options.merge_threshold_16dot16_percent;
|
||||
break;
|
||||
|
||||
case MDBX_opt_writethrough_threshold:
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
*pvalue = (env->flags & MDBX_NOMETASYNC) ? 0 : INT_MAX;
|
||||
#else
|
||||
*pvalue = env->options.writethrough_threshold;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case MDBX_opt_prefault_write_enable:
|
||||
*pvalue = env->options.prefault_write;
|
||||
break;
|
||||
|
||||
case MDBX_opt_prefer_waf_insteadof_balance:
|
||||
*pvalue = env->options.prefer_waf_insteadof_balance;
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_limit:
|
||||
*pvalue = env->options.subpage.limit;
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_room_threshold:
|
||||
*pvalue = env->options.subpage.room_threshold;
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_reserve_prereq:
|
||||
*pvalue = env->options.subpage.reserve_prereq;
|
||||
break;
|
||||
|
||||
case MDBX_opt_subpage_reserve_limit:
|
||||
*pvalue = env->options.subpage.reserve_limit;
|
||||
break;
|
||||
|
||||
default:
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
365
src/api-range-estimate.c
Normal file
365
src/api-range-estimate.c
Normal file
@ -0,0 +1,365 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
typedef struct diff_result {
|
||||
ptrdiff_t diff;
|
||||
intptr_t level;
|
||||
ptrdiff_t root_nkeys;
|
||||
} diff_t;
|
||||
|
||||
/* calculates: r = x - y */
|
||||
__hot static int cursor_diff(const MDBX_cursor *const __restrict x, const MDBX_cursor *const __restrict y,
|
||||
diff_t *const __restrict r) {
|
||||
r->diff = 0;
|
||||
r->level = 0;
|
||||
r->root_nkeys = 0;
|
||||
|
||||
int rc = check_txn(x->txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
if (unlikely(x->txn != y->txn))
|
||||
return MDBX_BAD_TXN;
|
||||
|
||||
if (unlikely(y->dbi_state != x->dbi_state))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
const intptr_t depth = (x->top < y->top) ? x->top : y->top;
|
||||
if (unlikely(depth < 0))
|
||||
return MDBX_ENODATA;
|
||||
|
||||
r->root_nkeys = page_numkeys(x->pg[0]);
|
||||
intptr_t nkeys = r->root_nkeys;
|
||||
for (;;) {
|
||||
if (unlikely(y->pg[r->level] != x->pg[r->level])) {
|
||||
ERROR("Mismatch cursors's pages at %zu level", r->level);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
r->diff = x->ki[r->level] - y->ki[r->level];
|
||||
if (r->diff)
|
||||
break;
|
||||
r->level += 1;
|
||||
if (r->level > depth) {
|
||||
r->diff = CMP2INT(x->flags & z_eof_hard, y->flags & z_eof_hard);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
nkeys = page_numkeys(x->pg[r->level]);
|
||||
}
|
||||
|
||||
while (unlikely(r->diff == 1) && likely(r->level < depth)) {
|
||||
r->level += 1;
|
||||
/* DB'PAGEs: 0------------------>MAX
|
||||
*
|
||||
* CURSORs: y < x
|
||||
* STACK[i ]: |
|
||||
* STACK[+1]: ...y++N|0++x...
|
||||
*/
|
||||
nkeys = page_numkeys(y->pg[r->level]);
|
||||
r->diff = (nkeys - y->ki[r->level]) + x->ki[r->level];
|
||||
assert(r->diff > 0);
|
||||
}
|
||||
|
||||
while (unlikely(r->diff == -1) && likely(r->level < depth)) {
|
||||
r->level += 1;
|
||||
/* DB'PAGEs: 0------------------>MAX
|
||||
*
|
||||
* CURSORs: x < y
|
||||
* STACK[i ]: |
|
||||
* STACK[+1]: ...x--N|0--y...
|
||||
*/
|
||||
nkeys = page_numkeys(x->pg[r->level]);
|
||||
r->diff = -(nkeys - x->ki[r->level]) - y->ki[r->level];
|
||||
assert(r->diff < 0);
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__hot static ptrdiff_t estimate(const tree_t *tree, diff_t *const __restrict dr) {
|
||||
/* root: branch-page => scale = leaf-factor * branch-factor^(N-1)
|
||||
* level-1: branch-page(s) => scale = leaf-factor * branch-factor^2
|
||||
* level-2: branch-page(s) => scale = leaf-factor * branch-factor
|
||||
* level-N: branch-page(s) => scale = leaf-factor
|
||||
* leaf-level: leaf-page(s) => scale = 1
|
||||
*/
|
||||
ptrdiff_t btree_power = (ptrdiff_t)tree->height - 2 - (ptrdiff_t)dr->level;
|
||||
if (btree_power < 0)
|
||||
return dr->diff;
|
||||
|
||||
ptrdiff_t estimated = (ptrdiff_t)tree->items * dr->diff / (ptrdiff_t)tree->leaf_pages;
|
||||
if (btree_power == 0)
|
||||
return estimated;
|
||||
|
||||
if (tree->height < 4) {
|
||||
assert(dr->level == 0 && btree_power == 1);
|
||||
return (ptrdiff_t)tree->items * dr->diff / (ptrdiff_t)dr->root_nkeys;
|
||||
}
|
||||
|
||||
/* average_branchpage_fillfactor = total(branch_entries) / branch_pages
|
||||
total(branch_entries) = leaf_pages + branch_pages - 1 (root page) */
|
||||
const size_t log2_fixedpoint = sizeof(size_t) - 1;
|
||||
const size_t half = UINT64_C(1) << (log2_fixedpoint - 1);
|
||||
const size_t factor = ((tree->leaf_pages + tree->branch_pages - 1) << log2_fixedpoint) / tree->branch_pages;
|
||||
while (1) {
|
||||
switch ((size_t)btree_power) {
|
||||
default: {
|
||||
const size_t square = (factor * factor + half) >> log2_fixedpoint;
|
||||
const size_t quad = (square * square + half) >> log2_fixedpoint;
|
||||
do {
|
||||
estimated = estimated * quad + half;
|
||||
estimated >>= log2_fixedpoint;
|
||||
btree_power -= 4;
|
||||
} while (btree_power >= 4);
|
||||
continue;
|
||||
}
|
||||
case 3:
|
||||
estimated = estimated * factor + half;
|
||||
estimated >>= log2_fixedpoint;
|
||||
__fallthrough /* fall through */;
|
||||
case 2:
|
||||
estimated = estimated * factor + half;
|
||||
estimated >>= log2_fixedpoint;
|
||||
__fallthrough /* fall through */;
|
||||
case 1:
|
||||
estimated = estimated * factor + half;
|
||||
estimated >>= log2_fixedpoint;
|
||||
__fallthrough /* fall through */;
|
||||
case 0:
|
||||
if (unlikely(estimated > (ptrdiff_t)tree->items))
|
||||
return (ptrdiff_t)tree->items;
|
||||
if (unlikely(estimated < -(ptrdiff_t)tree->items))
|
||||
return -(ptrdiff_t)tree->items;
|
||||
return estimated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Range-Estimation API */
|
||||
|
||||
__hot int mdbx_estimate_distance(const MDBX_cursor *first, const MDBX_cursor *last, ptrdiff_t *distance_items) {
|
||||
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;
|
||||
rc = cursor_diff(last, first, &dr);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
cASSERT(first, dr.diff || inner_pointed(first) == inner_pointed(last));
|
||||
if (unlikely(dr.diff == 0) && inner_pointed(first)) {
|
||||
first = &first->subcur->cursor;
|
||||
last = &last->subcur->cursor;
|
||||
rc = cursor_diff(first, last, &dr);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
if (likely(dr.diff != 0))
|
||||
*distance_items = estimate(first->tree, &dr);
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__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(!distance_items || move_op == MDBX_GET_CURRENT || move_op == MDBX_GET_MULTIPLE))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = cursor_check_ro(cursor);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!is_pointed(cursor)))
|
||||
return LOG_IFERR(MDBX_ENODATA);
|
||||
|
||||
cursor_couple_t next;
|
||||
rc = cursor_init(&next.outer, cursor->txn, cursor_dbi(cursor));
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
cursor_cpstk(cursor, &next.outer);
|
||||
if (cursor->tree->flags & MDBX_DUPSORT) {
|
||||
subcur_t *mx = &container_of(cursor, cursor_couple_t, outer)->inner;
|
||||
cursor_cpstk(&mx->cursor, &next.inner.cursor);
|
||||
}
|
||||
|
||||
MDBX_val stub_data;
|
||||
if (data == nullptr) {
|
||||
const unsigned mask = 1 << MDBX_GET_BOTH | 1 << MDBX_GET_BOTH_RANGE | 1 << MDBX_SET_KEY;
|
||||
if (unlikely(mask & (1 << move_op)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
stub_data.iov_base = nullptr;
|
||||
stub_data.iov_len = 0;
|
||||
data = &stub_data;
|
||||
}
|
||||
|
||||
MDBX_val stub_key;
|
||||
if (key == nullptr) {
|
||||
const unsigned mask =
|
||||
1 << MDBX_GET_BOTH | 1 << MDBX_GET_BOTH_RANGE | 1 << MDBX_SET_KEY | 1 << MDBX_SET | 1 << MDBX_SET_RANGE;
|
||||
if (unlikely(mask & (1 << move_op)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
stub_key.iov_base = nullptr;
|
||||
stub_key.iov_len = 0;
|
||||
key = &stub_key;
|
||||
}
|
||||
|
||||
next.outer.signature = cur_signature_live;
|
||||
rc = cursor_ops(&next.outer, key, data, move_op);
|
||||
if (unlikely(rc != MDBX_SUCCESS && (rc != MDBX_NOTFOUND || !is_pointed(&next.outer))))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (move_op == MDBX_LAST) {
|
||||
next.outer.flags |= z_eof_hard;
|
||||
next.inner.cursor.flags |= z_eof_hard;
|
||||
}
|
||||
return mdbx_estimate_distance(cursor, &next.outer, distance_items);
|
||||
}
|
||||
|
||||
__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) {
|
||||
if (unlikely(!size_items))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(begin_data && (begin_key == nullptr || begin_key == MDBX_EPSILON)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(end_data && (end_key == nullptr || end_key == MDBX_EPSILON)))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
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);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(begin.outer.tree->items == 0)) {
|
||||
*size_items = 0;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
if (!begin_key) {
|
||||
if (unlikely(!end_key)) {
|
||||
/* LY: FIRST..LAST case */
|
||||
*size_items = (ptrdiff_t)begin.outer.tree->items;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
rc = outer_first(&begin.outer, nullptr, nullptr);
|
||||
if (unlikely(end_key == MDBX_EPSILON)) {
|
||||
/* LY: FIRST..+epsilon case */
|
||||
return LOG_IFERR((rc == MDBX_SUCCESS) ? mdbx_cursor_count(&begin.outer, (size_t *)size_items) : rc);
|
||||
}
|
||||
} else {
|
||||
if (unlikely(begin_key == MDBX_EPSILON)) {
|
||||
if (end_key == nullptr) {
|
||||
/* LY: -epsilon..LAST case */
|
||||
rc = outer_last(&begin.outer, nullptr, nullptr);
|
||||
return LOG_IFERR((rc == MDBX_SUCCESS) ? mdbx_cursor_count(&begin.outer, (size_t *)size_items) : rc);
|
||||
}
|
||||
/* LY: -epsilon..value case */
|
||||
assert(end_key != MDBX_EPSILON);
|
||||
begin_key = end_key;
|
||||
} else if (unlikely(end_key == MDBX_EPSILON)) {
|
||||
/* LY: value..+epsilon case */
|
||||
assert(begin_key != MDBX_EPSILON);
|
||||
end_key = begin_key;
|
||||
}
|
||||
if (end_key && !begin_data && !end_data &&
|
||||
(begin_key == end_key || begin.outer.clc->k.cmp(begin_key, end_key) == 0)) {
|
||||
/* LY: single key case */
|
||||
rc = cursor_seek(&begin.outer, (MDBX_val *)begin_key, nullptr, MDBX_SET).err;
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
*size_items = 0;
|
||||
return LOG_IFERR((rc == MDBX_NOTFOUND) ? MDBX_SUCCESS : rc);
|
||||
}
|
||||
*size_items = 1;
|
||||
if (inner_pointed(&begin.outer))
|
||||
*size_items = (sizeof(*size_items) >= sizeof(begin.inner.nested_tree.items) ||
|
||||
begin.inner.nested_tree.items <= PTRDIFF_MAX)
|
||||
? (size_t)begin.inner.nested_tree.items
|
||||
: PTRDIFF_MAX;
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
} else {
|
||||
MDBX_val proxy_key = *begin_key;
|
||||
MDBX_val proxy_data = {nullptr, 0};
|
||||
if (begin_data)
|
||||
proxy_data = *begin_data;
|
||||
rc = LOG_IFERR(cursor_seek(&begin.outer, &proxy_key, &proxy_data, MDBX_SET_LOWERBOUND).err);
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
if (rc != MDBX_NOTFOUND || !is_pointed(&begin.outer))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
cursor_couple_t end;
|
||||
rc = cursor_init(&end.outer, txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
if (!end_key) {
|
||||
rc = outer_last(&end.outer, nullptr, nullptr);
|
||||
end.outer.flags |= z_eof_hard;
|
||||
end.inner.cursor.flags |= z_eof_hard;
|
||||
} else {
|
||||
MDBX_val proxy_key = *end_key;
|
||||
MDBX_val proxy_data = {nullptr, 0};
|
||||
if (end_data)
|
||||
proxy_data = *end_data;
|
||||
rc = cursor_seek(&end.outer, &proxy_key, &proxy_data, MDBX_SET_LOWERBOUND).err;
|
||||
}
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
if (rc != MDBX_NOTFOUND || !is_pointed(&end.outer))
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
rc = mdbx_estimate_distance(&begin.outer, &end.outer, size_items);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
assert(*size_items >= -(ptrdiff_t)begin.outer.tree->items && *size_items <= (ptrdiff_t)begin.outer.tree->items);
|
||||
|
||||
#if 0 /* LY: Was decided to returns as-is (i.e. negative) the estimation \
|
||||
* results for an inverted ranges. */
|
||||
|
||||
/* Commit 8ddfd1f34ad7cf7a3c4aa75d2e248ca7e639ed63
|
||||
Change-Id: If59eccf7311123ab6384c4b93f9b1fed5a0a10d1 */
|
||||
|
||||
if (*size_items < 0) {
|
||||
/* LY: inverted range case */
|
||||
*size_items += (ptrdiff_t)begin.outer.tree->items;
|
||||
} else if (*size_items == 0 && begin_key && end_key) {
|
||||
int cmp = begin.outer.kvx->cmp(&origin_begin_key, &origin_end_key);
|
||||
if (cmp == 0 && cursor_pointed(begin.inner.cursor.flags) &&
|
||||
begin_data && end_data)
|
||||
cmp = begin.outer.kvx->v.cmp(&origin_begin_data, &origin_end_data);
|
||||
if (cmp > 0) {
|
||||
/* LY: inverted range case with empty scope */
|
||||
*size_items = (ptrdiff_t)begin.outer.tree->items;
|
||||
}
|
||||
}
|
||||
assert(*size_items >= 0 &&
|
||||
*size_items <= (ptrdiff_t)begin.outer.tree->items);
|
||||
#endif
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
454
src/api-txn-data.c
Normal file
454
src/api-txn-data.c
Normal file
@ -0,0 +1,454 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
__cold int mdbx_dbi_dupsort_depthmask(const MDBX_txn *txn, MDBX_dbi dbi, uint32_t *mask) {
|
||||
if (unlikely(!mask))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
*mask = 0;
|
||||
|
||||
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);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if ((cx.outer.tree->flags & MDBX_DUPSORT) == 0)
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
MDBX_val key, data;
|
||||
rc = outer_first(&cx.outer, &key, &data);
|
||||
while (rc == MDBX_SUCCESS) {
|
||||
const node_t *node = page_node(cx.outer.pg[cx.outer.top], cx.outer.ki[cx.outer.top]);
|
||||
const tree_t *db = node_data(node);
|
||||
const unsigned flags = node_flags(node);
|
||||
switch (flags) {
|
||||
case N_BIG:
|
||||
case 0:
|
||||
/* single-value entry, deep = 0 */
|
||||
*mask |= 1 << 0;
|
||||
break;
|
||||
case N_DUP:
|
||||
/* single sub-page, deep = 1 */
|
||||
*mask |= 1 << 1;
|
||||
break;
|
||||
case N_DUP | N_TREE:
|
||||
/* sub-tree */
|
||||
*mask |= 1 << UNALIGNED_PEEK_16(db, tree_t, height);
|
||||
break;
|
||||
default:
|
||||
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid node-size", flags);
|
||||
return LOG_IFERR(MDBX_CORRUPTED);
|
||||
}
|
||||
rc = outer_next(&cx.outer, &key, &data, MDBX_NEXT_NODUP);
|
||||
}
|
||||
|
||||
return LOG_IFERR((rc == MDBX_NOTFOUND) ? MDBX_SUCCESS : rc);
|
||||
}
|
||||
|
||||
int mdbx_canary_get(const MDBX_txn *txn, MDBX_canary *canary) {
|
||||
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);
|
||||
}
|
||||
|
||||
*canary = txn->canary;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_get(const MDBX_txn *txn, MDBX_dbi dbi, const 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);
|
||||
|
||||
cursor_couple_t cx;
|
||||
rc = cursor_init(&cx.outer, txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
return LOG_IFERR(cursor_seek(&cx.outer, (MDBX_val *)key, data, MDBX_SET).err);
|
||||
}
|
||||
|
||||
int mdbx_get_equal_or_great(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data) {
|
||||
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);
|
||||
|
||||
cursor_couple_t cx;
|
||||
rc = cursor_init(&cx.outer, txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
return LOG_IFERR(cursor_ops(&cx.outer, key, data, MDBX_SET_LOWERBOUND));
|
||||
}
|
||||
|
||||
int mdbx_get_ex(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, size_t *values_count) {
|
||||
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);
|
||||
|
||||
cursor_couple_t cx;
|
||||
rc = cursor_init(&cx.outer, txn, dbi);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = cursor_seek(&cx.outer, key, data, MDBX_SET_KEY).err;
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
if (values_count)
|
||||
*values_count = 0;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
if (values_count) {
|
||||
*values_count = 1;
|
||||
if (inner_pointed(&cx.outer))
|
||||
*values_count =
|
||||
(sizeof(*values_count) >= sizeof(cx.inner.nested_tree.items) || cx.inner.nested_tree.items <= PTRDIFF_MAX)
|
||||
? (size_t)cx.inner.nested_tree.items
|
||||
: PTRDIFF_MAX;
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
int mdbx_canary_put(MDBX_txn *txn, const MDBX_canary *canary) {
|
||||
int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (likely(canary)) {
|
||||
if (txn->canary.x == canary->x && txn->canary.y == canary->y && txn->canary.z == canary->z)
|
||||
return MDBX_SUCCESS;
|
||||
txn->canary.x = canary->x;
|
||||
txn->canary.y = canary->y;
|
||||
txn->canary.z = canary->z;
|
||||
}
|
||||
txn->canary.v = txn->txnid;
|
||||
txn->flags |= MDBX_TXN_DIRTY;
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
/* Функция сообщает находится ли указанный адрес в "грязной" странице у
|
||||
* заданной пишущей транзакции. В конечном счете это позволяет избавиться от
|
||||
* лишнего копирования данных из НЕ-грязных страниц.
|
||||
*
|
||||
* "Грязные" страницы - это те, которые уже были изменены в ходе пишущей
|
||||
* транзакции. Соответственно, какие-либо дальнейшие изменения могут привести
|
||||
* к перезаписи таких страниц. Поэтому все функции, выполняющие изменения, в
|
||||
* качестве аргументов НЕ должны получать указатели на данные в таких
|
||||
* страницах. В свою очередь "НЕ грязные" страницы перед модификацией будут
|
||||
* скопированы.
|
||||
*
|
||||
* Другими словами, данные из "грязных" страниц должны быть либо скопированы
|
||||
* перед передачей в качестве аргументов для дальнейших модификаций, либо
|
||||
* отвергнуты на стадии проверки корректности аргументов.
|
||||
*
|
||||
* Таким образом, функция позволяет как избавится от лишнего копирования,
|
||||
* так и выполнить более полную проверку аргументов.
|
||||
*
|
||||
* ВАЖНО: Передаваемый указатель должен указывать на начало данных. Только
|
||||
* так гарантируется что актуальный заголовок страницы будет физически
|
||||
* расположен в той-же странице памяти, в том числе для многостраничных
|
||||
* P_LARGE страниц с длинными данными. */
|
||||
int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) {
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
const MDBX_env *env = txn->env;
|
||||
const ptrdiff_t offset = ptr_dist(ptr, env->dxb_mmap.base);
|
||||
if (offset >= 0) {
|
||||
const pgno_t pgno = bytes2pgno(env, offset);
|
||||
if (likely(pgno < txn->geo.first_unallocated)) {
|
||||
const page_t *page = pgno2page(env, pgno);
|
||||
if (unlikely(page->pgno != pgno || (page->flags & P_ILL_BITS) != 0)) {
|
||||
/* The ptr pointed into middle of a large page,
|
||||
* not to the beginning of a data. */
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
}
|
||||
return ((txn->flags & MDBX_TXN_RDONLY) || !is_modifable(txn, page)) ? MDBX_RESULT_FALSE : MDBX_RESULT_TRUE;
|
||||
}
|
||||
if ((size_t)offset < env->dxb_mmap.limit) {
|
||||
/* Указатель адресует что-то в пределах mmap, но за границей
|
||||
* распределенных страниц. Такое может случится если mdbx_is_dirty()
|
||||
* вызывается после операции, в ходе которой грязная страница была
|
||||
* возвращена в нераспределенное пространство. */
|
||||
return (txn->flags & MDBX_TXN_RDONLY) ? LOG_IFERR(MDBX_EINVAL) : MDBX_RESULT_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Страница вне используемого mmap-диапазона, т.е. либо в функцию был
|
||||
* передан некорректный адрес, либо адрес в теневой странице, которая была
|
||||
* выделена посредством malloc().
|
||||
*
|
||||
* Для режима MDBX_WRITE_MAP режима страница однозначно "не грязная",
|
||||
* а для режимов без MDBX_WRITE_MAP однозначно "не чистая". */
|
||||
return (txn->flags & (MDBX_WRITEMAP | MDBX_TXN_RDONLY)) ? LOG_IFERR(MDBX_EINVAL) : MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, const MDBX_val *data) {
|
||||
if (unlikely(!key))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(dbi <= FREE_DBI))
|
||||
return LOG_IFERR(MDBX_BAD_DBI);
|
||||
|
||||
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);
|
||||
|
||||
MDBX_val proxy;
|
||||
MDBX_cursor_op op = MDBX_SET;
|
||||
unsigned flags = MDBX_ALLDUPS;
|
||||
if (data) {
|
||||
proxy = *data;
|
||||
data = &proxy;
|
||||
op = MDBX_GET_BOTH;
|
||||
flags = 0;
|
||||
}
|
||||
rc = cursor_seek(&cx.outer, (MDBX_val *)key, (MDBX_val *)data, op).err;
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
cx.outer.next = txn->cursors[dbi];
|
||||
txn->cursors[dbi] = &cx.outer;
|
||||
rc = cursor_del(&cx.outer, flags);
|
||||
txn->cursors[dbi] = cx.outer.next;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags) {
|
||||
if (unlikely(!key || !data))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(dbi <= FREE_DBI))
|
||||
return LOG_IFERR(MDBX_BAD_DBI);
|
||||
|
||||
if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_ALLDUPS | MDBX_ALLDUPS | MDBX_RESERVE | MDBX_APPEND |
|
||||
MDBX_APPENDDUP | MDBX_CURRENT | MDBX_MULTIPLE)))
|
||||
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))
|
||||
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;
|
||||
|
||||
/* LY: support for update (explicit overwrite) */
|
||||
if (flags & MDBX_CURRENT) {
|
||||
rc = cursor_seek(&cx.outer, (MDBX_val *)key, nullptr, MDBX_SET).err;
|
||||
if (likely(rc == MDBX_SUCCESS) && (txn->dbs[dbi].flags & MDBX_DUPSORT) && (flags & MDBX_ALLDUPS) == 0) {
|
||||
/* LY: allows update (explicit overwrite) only for unique keys */
|
||||
node_t *node = page_node(cx.outer.pg[cx.outer.top], cx.outer.ki[cx.outer.top]);
|
||||
if (node_flags(node) & N_DUP) {
|
||||
tASSERT(txn, inner_pointed(&cx.outer) && cx.outer.subcur->nested_tree.items > 1);
|
||||
rc = MDBX_EMULTIVAL;
|
||||
if ((flags & MDBX_NOOVERWRITE) == 0) {
|
||||
flags -= MDBX_CURRENT;
|
||||
rc = cursor_del(&cx.outer, MDBX_ALLDUPS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
rc = cursor_put_checklen(&cx.outer, key, data, flags);
|
||||
txn->cursors[dbi] = cx.outer.next;
|
||||
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* Позволяет обновить или удалить существующую запись с получением
|
||||
* в old_data предыдущего значения данных. При этом если new_data равен
|
||||
* нулю, то выполняется удаление, иначе обновление/вставка.
|
||||
*
|
||||
* Текущее значение может находиться в уже измененной (грязной) странице.
|
||||
* В этом случае страница будет перезаписана при обновлении, а само старое
|
||||
* значение утрачено. Поэтому исходно в old_data должен быть передан
|
||||
* дополнительный буфер для копирования старого значения.
|
||||
* Если переданный буфер слишком мал, то функция вернет -1, установив
|
||||
* old_data->iov_len в соответствующее значение.
|
||||
*
|
||||
* Для не-уникальных ключей также возможен второй сценарий использования,
|
||||
* когда посредством old_data из записей с одинаковым ключом для
|
||||
* удаления/обновления выбирается конкретная. Для выбора этого сценария
|
||||
* во flags следует одновременно указать MDBX_CURRENT и MDBX_NOOVERWRITE.
|
||||
* Именно эта комбинация выбрана, так как она лишена смысла, и этим позволяет
|
||||
* идентифицировать запрос такого сценария.
|
||||
*
|
||||
* Функция может быть замещена соответствующими операциями с курсорами
|
||||
* после двух доработок (TODO):
|
||||
* - внешняя аллокация курсоров, в том числе на стеке (без malloc).
|
||||
* - получения dirty-статуса страницы по адресу (знать о MUTABLE/WRITEABLE).
|
||||
*/
|
||||
|
||||
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) {
|
||||
if (unlikely(!key || !old_data || old_data == new_data))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(old_data->iov_base == nullptr && old_data->iov_len))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(new_data == nullptr && (flags & (MDBX_CURRENT | MDBX_RESERVE)) != MDBX_CURRENT))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(dbi <= FREE_DBI))
|
||||
return LOG_IFERR(MDBX_BAD_DBI);
|
||||
|
||||
if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_ALLDUPS | MDBX_RESERVE | MDBX_APPEND |
|
||||
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))
|
||||
return LOG_IFERR(rc);
|
||||
cx.outer.next = txn->cursors[dbi];
|
||||
txn->cursors[dbi] = &cx.outer;
|
||||
|
||||
MDBX_val present_key = *key;
|
||||
if (F_ISSET(flags, MDBX_CURRENT | MDBX_NOOVERWRITE)) {
|
||||
/* в old_data значение для выбора конкретного дубликата */
|
||||
if (unlikely(!(txn->dbs[dbi].flags & MDBX_DUPSORT))) {
|
||||
rc = MDBX_EINVAL;
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
/* убираем лишний бит, он был признаком запрошенного режима */
|
||||
flags -= MDBX_NOOVERWRITE;
|
||||
|
||||
rc = cursor_seek(&cx.outer, &present_key, old_data, MDBX_GET_BOTH).err;
|
||||
if (rc != MDBX_SUCCESS)
|
||||
goto bailout;
|
||||
} else {
|
||||
/* в old_data буфер для сохранения предыдущего значения */
|
||||
if (unlikely(new_data && old_data->iov_base == new_data->iov_base))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
MDBX_val present_data;
|
||||
rc = cursor_seek(&cx.outer, &present_key, &present_data, MDBX_SET_KEY).err;
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
old_data->iov_base = nullptr;
|
||||
old_data->iov_len = 0;
|
||||
if (rc != MDBX_NOTFOUND || (flags & MDBX_CURRENT))
|
||||
goto bailout;
|
||||
} else if (flags & MDBX_NOOVERWRITE) {
|
||||
rc = MDBX_KEYEXIST;
|
||||
*old_data = present_data;
|
||||
goto bailout;
|
||||
} else {
|
||||
page_t *page = cx.outer.pg[cx.outer.top];
|
||||
if (txn->dbs[dbi].flags & MDBX_DUPSORT) {
|
||||
if (flags & MDBX_CURRENT) {
|
||||
/* disallow update/delete for multi-values */
|
||||
node_t *node = page_node(page, cx.outer.ki[cx.outer.top]);
|
||||
if (node_flags(node) & N_DUP) {
|
||||
tASSERT(txn, inner_pointed(&cx.outer) && cx.outer.subcur->nested_tree.items > 1);
|
||||
if (cx.outer.subcur->nested_tree.items > 1) {
|
||||
rc = MDBX_EMULTIVAL;
|
||||
goto bailout;
|
||||
}
|
||||
}
|
||||
/* В LMDB флажок MDBX_CURRENT здесь приведет
|
||||
* к замене данных без учета MDBX_DUPSORT сортировки,
|
||||
* но здесь это в любом случае допустимо, так как мы
|
||||
* проверили что для ключа есть только одно значение. */
|
||||
}
|
||||
}
|
||||
|
||||
if (is_modifable(txn, page)) {
|
||||
if (new_data && eq_fast(&present_data, new_data)) {
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
*old_data = *new_data;
|
||||
goto bailout;
|
||||
}
|
||||
rc = preserver ? preserver(preserver_context, old_data, present_data.iov_base, present_data.iov_len)
|
||||
: MDBX_SUCCESS;
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
} else {
|
||||
*old_data = present_data;
|
||||
}
|
||||
flags |= MDBX_CURRENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (likely(new_data))
|
||||
rc = cursor_put_checklen(&cx.outer, key, new_data, flags);
|
||||
else
|
||||
rc = cursor_del(&cx.outer, flags & MDBX_ALLDUPS);
|
||||
|
||||
bailout:
|
||||
txn->cursors[dbi] = cx.outer.next;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
static int default_value_preserver(void *context, MDBX_val *target, const void *src, size_t bytes) {
|
||||
(void)context;
|
||||
if (unlikely(target->iov_len < bytes)) {
|
||||
target->iov_base = nullptr;
|
||||
target->iov_len = bytes;
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
memcpy(target->iov_base, src, target->iov_len = bytes);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new_data, MDBX_val *old_data,
|
||||
MDBX_put_flags_t flags) {
|
||||
return mdbx_replace_ex(txn, dbi, key, new_data, old_data, flags, default_value_preserver, nullptr);
|
||||
}
|
540
src/api-txn.c
Normal file
540
src/api-txn.c
Normal file
@ -0,0 +1,540 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
/* LY: avoid tsan-trap by txn, mm_last_pg and geo.first_unallocated */
|
||||
__attribute__((__no_sanitize_thread__, __noinline__))
|
||||
#endif
|
||||
int mdbx_txn_straggler(const MDBX_txn *txn, int *percent)
|
||||
{
|
||||
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))
|
||||
return LOG_IFERR((rc > 0) ? -rc : rc);
|
||||
|
||||
if (unlikely((txn->flags & MDBX_TXN_RDONLY) == 0)) {
|
||||
if (percent)
|
||||
*percent = (int)((txn->geo.first_unallocated * UINT64_C(100) + txn->geo.end_pgno / 2) / txn->geo.end_pgno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
txnid_t lag;
|
||||
troika_t troika = meta_tap(txn->env);
|
||||
do {
|
||||
const meta_ptr_t head = meta_recent(txn->env, &troika);
|
||||
if (percent) {
|
||||
const pgno_t maxpg = head.ptr_v->geometry.now;
|
||||
*percent = (int)((head.ptr_v->geometry.first_unallocated * UINT64_C(100) + maxpg / 2) / maxpg);
|
||||
}
|
||||
lag = (head.txnid - txn->txnid) / xMDBX_TXNID_STEP;
|
||||
} while (unlikely(meta_should_retry(txn->env, &troika)));
|
||||
|
||||
return (lag > INT_MAX) ? INT_MAX : (int)lag;
|
||||
}
|
||||
|
||||
MDBX_env *mdbx_txn_env(const MDBX_txn *txn) {
|
||||
if (unlikely(!txn || txn->signature != txn_signature || txn->env->signature.weak != env_signature))
|
||||
return nullptr;
|
||||
return txn->env;
|
||||
}
|
||||
|
||||
uint64_t mdbx_txn_id(const MDBX_txn *txn) {
|
||||
if (unlikely(!txn || txn->signature != txn_signature))
|
||||
return 0;
|
||||
return txn->txnid;
|
||||
}
|
||||
|
||||
MDBX_txn_flags_t mdbx_txn_flags(const MDBX_txn *txn) {
|
||||
STATIC_ASSERT(
|
||||
(MDBX_TXN_INVALID & (MDBX_TXN_FINISHED | MDBX_TXN_ERROR | MDBX_TXN_DIRTY | MDBX_TXN_SPILLS | MDBX_TXN_HAS_CHILD |
|
||||
txn_gc_drained | txn_shrink_allowed | txn_rw_begin_flags | txn_ro_begin_flags)) == 0);
|
||||
if (unlikely(!txn || txn->signature != txn_signature))
|
||||
return MDBX_TXN_INVALID;
|
||||
assert(0 == (int)(txn->flags & MDBX_TXN_INVALID));
|
||||
|
||||
MDBX_txn_flags_t flags = txn->flags;
|
||||
if (F_ISSET(flags, MDBX_TXN_PARKED | MDBX_TXN_RDONLY) && txn->ro.slot &&
|
||||
safe64_read(&txn->ro.slot->tid) == MDBX_TID_TXN_OUSTED)
|
||||
flags |= MDBX_TXN_OUSTED;
|
||||
return flags;
|
||||
}
|
||||
|
||||
int mdbx_txn_reset(MDBX_txn *txn) {
|
||||
int rc = check_txn(txn, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = check_env(txn->env, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
/* This call is only valid for read-only txns */
|
||||
if (unlikely((txn->flags & MDBX_TXN_RDONLY) == 0))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
/* LY: don't close DBI-handles */
|
||||
rc = txn_end(txn, TXN_END_RESET | TXN_END_UPDATE);
|
||||
if (rc == MDBX_SUCCESS) {
|
||||
tASSERT(txn, txn->signature == txn_signature);
|
||||
tASSERT(txn, txn->owner == 0);
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_txn_break(MDBX_txn *txn) {
|
||||
do {
|
||||
int rc = check_txn(txn, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
txn->flags |= MDBX_TXN_ERROR;
|
||||
txn = txn->nested;
|
||||
} while (txn);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int mdbx_txn_abort(MDBX_txn *txn) {
|
||||
int rc = check_txn(txn, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = check_env(txn->env, true);
|
||||
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));
|
||||
}
|
||||
|
||||
int mdbx_txn_park(MDBX_txn *txn, bool autounpark) {
|
||||
STATIC_ASSERT(MDBX_TXN_BLOCKED > MDBX_TXN_ERROR);
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_ERROR);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = check_env(txn->env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely((txn->flags & MDBX_TXN_RDONLY) == 0))
|
||||
return LOG_IFERR(MDBX_TXN_INVALID);
|
||||
|
||||
if (unlikely((txn->flags & MDBX_TXN_ERROR))) {
|
||||
rc = txn_end(txn, TXN_END_RESET | TXN_END_UPDATE);
|
||||
return LOG_IFERR(rc ? rc : MDBX_OUSTED);
|
||||
}
|
||||
|
||||
return LOG_IFERR(txn_ro_park(txn, autounpark));
|
||||
}
|
||||
|
||||
int mdbx_txn_unpark(MDBX_txn *txn, bool restart_if_ousted) {
|
||||
STATIC_ASSERT(MDBX_TXN_BLOCKED > MDBX_TXN_PARKED + MDBX_TXN_ERROR);
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED - MDBX_TXN_ERROR);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = check_env(txn->env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!F_ISSET(txn->flags, MDBX_TXN_RDONLY | MDBX_TXN_PARKED)))
|
||||
return MDBX_SUCCESS;
|
||||
|
||||
rc = txn_ro_unpark(txn);
|
||||
if (likely(rc != MDBX_OUSTED) || !restart_if_ousted)
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
tASSERT(txn, txn->flags & MDBX_TXN_FINISHED);
|
||||
rc = txn_renew(txn, MDBX_TXN_RDONLY);
|
||||
return (rc == MDBX_SUCCESS) ? MDBX_RESULT_TRUE : LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_txn_renew(MDBX_txn *txn) {
|
||||
int rc = check_txn(txn, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
rc = check_env(txn->env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely((txn->flags & MDBX_TXN_RDONLY) == 0))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
if (unlikely(txn->owner != 0 || !(txn->flags & MDBX_TXN_FINISHED))) {
|
||||
rc = mdbx_txn_reset(txn);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = txn_renew(txn, MDBX_TXN_RDONLY);
|
||||
if (rc == MDBX_SUCCESS) {
|
||||
tASSERT(txn, txn->owner == (txn->flags & MDBX_NOSTICKYTHREADS) ? 0 : osal_thread_self());
|
||||
DEBUG("renew txn %" PRIaTXN "%c %p on env %p, root page %" PRIaPGNO "/%" PRIaPGNO, txn->txnid,
|
||||
(txn->flags & MDBX_TXN_RDONLY) ? 'r' : 'w', (void *)txn, (void *)txn->env, txn->dbs[MAIN_DBI].root,
|
||||
txn->dbs[FREE_DBI].root);
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_txn_set_userctx(MDBX_txn *txn, void *ctx) {
|
||||
int rc = check_txn(txn, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
txn->userctx = ctx;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
void *mdbx_txn_get_userctx(const MDBX_txn *txn) { return check_txn(txn, MDBX_TXN_FINISHED) ? nullptr : txn->userctx; }
|
||||
|
||||
int mdbx_txn_begin_ex(MDBX_env *env, MDBX_txn *parent, MDBX_txn_flags_t flags, MDBX_txn **ret, void *context) {
|
||||
if (unlikely(!ret))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
*ret = nullptr;
|
||||
|
||||
if (unlikely((flags & ~txn_rw_begin_flags) && (parent || (flags & ~txn_ro_begin_flags))))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
int rc = check_env(env, true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(env->flags & MDBX_RDONLY & ~flags)) /* write txn in RDONLY env */
|
||||
return LOG_IFERR(MDBX_EACCESS);
|
||||
|
||||
/* Reuse preallocated write txn. However, do not touch it until
|
||||
* txn_renew() succeeds, since it currently may be active. */
|
||||
MDBX_txn *txn = nullptr;
|
||||
if (parent) {
|
||||
/* Nested transactions: Max 1 child, write txns only, no writemap */
|
||||
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;
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
if (unlikely(parent->env != env))
|
||||
return LOG_IFERR(MDBX_BAD_TXN);
|
||||
|
||||
flags |= parent->flags & (txn_rw_begin_flags | MDBX_TXN_SPILLS | MDBX_NOSTICKYTHREADS | MDBX_WRITEMAP);
|
||||
rc = txn_nested_create(parent, flags);
|
||||
txn = parent->nested;
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
int err = txn_end(txn, TXN_END_FAIL_BEGIN_NESTED);
|
||||
return err ? err : rc;
|
||||
}
|
||||
if (AUDIT_ENABLED() && ASSERT_ENABLED()) {
|
||||
txn->signature = txn_signature;
|
||||
tASSERT(txn, audit_ex(txn, 0, false) == 0);
|
||||
}
|
||||
} else {
|
||||
txn = env->basal_txn;
|
||||
if (flags & MDBX_TXN_RDONLY) {
|
||||
txn = txn_alloc(flags, env);
|
||||
if (unlikely(!txn))
|
||||
return LOG_IFERR(MDBX_ENOMEM);
|
||||
}
|
||||
rc = txn_renew(txn, flags);
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
if (txn != env->basal_txn)
|
||||
osal_free(txn);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & (MDBX_TXN_RDONLY_PREPARE - MDBX_TXN_RDONLY))
|
||||
eASSERT(env, txn->flags == (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED));
|
||||
else if (flags & MDBX_TXN_RDONLY)
|
||||
eASSERT(env, (txn->flags & ~(MDBX_NOSTICKYTHREADS | MDBX_TXN_RDONLY | MDBX_WRITEMAP |
|
||||
/* Win32: SRWL flag */ txn_shrink_allowed)) == 0);
|
||||
else {
|
||||
eASSERT(env, (txn->flags & ~(MDBX_NOSTICKYTHREADS | MDBX_WRITEMAP | txn_shrink_allowed | txn_may_have_cursors |
|
||||
MDBX_NOMETASYNC | MDBX_SAFE_NOSYNC | MDBX_TXN_SPILLS)) == 0);
|
||||
assert(!txn->wr.spilled.list && !txn->wr.spilled.least_removed);
|
||||
}
|
||||
txn->signature = txn_signature;
|
||||
txn->userctx = context;
|
||||
*ret = txn;
|
||||
DEBUG("begin txn %" PRIaTXN "%c %p on env %p, root page %" PRIaPGNO "/%" PRIaPGNO, txn->txnid,
|
||||
(flags & MDBX_TXN_RDONLY) ? 'r' : 'w', (void *)txn, (void *)env, txn->dbs[MAIN_DBI].root,
|
||||
txn->dbs[FREE_DBI].root);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static void latency_gcprof(MDBX_commit_latency *latency, const MDBX_txn *txn) {
|
||||
MDBX_env *const env = txn->env;
|
||||
if (latency && likely(env->lck) && MDBX_ENABLE_PROFGC) {
|
||||
pgop_stat_t *const ptr = &env->lck->pgops;
|
||||
latency->gc_prof.work_counter = ptr->gc_prof.work.spe_counter;
|
||||
latency->gc_prof.work_rtime_monotonic = osal_monotime_to_16dot16(ptr->gc_prof.work.rtime_monotonic);
|
||||
latency->gc_prof.work_xtime_cpu = osal_monotime_to_16dot16(ptr->gc_prof.work.xtime_cpu);
|
||||
latency->gc_prof.work_rsteps = ptr->gc_prof.work.rsteps;
|
||||
latency->gc_prof.work_xpages = ptr->gc_prof.work.xpages;
|
||||
latency->gc_prof.work_majflt = ptr->gc_prof.work.majflt;
|
||||
|
||||
latency->gc_prof.self_counter = ptr->gc_prof.self.spe_counter;
|
||||
latency->gc_prof.self_rtime_monotonic = osal_monotime_to_16dot16(ptr->gc_prof.self.rtime_monotonic);
|
||||
latency->gc_prof.self_xtime_cpu = osal_monotime_to_16dot16(ptr->gc_prof.self.xtime_cpu);
|
||||
latency->gc_prof.self_rsteps = ptr->gc_prof.self.rsteps;
|
||||
latency->gc_prof.self_xpages = ptr->gc_prof.self.xpages;
|
||||
latency->gc_prof.self_majflt = ptr->gc_prof.self.majflt;
|
||||
|
||||
latency->gc_prof.wloops = ptr->gc_prof.wloops;
|
||||
latency->gc_prof.coalescences = ptr->gc_prof.coalescences;
|
||||
latency->gc_prof.wipes = ptr->gc_prof.wipes;
|
||||
latency->gc_prof.flushes = ptr->gc_prof.flushes;
|
||||
latency->gc_prof.kicks = ptr->gc_prof.kicks;
|
||||
|
||||
latency->gc_prof.pnl_merge_work.time = osal_monotime_to_16dot16(ptr->gc_prof.work.pnl_merge.time);
|
||||
latency->gc_prof.pnl_merge_work.calls = ptr->gc_prof.work.pnl_merge.calls;
|
||||
latency->gc_prof.pnl_merge_work.volume = ptr->gc_prof.work.pnl_merge.volume;
|
||||
latency->gc_prof.pnl_merge_self.time = osal_monotime_to_16dot16(ptr->gc_prof.self.pnl_merge.time);
|
||||
latency->gc_prof.pnl_merge_self.calls = ptr->gc_prof.self.pnl_merge.calls;
|
||||
latency->gc_prof.pnl_merge_self.volume = ptr->gc_prof.self.pnl_merge.volume;
|
||||
|
||||
if (txn == env->basal_txn)
|
||||
memset(&ptr->gc_prof, 0, sizeof(ptr->gc_prof));
|
||||
}
|
||||
}
|
||||
|
||||
static void latency_init(MDBX_commit_latency *latency, struct commit_timestamp *ts) {
|
||||
ts->start = 0;
|
||||
ts->gc_cpu = 0;
|
||||
if (latency) {
|
||||
ts->start = osal_monotime();
|
||||
memset(latency, 0, sizeof(*latency));
|
||||
}
|
||||
ts->prep = ts->gc = ts->audit = ts->write = ts->sync = ts->start;
|
||||
}
|
||||
|
||||
static void latency_done(MDBX_commit_latency *latency, struct commit_timestamp *ts) {
|
||||
if (latency) {
|
||||
latency->preparation = (ts->prep > ts->start) ? osal_monotime_to_16dot16(ts->prep - ts->start) : 0;
|
||||
latency->gc_wallclock = (ts->gc > ts->prep) ? osal_monotime_to_16dot16(ts->gc - ts->prep) : 0;
|
||||
latency->gc_cputime = ts->gc_cpu ? osal_monotime_to_16dot16(ts->gc_cpu) : 0;
|
||||
latency->audit = (ts->audit > ts->gc) ? osal_monotime_to_16dot16(ts->audit - ts->gc) : 0;
|
||||
latency->write = (ts->write > ts->audit) ? osal_monotime_to_16dot16(ts->write - ts->audit) : 0;
|
||||
latency->sync = (ts->sync > ts->write) ? osal_monotime_to_16dot16(ts->sync - ts->write) : 0;
|
||||
const uint64_t ts_end = osal_monotime();
|
||||
latency->ending = (ts_end > ts->sync) ? osal_monotime_to_16dot16(ts_end - ts->sync) : 0;
|
||||
latency->whole = osal_monotime_to_16dot16_noUnderflow(ts_end - ts->start);
|
||||
}
|
||||
}
|
||||
|
||||
int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
|
||||
STATIC_ASSERT(MDBX_TXN_FINISHED == MDBX_TXN_BLOCKED - MDBX_TXN_HAS_CHILD - MDBX_TXN_ERROR - MDBX_TXN_PARKED);
|
||||
|
||||
struct commit_timestamp ts;
|
||||
latency_init(latency, &ts);
|
||||
|
||||
int rc = check_txn(txn, MDBX_TXN_FINISHED);
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
if (rc == MDBX_BAD_TXN && F_ISSET(txn->flags, MDBX_TXN_FINISHED | MDBX_TXN_RDONLY)) {
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
goto fail;
|
||||
}
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
MDBX_env *const env = txn->env;
|
||||
if (MDBX_ENV_CHECKPID && unlikely(env->pid != osal_getpid())) {
|
||||
env->flags |= ENV_FATAL_ERROR;
|
||||
rc = MDBX_PANIC;
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
if (txn->flags & MDBX_TXN_RDONLY) {
|
||||
if (unlikely(txn->parent || (txn->flags & MDBX_TXN_HAS_CHILD) || txn == env->txn || txn == env->basal_txn)) {
|
||||
ERROR("attempt to commit %s txn %p", "strange read-only", (void *)txn);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
latency_gcprof(latency, txn);
|
||||
rc = (txn->flags & MDBX_TXN_ERROR) ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
|
||||
txn_end(txn, TXN_END_PURE_COMMIT | TXN_END_UPDATE | TXN_END_SLOT | TXN_END_FREE);
|
||||
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;
|
||||
fail:
|
||||
latency_gcprof(latency, txn);
|
||||
int err = txn_abort(txn);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
rc = err;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (txn->nested) {
|
||||
rc = mdbx_txn_commit_ex(txn->nested, nullptr);
|
||||
tASSERT(txn, txn->nested == nullptr);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (unlikely(txn != env->txn)) {
|
||||
ERROR("attempt to commit %s txn %p", "unknown", (void *)txn);
|
||||
return MDBX_EINVAL;
|
||||
}
|
||||
|
||||
if (txn->parent) {
|
||||
if (unlikely(txn->parent->nested != txn || txn->parent->env != env)) {
|
||||
ERROR("attempt to commit %s txn %p", "strange nested", (void *)txn);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
|
||||
latency_gcprof(latency, txn);
|
||||
rc = txn_nested_join(txn, latency ? &ts : nullptr);
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = txn_basal_commit(txn, latency ? &ts : nullptr);
|
||||
latency_gcprof(latency, txn);
|
||||
int end = TXN_END_COMMITTED | TXN_END_UPDATE;
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
end = TXN_END_ABORT;
|
||||
if (rc == MDBX_RESULT_TRUE) {
|
||||
end = TXN_END_PURE_COMMIT | TXN_END_UPDATE;
|
||||
rc = MDBX_NOSUCCESS_PURE_COMMIT ? MDBX_RESULT_TRUE : MDBX_SUCCESS;
|
||||
}
|
||||
}
|
||||
int err = txn_end(txn, end);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
rc = err;
|
||||
|
||||
done:
|
||||
latency_done(latency, &ts);
|
||||
return LOG_IFERR(rc);
|
||||
}
|
||||
|
||||
int mdbx_txn_info(const MDBX_txn *txn, MDBX_txn_info *info, bool scan_rlt) {
|
||||
int rc = check_txn(txn, MDBX_TXN_FINISHED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return LOG_IFERR(rc);
|
||||
|
||||
if (unlikely(!info))
|
||||
return LOG_IFERR(MDBX_EINVAL);
|
||||
|
||||
MDBX_env *const env = txn->env;
|
||||
#if MDBX_ENV_CHECKPID
|
||||
if (unlikely(env->pid != osal_getpid())) {
|
||||
env->flags |= ENV_FATAL_ERROR;
|
||||
return LOG_IFERR(MDBX_PANIC);
|
||||
}
|
||||
#endif /* MDBX_ENV_CHECKPID */
|
||||
|
||||
info->txn_id = txn->txnid;
|
||||
info->txn_space_used = pgno2bytes(env, txn->geo.first_unallocated);
|
||||
|
||||
if (txn->flags & MDBX_TXN_RDONLY) {
|
||||
meta_ptr_t head;
|
||||
uint64_t head_retired;
|
||||
troika_t troika = meta_tap(env);
|
||||
do {
|
||||
/* fetch info from volatile head */
|
||||
head = meta_recent(env, &troika);
|
||||
head_retired = unaligned_peek_u64_volatile(4, head.ptr_v->pages_retired);
|
||||
info->txn_space_limit_soft = pgno2bytes(env, head.ptr_v->geometry.now);
|
||||
info->txn_space_limit_hard = pgno2bytes(env, head.ptr_v->geometry.upper);
|
||||
info->txn_space_leftover = pgno2bytes(env, head.ptr_v->geometry.now - head.ptr_v->geometry.first_unallocated);
|
||||
} while (unlikely(meta_should_retry(env, &troika)));
|
||||
|
||||
info->txn_reader_lag = head.txnid - info->txn_id;
|
||||
info->txn_space_dirty = info->txn_space_retired = 0;
|
||||
uint64_t reader_snapshot_pages_retired = 0;
|
||||
if (txn->ro.slot &&
|
||||
((txn->flags & MDBX_TXN_PARKED) == 0 || safe64_read(&txn->ro.slot->tid) != MDBX_TID_TXN_OUSTED) &&
|
||||
head_retired >
|
||||
(reader_snapshot_pages_retired = atomic_load64(&txn->ro.slot->snapshot_pages_retired, mo_Relaxed))) {
|
||||
info->txn_space_dirty = info->txn_space_retired =
|
||||
pgno2bytes(env, (pgno_t)(head_retired - reader_snapshot_pages_retired));
|
||||
|
||||
size_t retired_next_reader = 0;
|
||||
lck_t *const lck = env->lck_mmap.lck;
|
||||
if (scan_rlt && info->txn_reader_lag > 1 && lck) {
|
||||
/* find next more recent reader */
|
||||
txnid_t next_reader = head.txnid;
|
||||
const size_t snap_nreaders = atomic_load32(&lck->rdt_length, mo_AcquireRelease);
|
||||
for (size_t i = 0; i < snap_nreaders; ++i) {
|
||||
retry:
|
||||
if (atomic_load32(&lck->rdt[i].pid, mo_AcquireRelease)) {
|
||||
jitter4testing(true);
|
||||
const uint64_t snap_tid = safe64_read(&lck->rdt[i].tid);
|
||||
const txnid_t snap_txnid = safe64_read(&lck->rdt[i].txnid);
|
||||
const uint64_t snap_retired = atomic_load64(&lck->rdt[i].snapshot_pages_retired, mo_AcquireRelease);
|
||||
if (unlikely(snap_retired != atomic_load64(&lck->rdt[i].snapshot_pages_retired, mo_Relaxed)) ||
|
||||
snap_txnid != safe64_read(&lck->rdt[i].txnid) || snap_tid != safe64_read(&lck->rdt[i].tid))
|
||||
goto retry;
|
||||
if (snap_txnid <= txn->txnid) {
|
||||
retired_next_reader = 0;
|
||||
break;
|
||||
}
|
||||
if (snap_txnid < next_reader && snap_tid >= MDBX_TID_TXN_OUSTED) {
|
||||
next_reader = snap_txnid;
|
||||
retired_next_reader = pgno2bytes(
|
||||
env, (pgno_t)(snap_retired - atomic_load64(&txn->ro.slot->snapshot_pages_retired, mo_Relaxed)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info->txn_space_dirty = retired_next_reader;
|
||||
}
|
||||
} else {
|
||||
info->txn_space_limit_soft = pgno2bytes(env, txn->geo.now);
|
||||
info->txn_space_limit_hard = pgno2bytes(env, txn->geo.upper);
|
||||
info->txn_space_retired =
|
||||
pgno2bytes(env, txn->nested ? (size_t)txn->wr.retired_pages : MDBX_PNL_GETSIZE(txn->wr.retired_pages));
|
||||
info->txn_space_leftover = pgno2bytes(env, txn->wr.dirtyroom);
|
||||
info->txn_space_dirty =
|
||||
pgno2bytes(env, txn->wr.dirtylist ? txn->wr.dirtylist->pages_including_loose
|
||||
: (txn->wr.writemap_dirty_npages + txn->wr.writemap_spilled_npages));
|
||||
info->txn_reader_lag = INT64_MAX;
|
||||
lck_t *const lck = env->lck_mmap.lck;
|
||||
if (scan_rlt && lck) {
|
||||
txnid_t oldest_reading = txn->txnid;
|
||||
const size_t snap_nreaders = atomic_load32(&lck->rdt_length, mo_AcquireRelease);
|
||||
if (snap_nreaders) {
|
||||
txn_gc_detent(txn);
|
||||
oldest_reading = txn->env->gc.detent;
|
||||
if (oldest_reading == txn->wr.troika.txnid[txn->wr.troika.recent]) {
|
||||
/* Если самый старый используемый снимок является предыдущим, т. е. непосредственно предшествующим текущей
|
||||
* транзакции, то просматриваем таблицу читателей чтобы выяснить действительно ли снимок используется
|
||||
* читателями. */
|
||||
oldest_reading = txn->txnid;
|
||||
for (size_t i = 0; i < snap_nreaders; ++i) {
|
||||
if (atomic_load32(&lck->rdt[i].pid, mo_Relaxed) && txn->env->gc.detent == safe64_read(&lck->rdt[i].txnid)) {
|
||||
oldest_reading = txn->env->gc.detent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info->txn_reader_lag = txn->txnid - oldest_reading;
|
||||
}
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
364
src/atomics-ops.h
Normal file
364
src/atomics-ops.h
Normal file
@ -0,0 +1,364 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
#ifndef __cplusplus
|
||||
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
#define osal_memory_fence(order, write) atomic_thread_fence((write) ? mo_c11_store(order) : mo_c11_load(order))
|
||||
#else /* MDBX_HAVE_C11ATOMICS */
|
||||
#define osal_memory_fence(order, write) \
|
||||
do { \
|
||||
osal_compiler_barrier(); \
|
||||
if (write && order > (MDBX_CPU_WRITEBACK_INCOHERENT ? mo_Relaxed : mo_AcquireRelease)) \
|
||||
osal_memory_barrier(); \
|
||||
} while (0)
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
|
||||
#if defined(MDBX_HAVE_C11ATOMICS) && defined(__LCC__)
|
||||
#define atomic_store32(p, value, order) \
|
||||
({ \
|
||||
const uint32_t value_to_store = (value); \
|
||||
atomic_store_explicit(MDBX_c11a_rw(uint32_t, p), value_to_store, mo_c11_store(order)); \
|
||||
value_to_store; \
|
||||
})
|
||||
#define atomic_load32(p, order) atomic_load_explicit(MDBX_c11a_ro(uint32_t, p), mo_c11_load(order))
|
||||
#define atomic_store64(p, value, order) \
|
||||
({ \
|
||||
const uint64_t value_to_store = (value); \
|
||||
atomic_store_explicit(MDBX_c11a_rw(uint64_t, p), value_to_store, mo_c11_store(order)); \
|
||||
value_to_store; \
|
||||
})
|
||||
#define atomic_load64(p, order) atomic_load_explicit(MDBX_c11a_ro(uint64_t, p), mo_c11_load(order))
|
||||
#endif /* LCC && MDBX_HAVE_C11ATOMICS */
|
||||
|
||||
#ifndef atomic_store32
|
||||
MDBX_MAYBE_UNUSED static __always_inline uint32_t atomic_store32(mdbx_atomic_uint32_t *p, const uint32_t value,
|
||||
enum mdbx_memory_order order) {
|
||||
STATIC_ASSERT(sizeof(mdbx_atomic_uint32_t) == 4);
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
assert(atomic_is_lock_free(MDBX_c11a_rw(uint32_t, p)));
|
||||
atomic_store_explicit(MDBX_c11a_rw(uint32_t, p), value, mo_c11_store(order));
|
||||
#else /* MDBX_HAVE_C11ATOMICS */
|
||||
if (order != mo_Relaxed)
|
||||
osal_compiler_barrier();
|
||||
p->weak = value;
|
||||
osal_memory_fence(order, true);
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
return value;
|
||||
}
|
||||
#endif /* atomic_store32 */
|
||||
|
||||
#ifndef atomic_load32
|
||||
MDBX_MAYBE_UNUSED static __always_inline uint32_t atomic_load32(const volatile mdbx_atomic_uint32_t *p,
|
||||
enum mdbx_memory_order order) {
|
||||
STATIC_ASSERT(sizeof(mdbx_atomic_uint32_t) == 4);
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
assert(atomic_is_lock_free(MDBX_c11a_ro(uint32_t, p)));
|
||||
return atomic_load_explicit(MDBX_c11a_ro(uint32_t, p), mo_c11_load(order));
|
||||
#else /* MDBX_HAVE_C11ATOMICS */
|
||||
osal_memory_fence(order, false);
|
||||
const uint32_t value = p->weak;
|
||||
if (order != mo_Relaxed)
|
||||
osal_compiler_barrier();
|
||||
return value;
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
}
|
||||
#endif /* atomic_load32 */
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* safe read/write volatile 64-bit fields on 32-bit architectures. */
|
||||
|
||||
/* LY: for testing non-atomic 64-bit txnid on 32-bit arches.
|
||||
* #define xMDBX_TXNID_STEP (UINT32_MAX / 3) */
|
||||
#ifndef xMDBX_TXNID_STEP
|
||||
#if MDBX_64BIT_CAS
|
||||
#define xMDBX_TXNID_STEP 1u
|
||||
#else
|
||||
#define xMDBX_TXNID_STEP 2u
|
||||
#endif
|
||||
#endif /* xMDBX_TXNID_STEP */
|
||||
|
||||
#ifndef atomic_store64
|
||||
MDBX_MAYBE_UNUSED static __always_inline uint64_t atomic_store64(mdbx_atomic_uint64_t *p, const uint64_t value,
|
||||
enum mdbx_memory_order order) {
|
||||
STATIC_ASSERT(sizeof(mdbx_atomic_uint64_t) == 8);
|
||||
#if MDBX_64BIT_ATOMIC
|
||||
#if __GNUC_PREREQ(11, 0)
|
||||
STATIC_ASSERT(__alignof__(mdbx_atomic_uint64_t) >= sizeof(uint64_t));
|
||||
#endif /* GNU C >= 11 */
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
assert(atomic_is_lock_free(MDBX_c11a_rw(uint64_t, p)));
|
||||
atomic_store_explicit(MDBX_c11a_rw(uint64_t, p), value, mo_c11_store(order));
|
||||
#else /* MDBX_HAVE_C11ATOMICS */
|
||||
if (order != mo_Relaxed)
|
||||
osal_compiler_barrier();
|
||||
p->weak = value;
|
||||
osal_memory_fence(order, true);
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
#else /* !MDBX_64BIT_ATOMIC */
|
||||
osal_compiler_barrier();
|
||||
atomic_store32(&p->low, (uint32_t)value, mo_Relaxed);
|
||||
jitter4testing(true);
|
||||
atomic_store32(&p->high, (uint32_t)(value >> 32), order);
|
||||
jitter4testing(true);
|
||||
#endif /* !MDBX_64BIT_ATOMIC */
|
||||
return value;
|
||||
}
|
||||
#endif /* atomic_store64 */
|
||||
|
||||
#ifndef atomic_load64
|
||||
MDBX_MAYBE_UNUSED static
|
||||
#if MDBX_64BIT_ATOMIC
|
||||
__always_inline
|
||||
#endif /* MDBX_64BIT_ATOMIC */
|
||||
uint64_t
|
||||
atomic_load64(const volatile mdbx_atomic_uint64_t *p, enum mdbx_memory_order order) {
|
||||
STATIC_ASSERT(sizeof(mdbx_atomic_uint64_t) == 8);
|
||||
#if MDBX_64BIT_ATOMIC
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
assert(atomic_is_lock_free(MDBX_c11a_ro(uint64_t, p)));
|
||||
return atomic_load_explicit(MDBX_c11a_ro(uint64_t, p), mo_c11_load(order));
|
||||
#else /* MDBX_HAVE_C11ATOMICS */
|
||||
osal_memory_fence(order, false);
|
||||
const uint64_t value = p->weak;
|
||||
if (order != mo_Relaxed)
|
||||
osal_compiler_barrier();
|
||||
return value;
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
#else /* !MDBX_64BIT_ATOMIC */
|
||||
osal_compiler_barrier();
|
||||
uint64_t value = (uint64_t)atomic_load32(&p->high, order) << 32;
|
||||
jitter4testing(true);
|
||||
value |= atomic_load32(&p->low, (order == mo_Relaxed) ? mo_Relaxed : mo_AcquireRelease);
|
||||
jitter4testing(true);
|
||||
for (;;) {
|
||||
osal_compiler_barrier();
|
||||
uint64_t again = (uint64_t)atomic_load32(&p->high, order) << 32;
|
||||
jitter4testing(true);
|
||||
again |= atomic_load32(&p->low, (order == mo_Relaxed) ? mo_Relaxed : mo_AcquireRelease);
|
||||
jitter4testing(true);
|
||||
if (likely(value == again))
|
||||
return value;
|
||||
value = again;
|
||||
}
|
||||
#endif /* !MDBX_64BIT_ATOMIC */
|
||||
}
|
||||
#endif /* atomic_load64 */
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline void atomic_yield(void) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
YieldProcessor();
|
||||
#elif defined(__ia32__) || defined(__e2k__)
|
||||
__builtin_ia32_pause();
|
||||
#elif defined(__ia64__)
|
||||
#if defined(__HP_cc__) || defined(__HP_aCC__)
|
||||
_Asm_hint(_HINT_PAUSE);
|
||||
#else
|
||||
__asm__ __volatile__("hint @pause");
|
||||
#endif
|
||||
#elif defined(__aarch64__) || (defined(__ARM_ARCH) && __ARM_ARCH > 6) || defined(__ARM_ARCH_6K__)
|
||||
#ifdef __CC_ARM
|
||||
__yield();
|
||||
#else
|
||||
__asm__ __volatile__("yield");
|
||||
#endif
|
||||
#elif (defined(__mips64) || defined(__mips64__)) && defined(__mips_isa_rev) && __mips_isa_rev >= 2
|
||||
__asm__ __volatile__("pause");
|
||||
#elif defined(__mips) || defined(__mips__) || defined(__mips64) || defined(__mips64__) || defined(_M_MRX000) || \
|
||||
defined(_MIPS_) || defined(__MWERKS__) || defined(__sgi)
|
||||
__asm__ __volatile__(".word 0x00000140");
|
||||
#elif defined(__linux__) || defined(__gnu_linux__) || defined(_UNIX03_SOURCE)
|
||||
sched_yield();
|
||||
#elif (defined(_GNU_SOURCE) && __GLIBC_PREREQ(2, 1)) || defined(_OPEN_THREADS)
|
||||
pthread_yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MDBX_64BIT_CAS
|
||||
MDBX_MAYBE_UNUSED static __always_inline bool atomic_cas64(mdbx_atomic_uint64_t *p, uint64_t c, uint64_t v) {
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
STATIC_ASSERT(sizeof(long long) >= sizeof(uint64_t));
|
||||
assert(atomic_is_lock_free(MDBX_c11a_rw(uint64_t, p)));
|
||||
return atomic_compare_exchange_strong(MDBX_c11a_rw(uint64_t, p), &c, v);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
return __sync_bool_compare_and_swap(&p->weak, c, v);
|
||||
#elif defined(_MSC_VER)
|
||||
return c == (uint64_t)_InterlockedCompareExchange64((volatile __int64 *)&p->weak, v, c);
|
||||
#elif defined(__APPLE__)
|
||||
return OSAtomicCompareAndSwap64Barrier(c, v, &p->weak);
|
||||
#else
|
||||
#error FIXME: Unsupported compiler
|
||||
#endif
|
||||
}
|
||||
#endif /* MDBX_64BIT_CAS */
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline bool atomic_cas32(mdbx_atomic_uint32_t *p, uint32_t c, uint32_t v) {
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
STATIC_ASSERT(sizeof(int) >= sizeof(uint32_t));
|
||||
assert(atomic_is_lock_free(MDBX_c11a_rw(uint32_t, p)));
|
||||
return atomic_compare_exchange_strong(MDBX_c11a_rw(uint32_t, p), &c, v);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
return __sync_bool_compare_and_swap(&p->weak, c, v);
|
||||
#elif defined(_MSC_VER)
|
||||
STATIC_ASSERT(sizeof(volatile long) == sizeof(volatile uint32_t));
|
||||
return c == (uint32_t)_InterlockedCompareExchange((volatile long *)&p->weak, v, c);
|
||||
#elif defined(__APPLE__)
|
||||
return OSAtomicCompareAndSwap32Barrier(c, v, &p->weak);
|
||||
#else
|
||||
#error FIXME: Unsupported compiler
|
||||
#endif
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline uint32_t atomic_add32(mdbx_atomic_uint32_t *p, uint32_t v) {
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
STATIC_ASSERT(sizeof(int) >= sizeof(uint32_t));
|
||||
assert(atomic_is_lock_free(MDBX_c11a_rw(uint32_t, p)));
|
||||
return atomic_fetch_add(MDBX_c11a_rw(uint32_t, p), v);
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
return __sync_fetch_and_add(&p->weak, v);
|
||||
#elif defined(_MSC_VER)
|
||||
STATIC_ASSERT(sizeof(volatile long) == sizeof(volatile uint32_t));
|
||||
return (uint32_t)_InterlockedExchangeAdd((volatile long *)&p->weak, v);
|
||||
#elif defined(__APPLE__)
|
||||
return OSAtomicAdd32Barrier(v, &p->weak);
|
||||
#else
|
||||
#error FIXME: Unsupported compiler
|
||||
#endif
|
||||
}
|
||||
|
||||
#define atomic_sub32(p, v) atomic_add32(p, 0 - (v))
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline uint64_t safe64_txnid_next(uint64_t txnid) {
|
||||
txnid += xMDBX_TXNID_STEP;
|
||||
#if !MDBX_64BIT_CAS
|
||||
/* avoid overflow of low-part in safe64_reset() */
|
||||
txnid += (UINT32_MAX == (uint32_t)txnid);
|
||||
#endif
|
||||
return txnid;
|
||||
}
|
||||
|
||||
/* Atomically make target value >= SAFE64_INVALID_THRESHOLD */
|
||||
MDBX_MAYBE_UNUSED static __always_inline void safe64_reset(mdbx_atomic_uint64_t *p, bool single_writer) {
|
||||
if (single_writer) {
|
||||
#if MDBX_64BIT_ATOMIC && MDBX_WORDBITS >= 64
|
||||
atomic_store64(p, UINT64_MAX, mo_AcquireRelease);
|
||||
#else
|
||||
atomic_store32(&p->high, UINT32_MAX, mo_AcquireRelease);
|
||||
#endif /* MDBX_64BIT_ATOMIC && MDBX_WORDBITS >= 64 */
|
||||
} else {
|
||||
#if MDBX_64BIT_CAS && MDBX_64BIT_ATOMIC
|
||||
/* atomically make value >= SAFE64_INVALID_THRESHOLD by 64-bit operation */
|
||||
atomic_store64(p, UINT64_MAX, mo_AcquireRelease);
|
||||
#elif MDBX_64BIT_CAS
|
||||
/* atomically make value >= SAFE64_INVALID_THRESHOLD by 32-bit operation */
|
||||
atomic_store32(&p->high, UINT32_MAX, mo_AcquireRelease);
|
||||
#else
|
||||
/* it is safe to increment low-part to avoid ABA, since xMDBX_TXNID_STEP > 1
|
||||
* and overflow was preserved in safe64_txnid_next() */
|
||||
STATIC_ASSERT(xMDBX_TXNID_STEP > 1);
|
||||
atomic_add32(&p->low, 1) /* avoid ABA in safe64_reset_compare() */;
|
||||
atomic_store32(&p->high, UINT32_MAX, mo_AcquireRelease);
|
||||
atomic_add32(&p->low, 1) /* avoid ABA in safe64_reset_compare() */;
|
||||
#endif /* MDBX_64BIT_CAS && MDBX_64BIT_ATOMIC */
|
||||
}
|
||||
assert(p->weak >= SAFE64_INVALID_THRESHOLD);
|
||||
jitter4testing(true);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline bool safe64_reset_compare(mdbx_atomic_uint64_t *p, uint64_t compare) {
|
||||
/* LY: This function is used to reset `txnid` from hsr-handler in case
|
||||
* the asynchronously cancellation of read transaction. Therefore,
|
||||
* there may be a collision between the cleanup performed here and
|
||||
* asynchronous termination and restarting of the read transaction
|
||||
* in another process/thread. In general we MUST NOT reset the `txnid`
|
||||
* if a new transaction was started (i.e. if `txnid` was changed). */
|
||||
#if MDBX_64BIT_CAS
|
||||
bool rc = atomic_cas64(p, compare, UINT64_MAX);
|
||||
#else
|
||||
/* LY: There is no gold ratio here since shared mutex is too costly,
|
||||
* in such way we must acquire/release it for every update of txnid,
|
||||
* i.e. twice for each read transaction). */
|
||||
bool rc = false;
|
||||
if (likely(atomic_load32(&p->low, mo_AcquireRelease) == (uint32_t)compare &&
|
||||
atomic_cas32(&p->high, (uint32_t)(compare >> 32), UINT32_MAX))) {
|
||||
if (unlikely(atomic_load32(&p->low, mo_AcquireRelease) != (uint32_t)compare))
|
||||
atomic_cas32(&p->high, UINT32_MAX, (uint32_t)(compare >> 32));
|
||||
else
|
||||
rc = true;
|
||||
}
|
||||
#endif /* MDBX_64BIT_CAS */
|
||||
jitter4testing(true);
|
||||
return rc;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline void safe64_write(mdbx_atomic_uint64_t *p, const uint64_t v) {
|
||||
assert(p->weak >= SAFE64_INVALID_THRESHOLD);
|
||||
#if MDBX_64BIT_ATOMIC && MDBX_64BIT_CAS
|
||||
atomic_store64(p, v, mo_AcquireRelease);
|
||||
#else /* MDBX_64BIT_ATOMIC */
|
||||
osal_compiler_barrier();
|
||||
/* update low-part but still value >= SAFE64_INVALID_THRESHOLD */
|
||||
atomic_store32(&p->low, (uint32_t)v, mo_Relaxed);
|
||||
assert(p->weak >= SAFE64_INVALID_THRESHOLD);
|
||||
jitter4testing(true);
|
||||
/* update high-part from SAFE64_INVALID_THRESHOLD to actual value */
|
||||
atomic_store32(&p->high, (uint32_t)(v >> 32), mo_AcquireRelease);
|
||||
#endif /* MDBX_64BIT_ATOMIC */
|
||||
assert(p->weak == v);
|
||||
jitter4testing(true);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline uint64_t safe64_read(const mdbx_atomic_uint64_t *p) {
|
||||
jitter4testing(true);
|
||||
uint64_t v;
|
||||
do
|
||||
v = atomic_load64(p, mo_AcquireRelease);
|
||||
while (!MDBX_64BIT_ATOMIC && unlikely(v != p->weak));
|
||||
return v;
|
||||
}
|
||||
|
||||
#if 0 /* unused for now */
|
||||
MDBX_MAYBE_UNUSED static __always_inline bool safe64_is_valid(uint64_t v) {
|
||||
#if MDBX_WORDBITS >= 64
|
||||
return v < SAFE64_INVALID_THRESHOLD;
|
||||
#else
|
||||
return (v >> 32) != UINT32_MAX;
|
||||
#endif /* MDBX_WORDBITS */
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static __always_inline bool
|
||||
safe64_is_valid_ptr(const mdbx_atomic_uint64_t *p) {
|
||||
#if MDBX_64BIT_ATOMIC
|
||||
return atomic_load64(p, mo_AcquireRelease) < SAFE64_INVALID_THRESHOLD;
|
||||
#else
|
||||
return atomic_load32(&p->high, mo_AcquireRelease) != UINT32_MAX;
|
||||
#endif /* MDBX_64BIT_ATOMIC */
|
||||
}
|
||||
#endif /* unused for now */
|
||||
|
||||
/* non-atomic write with safety for reading a half-updated value */
|
||||
MDBX_MAYBE_UNUSED static __always_inline void safe64_update(mdbx_atomic_uint64_t *p, const uint64_t v) {
|
||||
#if MDBX_64BIT_ATOMIC
|
||||
atomic_store64(p, v, mo_Relaxed);
|
||||
#else
|
||||
safe64_reset(p, true);
|
||||
safe64_write(p, v);
|
||||
#endif /* MDBX_64BIT_ATOMIC */
|
||||
}
|
||||
|
||||
/* non-atomic increment with safety for reading a half-updated value */
|
||||
MDBX_MAYBE_UNUSED static
|
||||
#if MDBX_64BIT_ATOMIC
|
||||
__always_inline
|
||||
#endif /* MDBX_64BIT_ATOMIC */
|
||||
void
|
||||
safe64_inc(mdbx_atomic_uint64_t *p, const uint64_t v) {
|
||||
assert(v > 0);
|
||||
safe64_update(p, safe64_read(p) + v);
|
||||
}
|
||||
|
||||
#endif /* !__cplusplus */
|
97
src/atomics-types.h
Normal file
97
src/atomics-types.h
Normal file
@ -0,0 +1,97 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
#ifndef MDBX_64BIT_ATOMIC
|
||||
#error "The MDBX_64BIT_ATOMIC must be defined before"
|
||||
#endif /* MDBX_64BIT_ATOMIC */
|
||||
|
||||
#ifndef MDBX_64BIT_CAS
|
||||
#error "The MDBX_64BIT_CAS must be defined before"
|
||||
#endif /* MDBX_64BIT_CAS */
|
||||
|
||||
#if defined(__cplusplus) && !defined(__STDC_NO_ATOMICS__) && __has_include(<cstdatomic>)
|
||||
#include <cstdatomic>
|
||||
#define MDBX_HAVE_C11ATOMICS
|
||||
#elif !defined(__cplusplus) && (__STDC_VERSION__ >= 201112L || __has_extension(c_atomic)) && \
|
||||
!defined(__STDC_NO_ATOMICS__) && \
|
||||
(__GNUC_PREREQ(4, 9) || __CLANG_PREREQ(3, 8) || !(defined(__GNUC__) || defined(__clang__)))
|
||||
#include <stdatomic.h>
|
||||
#define MDBX_HAVE_C11ATOMICS
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(disable : 4163) /* 'xyz': not available as an intrinsic */
|
||||
#pragma warning(disable : 4133) /* 'function': incompatible types - from \
|
||||
'size_t' to 'LONGLONG' */
|
||||
#pragma warning(disable : 4244) /* 'return': conversion from 'LONGLONG' to \
|
||||
'std::size_t', possible loss of data */
|
||||
#pragma warning(disable : 4267) /* 'function': conversion from 'size_t' to \
|
||||
'long', possible loss of data */
|
||||
#pragma intrinsic(_InterlockedExchangeAdd, _InterlockedCompareExchange)
|
||||
#pragma intrinsic(_InterlockedExchangeAdd64, _InterlockedCompareExchange64)
|
||||
#elif defined(__APPLE__)
|
||||
#include <libkern/OSAtomic.h>
|
||||
#else
|
||||
#error FIXME atomic-ops
|
||||
#endif
|
||||
|
||||
typedef enum mdbx_memory_order {
|
||||
mo_Relaxed,
|
||||
mo_AcquireRelease
|
||||
/* , mo_SequentialConsistency */
|
||||
} mdbx_memory_order_t;
|
||||
|
||||
typedef union {
|
||||
volatile uint32_t weak;
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
volatile _Atomic uint32_t c11a;
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
} mdbx_atomic_uint32_t;
|
||||
|
||||
typedef union {
|
||||
volatile uint64_t weak;
|
||||
#if defined(MDBX_HAVE_C11ATOMICS) && (MDBX_64BIT_CAS || MDBX_64BIT_ATOMIC)
|
||||
volatile _Atomic uint64_t c11a;
|
||||
#endif
|
||||
#if !defined(MDBX_HAVE_C11ATOMICS) || !MDBX_64BIT_CAS || !MDBX_64BIT_ATOMIC
|
||||
__anonymous_struct_extension__ struct {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
mdbx_atomic_uint32_t low, high;
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
mdbx_atomic_uint32_t high, low;
|
||||
#else
|
||||
#error "FIXME: Unsupported byte order"
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
};
|
||||
#endif
|
||||
} mdbx_atomic_uint64_t;
|
||||
|
||||
#ifdef MDBX_HAVE_C11ATOMICS
|
||||
|
||||
/* Crutches for C11 atomic compiler's bugs */
|
||||
#if defined(__e2k__) && defined(__LCC__) && __LCC__ < /* FIXME */ 127
|
||||
#define MDBX_c11a_ro(type, ptr) (&(ptr)->weak)
|
||||
#define MDBX_c11a_rw(type, ptr) (&(ptr)->weak)
|
||||
#elif defined(__clang__) && __clang__ < 8
|
||||
#define MDBX_c11a_ro(type, ptr) ((volatile _Atomic(type) *)&(ptr)->c11a)
|
||||
#define MDBX_c11a_rw(type, ptr) (&(ptr)->c11a)
|
||||
#else
|
||||
#define MDBX_c11a_ro(type, ptr) (&(ptr)->c11a)
|
||||
#define MDBX_c11a_rw(type, ptr) (&(ptr)->c11a)
|
||||
#endif /* Crutches for C11 atomic compiler's bugs */
|
||||
|
||||
#define mo_c11_store(fence) \
|
||||
(((fence) == mo_Relaxed) ? memory_order_relaxed \
|
||||
: ((fence) == mo_AcquireRelease) ? memory_order_release \
|
||||
: memory_order_seq_cst)
|
||||
#define mo_c11_load(fence) \
|
||||
(((fence) == mo_Relaxed) ? memory_order_relaxed \
|
||||
: ((fence) == mo_AcquireRelease) ? memory_order_acquire \
|
||||
: memory_order_seq_cst)
|
||||
|
||||
#endif /* MDBX_HAVE_C11ATOMICS */
|
||||
|
||||
#define SAFE64_INVALID_THRESHOLD UINT64_C(0xffffFFFF00000000)
|
107
src/audit.c
Normal file
107
src/audit.c
Normal file
@ -0,0 +1,107 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
struct audit_ctx {
|
||||
size_t used;
|
||||
uint8_t *const done_bitmap;
|
||||
};
|
||||
|
||||
static int audit_dbi(void *ctx, const MDBX_txn *txn, const MDBX_val *name, MDBX_db_flags_t flags,
|
||||
const struct MDBX_stat *stat, MDBX_dbi dbi) {
|
||||
struct audit_ctx *audit_ctx = ctx;
|
||||
(void)name;
|
||||
(void)txn;
|
||||
(void)flags;
|
||||
audit_ctx->used += (size_t)stat->ms_branch_pages + (size_t)stat->ms_leaf_pages + (size_t)stat->ms_overflow_pages;
|
||||
if (dbi)
|
||||
audit_ctx->done_bitmap[dbi / CHAR_BIT] |= 1 << dbi % CHAR_BIT;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static size_t audit_db_used(const tree_t *db) {
|
||||
return db ? (size_t)db->branch_pages + (size_t)db->leaf_pages + (size_t)db->large_pages : 0;
|
||||
}
|
||||
|
||||
__cold static int audit_ex_locked(MDBX_txn *txn, const size_t retired_stored, const bool dont_filter_gc) {
|
||||
const MDBX_env *const env = txn->env;
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
const size_t pending = txn->wr.loose_count + MDBX_PNL_GETSIZE(txn->wr.repnl) +
|
||||
(MDBX_PNL_GETSIZE(txn->wr.retired_pages) - retired_stored);
|
||||
|
||||
cursor_couple_t cx;
|
||||
int rc = cursor_init(&cx.outer, txn, FREE_DBI);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
size_t gc = 0;
|
||||
MDBX_val key, data;
|
||||
rc = outer_first(&cx.outer, &key, &data);
|
||||
while (rc == MDBX_SUCCESS) {
|
||||
if (unlikely(key.iov_len != sizeof(txnid_t))) {
|
||||
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-key size", (unsigned)key.iov_len);
|
||||
return MDBX_CORRUPTED;
|
||||
}
|
||||
const txnid_t id = unaligned_peek_u64(4, key.iov_base);
|
||||
const size_t len = *(pgno_t *)data.iov_base;
|
||||
const bool acc = dont_filter_gc || !gc_is_reclaimed(txn, id);
|
||||
TRACE("%s id %" PRIaTXN " len %zu", acc ? "acc" : "skip", id, len);
|
||||
if (acc)
|
||||
gc += len;
|
||||
rc = outer_next(&cx.outer, &key, &data, MDBX_NEXT);
|
||||
}
|
||||
tASSERT(txn, rc == MDBX_NOTFOUND);
|
||||
|
||||
const size_t done_bitmap_size = (txn->n_dbi + CHAR_BIT - 1) / CHAR_BIT;
|
||||
if (txn->parent) {
|
||||
tASSERT(txn, txn->n_dbi == txn->parent->n_dbi && txn->n_dbi == txn->env->txn->n_dbi);
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
tASSERT(txn, txn->dbi_sparse == txn->parent->dbi_sparse && txn->dbi_sparse == txn->env->txn->dbi_sparse);
|
||||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||||
}
|
||||
|
||||
struct audit_ctx ctx = {0, alloca(done_bitmap_size)};
|
||||
memset(ctx.done_bitmap, 0, done_bitmap_size);
|
||||
ctx.used =
|
||||
NUM_METAS + audit_db_used(dbi_dig(txn, FREE_DBI, nullptr)) + audit_db_used(dbi_dig(txn, MAIN_DBI, nullptr));
|
||||
|
||||
rc = mdbx_enumerate_tables(txn, audit_dbi, &ctx);
|
||||
tASSERT(txn, rc == MDBX_SUCCESS);
|
||||
|
||||
for (size_t dbi = CORE_DBS; dbi < txn->n_dbi; ++dbi) {
|
||||
if (ctx.done_bitmap[dbi / CHAR_BIT] & (1 << dbi % CHAR_BIT))
|
||||
continue;
|
||||
const tree_t *db = dbi_dig(txn, dbi, nullptr);
|
||||
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-" : "",
|
||||
txn->txnid, dbi, (int)env->kvs[dbi].name.iov_len, (const char *)env->kvs[dbi].name.iov_base,
|
||||
dbi_state(txn, dbi));
|
||||
}
|
||||
|
||||
if (pending + gc + ctx.used == txn->geo.first_unallocated)
|
||||
return MDBX_SUCCESS;
|
||||
|
||||
if ((txn->flags & MDBX_TXN_RDONLY) == 0)
|
||||
ERROR("audit @%" PRIaTXN ": %zu(pending) = %zu(loose) + "
|
||||
"%zu(reclaimed) + %zu(retired-pending) - %zu(retired-stored)",
|
||||
txn->txnid, pending, txn->wr.loose_count, MDBX_PNL_GETSIZE(txn->wr.repnl),
|
||||
txn->wr.retired_pages ? MDBX_PNL_GETSIZE(txn->wr.retired_pages) : 0, retired_stored);
|
||||
ERROR("audit @%" PRIaTXN ": %zu(pending) + %zu"
|
||||
"(gc) + %zu(count) = %zu(total) <> %zu"
|
||||
"(allocated)",
|
||||
txn->txnid, pending, gc, ctx.used, pending + gc + ctx.used, (size_t)txn->geo.first_unallocated);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
|
||||
__cold int audit_ex(MDBX_txn *txn, size_t retired_stored, bool dont_filter_gc) {
|
||||
MDBX_env *const env = txn->env;
|
||||
int rc = osal_fastmutex_acquire(&env->dbi_lock);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
rc = audit_ex_locked(txn, retired_stored, dont_filter_gc);
|
||||
ENSURE(txn->env, osal_fastmutex_release(&env->dbi_lock) == MDBX_SUCCESS);
|
||||
}
|
||||
return rc;
|
||||
}
|
1207
src/bits.h
1207
src/bits.h
File diff suppressed because it is too large
Load Diff
34
src/bits.md
Normal file
34
src/bits.md
Normal file
@ -0,0 +1,34 @@
|
||||
N | MASK | ENV | TXN | DB | PUT | DBI | NODE | PAGE | MRESIZE |
|
||||
--|---------|-----------|--------------|----------|-----------|------------|---------|----------|---------|
|
||||
0 |0000 0001|ALLOC_RSRV |TXN_FINISHED | | |DBI_DIRTY |N_BIG |P_BRANCH | |
|
||||
1 |0000 0002|ALLOC_UNIMP|TXN_ERROR |REVERSEKEY|N_TREE |DBI_STALE |N_TREE |P_LEAF | |
|
||||
2 |0000 0004|ALLOC_COLSC|TXN_DIRTY |DUPSORT | |DBI_FRESH |N_DUP |P_LARGE | |
|
||||
3 |0000 0008|ALLOC_SSCAN|TXN_SPILLS |INTEGERKEY| |DBI_CREAT | |P_META | |
|
||||
4 |0000 0010|ALLOC_FIFO |TXN_HAS_CHILD |DUPFIXED |NOOVERWRITE|DBI_VALID | |P_BAD | |
|
||||
5 |0000 0020| |TXN_PARKED |INTEGERDUP|NODUPDATA | | |P_DUPFIX | |
|
||||
6 |0000 0040| |TXN_AUTOUNPARK|REVERSEDUP|CURRENT |DBI_OLDEN | |P_SUBP | |
|
||||
7 |0000 0080| |TXN_DRAINED_GC|DB_VALID |ALLDUPS |DBI_LINDO | | | |
|
||||
8 |0000 0100| _MAY_MOVE |TXN_CURSORS | | | | | | <= |
|
||||
9 |0000 0200| _MAY_UNMAP| | | | | | | <= |
|
||||
10|0000 0400| | | | | | | | |
|
||||
11|0000 0800| | | | | | | | |
|
||||
12|0000 1000| | | | | | | | |
|
||||
13|0000 2000|VALIDATION | | | | | |P_SPILLED | |
|
||||
14|0000 4000|NOSUBDIR | | | | | |P_LOOSE | |
|
||||
15|0000 8000| | | | | | |P_FROZEN | |
|
||||
16|0001 0000|SAFE_NOSYNC|TXN_NOSYNC | |RESERVE | |RESERVE | | |
|
||||
17|0002 0000|RDONLY |TXN_RDONLY | |APPEND | |APPEND | | <= |
|
||||
18|0004 0000|NOMETASYNC |TXN_NOMETASYNC|CREATE |APPENDDUP | | | | |
|
||||
19|0008 0000|WRITEMAP |<= | |MULTIPLE | | | | <= |
|
||||
20|0010 0000|UTTERLY | | | | | | | <= |
|
||||
21|0020 0000|NOSTICKYTHR|<= | | | | | | |
|
||||
22|0040 0000|EXCLUSIVE | | | | | | | |
|
||||
23|0080 0000|NORDAHEAD | | | | | | | |
|
||||
24|0100 0000|NOMEMINIT |TXN_PREPARE | | | | | | |
|
||||
25|0200 0000|COALESCE | | | | | | | |
|
||||
26|0400 0000|LIFORECLAIM| | | | | | | |
|
||||
27|0800 0000|PAGEPERTURB| | | | | | | |
|
||||
28|1000 0000|ENV_TXKEY |TXN_TRY | | | | | | |
|
||||
29|2000 0000|ENV_ACTIVE | | | | | | | |
|
||||
30|4000 0000|ACCEDE |SHRINK_ALLOWED|DB_ACCEDE | | | | | |
|
||||
31|8000 0000|FATAL_ERROR| | | | | | | |
|
309
src/cogs.c
Normal file
309
src/cogs.c
Normal file
@ -0,0 +1,309 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Pack/Unpack 16-bit values for Grow step & Shrink threshold */
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline pgno_t me2v(size_t m, size_t e) {
|
||||
assert(m < 2048 && e < 8);
|
||||
return (pgno_t)(32768 + ((m + 1) << (e + 8)));
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline uint16_t v2me(size_t v, size_t e) {
|
||||
assert(v > (e ? me2v(2047, e - 1) : 32768));
|
||||
assert(v <= me2v(2047, e));
|
||||
size_t m = (v - 32768 + ((size_t)1 << (e + 8)) - 1) >> (e + 8);
|
||||
m -= m > 0;
|
||||
assert(m < 2048 && e < 8);
|
||||
// f e d c b a 9 8 7 6 5 4 3 2 1 0
|
||||
// 1 e e e m m m m m m m m m m m 1
|
||||
const uint16_t pv = (uint16_t)(0x8001 + (e << 12) + (m << 1));
|
||||
assert(pv != 65535);
|
||||
return pv;
|
||||
}
|
||||
|
||||
/* Convert 16-bit packed (exponential quantized) value to number of pages */
|
||||
pgno_t pv2pages(uint16_t pv) {
|
||||
if ((pv & 0x8001) != 0x8001)
|
||||
return pv;
|
||||
if (pv == 65535)
|
||||
return 65536;
|
||||
// f e d c b a 9 8 7 6 5 4 3 2 1 0
|
||||
// 1 e e e m m m m m m m m m m m 1
|
||||
return me2v((pv >> 1) & 2047, (pv >> 12) & 7);
|
||||
}
|
||||
|
||||
/* Convert number of pages to 16-bit packed (exponential quantized) value */
|
||||
uint16_t pages2pv(size_t pages) {
|
||||
if (pages < 32769 || (pages < 65536 && (pages & 1) == 0))
|
||||
return (uint16_t)pages;
|
||||
if (pages <= me2v(2047, 0))
|
||||
return v2me(pages, 0);
|
||||
if (pages <= me2v(2047, 1))
|
||||
return v2me(pages, 1);
|
||||
if (pages <= me2v(2047, 2))
|
||||
return v2me(pages, 2);
|
||||
if (pages <= me2v(2047, 3))
|
||||
return v2me(pages, 3);
|
||||
if (pages <= me2v(2047, 4))
|
||||
return v2me(pages, 4);
|
||||
if (pages <= me2v(2047, 5))
|
||||
return v2me(pages, 5);
|
||||
if (pages <= me2v(2047, 6))
|
||||
return v2me(pages, 6);
|
||||
return (pages < me2v(2046, 7)) ? v2me(pages, 7) : 65533;
|
||||
}
|
||||
|
||||
__cold bool pv2pages_verify(void) {
|
||||
bool ok = true, dump_translation = false;
|
||||
for (size_t i = 0; i < 65536; ++i) {
|
||||
size_t pages = pv2pages(i);
|
||||
size_t x = pages2pv(pages);
|
||||
size_t xp = pv2pages(x);
|
||||
if (pages != xp) {
|
||||
ERROR("%zu => %zu => %zu => %zu\n", i, pages, x, xp);
|
||||
ok = false;
|
||||
} else if (dump_translation && !(x == i || (x % 2 == 0 && x < 65536))) {
|
||||
DEBUG("%zu => %zu => %zu => %zu\n", i, pages, x, xp);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION size_t bytes_align2os_bytes(const MDBX_env *env, size_t bytes) {
|
||||
return ceil_powerof2(bytes, (env->ps > globals.sys_pagesize) ? env->ps : globals.sys_pagesize);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION size_t pgno_align2os_bytes(const MDBX_env *env, size_t pgno) {
|
||||
return ceil_powerof2(pgno2bytes(env, pgno), globals.sys_pagesize);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION pgno_t pgno_align2os_pgno(const MDBX_env *env, size_t pgno) {
|
||||
return bytes2pgno(env, pgno_align2os_bytes(env, pgno));
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static __always_inline int cmp_int_inline(const size_t expected_alignment, const MDBX_val *a,
|
||||
const MDBX_val *b) {
|
||||
if (likely(a->iov_len == b->iov_len)) {
|
||||
if (sizeof(size_t) > 7 && likely(a->iov_len == 8))
|
||||
return CMP2INT(unaligned_peek_u64(expected_alignment, a->iov_base),
|
||||
unaligned_peek_u64(expected_alignment, b->iov_base));
|
||||
if (likely(a->iov_len == 4))
|
||||
return CMP2INT(unaligned_peek_u32(expected_alignment, a->iov_base),
|
||||
unaligned_peek_u32(expected_alignment, b->iov_base));
|
||||
if (sizeof(size_t) < 8 && likely(a->iov_len == 8))
|
||||
return CMP2INT(unaligned_peek_u64(expected_alignment, a->iov_base),
|
||||
unaligned_peek_u64(expected_alignment, b->iov_base));
|
||||
}
|
||||
ERROR("mismatch and/or invalid size %p.%zu/%p.%zu for INTEGERKEY/INTEGERDUP", a->iov_base, a->iov_len, b->iov_base,
|
||||
b->iov_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot int cmp_int_unaligned(const MDBX_val *a, const MDBX_val *b) {
|
||||
return cmp_int_inline(1, a, b);
|
||||
}
|
||||
|
||||
#ifndef cmp_int_align2
|
||||
/* Compare two items pointing at 2-byte aligned unsigned int's. */
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot int cmp_int_align2(const MDBX_val *a, const MDBX_val *b) {
|
||||
return cmp_int_inline(2, a, b);
|
||||
}
|
||||
#endif /* cmp_int_align2 */
|
||||
|
||||
#ifndef cmp_int_align4
|
||||
/* Compare two items pointing at 4-byte aligned unsigned int's. */
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot int cmp_int_align4(const MDBX_val *a, const MDBX_val *b) {
|
||||
return cmp_int_inline(4, a, b);
|
||||
}
|
||||
#endif /* cmp_int_align4 */
|
||||
|
||||
/* Compare two items lexically */
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot int cmp_lexical(const MDBX_val *a, const MDBX_val *b) {
|
||||
if (a->iov_len == b->iov_len)
|
||||
return a->iov_len ? memcmp(a->iov_base, b->iov_base, a->iov_len) : 0;
|
||||
|
||||
const int diff_len = (a->iov_len < b->iov_len) ? -1 : 1;
|
||||
const size_t shortest = (a->iov_len < b->iov_len) ? a->iov_len : b->iov_len;
|
||||
int diff_data = shortest ? memcmp(a->iov_base, b->iov_base, shortest) : 0;
|
||||
return likely(diff_data) ? diff_data : diff_len;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static __always_inline unsigned tail3le(const uint8_t *p, size_t l) {
|
||||
STATIC_ASSERT(sizeof(unsigned) > 2);
|
||||
// 1: 0 0 0
|
||||
// 2: 0 1 1
|
||||
// 3: 0 1 2
|
||||
return p[0] | p[l >> 1] << 8 | p[l - 1] << 16;
|
||||
}
|
||||
|
||||
/* Compare two items in reverse byte order */
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot int cmp_reverse(const MDBX_val *a, const MDBX_val *b) {
|
||||
size_t left = (a->iov_len < b->iov_len) ? a->iov_len : b->iov_len;
|
||||
if (likely(left)) {
|
||||
const uint8_t *pa = ptr_disp(a->iov_base, a->iov_len);
|
||||
const uint8_t *pb = ptr_disp(b->iov_base, b->iov_len);
|
||||
while (left >= sizeof(size_t)) {
|
||||
pa -= sizeof(size_t);
|
||||
pb -= sizeof(size_t);
|
||||
left -= sizeof(size_t);
|
||||
STATIC_ASSERT(sizeof(size_t) == 4 || sizeof(size_t) == 8);
|
||||
if (sizeof(size_t) == 4) {
|
||||
uint32_t xa = unaligned_peek_u32(1, pa);
|
||||
uint32_t xb = unaligned_peek_u32(1, pb);
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
xa = osal_bswap32(xa);
|
||||
xb = osal_bswap32(xb);
|
||||
#endif /* __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ */
|
||||
if (xa != xb)
|
||||
return (xa < xb) ? -1 : 1;
|
||||
} else {
|
||||
uint64_t xa = unaligned_peek_u64(1, pa);
|
||||
uint64_t xb = unaligned_peek_u64(1, pb);
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
xa = osal_bswap64(xa);
|
||||
xb = osal_bswap64(xb);
|
||||
#endif /* __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ */
|
||||
if (xa != xb)
|
||||
return (xa < xb) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
if (sizeof(size_t) == 8 && left >= 4) {
|
||||
pa -= 4;
|
||||
pb -= 4;
|
||||
left -= 4;
|
||||
uint32_t xa = unaligned_peek_u32(1, pa);
|
||||
uint32_t xb = unaligned_peek_u32(1, pb);
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
xa = osal_bswap32(xa);
|
||||
xb = osal_bswap32(xb);
|
||||
#endif /* __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ */
|
||||
if (xa != xb)
|
||||
return (xa < xb) ? -1 : 1;
|
||||
}
|
||||
if (left) {
|
||||
unsigned xa = tail3le(pa - left, left);
|
||||
unsigned xb = tail3le(pb - left, left);
|
||||
if (xa != xb)
|
||||
return (xa < xb) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
return CMP2INT(a->iov_len, b->iov_len);
|
||||
}
|
||||
|
||||
/* Fast non-lexically comparator */
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot int cmp_lenfast(const MDBX_val *a, const MDBX_val *b) {
|
||||
int diff = CMP2INT(a->iov_len, b->iov_len);
|
||||
return (likely(diff) || a->iov_len == 0) ? diff : memcmp(a->iov_base, b->iov_base, a->iov_len);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION __hot bool eq_fast_slowpath(const uint8_t *a, const uint8_t *b, size_t l) {
|
||||
if (likely(l > 3)) {
|
||||
if (MDBX_UNALIGNED_OK >= 4 && likely(l < 9))
|
||||
return ((unaligned_peek_u32(1, a) - unaligned_peek_u32(1, b)) |
|
||||
(unaligned_peek_u32(1, a + l - 4) - unaligned_peek_u32(1, b + l - 4))) == 0;
|
||||
if (MDBX_UNALIGNED_OK >= 8 && sizeof(size_t) > 7 && likely(l < 17))
|
||||
return ((unaligned_peek_u64(1, a) - unaligned_peek_u64(1, b)) |
|
||||
(unaligned_peek_u64(1, a + l - 8) - unaligned_peek_u64(1, b + l - 8))) == 0;
|
||||
return memcmp(a, b, l) == 0;
|
||||
}
|
||||
if (likely(l))
|
||||
return tail3le(a, l) == tail3le(b, l);
|
||||
return true;
|
||||
}
|
||||
|
||||
int cmp_equal_or_greater(const MDBX_val *a, const MDBX_val *b) { return eq_fast(a, b) ? 0 : 1; }
|
||||
|
||||
int cmp_equal_or_wrong(const MDBX_val *a, const MDBX_val *b) { return eq_fast(a, b) ? 0 : -1; }
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
__cold void update_mlcnt(const MDBX_env *env, const pgno_t new_aligned_mlocked_pgno, const bool lock_not_release) {
|
||||
for (;;) {
|
||||
const pgno_t mlock_pgno_before = atomic_load32(&env->mlocked_pgno, mo_AcquireRelease);
|
||||
eASSERT(env, pgno_align2os_pgno(env, mlock_pgno_before) == mlock_pgno_before);
|
||||
eASSERT(env, pgno_align2os_pgno(env, new_aligned_mlocked_pgno) == new_aligned_mlocked_pgno);
|
||||
if (lock_not_release ? (mlock_pgno_before >= new_aligned_mlocked_pgno)
|
||||
: (mlock_pgno_before <= new_aligned_mlocked_pgno))
|
||||
break;
|
||||
if (likely(atomic_cas32(&((MDBX_env *)env)->mlocked_pgno, mlock_pgno_before, new_aligned_mlocked_pgno)))
|
||||
for (;;) {
|
||||
mdbx_atomic_uint32_t *const mlcnt = env->lck->mlcnt;
|
||||
const int32_t snap_locked = atomic_load32(mlcnt + 0, mo_Relaxed);
|
||||
const int32_t snap_unlocked = atomic_load32(mlcnt + 1, mo_Relaxed);
|
||||
if (mlock_pgno_before == 0 && (snap_locked - snap_unlocked) < INT_MAX) {
|
||||
eASSERT(env, lock_not_release);
|
||||
if (unlikely(!atomic_cas32(mlcnt + 0, snap_locked, snap_locked + 1)))
|
||||
continue;
|
||||
}
|
||||
if (new_aligned_mlocked_pgno == 0 && (snap_locked - snap_unlocked) > 0) {
|
||||
eASSERT(env, !lock_not_release);
|
||||
if (unlikely(!atomic_cas32(mlcnt + 1, snap_unlocked, snap_unlocked + 1)))
|
||||
continue;
|
||||
}
|
||||
NOTICE("%s-pages %u..%u, mlocked-process(es) %u -> %u", lock_not_release ? "lock" : "unlock",
|
||||
lock_not_release ? mlock_pgno_before : new_aligned_mlocked_pgno,
|
||||
lock_not_release ? new_aligned_mlocked_pgno : mlock_pgno_before, snap_locked - snap_unlocked,
|
||||
atomic_load32(mlcnt + 0, mo_Relaxed) - atomic_load32(mlcnt + 1, mo_Relaxed));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__cold void munlock_after(const MDBX_env *env, const pgno_t aligned_pgno, const size_t end_bytes) {
|
||||
if (atomic_load32(&env->mlocked_pgno, mo_AcquireRelease) > aligned_pgno) {
|
||||
int err = MDBX_ENOSYS;
|
||||
const size_t munlock_begin = pgno2bytes(env, aligned_pgno);
|
||||
const size_t munlock_size = end_bytes - munlock_begin;
|
||||
eASSERT(env, end_bytes % globals.sys_pagesize == 0 && munlock_begin % globals.sys_pagesize == 0 &&
|
||||
munlock_size % globals.sys_pagesize == 0);
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
err = VirtualUnlock(ptr_disp(env->dxb_mmap.base, munlock_begin), munlock_size) ? MDBX_SUCCESS : (int)GetLastError();
|
||||
if (err == ERROR_NOT_LOCKED)
|
||||
err = MDBX_SUCCESS;
|
||||
#elif defined(_POSIX_MEMLOCK_RANGE)
|
||||
err = munlock(ptr_disp(env->dxb_mmap.base, munlock_begin), munlock_size) ? errno : MDBX_SUCCESS;
|
||||
#endif
|
||||
if (likely(err == MDBX_SUCCESS))
|
||||
update_mlcnt(env, aligned_pgno, false);
|
||||
else {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
WARNING("VirtualUnlock(%zu, %zu) error %d", munlock_begin, munlock_size, err);
|
||||
#else
|
||||
WARNING("munlock(%zu, %zu) error %d", munlock_begin, munlock_size, err);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__cold void munlock_all(const MDBX_env *env) {
|
||||
munlock_after(env, 0, bytes_align2os_bytes(env, env->dxb_mmap.current));
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
uint32_t combine_durability_flags(const uint32_t a, const uint32_t b) {
|
||||
uint32_t r = a | b;
|
||||
|
||||
/* avoid false MDBX_UTTERLY_NOSYNC */
|
||||
if (F_ISSET(r, MDBX_UTTERLY_NOSYNC) && !F_ISSET(a, MDBX_UTTERLY_NOSYNC) && !F_ISSET(b, MDBX_UTTERLY_NOSYNC))
|
||||
r = (r - MDBX_UTTERLY_NOSYNC) | MDBX_SAFE_NOSYNC;
|
||||
|
||||
/* convert DEPRECATED_MAPASYNC to MDBX_SAFE_NOSYNC */
|
||||
if ((r & (MDBX_WRITEMAP | DEPRECATED_MAPASYNC)) == (MDBX_WRITEMAP | DEPRECATED_MAPASYNC) &&
|
||||
!F_ISSET(r, MDBX_UTTERLY_NOSYNC))
|
||||
r = (r - DEPRECATED_MAPASYNC) | MDBX_SAFE_NOSYNC;
|
||||
|
||||
/* force MDBX_NOMETASYNC if NOSYNC enabled */
|
||||
if (r & (MDBX_SAFE_NOSYNC | MDBX_UTTERLY_NOSYNC))
|
||||
r |= MDBX_NOMETASYNC;
|
||||
|
||||
assert(!(F_ISSET(r, MDBX_UTTERLY_NOSYNC) && !F_ISSET(a, MDBX_UTTERLY_NOSYNC) && !F_ISSET(b, MDBX_UTTERLY_NOSYNC)));
|
||||
return r;
|
||||
}
|
507
src/cogs.h
Normal file
507
src/cogs.h
Normal file
@ -0,0 +1,507 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL pgno_t pv2pages(uint16_t pv);
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_INTERNAL uint16_t pages2pv(size_t pages);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool pv2pages_verify(void);
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Nodes, Keys & Values length limitation factors:
|
||||
*
|
||||
* BRANCH_NODE_MAX
|
||||
* Branch-page must contain at least two nodes, within each a key and a child
|
||||
* page number. But page can't be split if it contains less that 4 keys,
|
||||
* i.e. a page should not overflow before adding the fourth key. Therefore,
|
||||
* at least 3 branch-node should fit in the single branch-page. Further, the
|
||||
* first node of a branch-page doesn't contain a key, i.e. the first node
|
||||
* is always require space just for itself. Thus:
|
||||
* PAGESPACE = pagesize - page_hdr_len;
|
||||
* BRANCH_NODE_MAX = even_floor(
|
||||
* (PAGESPACE - sizeof(indx_t) - NODESIZE) / (3 - 1) - sizeof(indx_t));
|
||||
* KEYLEN_MAX = BRANCH_NODE_MAX - node_hdr_len;
|
||||
*
|
||||
* LEAF_NODE_MAX
|
||||
* Leaf-node must fit into single leaf-page, where a value could be placed on
|
||||
* a large/overflow page. However, may require to insert a nearly page-sized
|
||||
* node between two large nodes are already fill-up a page. In this case the
|
||||
* page must be split to two if some pair of nodes fits on one page, or
|
||||
* otherwise the page should be split to the THREE with a single node
|
||||
* per each of ones. Such 1-into-3 page splitting is costly and complex since
|
||||
* requires TWO insertion into the parent page, that could lead to split it
|
||||
* and so on up to the root. Therefore double-splitting is avoided here and
|
||||
* the maximum node size is half of a leaf page space:
|
||||
* LEAF_NODE_MAX = even_floor(PAGESPACE / 2 - sizeof(indx_t));
|
||||
* DATALEN_NO_OVERFLOW = LEAF_NODE_MAX - NODESIZE - KEYLEN_MAX;
|
||||
*
|
||||
* - Table-node must fit into one leaf-page:
|
||||
* TABLE_NAME_MAX = LEAF_NODE_MAX - node_hdr_len - sizeof(tree_t);
|
||||
*
|
||||
* - Dupsort values itself are a keys in a dupsort-table and couldn't be longer
|
||||
* than the KEYLEN_MAX. But dupsort node must not great than LEAF_NODE_MAX,
|
||||
* since dupsort value couldn't be placed on a large/overflow page:
|
||||
* DUPSORT_DATALEN_MAX = min(KEYLEN_MAX,
|
||||
* max(DATALEN_NO_OVERFLOW, sizeof(tree_t));
|
||||
*/
|
||||
|
||||
#define PAGESPACE(pagesize) ((pagesize) - PAGEHDRSZ)
|
||||
|
||||
#define BRANCH_NODE_MAX(pagesize) \
|
||||
(EVEN_FLOOR((PAGESPACE(pagesize) - sizeof(indx_t) - NODESIZE) / (3 - 1) - sizeof(indx_t)))
|
||||
|
||||
#define LEAF_NODE_MAX(pagesize) (EVEN_FLOOR(PAGESPACE(pagesize) / 2) - sizeof(indx_t))
|
||||
|
||||
#define MAX_GC1OVPAGE(pagesize) (PAGESPACE(pagesize) / sizeof(pgno_t) - 1)
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline size_t keysize_max(size_t pagesize, MDBX_db_flags_t flags) {
|
||||
assert(pagesize >= MDBX_MIN_PAGESIZE && pagesize <= MDBX_MAX_PAGESIZE && is_powerof2(pagesize));
|
||||
STATIC_ASSERT(BRANCH_NODE_MAX(MDBX_MIN_PAGESIZE) - NODESIZE >= 8);
|
||||
if (flags & MDBX_INTEGERKEY)
|
||||
return 8 /* sizeof(uint64_t) */;
|
||||
|
||||
const intptr_t max_branch_key = BRANCH_NODE_MAX(pagesize) - NODESIZE;
|
||||
STATIC_ASSERT(LEAF_NODE_MAX(MDBX_MIN_PAGESIZE) - NODESIZE -
|
||||
/* sizeof(uint64) as a key */ 8 >
|
||||
sizeof(tree_t));
|
||||
if (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_REVERSEDUP | MDBX_INTEGERDUP)) {
|
||||
const intptr_t max_dupsort_leaf_key = LEAF_NODE_MAX(pagesize) - NODESIZE - sizeof(tree_t);
|
||||
return (max_branch_key < max_dupsort_leaf_key) ? max_branch_key : max_dupsort_leaf_key;
|
||||
}
|
||||
return max_branch_key;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline size_t env_keysize_max(const MDBX_env *env, MDBX_db_flags_t flags) {
|
||||
size_t size_max;
|
||||
if (flags & MDBX_INTEGERKEY)
|
||||
size_max = 8 /* sizeof(uint64_t) */;
|
||||
else {
|
||||
const intptr_t max_branch_key = env->branch_nodemax - NODESIZE;
|
||||
STATIC_ASSERT(LEAF_NODE_MAX(MDBX_MIN_PAGESIZE) - NODESIZE -
|
||||
/* sizeof(uint64) as a key */ 8 >
|
||||
sizeof(tree_t));
|
||||
if (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_REVERSEDUP | MDBX_INTEGERDUP)) {
|
||||
const intptr_t max_dupsort_leaf_key = env->leaf_nodemax - NODESIZE - sizeof(tree_t);
|
||||
size_max = (max_branch_key < max_dupsort_leaf_key) ? max_branch_key : max_dupsort_leaf_key;
|
||||
} else
|
||||
size_max = max_branch_key;
|
||||
}
|
||||
eASSERT(env, size_max == keysize_max(env->ps, flags));
|
||||
return size_max;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline size_t keysize_min(MDBX_db_flags_t flags) {
|
||||
return (flags & MDBX_INTEGERKEY) ? 4 /* sizeof(uint32_t) */ : 0;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline size_t valsize_min(MDBX_db_flags_t flags) {
|
||||
if (flags & MDBX_INTEGERDUP)
|
||||
return 4 /* sizeof(uint32_t) */;
|
||||
else if (flags & MDBX_DUPFIXED)
|
||||
return sizeof(indx_t);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline size_t valsize_max(size_t pagesize, MDBX_db_flags_t flags) {
|
||||
assert(pagesize >= MDBX_MIN_PAGESIZE && pagesize <= MDBX_MAX_PAGESIZE && is_powerof2(pagesize));
|
||||
|
||||
if (flags & MDBX_INTEGERDUP)
|
||||
return 8 /* sizeof(uint64_t) */;
|
||||
|
||||
if (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_REVERSEDUP))
|
||||
return keysize_max(pagesize, 0);
|
||||
|
||||
const unsigned page_ln2 = log2n_powerof2(pagesize);
|
||||
const size_t hard = 0x7FF00000ul;
|
||||
const size_t hard_pages = hard >> page_ln2;
|
||||
STATIC_ASSERT(PAGELIST_LIMIT <= MAX_PAGENO);
|
||||
const size_t pages_limit = PAGELIST_LIMIT / 4;
|
||||
const size_t limit = (hard_pages < pages_limit) ? hard : (pages_limit << page_ln2);
|
||||
return (limit < MAX_MAPSIZE / 2) ? limit : MAX_MAPSIZE / 2;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline size_t env_valsize_max(const MDBX_env *env, MDBX_db_flags_t flags) {
|
||||
size_t size_max;
|
||||
if (flags & MDBX_INTEGERDUP)
|
||||
size_max = 8 /* sizeof(uint64_t) */;
|
||||
else if (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_REVERSEDUP))
|
||||
size_max = env_keysize_max(env, 0);
|
||||
else {
|
||||
const size_t hard = 0x7FF00000ul;
|
||||
const size_t hard_pages = hard >> env->ps2ln;
|
||||
STATIC_ASSERT(PAGELIST_LIMIT <= MAX_PAGENO);
|
||||
const size_t pages_limit = PAGELIST_LIMIT / 4;
|
||||
const size_t limit = (hard_pages < pages_limit) ? hard : (pages_limit << env->ps2ln);
|
||||
size_max = (limit < MAX_MAPSIZE / 2) ? limit : MAX_MAPSIZE / 2;
|
||||
}
|
||||
eASSERT(env, size_max == valsize_max(env->ps, flags));
|
||||
return size_max;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t leaf_size(const MDBX_env *env, const MDBX_val *key,
|
||||
const MDBX_val *data) {
|
||||
size_t node_bytes = node_size(key, data);
|
||||
if (node_bytes > env->leaf_nodemax)
|
||||
/* put on large/overflow page */
|
||||
node_bytes = node_size_len(key->iov_len, 0) + sizeof(pgno_t);
|
||||
|
||||
return node_bytes + sizeof(indx_t);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t branch_size(const MDBX_env *env, const MDBX_val *key) {
|
||||
/* Size of a node in a branch page with a given key.
|
||||
* This is just the node header plus the key, there is no data. */
|
||||
size_t node_bytes = node_size(key, nullptr);
|
||||
if (unlikely(node_bytes > env->branch_nodemax)) {
|
||||
/* put on large/overflow page, not implemented */
|
||||
mdbx_panic("node_size(key) %zu > %u branch_nodemax", node_bytes, env->branch_nodemax);
|
||||
node_bytes = node_size(key, nullptr) + sizeof(pgno_t);
|
||||
}
|
||||
|
||||
return node_bytes + sizeof(indx_t);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION static inline uint16_t flags_db2sub(uint16_t db_flags) {
|
||||
uint16_t sub_flags = db_flags & MDBX_DUPFIXED;
|
||||
|
||||
/* MDBX_INTEGERDUP => MDBX_INTEGERKEY */
|
||||
#define SHIFT_INTEGERDUP_TO_INTEGERKEY 2
|
||||
STATIC_ASSERT((MDBX_INTEGERDUP >> SHIFT_INTEGERDUP_TO_INTEGERKEY) == MDBX_INTEGERKEY);
|
||||
sub_flags |= (db_flags & MDBX_INTEGERDUP) >> SHIFT_INTEGERDUP_TO_INTEGERKEY;
|
||||
|
||||
/* MDBX_REVERSEDUP => MDBX_REVERSEKEY */
|
||||
#define SHIFT_REVERSEDUP_TO_REVERSEKEY 5
|
||||
STATIC_ASSERT((MDBX_REVERSEDUP >> SHIFT_REVERSEDUP_TO_REVERSEKEY) == MDBX_REVERSEKEY);
|
||||
sub_flags |= (db_flags & MDBX_REVERSEDUP) >> SHIFT_REVERSEDUP_TO_REVERSEKEY;
|
||||
|
||||
return sub_flags;
|
||||
}
|
||||
|
||||
static inline bool check_table_flags(unsigned flags) {
|
||||
switch (flags & ~(MDBX_REVERSEKEY | MDBX_INTEGERKEY)) {
|
||||
default:
|
||||
NOTICE("invalid db-flags 0x%x", flags);
|
||||
return false;
|
||||
case MDBX_DUPSORT:
|
||||
case MDBX_DUPSORT | MDBX_REVERSEDUP:
|
||||
case MDBX_DUPSORT | MDBX_DUPFIXED:
|
||||
case MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_REVERSEDUP:
|
||||
case MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP:
|
||||
case MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP:
|
||||
case MDBX_DB_DEFAULTS:
|
||||
return (flags & (MDBX_REVERSEKEY | MDBX_INTEGERKEY)) != (MDBX_REVERSEKEY | MDBX_INTEGERKEY);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
eASSERT(env, (1u << env->ps2ln) == env->ps);
|
||||
return ((size_t)pgno) << env->ps2ln;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline page_t *pgno2page(const MDBX_env *env, size_t pgno) {
|
||||
return ptr_disp(env->dxb_mmap.base, pgno2bytes(env, pgno));
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t bytes2pgno(const MDBX_env *env, size_t bytes) {
|
||||
eASSERT(env, (env->ps >> env->ps2ln) == 1);
|
||||
return (pgno_t)(bytes >> env->ps2ln);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t bytes_align2os_bytes(const MDBX_env *env, size_t bytes);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL size_t pgno_align2os_bytes(const MDBX_env *env, size_t pgno);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL pgno_t pgno_align2os_pgno(const MDBX_env *env, size_t pgno);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t largechunk_npages(const MDBX_env *env, size_t bytes) {
|
||||
return bytes2pgno(env, PAGEHDRSZ - 1 + bytes) + 1;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline MDBX_val get_key(const node_t *node) {
|
||||
MDBX_val key;
|
||||
key.iov_len = node_ks(node);
|
||||
key.iov_base = node_key(node);
|
||||
return key;
|
||||
}
|
||||
|
||||
static inline void get_key_optional(const node_t *node, MDBX_val *keyptr /* __may_null */) {
|
||||
if (keyptr)
|
||||
*keyptr = get_key(node);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline void *page_data(const page_t *mp) { return ptr_disp(mp, PAGEHDRSZ); }
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline const page_t *data_page(const void *data) {
|
||||
return container_of(data, page_t, entries);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline meta_t *page_meta(page_t *mp) { return (meta_t *)page_data(mp); }
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_numkeys(const page_t *mp) {
|
||||
assert(mp->lower <= mp->upper);
|
||||
return mp->lower >> 1;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_room(const page_t *mp) {
|
||||
assert(mp->lower <= mp->upper);
|
||||
return mp->upper - mp->lower;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_space(const MDBX_env *env) {
|
||||
STATIC_ASSERT(PAGEHDRSZ % 2 == 0);
|
||||
return env->ps - PAGEHDRSZ;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_used(const MDBX_env *env, const page_t *mp) {
|
||||
return page_space(env) - page_room(mp);
|
||||
}
|
||||
|
||||
/* The percentage of space used in the page, in a percents. */
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline unsigned page_fill_percentum_x10(const MDBX_env *env,
|
||||
const page_t *mp) {
|
||||
const size_t space = page_space(env);
|
||||
return (unsigned)((page_used(env, mp) * 1000 + space / 2) / space);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline node_t *page_node(const page_t *mp, size_t i) {
|
||||
assert(page_type_compat(mp) == P_LEAF || page_type(mp) == P_BRANCH);
|
||||
assert(page_numkeys(mp) > i);
|
||||
assert(mp->entries[i] % 2 == 0);
|
||||
return ptr_disp(mp, mp->entries[i] + PAGEHDRSZ);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline void *page_dupfix_ptr(const page_t *mp, size_t i, size_t keysize) {
|
||||
assert(page_type_compat(mp) == (P_LEAF | P_DUPFIX) && i == (indx_t)i && mp->dupfix_ksize == keysize);
|
||||
(void)keysize;
|
||||
return ptr_disp(mp, PAGEHDRSZ + mp->dupfix_ksize * (indx_t)i);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline MDBX_val page_dupfix_key(const page_t *mp, size_t i, size_t keysize) {
|
||||
MDBX_val r;
|
||||
r.iov_base = page_dupfix_ptr(mp, i, keysize);
|
||||
r.iov_len = mp->dupfix_ksize;
|
||||
return r;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int cmp_int_unaligned(const MDBX_val *a, const MDBX_val *b);
|
||||
|
||||
#if MDBX_UNALIGNED_OK < 2 || (MDBX_DEBUG || MDBX_FORCE_ASSERTIONS || !defined(NDEBUG))
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int
|
||||
/* Compare two items pointing at 2-byte aligned unsigned int's. */
|
||||
cmp_int_align2(const MDBX_val *a, const MDBX_val *b);
|
||||
#else
|
||||
#define cmp_int_align2 cmp_int_unaligned
|
||||
#endif /* !MDBX_UNALIGNED_OK || debug */
|
||||
|
||||
#if MDBX_UNALIGNED_OK < 4 || (MDBX_DEBUG || MDBX_FORCE_ASSERTIONS || !defined(NDEBUG))
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int
|
||||
/* Compare two items pointing at 4-byte aligned unsigned int's. */
|
||||
cmp_int_align4(const MDBX_val *a, const MDBX_val *b);
|
||||
#else
|
||||
#define cmp_int_align4 cmp_int_unaligned
|
||||
#endif /* !MDBX_UNALIGNED_OK || debug */
|
||||
|
||||
/* Compare two items lexically */
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int cmp_lexical(const MDBX_val *a, const MDBX_val *b);
|
||||
|
||||
/* Compare two items in reverse byte order */
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int cmp_reverse(const MDBX_val *a, const MDBX_val *b);
|
||||
|
||||
/* Fast non-lexically comparator */
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int cmp_lenfast(const MDBX_val *a, const MDBX_val *b);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL bool eq_fast_slowpath(const uint8_t *a, const uint8_t *b, size_t l);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline bool eq_fast(const MDBX_val *a, const MDBX_val *b) {
|
||||
return unlikely(a->iov_len == b->iov_len) && eq_fast_slowpath(a->iov_base, b->iov_base, a->iov_len);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int cmp_equal_or_greater(const MDBX_val *a, const MDBX_val *b);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL int cmp_equal_or_wrong(const MDBX_val *a, const MDBX_val *b);
|
||||
|
||||
static inline MDBX_cmp_func *builtin_keycmp(MDBX_db_flags_t flags) {
|
||||
return (flags & MDBX_REVERSEKEY) ? cmp_reverse : (flags & MDBX_INTEGERKEY) ? cmp_int_align2 : cmp_lexical;
|
||||
}
|
||||
|
||||
static inline MDBX_cmp_func *builtin_datacmp(MDBX_db_flags_t flags) {
|
||||
return !(flags & MDBX_DUPSORT)
|
||||
? cmp_lenfast
|
||||
: ((flags & MDBX_INTEGERDUP) ? cmp_int_unaligned
|
||||
: ((flags & MDBX_REVERSEDUP) ? cmp_reverse : cmp_lexical));
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_INTERNAL uint32_t combine_durability_flags(const uint32_t a, const uint32_t b);
|
||||
|
||||
MDBX_CONST_FUNCTION static inline lck_t *lckless_stub(const MDBX_env *env) {
|
||||
uintptr_t stub = (uintptr_t)&env->lckless_placeholder;
|
||||
/* align to avoid false-positive alarm from UndefinedBehaviorSanitizer */
|
||||
stub = (stub + MDBX_CACHELINE_SIZE - 1) & ~(MDBX_CACHELINE_SIZE - 1);
|
||||
return (lck_t *)stub;
|
||||
}
|
||||
|
||||
#if !(defined(_WIN32) || defined(_WIN64))
|
||||
MDBX_CONST_FUNCTION static inline int ignore_enosys(int err) {
|
||||
#ifdef ENOSYS
|
||||
if (err == ENOSYS)
|
||||
return MDBX_RESULT_TRUE;
|
||||
#endif /* ENOSYS */
|
||||
#ifdef ENOIMPL
|
||||
if (err == ENOIMPL)
|
||||
return MDBX_RESULT_TRUE;
|
||||
#endif /* ENOIMPL */
|
||||
#ifdef ENOTSUP
|
||||
if (err == ENOTSUP)
|
||||
return MDBX_RESULT_TRUE;
|
||||
#endif /* ENOTSUP */
|
||||
#ifdef ENOSUPP
|
||||
if (err == ENOSUPP)
|
||||
return MDBX_RESULT_TRUE;
|
||||
#endif /* ENOSUPP */
|
||||
#ifdef EOPNOTSUPP
|
||||
if (err == EOPNOTSUPP)
|
||||
return MDBX_RESULT_TRUE;
|
||||
#endif /* EOPNOTSUPP */
|
||||
return err;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_CONST_FUNCTION static inline int ignore_enosys_and_eagain(int err) {
|
||||
return (err == EAGAIN) ? MDBX_RESULT_TRUE : ignore_enosys(err);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_CONST_FUNCTION static inline int ignore_enosys_and_einval(int err) {
|
||||
return (err == EINVAL) ? MDBX_RESULT_TRUE : ignore_enosys(err);
|
||||
}
|
||||
#endif /* defined(_WIN32) || defined(_WIN64) */
|
||||
|
||||
static inline int check_env(const MDBX_env *env, const bool wanna_active) {
|
||||
if (unlikely(!env))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
if (unlikely(env->signature.weak != env_signature))
|
||||
return MDBX_EBADSIGN;
|
||||
|
||||
if (unlikely(env->flags & ENV_FATAL_ERROR))
|
||||
return MDBX_PANIC;
|
||||
|
||||
if (wanna_active) {
|
||||
#if MDBX_ENV_CHECKPID
|
||||
if (unlikely(env->pid != osal_getpid()) && env->pid) {
|
||||
((MDBX_env *)env)->flags |= ENV_FATAL_ERROR;
|
||||
return MDBX_PANIC;
|
||||
}
|
||||
#endif /* MDBX_ENV_CHECKPID */
|
||||
if (unlikely((env->flags & ENV_ACTIVE) == 0))
|
||||
return MDBX_EPERM;
|
||||
eASSERT(env, env->dxb_mmap.base != nullptr);
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
|
||||
if (unlikely(!txn))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
if (unlikely(txn->signature != txn_signature))
|
||||
return MDBX_EBADSIGN;
|
||||
|
||||
if (bad_bits) {
|
||||
if (unlikely(!txn->env->dxb_mmap.base))
|
||||
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;
|
||||
return txn_check_badbits_parked(txn, bad_bits);
|
||||
}
|
||||
}
|
||||
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_FINISHED) ||
|
||||
(txn->flags & MDBX_NOSTICKYTHREADS) == (txn->env->flags & MDBX_NOSTICKYTHREADS));
|
||||
#if MDBX_TXN_CHECKOWNER
|
||||
if ((txn->flags & (MDBX_NOSTICKYTHREADS | MDBX_TXN_FINISHED)) != MDBX_NOSTICKYTHREADS &&
|
||||
!(bad_bits /* abort/reset/txn-break */ == 0 &&
|
||||
((txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED)) == (MDBX_TXN_RDONLY | MDBX_TXN_FINISHED))) &&
|
||||
unlikely(txn->owner != osal_thread_self()))
|
||||
return txn->owner ? MDBX_THREAD_MISMATCH : MDBX_BAD_TXN;
|
||||
#endif /* MDBX_TXN_CHECKOWNER */
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static inline int check_txn_rw(const MDBX_txn *txn, int bad_bits) {
|
||||
return check_txn(txn, (bad_bits | MDBX_TXN_RDONLY) & ~MDBX_TXN_PARKED);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_INTERNAL void mincore_clean_cache(const MDBX_env *const env);
|
||||
|
||||
MDBX_INTERNAL void update_mlcnt(const MDBX_env *env, const pgno_t new_aligned_mlocked_pgno,
|
||||
const bool lock_not_release);
|
||||
|
||||
MDBX_INTERNAL void munlock_after(const MDBX_env *env, const pgno_t aligned_pgno, const size_t end_bytes);
|
||||
|
||||
MDBX_INTERNAL void munlock_all(const MDBX_env *env);
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Cache coherence and mmap invalidation */
|
||||
#ifndef MDBX_CPU_WRITEBACK_INCOHERENT
|
||||
#error "The MDBX_CPU_WRITEBACK_INCOHERENT must be defined before"
|
||||
#elif MDBX_CPU_WRITEBACK_INCOHERENT
|
||||
#define osal_flush_incoherent_cpu_writeback() osal_memory_barrier()
|
||||
#else
|
||||
#define osal_flush_incoherent_cpu_writeback() osal_compiler_barrier()
|
||||
#endif /* MDBX_CPU_WRITEBACK_INCOHERENT */
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline void osal_flush_incoherent_mmap(const void *addr, size_t nbytes,
|
||||
const intptr_t pagesize) {
|
||||
#ifndef MDBX_MMAP_INCOHERENT_FILE_WRITE
|
||||
#error "The MDBX_MMAP_INCOHERENT_FILE_WRITE must be defined before"
|
||||
#elif MDBX_MMAP_INCOHERENT_FILE_WRITE
|
||||
char *const begin = (char *)(-pagesize & (intptr_t)addr);
|
||||
char *const end = (char *)(-pagesize & (intptr_t)((char *)addr + nbytes + pagesize - 1));
|
||||
int err = msync(begin, end - begin, MS_SYNC | MS_INVALIDATE) ? errno : 0;
|
||||
eASSERT(nullptr, err == 0);
|
||||
(void)err;
|
||||
#else
|
||||
(void)pagesize;
|
||||
#endif /* MDBX_MMAP_INCOHERENT_FILE_WRITE */
|
||||
|
||||
#ifndef MDBX_MMAP_INCOHERENT_CPU_CACHE
|
||||
#error "The MDBX_MMAP_INCOHERENT_CPU_CACHE must be defined before"
|
||||
#elif MDBX_MMAP_INCOHERENT_CPU_CACHE
|
||||
#ifdef DCACHE
|
||||
/* MIPS has cache coherency issues.
|
||||
* Note: for any nbytes >= on-chip cache size, entire is flushed. */
|
||||
cacheflush((void *)addr, nbytes, DCACHE);
|
||||
#else
|
||||
#error "Oops, cacheflush() not available"
|
||||
#endif /* DCACHE */
|
||||
#endif /* MDBX_MMAP_INCOHERENT_CPU_CACHE */
|
||||
|
||||
#if !MDBX_MMAP_INCOHERENT_FILE_WRITE && !MDBX_MMAP_INCOHERENT_CPU_CACHE
|
||||
(void)addr;
|
||||
(void)nbytes;
|
||||
#endif
|
||||
}
|
170
src/coherency.c
Normal file
170
src/coherency.c
Normal file
@ -0,0 +1,170 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/* check against https://libmdbx.dqdkfa.ru/dead-github/issues/269 */
|
||||
static bool coherency_check(const MDBX_env *env, const txnid_t txnid, const volatile tree_t *trees,
|
||||
const volatile meta_t *meta, bool report) {
|
||||
const txnid_t freedb_mod_txnid = trees[FREE_DBI].mod_txnid;
|
||||
const txnid_t maindb_mod_txnid = trees[MAIN_DBI].mod_txnid;
|
||||
const pgno_t last_pgno = meta->geometry.now;
|
||||
|
||||
const pgno_t freedb_root_pgno = trees[FREE_DBI].root;
|
||||
const page_t *freedb_root =
|
||||
(env->dxb_mmap.base && freedb_root_pgno < last_pgno) ? pgno2page(env, freedb_root_pgno) : nullptr;
|
||||
|
||||
const pgno_t maindb_root_pgno = trees[MAIN_DBI].root;
|
||||
const page_t *maindb_root =
|
||||
(env->dxb_mmap.base && maindb_root_pgno < last_pgno) ? pgno2page(env, maindb_root_pgno) : nullptr;
|
||||
const uint64_t magic_and_version = unaligned_peek_u64_volatile(4, &meta->magic_and_version);
|
||||
|
||||
bool ok = true;
|
||||
if (freedb_root_pgno != P_INVALID && unlikely(freedb_root_pgno >= last_pgno)) {
|
||||
if (report)
|
||||
WARNING("catch invalid %s-db root %" PRIaPGNO " for meta_txnid %" PRIaTXN " %s", "free", freedb_root_pgno, txnid,
|
||||
(env->stuck_meta < 0) ? "(workaround for incoherent flaw of unified page/buffer cache)"
|
||||
: "(wagering meta)");
|
||||
ok = false;
|
||||
}
|
||||
if (maindb_root_pgno != P_INVALID && unlikely(maindb_root_pgno >= last_pgno)) {
|
||||
if (report)
|
||||
WARNING("catch invalid %s-db root %" PRIaPGNO " for meta_txnid %" PRIaTXN " %s", "main", maindb_root_pgno, txnid,
|
||||
(env->stuck_meta < 0) ? "(workaround for incoherent flaw of unified page/buffer cache)"
|
||||
: "(wagering meta)");
|
||||
ok = false;
|
||||
}
|
||||
if (unlikely(txnid < freedb_mod_txnid ||
|
||||
(!freedb_mod_txnid && freedb_root && likely(magic_and_version == MDBX_DATA_MAGIC)))) {
|
||||
if (report)
|
||||
WARNING(
|
||||
"catch invalid %s-db.mod_txnid %" PRIaTXN " for meta_txnid %" PRIaTXN " %s", "free", freedb_mod_txnid, txnid,
|
||||
(env->stuck_meta < 0) ? "(workaround for incoherent flaw of unified page/buffer cache)" : "(wagering meta)");
|
||||
ok = false;
|
||||
}
|
||||
if (unlikely(txnid < maindb_mod_txnid ||
|
||||
(!maindb_mod_txnid && maindb_root && likely(magic_and_version == MDBX_DATA_MAGIC)))) {
|
||||
if (report)
|
||||
WARNING(
|
||||
"catch invalid %s-db.mod_txnid %" PRIaTXN " for meta_txnid %" PRIaTXN " %s", "main", maindb_mod_txnid, txnid,
|
||||
(env->stuck_meta < 0) ? "(workaround for incoherent flaw of unified page/buffer cache)" : "(wagering meta)");
|
||||
ok = false;
|
||||
}
|
||||
|
||||
/* Проверяем отметки внутри корневых страниц только если сами страницы
|
||||
* в пределах текущего отображения. Иначе возможны SIGSEGV до переноса
|
||||
* вызова coherency_check_head() после dxb_resize() внутри txn_renew(). */
|
||||
if (likely(freedb_root && freedb_mod_txnid &&
|
||||
(size_t)ptr_dist(env->dxb_mmap.base, freedb_root) < env->dxb_mmap.limit)) {
|
||||
VALGRIND_MAKE_MEM_DEFINED(freedb_root, sizeof(freedb_root->txnid));
|
||||
MDBX_ASAN_UNPOISON_MEMORY_REGION(freedb_root, sizeof(freedb_root->txnid));
|
||||
const txnid_t root_txnid = freedb_root->txnid;
|
||||
if (unlikely(root_txnid != freedb_mod_txnid)) {
|
||||
if (report)
|
||||
WARNING("catch invalid root_page %" PRIaPGNO " mod_txnid %" PRIaTXN " for %s-db.mod_txnid %" PRIaTXN " %s",
|
||||
freedb_root_pgno, root_txnid, "free", freedb_mod_txnid,
|
||||
(env->stuck_meta < 0) ? "(workaround for incoherent flaw of "
|
||||
"unified page/buffer cache)"
|
||||
: "(wagering meta)");
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (likely(maindb_root && maindb_mod_txnid &&
|
||||
(size_t)ptr_dist(env->dxb_mmap.base, maindb_root) < env->dxb_mmap.limit)) {
|
||||
VALGRIND_MAKE_MEM_DEFINED(maindb_root, sizeof(maindb_root->txnid));
|
||||
MDBX_ASAN_UNPOISON_MEMORY_REGION(maindb_root, sizeof(maindb_root->txnid));
|
||||
const txnid_t root_txnid = maindb_root->txnid;
|
||||
if (unlikely(root_txnid != maindb_mod_txnid)) {
|
||||
if (report)
|
||||
WARNING("catch invalid root_page %" PRIaPGNO " mod_txnid %" PRIaTXN " for %s-db.mod_txnid %" PRIaTXN " %s",
|
||||
maindb_root_pgno, root_txnid, "main", maindb_mod_txnid,
|
||||
(env->stuck_meta < 0) ? "(workaround for incoherent flaw of "
|
||||
"unified page/buffer cache)"
|
||||
: "(wagering meta)");
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (unlikely(!ok) && report)
|
||||
env->lck->pgops.incoherence.weak =
|
||||
(env->lck->pgops.incoherence.weak >= INT32_MAX) ? INT32_MAX : env->lck->pgops.incoherence.weak + 1;
|
||||
return ok;
|
||||
}
|
||||
|
||||
__cold int coherency_timeout(uint64_t *timestamp, intptr_t pgno, const MDBX_env *env) {
|
||||
if (likely(timestamp && *timestamp == 0))
|
||||
*timestamp = osal_monotime();
|
||||
else if (unlikely(!timestamp || osal_monotime() - *timestamp > osal_16dot16_to_monotime(65536 / 10))) {
|
||||
if (pgno >= 0 && pgno != env->stuck_meta)
|
||||
ERROR("bailout waiting for %" PRIuSIZE " page arrival %s", pgno,
|
||||
"(workaround for incoherent flaw of unified page/buffer cache)");
|
||||
else if (env->stuck_meta < 0)
|
||||
ERROR("bailout waiting for valid snapshot (%s)", "workaround for incoherent flaw of unified page/buffer cache");
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
|
||||
osal_memory_fence(mo_AcquireRelease, true);
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
SwitchToThread();
|
||||
#elif defined(__linux__) || defined(__gnu_linux__) || defined(_UNIX03_SOURCE)
|
||||
sched_yield();
|
||||
#elif (defined(_GNU_SOURCE) && __GLIBC_PREREQ(2, 1)) || defined(_OPEN_THREADS)
|
||||
pthread_yield();
|
||||
#else
|
||||
usleep(42);
|
||||
#endif
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
/* check with timeout as the workaround
|
||||
* for https://libmdbx.dqdkfa.ru/dead-github/issues/269 */
|
||||
__hot int coherency_fetch_head(MDBX_txn *txn, const meta_ptr_t head, uint64_t *timestamp) {
|
||||
/* Copy the DB info and flags */
|
||||
txn->txnid = head.txnid;
|
||||
txn->geo = head.ptr_c->geometry;
|
||||
memcpy(txn->dbs, &head.ptr_c->trees, sizeof(head.ptr_c->trees));
|
||||
STATIC_ASSERT(sizeof(head.ptr_c->trees) == CORE_DBS * sizeof(tree_t));
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(txn->dbs + CORE_DBS, txn->env->max_dbi - CORE_DBS);
|
||||
txn->canary = head.ptr_c->canary;
|
||||
|
||||
if (unlikely(!coherency_check(txn->env, head.txnid, txn->dbs, head.ptr_v, *timestamp == 0) ||
|
||||
txn->txnid != meta_txnid(head.ptr_v)))
|
||||
return coherency_timeout(timestamp, -1, txn->env);
|
||||
|
||||
if (unlikely(txn->dbs[FREE_DBI].flags != MDBX_INTEGERKEY)) {
|
||||
if ((txn->dbs[FREE_DBI].flags & DB_PERSISTENT_FLAGS) != MDBX_INTEGERKEY ||
|
||||
unaligned_peek_u64(4, &head.ptr_c->magic_and_version) == MDBX_DATA_MAGIC) {
|
||||
ERROR("unexpected/invalid db-flags 0x%x for %s", txn->dbs[FREE_DBI].flags, "GC/FreeDB");
|
||||
return MDBX_INCOMPATIBLE;
|
||||
}
|
||||
txn->dbs[FREE_DBI].flags &= DB_PERSISTENT_FLAGS;
|
||||
}
|
||||
tASSERT(txn, txn->dbs[FREE_DBI].flags == MDBX_INTEGERKEY);
|
||||
tASSERT(txn, check_table_flags(txn->dbs[MAIN_DBI].flags));
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int coherency_check_written(const MDBX_env *env, const txnid_t txnid, const volatile meta_t *meta, const intptr_t pgno,
|
||||
uint64_t *timestamp) {
|
||||
const bool report = !(timestamp && *timestamp);
|
||||
const txnid_t head_txnid = meta_txnid(meta);
|
||||
if (likely(head_txnid >= MIN_TXNID && head_txnid >= txnid)) {
|
||||
if (likely(coherency_check(env, head_txnid, &meta->trees.gc, meta, report))) {
|
||||
eASSERT(env, meta->trees.gc.flags == MDBX_INTEGERKEY);
|
||||
eASSERT(env, check_table_flags(meta->trees.main.flags));
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
} else if (report) {
|
||||
env->lck->pgops.incoherence.weak =
|
||||
(env->lck->pgops.incoherence.weak >= INT32_MAX) ? INT32_MAX : env->lck->pgops.incoherence.weak + 1;
|
||||
WARNING("catch %s txnid %" PRIaTXN " for meta_%" PRIaPGNO " %s",
|
||||
(head_txnid < MIN_TXNID) ? "invalid" : "unexpected", head_txnid,
|
||||
bytes2pgno(env, ptr_dist(meta, env->dxb_mmap.base)),
|
||||
"(workaround for incoherent flaw of unified page/buffer cache)");
|
||||
}
|
||||
return coherency_timeout(timestamp, pgno, env);
|
||||
}
|
||||
|
||||
bool coherency_check_meta(const MDBX_env *env, const volatile meta_t *meta, bool report) {
|
||||
uint64_t timestamp = 0;
|
||||
return coherency_check_written(env, 0, meta, -1, report ? ×tamp : nullptr) == MDBX_SUCCESS;
|
||||
}
|
88
src/config.h.in
Normal file
88
src/config.h.in
Normal file
@ -0,0 +1,88 @@
|
||||
/* This is CMake-template for libmdbx's config.h
|
||||
******************************************************************************/
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
/* clang-format off */
|
||||
|
||||
#cmakedefine LTO_ENABLED
|
||||
#cmakedefine ENABLE_MEMCHECK
|
||||
#cmakedefine ENABLE_GPROF
|
||||
#cmakedefine ENABLE_GCOV
|
||||
#cmakedefine ENABLE_ASAN
|
||||
#cmakedefine ENABLE_UBSAN
|
||||
#cmakedefine01 MDBX_FORCE_ASSERTIONS
|
||||
#if !defined(MDBX_BUILD_TEST) && !defined(MDBX_BUILD_CXX)
|
||||
#cmakedefine01 MDBX_BUILD_CXX
|
||||
#endif
|
||||
|
||||
/* Common */
|
||||
#cmakedefine01 MDBX_TXN_CHECKOWNER
|
||||
#cmakedefine MDBX_ENV_CHECKPID_AUTO
|
||||
#ifndef MDBX_ENV_CHECKPID_AUTO
|
||||
#cmakedefine01 MDBX_ENV_CHECKPID
|
||||
#endif
|
||||
#cmakedefine MDBX_LOCKING_AUTO
|
||||
#ifndef MDBX_LOCKING_AUTO
|
||||
#cmakedefine MDBX_LOCKING @MDBX_LOCKING@
|
||||
#endif
|
||||
#cmakedefine MDBX_TRUST_RTC_AUTO
|
||||
#ifndef MDBX_TRUST_RTC_AUTO
|
||||
#cmakedefine01 MDBX_TRUST_RTC
|
||||
#endif
|
||||
#cmakedefine01 MDBX_DISABLE_VALIDATION
|
||||
#cmakedefine01 MDBX_AVOID_MSYNC
|
||||
#cmakedefine01 MDBX_ENABLE_REFUND
|
||||
#cmakedefine01 MDBX_ENABLE_BIGFOOT
|
||||
#cmakedefine01 MDBX_ENABLE_PGOP_STAT
|
||||
#cmakedefine01 MDBX_ENABLE_PROFGC
|
||||
#cmakedefine01 MDBX_ENABLE_DBI_SPARSE
|
||||
#cmakedefine01 MDBX_ENABLE_DBI_LOCKFREE
|
||||
|
||||
/* Windows */
|
||||
#if defined(MDBX_BUILD_TEST) || !defined(MDBX_BUILD_CXX) || MDBX_BUILD_CXX
|
||||
#define MDBX_WITHOUT_MSVC_CRT 0
|
||||
#else
|
||||
#cmakedefine01 MDBX_WITHOUT_MSVC_CRT
|
||||
#endif /* MDBX_WITHOUT_MSVC_CRT */
|
||||
|
||||
/* MacOS & iOS */
|
||||
#cmakedefine01 MDBX_APPLE_SPEED_INSTEADOF_DURABILITY
|
||||
|
||||
/* POSIX */
|
||||
#cmakedefine01 MDBX_DISABLE_GNU_SOURCE
|
||||
|
||||
#cmakedefine MDBX_USE_OFDLOCKS_AUTO
|
||||
#ifndef MDBX_USE_OFDLOCKS_AUTO
|
||||
#cmakedefine01 MDBX_USE_OFDLOCKS
|
||||
#endif /* MDBX_USE_OFDLOCKS */
|
||||
|
||||
#cmakedefine MDBX_MMAP_NEEDS_JOLT_AUTO
|
||||
#ifndef MDBX_MMAP_NEEDS_JOLT_AUTO
|
||||
#cmakedefine01 MDBX_MMAP_NEEDS_JOLT
|
||||
#endif /* MDBX_MMAP_NEEDS_JOLT */
|
||||
|
||||
#cmakedefine01 MDBX_USE_MINCORE
|
||||
|
||||
/* Build Info */
|
||||
#ifndef MDBX_BUILD_TIMESTAMP
|
||||
#cmakedefine MDBX_BUILD_TIMESTAMP "@MDBX_BUILD_TIMESTAMP@"
|
||||
#endif
|
||||
#ifndef MDBX_BUILD_TARGET
|
||||
#cmakedefine MDBX_BUILD_TARGET "@MDBX_BUILD_TARGET@"
|
||||
#endif
|
||||
#ifndef MDBX_BUILD_TYPE
|
||||
#cmakedefine MDBX_BUILD_TYPE "@MDBX_BUILD_TYPE@"
|
||||
#endif
|
||||
#ifndef MDBX_BUILD_COMPILER
|
||||
#cmakedefine MDBX_BUILD_COMPILER "@MDBX_BUILD_COMPILER@"
|
||||
#endif
|
||||
#ifndef MDBX_BUILD_FLAGS
|
||||
#cmakedefine MDBX_BUILD_FLAGS "@MDBX_BUILD_FLAGS@"
|
||||
#endif
|
||||
#ifndef MDBX_BUILD_METADATA
|
||||
#cmakedefine MDBX_BUILD_METADATA "@MDBX_BUILD_METADATA@"
|
||||
#endif
|
||||
#cmakedefine MDBX_BUILD_SOURCERY @MDBX_BUILD_SOURCERY@
|
||||
|
||||
/* *INDENT-ON* */
|
||||
/* clang-format on */
|
2396
src/cursor.c
Normal file
2396
src/cursor.c
Normal file
File diff suppressed because it is too large
Load Diff
391
src/cursor.h
Normal file
391
src/cursor.h
Normal file
@ -0,0 +1,391 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
/* Состояние курсора.
|
||||
*
|
||||
* плохой/poor:
|
||||
* - неустановленный курсор с незаполненым стеком;
|
||||
* - следует пропускать во всех циклах отслеживания/корректировки
|
||||
* позиций курсоров;
|
||||
* - допускаются только операции предполагающие установку абсолютной позиции;
|
||||
* - в остальных случаях возвращается ENODATA.
|
||||
*
|
||||
* У таких курсоров top = -1 и flags < 0, что позволяет дешево проверять и
|
||||
* пропускать такие курсоры в циклах отслеживания/корректировки по условию
|
||||
* probe_cursor->top < this_cursor->top.
|
||||
*
|
||||
* пустой/hollow:
|
||||
* - частично инициализированный курсор, но без доступной пользователю позиции,
|
||||
* поэтому нельзя выполнить какую-либо операцию без абсолютного (не
|
||||
* относительного) позиционирования;
|
||||
* - ki[top] может быть некорректным, в том числе >= page_numkeys(pg[top]).
|
||||
*
|
||||
* У таких курсоров top >= 0, но flags < 0 (есть флажок z_hollow).
|
||||
*
|
||||
* установленный/pointed:
|
||||
* - полностью инициализированный курсор с конкретной позицией с данными;
|
||||
* - можно прочитать текущую строку, удалить её, либо выполнить
|
||||
* относительное перемещение;
|
||||
* - может иметь флажки z_after_delete, z_eof_hard и z_eof_soft;
|
||||
* - наличие z_eof_soft означает что курсор перемещен за пределы данных,
|
||||
* поэтому нелья прочитать текущие данные, либо удалить их.
|
||||
*
|
||||
* У таких курсоров top >= 0 и flags >= 0 (нет флажка z_hollow).
|
||||
*
|
||||
* наполненный данными/filled:
|
||||
* - это установленный/pointed курсор без флагов z_eof_soft;
|
||||
* - за курсором есть даные, возможны CRUD операции в текущей позиции.
|
||||
*
|
||||
* У таких курсоров top >= 0 и (unsigned)flags < z_eof_soft.
|
||||
*
|
||||
* Изменения состояния.
|
||||
*
|
||||
* - Сбрасывается состояние курсора посредством top_and_flags |= z_poor_mark,
|
||||
* что равносильно top = -1 вместе с flags |= z_poor_mark;
|
||||
* - При позиционировании курсора сначала устанавливается top, а flags
|
||||
* только в самом конце при отсутстви ошибок.
|
||||
* - Повторное позиционирование first/last может начинаться
|
||||
* с установки/обнуления только top без сброса flags, что позволяет работать
|
||||
* быстрому пути внутри tree_search_finalize().
|
||||
*
|
||||
* - Заморочки с концом данных:
|
||||
* - mdbx_cursor_get(NEXT) выполняет две операции (перемещение и чтение),
|
||||
* поэтому перемещение на последнюю строку строку всегда успешно,
|
||||
* а ошибка возвращается только при последующем next().
|
||||
* Однако, из-за этой двойственности семантика ситуации возврата ошибки
|
||||
* из mdbx_cursor_get(NEXT) допускает разночтение/неопределенность, ибо
|
||||
* не понятно к чему относится ошибка:
|
||||
* - Если к чтению данных, то курсор перемещен и стоит после последней
|
||||
* строки. Соответственно, чтение в текущей позиции запрещено,
|
||||
* а при выполнении prev() курсор вернется на последнюю строку;
|
||||
* - Если же ошибка относится к перемещению, то курсор не перемещен и
|
||||
* остается на последней строке. Соответственно, чтение в текущей
|
||||
* позиции допустимо, а при выполнении prev() курсор встанет
|
||||
* на пред-последнюю строку.
|
||||
* - Пикантность в том, что пользователи (так или иначе) полагаются
|
||||
* на оба варианта поведения, при этом конечно ожидают что после
|
||||
* ошибки MDBX_NEXT функция mdbx_cursor_eof() будет возвращать true.
|
||||
* - далее добавляется схожая ситуация с MDBX_GET_RANGE, MDBX_LOWERBOUND,
|
||||
* MDBX_GET_BOTH_RANGE и MDBX_UPPERBOUND. Тут при неуспехе поиска курсор
|
||||
* может/должен стоять после последней строки.
|
||||
* - далее добавляется MDBX_LAST. Тут курсор должен стоять на последней
|
||||
* строке и допускать чтение в текузщей позиции,
|
||||
* но mdbx_cursor_eof() должен возвращать true.
|
||||
*
|
||||
* Решение = делаем два флажка z_eof_soft и z_eof_hard:
|
||||
* - Когда установлен только z_eof_soft,
|
||||
* функция mdbx_cursor_eof() возвращает true, но допускается
|
||||
* чтение данных в текущей позиции, а prev() передвигает курсор
|
||||
* на пред-последнюю строку.
|
||||
* - Когда установлен z_eof_hard, чтение данных в текущей позиции
|
||||
* не допускается, и mdbx_cursor_eof() также возвращает true,
|
||||
* а prev() устанавливает курсора на последюю строку. */
|
||||
enum cursor_state {
|
||||
/* Это вложенный курсор для вложенного дерева/страницы и является
|
||||
inner-элементом struct cursor_couple. */
|
||||
z_inner = 0x01,
|
||||
|
||||
/* Происходит подготовка к обновлению GC,
|
||||
поэтому можно брать страницы из GC даже для FREE_DBI. */
|
||||
z_gcu_preparation = 0x02,
|
||||
|
||||
/* Курсор только-что создан, поэтому допускается авто-установка
|
||||
в начало/конец, вместо возврата ошибки. */
|
||||
z_fresh = 0x04,
|
||||
|
||||
/* Предыдущей операцией было удаление, поэтому курсор уже физически указывает
|
||||
на следующий элемент и соответствующая операция перемещения должна
|
||||
игнорироваться. */
|
||||
z_after_delete = 0x08,
|
||||
|
||||
/* */
|
||||
z_disable_tree_search_fastpath = 0x10,
|
||||
|
||||
/* Курсор логически в конце данных, но физически на последней строке,
|
||||
* ki[top] == page_numkeys(pg[top]) - 1 и читать данные в текущей позиции. */
|
||||
z_eof_soft = 0x20,
|
||||
|
||||
/* Курсор логически за концом данных, поэтому следующий переход "назад"
|
||||
должен игнорироваться и/или приводить к установке на последнюю строку.
|
||||
В текущем же состоянии нельзя делать CRUD операции. */
|
||||
z_eof_hard = 0x40,
|
||||
|
||||
/* За курсором нет данных, логически его позиция не определена,
|
||||
нельзя делать CRUD операции в текущей позиции.
|
||||
Относительное перемещение запрещено. */
|
||||
z_hollow = -128 /* 0x80 */,
|
||||
|
||||
/* Маски для сброса/установки состояния. */
|
||||
z_clear_mask = z_inner | z_gcu_preparation,
|
||||
z_poor_mark = z_eof_hard | z_hollow | z_disable_tree_search_fastpath,
|
||||
z_fresh_mark = z_poor_mark | z_fresh
|
||||
};
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_inner(const MDBX_cursor *mc) {
|
||||
return (mc->flags & z_inner) != 0;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_poor(const MDBX_cursor *mc) {
|
||||
const bool r = mc->top < 0;
|
||||
cASSERT(mc, r == (mc->top_and_flags < 0));
|
||||
if (r && mc->subcur)
|
||||
cASSERT(mc, mc->subcur->cursor.flags < 0 && mc->subcur->cursor.top < 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_pointed(const MDBX_cursor *mc) {
|
||||
const bool r = mc->top >= 0;
|
||||
cASSERT(mc, r == (mc->top_and_flags >= 0));
|
||||
if (!r && mc->subcur)
|
||||
cASSERT(mc, is_poor(&mc->subcur->cursor));
|
||||
return r;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_hollow(const MDBX_cursor *mc) {
|
||||
const bool r = mc->flags < 0;
|
||||
if (!r) {
|
||||
cASSERT(mc, mc->top >= 0);
|
||||
cASSERT(mc, (mc->flags & z_eof_hard) || mc->ki[mc->top] < page_numkeys(mc->pg[mc->top]));
|
||||
} else if (mc->subcur)
|
||||
cASSERT(mc, is_poor(&mc->subcur->cursor) || (is_pointed(mc) && mc->subcur->cursor.flags < 0));
|
||||
return r;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_eof(const MDBX_cursor *mc) {
|
||||
const bool r = z_eof_soft <= (uint8_t)mc->flags;
|
||||
return r;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_filled(const MDBX_cursor *mc) {
|
||||
const bool r = z_eof_hard > (uint8_t)mc->flags;
|
||||
return r;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool inner_filled(const MDBX_cursor *mc) {
|
||||
return mc->subcur && is_filled(&mc->subcur->cursor);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool inner_pointed(const MDBX_cursor *mc) {
|
||||
return mc->subcur && is_pointed(&mc->subcur->cursor);
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool inner_hollow(const MDBX_cursor *mc) {
|
||||
const bool r = !mc->subcur || is_hollow(&mc->subcur->cursor);
|
||||
#if MDBX_DEBUG || MDBX_FORCE_ASSERTIONS
|
||||
if (!r) {
|
||||
cASSERT(mc, is_filled(mc));
|
||||
const page_t *mp = mc->pg[mc->top];
|
||||
const node_t *node = page_node(mp, mc->ki[mc->top]);
|
||||
cASSERT(mc, node_flags(node) & N_DUP);
|
||||
}
|
||||
#endif /* MDBX_DEBUG || MDBX_FORCE_ASSERTIONS */
|
||||
return r;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline void inner_gone(MDBX_cursor *mc) {
|
||||
if (mc->subcur) {
|
||||
TRACE("reset inner cursor %p", __Wpedantic_format_voidptr(&mc->subcur->cursor));
|
||||
mc->subcur->nested_tree.root = 0;
|
||||
mc->subcur->cursor.top_and_flags = z_inner | z_poor_mark;
|
||||
}
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline void be_poor(MDBX_cursor *mc) {
|
||||
const bool inner = is_inner(mc);
|
||||
if (inner) {
|
||||
mc->tree->root = 0;
|
||||
mc->top_and_flags = z_inner | z_poor_mark;
|
||||
} else {
|
||||
mc->top_and_flags |= z_poor_mark;
|
||||
inner_gone(mc);
|
||||
}
|
||||
cASSERT(mc, is_poor(mc) && !is_pointed(mc) && !is_filled(mc));
|
||||
cASSERT(mc, inner == is_inner(mc));
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline void be_filled(MDBX_cursor *mc) {
|
||||
cASSERT(mc, mc->top >= 0);
|
||||
cASSERT(mc, mc->ki[mc->top] < page_numkeys(mc->pg[mc->top]));
|
||||
const bool inner = is_inner(mc);
|
||||
mc->flags &= z_clear_mask;
|
||||
cASSERT(mc, is_filled(mc));
|
||||
cASSERT(mc, inner == is_inner(mc));
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline bool is_related(const MDBX_cursor *base, const MDBX_cursor *scan) {
|
||||
cASSERT(base, base->top >= 0);
|
||||
return base->top <= scan->top && base != scan;
|
||||
}
|
||||
|
||||
/* Флаги контроля/проверки курсора. */
|
||||
enum cursor_checking {
|
||||
z_branch = 0x01 /* same as P_BRANCH for check_leaf_type() */,
|
||||
z_leaf = 0x02 /* same as P_LEAF for check_leaf_type() */,
|
||||
z_largepage = 0x04 /* same as P_LARGE for check_leaf_type() */,
|
||||
z_updating = 0x08 /* update/rebalance pending */,
|
||||
z_ignord = 0x10 /* don't check keys ordering */,
|
||||
z_dupfix = 0x20 /* same as P_DUPFIX for check_leaf_type() */,
|
||||
z_retiring = 0x40 /* refs to child pages may be invalid */,
|
||||
z_pagecheck = 0x80 /* perform page checking, see MDBX_VALIDATION */
|
||||
};
|
||||
|
||||
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);
|
||||
size_t dbi = mc->dbi_state - mc->txn->dbi_state;
|
||||
cASSERT(mc, dbi < mc->txn->env->n_dbi);
|
||||
return dbi;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool cursor_dbi_changed(const MDBX_cursor *mc) {
|
||||
return dbi_changed(mc->txn, cursor_dbi(mc));
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint8_t *cursor_dbi_state(const MDBX_cursor *mc) {
|
||||
return mc->dbi_state;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool cursor_is_gc(const MDBX_cursor *mc) {
|
||||
return mc->dbi_state == mc->txn->dbi_state + FREE_DBI;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool cursor_is_main(const MDBX_cursor *mc) {
|
||||
return mc->dbi_state == mc->txn->dbi_state + MAIN_DBI;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool cursor_is_core(const MDBX_cursor *mc) {
|
||||
return mc->dbi_state < mc->txn->dbi_state + CORE_DBS;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline int cursor_dbi_dbg(const MDBX_cursor *mc) {
|
||||
/* Debugging output value of a cursor's DBI: Negative for a sub-cursor. */
|
||||
const int dbi = cursor_dbi(mc);
|
||||
return (mc->flags & z_inner) ? -dbi : dbi;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline int __must_check_result cursor_push(MDBX_cursor *mc, page_t *mp, indx_t ki) {
|
||||
TRACE("pushing page %" PRIaPGNO " on db %d cursor %p", mp->pgno, cursor_dbi_dbg(mc), __Wpedantic_format_voidptr(mc));
|
||||
if (unlikely(mc->top >= CURSOR_STACK_SIZE - 1)) {
|
||||
be_poor(mc);
|
||||
mc->txn->flags |= MDBX_TXN_ERROR;
|
||||
return MDBX_CURSOR_FULL;
|
||||
}
|
||||
mc->top += 1;
|
||||
mc->pg[mc->top] = mp;
|
||||
mc->ki[mc->top] = ki;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline void cursor_pop(MDBX_cursor *mc) {
|
||||
TRACE("popped page %" PRIaPGNO " off db %d cursor %p", mc->pg[mc->top]->pgno, cursor_dbi_dbg(mc),
|
||||
__Wpedantic_format_voidptr(mc));
|
||||
cASSERT(mc, mc->top >= 0);
|
||||
mc->top -= 1;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline bool check_leaf_type(const MDBX_cursor *mc, const page_t *mp) {
|
||||
return (((page_type(mp) ^ mc->checking) & (z_branch | z_leaf | z_largepage | z_dupfix)) == 0);
|
||||
}
|
||||
|
||||
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_validate_updating(MDBX_cursor *mc);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result cursor_del(MDBX_cursor *mc, unsigned flags);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result cursor_sibling_left(MDBX_cursor *mc);
|
||||
MDBX_INTERNAL int __must_check_result cursor_sibling_right(MDBX_cursor *mc);
|
||||
|
||||
typedef struct cursor_set_result {
|
||||
int err;
|
||||
bool exact;
|
||||
} csr_t;
|
||||
|
||||
MDBX_INTERNAL csr_t cursor_seek(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, MDBX_cursor_op op);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result inner_first(MDBX_cursor *__restrict mc, MDBX_val *__restrict data);
|
||||
MDBX_INTERNAL int __must_check_result inner_last(MDBX_cursor *__restrict mc, MDBX_val *__restrict data);
|
||||
MDBX_INTERNAL int __must_check_result outer_first(MDBX_cursor *__restrict mc, MDBX_val *__restrict key,
|
||||
MDBX_val *__restrict data);
|
||||
MDBX_INTERNAL int __must_check_result outer_last(MDBX_cursor *__restrict mc, MDBX_val *__restrict key,
|
||||
MDBX_val *__restrict data);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result inner_next(MDBX_cursor *__restrict mc, MDBX_val *__restrict data);
|
||||
MDBX_INTERNAL int __must_check_result inner_prev(MDBX_cursor *__restrict mc, MDBX_val *__restrict data);
|
||||
MDBX_INTERNAL int __must_check_result outer_next(MDBX_cursor *__restrict mc, MDBX_val *__restrict key,
|
||||
MDBX_val *__restrict data, MDBX_cursor_op op);
|
||||
MDBX_INTERNAL int __must_check_result outer_prev(MDBX_cursor *__restrict mc, MDBX_val *__restrict key,
|
||||
MDBX_val *__restrict data, MDBX_cursor_op op);
|
||||
|
||||
MDBX_INTERNAL int cursor_init4walk(cursor_couple_t *couple, const MDBX_txn *const txn, tree_t *const tree,
|
||||
kvx_t *const kvx);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result cursor_init(MDBX_cursor *mc, const MDBX_txn *txn, size_t dbi);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result cursor_dupsort_setup(MDBX_cursor *mc, const node_t *node, const page_t *mp);
|
||||
|
||||
MDBX_INTERNAL int __must_check_result cursor_touch(MDBX_cursor *const mc, const MDBX_val *key, const MDBX_val *data);
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Update sub-page pointer, if any, in mc->subcur.
|
||||
* Needed when the node which contains the sub-page may have moved.
|
||||
* Called with mp = mc->pg[mc->top], ki = mc->ki[mc->top]. */
|
||||
MDBX_MAYBE_UNUSED static inline void cursor_inner_refresh(const MDBX_cursor *mc, const page_t *mp, unsigned ki) {
|
||||
cASSERT(mc, is_leaf(mp));
|
||||
const node_t *node = page_node(mp, ki);
|
||||
if ((node_flags(node) & (N_DUP | N_TREE)) == N_DUP)
|
||||
mc->subcur->cursor.pg[0] = node_data(node);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
692
src/dbi.c
Normal file
692
src/dbi.c
Normal file
@ -0,0 +1,692 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
size_t dbi_bitmap_ctz_fallback(const MDBX_txn *txn, intptr_t bmi) {
|
||||
tASSERT(txn, bmi > 0);
|
||||
bmi &= -bmi;
|
||||
if (sizeof(txn->dbi_sparse[0]) > 4) {
|
||||
static const uint8_t debruijn_ctz64[64] = {0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28,
|
||||
62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11,
|
||||
63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10,
|
||||
51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12};
|
||||
return debruijn_ctz64[(UINT64_C(0x022FDD63CC95386D) * (uint64_t)bmi) >> 58];
|
||||
} else {
|
||||
static const uint8_t debruijn_ctz32[32] = {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
|
||||
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
|
||||
return debruijn_ctz32[(UINT32_C(0x077CB531) * (uint32_t)bmi) >> 27];
|
||||
}
|
||||
}
|
||||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||||
|
||||
struct dbi_snap_result dbi_snap(const MDBX_env *env, const size_t dbi) {
|
||||
eASSERT(env, dbi < env->n_dbi);
|
||||
struct dbi_snap_result r;
|
||||
uint32_t snap = atomic_load32(&env->dbi_seqs[dbi], mo_AcquireRelease);
|
||||
do {
|
||||
r.sequence = snap;
|
||||
r.flags = env->dbs_flags[dbi];
|
||||
snap = atomic_load32(&env->dbi_seqs[dbi], mo_AcquireRelease);
|
||||
} while (unlikely(snap != r.sequence));
|
||||
return r;
|
||||
}
|
||||
|
||||
__noinline int dbi_import(MDBX_txn *txn, const size_t dbi) {
|
||||
const MDBX_env *const env = txn->env;
|
||||
if (dbi >= env->n_dbi || !env->dbs_flags[dbi])
|
||||
return MDBX_BAD_DBI;
|
||||
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
const size_t bitmap_chunk = CHAR_BIT * sizeof(txn->dbi_sparse[0]);
|
||||
const size_t bitmap_indx = dbi / bitmap_chunk;
|
||||
const size_t bitmap_mask = (size_t)1 << dbi % bitmap_chunk;
|
||||
if (dbi >= txn->n_dbi) {
|
||||
for (size_t i = (txn->n_dbi + bitmap_chunk - 1) / bitmap_chunk; bitmap_indx >= i; ++i)
|
||||
txn->dbi_sparse[i] = 0;
|
||||
eASSERT(env, (txn->dbi_sparse[bitmap_indx] & bitmap_mask) == 0);
|
||||
MDBX_txn *scan = txn;
|
||||
do {
|
||||
eASSERT(env, scan->dbi_sparse == txn->dbi_sparse);
|
||||
eASSERT(env, scan->n_dbi < dbi + 1);
|
||||
scan->n_dbi = (unsigned)dbi + 1;
|
||||
scan->dbi_state[dbi] = 0;
|
||||
scan = scan->parent;
|
||||
} while (scan /* && scan->dbi_sparse == txn->dbi_sparse */);
|
||||
txn->dbi_sparse[bitmap_indx] |= bitmap_mask;
|
||||
goto lindo;
|
||||
}
|
||||
if ((txn->dbi_sparse[bitmap_indx] & bitmap_mask) == 0) {
|
||||
MDBX_txn *scan = txn;
|
||||
do {
|
||||
eASSERT(env, scan->dbi_sparse == txn->dbi_sparse);
|
||||
eASSERT(env, scan->n_dbi == txn->n_dbi);
|
||||
scan->dbi_state[dbi] = 0;
|
||||
scan = scan->parent;
|
||||
} while (scan /* && scan->dbi_sparse == txn->dbi_sparse */);
|
||||
txn->dbi_sparse[bitmap_indx] |= bitmap_mask;
|
||||
goto lindo;
|
||||
}
|
||||
#else
|
||||
if (dbi >= txn->n_dbi) {
|
||||
size_t i = txn->n_dbi;
|
||||
do
|
||||
txn->dbi_state[i] = 0;
|
||||
while (dbi >= ++i);
|
||||
txn->n_dbi = i;
|
||||
goto lindo;
|
||||
}
|
||||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||||
|
||||
if (!txn->dbi_state[dbi]) {
|
||||
lindo:
|
||||
/* dbi-слот еще не инициализирован в транзакции, а хендл не использовался */
|
||||
txn->cursors[dbi] = nullptr;
|
||||
MDBX_txn *const parent = txn->parent;
|
||||
if (parent) {
|
||||
/* вложенная пишущая транзакция */
|
||||
int rc = dbi_check(parent, dbi);
|
||||
/* копируем состояние dbi-хендла очищая new-флаги. */
|
||||
eASSERT(env, txn->dbi_seqs == parent->dbi_seqs);
|
||||
txn->dbi_state[dbi] = parent->dbi_state[dbi] & ~(DBI_FRESH | DBI_CREAT | DBI_DIRTY);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
txn->dbs[dbi] = parent->dbs[dbi];
|
||||
rc = txn_shadow_cursors(parent, dbi);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
txn->dbi_seqs[dbi] = 0;
|
||||
txn->dbi_state[dbi] = DBI_LINDO;
|
||||
} else {
|
||||
eASSERT(env, txn->dbi_seqs[dbi] != env->dbi_seqs[dbi].weak);
|
||||
if (unlikely((txn->dbi_state[dbi] & (DBI_VALID | DBI_OLDEN)) || txn->cursors[dbi])) {
|
||||
/* хендл уже использовался в транзакции, но был закрыт или переоткрыт,
|
||||
* либо при явном пере-открытии хендла есть висячие курсоры */
|
||||
eASSERT(env, (txn->dbi_state[dbi] & DBI_STALE) == 0);
|
||||
txn->dbi_seqs[dbi] = env->dbi_seqs[dbi].weak;
|
||||
txn->dbi_state[dbi] = DBI_OLDEN | DBI_LINDO;
|
||||
return txn->cursors[dbi] ? MDBX_DANGLING_DBI : MDBX_BAD_DBI;
|
||||
}
|
||||
}
|
||||
|
||||
/* хендл не использовался в транзакции, либо явно пере-отрывается при
|
||||
* отсутствии висячих курсоров */
|
||||
eASSERT(env, (txn->dbi_state[dbi] & DBI_LINDO) && !txn->cursors[dbi]);
|
||||
|
||||
/* читаем актуальные флаги и sequence */
|
||||
struct dbi_snap_result snap = dbi_snap(env, dbi);
|
||||
txn->dbi_seqs[dbi] = snap.sequence;
|
||||
if (snap.flags & DB_VALID) {
|
||||
txn->dbs[dbi].flags = snap.flags & DB_PERSISTENT_FLAGS;
|
||||
txn->dbi_state[dbi] = DBI_LINDO | DBI_VALID | DBI_STALE;
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
return MDBX_BAD_DBI;
|
||||
}
|
||||
|
||||
int dbi_defer_release(MDBX_env *const env, defer_free_item_t *const chain) {
|
||||
size_t length = 0;
|
||||
defer_free_item_t *obsolete_chain = nullptr;
|
||||
#if MDBX_ENABLE_DBI_LOCKFREE
|
||||
const uint64_t now = osal_monotime();
|
||||
defer_free_item_t **scan = &env->defer_free;
|
||||
if (env->defer_free) {
|
||||
const uint64_t threshold_1second = osal_16dot16_to_monotime(1 * 65536);
|
||||
do {
|
||||
defer_free_item_t *item = *scan;
|
||||
if (now - item->timestamp < threshold_1second) {
|
||||
scan = &item->next;
|
||||
length += 1;
|
||||
} else {
|
||||
*scan = item->next;
|
||||
item->next = obsolete_chain;
|
||||
obsolete_chain = item;
|
||||
}
|
||||
} while (*scan);
|
||||
}
|
||||
|
||||
eASSERT(env, *scan == nullptr);
|
||||
if (chain) {
|
||||
defer_free_item_t *item = chain;
|
||||
do {
|
||||
item->timestamp = now;
|
||||
item = item->next;
|
||||
} while (item);
|
||||
*scan = chain;
|
||||
}
|
||||
#else /* MDBX_ENABLE_DBI_LOCKFREE */
|
||||
obsolete_chain = chain;
|
||||
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
|
||||
|
||||
ENSURE(env, osal_fastmutex_release(&env->dbi_lock) == MDBX_SUCCESS);
|
||||
if (length > 42) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
SwitchToThread();
|
||||
#else
|
||||
sched_yield();
|
||||
#endif /* Windows */
|
||||
}
|
||||
while (obsolete_chain) {
|
||||
defer_free_item_t *item = obsolete_chain;
|
||||
obsolete_chain = obsolete_chain->next;
|
||||
osal_free(item);
|
||||
}
|
||||
return chain ? MDBX_SUCCESS : MDBX_BAD_DBI;
|
||||
}
|
||||
|
||||
/* Export or close DBI handles opened in this txn. */
|
||||
int dbi_update(MDBX_txn *txn, bool keep) {
|
||||
MDBX_env *const env = txn->env;
|
||||
tASSERT(txn, !txn->parent && txn == env->basal_txn);
|
||||
bool locked = false;
|
||||
defer_free_item_t *defer_chain = nullptr;
|
||||
TXN_FOREACH_DBI_USER(txn, dbi) {
|
||||
if (likely((txn->dbi_state[dbi] & DBI_CREAT) == 0))
|
||||
continue;
|
||||
if (!locked) {
|
||||
int err = osal_fastmutex_acquire(&env->dbi_lock);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
locked = true;
|
||||
if (dbi >= env->n_dbi)
|
||||
/* хендл был закрыт из другого потока пока захватывали блокировку */
|
||||
continue;
|
||||
}
|
||||
tASSERT(txn, dbi < env->n_dbi);
|
||||
if (keep) {
|
||||
env->dbs_flags[dbi] = txn->dbs[dbi].flags | DB_VALID;
|
||||
} else {
|
||||
uint32_t seq = dbi_seq_next(env, dbi);
|
||||
defer_free_item_t *item = env->kvs[dbi].name.iov_base;
|
||||
if (item) {
|
||||
env->dbs_flags[dbi] = 0;
|
||||
env->kvs[dbi].name.iov_len = 0;
|
||||
env->kvs[dbi].name.iov_base = nullptr;
|
||||
atomic_store32(&env->dbi_seqs[dbi], seq, mo_AcquireRelease);
|
||||
osal_flush_incoherent_cpu_writeback();
|
||||
item->next = defer_chain;
|
||||
defer_chain = item;
|
||||
} else {
|
||||
eASSERT(env, env->kvs[dbi].name.iov_len == 0);
|
||||
eASSERT(env, env->dbs_flags[dbi] == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (locked) {
|
||||
size_t i = env->n_dbi;
|
||||
eASSERT(env, env->n_dbi >= CORE_DBS);
|
||||
while ((env->dbs_flags[i - 1] & DB_VALID) == 0) {
|
||||
--i;
|
||||
eASSERT(env, i >= CORE_DBS);
|
||||
eASSERT(env, !env->dbs_flags[i] && !env->kvs[i].name.iov_len && !env->kvs[i].name.iov_base);
|
||||
}
|
||||
env->n_dbi = (unsigned)i;
|
||||
dbi_defer_release(env, defer_chain);
|
||||
}
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
int dbi_bind(MDBX_txn *txn, const size_t dbi, unsigned user_flags, MDBX_cmp_func *keycmp, MDBX_cmp_func *datacmp) {
|
||||
const MDBX_env *const env = txn->env;
|
||||
eASSERT(env, dbi < txn->n_dbi && dbi < env->n_dbi);
|
||||
eASSERT(env, dbi_state(txn, dbi) & DBI_LINDO);
|
||||
eASSERT(env, env->dbs_flags[dbi] != DB_POISON);
|
||||
if ((env->dbs_flags[dbi] & DB_VALID) == 0) {
|
||||
eASSERT(env, !env->kvs[dbi].clc.k.cmp && !env->kvs[dbi].clc.v.cmp && !env->kvs[dbi].name.iov_len &&
|
||||
!env->kvs[dbi].name.iov_base && !env->kvs[dbi].clc.k.lmax && !env->kvs[dbi].clc.k.lmin &&
|
||||
!env->kvs[dbi].clc.v.lmax && !env->kvs[dbi].clc.v.lmin);
|
||||
} else {
|
||||
eASSERT(env, !(txn->dbi_state[dbi] & DBI_VALID) || (txn->dbs[dbi].flags | DB_VALID) == env->dbs_flags[dbi]);
|
||||
eASSERT(env, env->kvs[dbi].name.iov_base || dbi < CORE_DBS);
|
||||
}
|
||||
|
||||
/* Если dbi уже использовался, то корректными считаем четыре варианта:
|
||||
* 1) user_flags равны MDBX_DB_ACCEDE
|
||||
* = предполагаем что пользователь открывает существующую table,
|
||||
* при этом код проверки не позволит установить другие компараторы.
|
||||
* 2) user_flags нулевые, а оба компаратора пустые/нулевые или равны текущим
|
||||
* = предполагаем что пользователь открывает существующую table
|
||||
* старым способом с нулевыми с флагами по-умолчанию.
|
||||
* 3) user_flags совпадают, а компараторы не заданы или те же
|
||||
* = предполагаем что пользователь открывает table указывая все параметры;
|
||||
* 4) user_flags отличаются, но table пустая и задан флаг MDBX_CREATE
|
||||
* = предполагаем что пользователь пересоздает table;
|
||||
*/
|
||||
if ((user_flags & ~MDBX_CREATE) != (unsigned)(env->dbs_flags[dbi] & DB_PERSISTENT_FLAGS)) {
|
||||
/* flags are differs, check other conditions */
|
||||
if ((!user_flags && (!keycmp || keycmp == env->kvs[dbi].clc.k.cmp) &&
|
||||
(!datacmp || datacmp == env->kvs[dbi].clc.v.cmp)) ||
|
||||
user_flags == MDBX_DB_ACCEDE) {
|
||||
user_flags = env->dbs_flags[dbi] & DB_PERSISTENT_FLAGS;
|
||||
} else if ((user_flags & MDBX_CREATE) == 0)
|
||||
return /* FIXME: return extended info */ MDBX_INCOMPATIBLE;
|
||||
else {
|
||||
if (txn->dbi_state[dbi] & DBI_STALE) {
|
||||
eASSERT(env, env->dbs_flags[dbi] & DB_VALID);
|
||||
int err = tbl_fetch(txn, dbi);
|
||||
if (unlikely(err == MDBX_SUCCESS))
|
||||
return err;
|
||||
}
|
||||
eASSERT(env, ((env->dbs_flags[dbi] ^ txn->dbs[dbi].flags) & DB_PERSISTENT_FLAGS) == 0);
|
||||
eASSERT(env, (txn->dbi_state[dbi] & (DBI_LINDO | DBI_VALID | DBI_STALE)) == (DBI_LINDO | DBI_VALID));
|
||||
if (unlikely(txn->dbs[dbi].leaf_pages))
|
||||
return /* FIXME: return extended info */ MDBX_INCOMPATIBLE;
|
||||
|
||||
/* Пересоздаём table если там пусто */
|
||||
if (unlikely(txn->cursors[dbi]))
|
||||
return MDBX_DANGLING_DBI;
|
||||
env->dbs_flags[dbi] = DB_POISON;
|
||||
atomic_store32(&env->dbi_seqs[dbi], dbi_seq_next(env, dbi), mo_AcquireRelease);
|
||||
|
||||
const uint32_t seq = dbi_seq_next(env, dbi);
|
||||
const uint16_t db_flags = user_flags & DB_PERSISTENT_FLAGS;
|
||||
eASSERT(env, txn->dbs[dbi].height == 0 && txn->dbs[dbi].items == 0 && txn->dbs[dbi].root == P_INVALID);
|
||||
env->kvs[dbi].clc.k.cmp = keycmp ? keycmp : builtin_keycmp(user_flags);
|
||||
env->kvs[dbi].clc.v.cmp = datacmp ? datacmp : builtin_datacmp(user_flags);
|
||||
txn->dbs[dbi].flags = db_flags;
|
||||
txn->dbs[dbi].dupfix_size = 0;
|
||||
if (unlikely(tbl_setup(env, &env->kvs[dbi], &txn->dbs[dbi]))) {
|
||||
txn->dbi_state[dbi] = DBI_LINDO;
|
||||
txn->flags |= MDBX_TXN_ERROR;
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
|
||||
env->dbs_flags[dbi] = db_flags | DB_VALID;
|
||||
atomic_store32(&env->dbi_seqs[dbi], seq, mo_AcquireRelease);
|
||||
txn->dbi_seqs[dbi] = seq;
|
||||
txn->dbi_state[dbi] = DBI_LINDO | DBI_VALID | DBI_CREAT | DBI_DIRTY;
|
||||
txn->flags |= MDBX_TXN_DIRTY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keycmp)
|
||||
keycmp = (env->dbs_flags[dbi] & DB_VALID) ? env->kvs[dbi].clc.k.cmp : builtin_keycmp(user_flags);
|
||||
if (env->kvs[dbi].clc.k.cmp != keycmp) {
|
||||
if (env->dbs_flags[dbi] & DB_VALID)
|
||||
return MDBX_EINVAL;
|
||||
env->kvs[dbi].clc.k.cmp = keycmp;
|
||||
}
|
||||
|
||||
if (!datacmp)
|
||||
datacmp = (env->dbs_flags[dbi] & DB_VALID) ? env->kvs[dbi].clc.v.cmp : builtin_datacmp(user_flags);
|
||||
if (env->kvs[dbi].clc.v.cmp != datacmp) {
|
||||
if (env->dbs_flags[dbi] & DB_VALID)
|
||||
return MDBX_EINVAL;
|
||||
env->kvs[dbi].clc.v.cmp = datacmp;
|
||||
}
|
||||
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
static inline size_t dbi_namelen(const MDBX_val name) {
|
||||
return (name.iov_len > sizeof(defer_free_item_t)) ? name.iov_len : sizeof(defer_free_item_t);
|
||||
}
|
||||
|
||||
static int dbi_open_locked(MDBX_txn *txn, unsigned user_flags, MDBX_dbi *dbi, MDBX_cmp_func *keycmp,
|
||||
MDBX_cmp_func *datacmp, MDBX_val name) {
|
||||
MDBX_env *const env = txn->env;
|
||||
|
||||
/* Cannot mix named table(s) with DUPSORT flags */
|
||||
tASSERT(txn, (txn->dbi_state[MAIN_DBI] & (DBI_LINDO | DBI_VALID | DBI_STALE)) == (DBI_LINDO | DBI_VALID));
|
||||
if (unlikely(txn->dbs[MAIN_DBI].flags & MDBX_DUPSORT)) {
|
||||
if (unlikely((user_flags & MDBX_CREATE) == 0))
|
||||
return MDBX_NOTFOUND;
|
||||
if (unlikely(txn->dbs[MAIN_DBI].leaf_pages))
|
||||
/* В MainDB есть записи, либо она уже использовалась. */
|
||||
return MDBX_INCOMPATIBLE;
|
||||
|
||||
/* Пересоздаём MainDB когда там пусто. */
|
||||
tASSERT(txn,
|
||||
txn->dbs[MAIN_DBI].height == 0 && txn->dbs[MAIN_DBI].items == 0 && txn->dbs[MAIN_DBI].root == P_INVALID);
|
||||
if (unlikely(txn->cursors[MAIN_DBI]))
|
||||
return MDBX_DANGLING_DBI;
|
||||
env->dbs_flags[MAIN_DBI] = DB_POISON;
|
||||
atomic_store32(&env->dbi_seqs[MAIN_DBI], dbi_seq_next(env, MAIN_DBI), mo_AcquireRelease);
|
||||
|
||||
const uint32_t seq = dbi_seq_next(env, MAIN_DBI);
|
||||
const uint16_t main_flags = txn->dbs[MAIN_DBI].flags & (MDBX_REVERSEKEY | MDBX_INTEGERKEY);
|
||||
env->kvs[MAIN_DBI].clc.k.cmp = builtin_keycmp(main_flags);
|
||||
env->kvs[MAIN_DBI].clc.v.cmp = builtin_datacmp(main_flags);
|
||||
txn->dbs[MAIN_DBI].flags = main_flags;
|
||||
txn->dbs[MAIN_DBI].dupfix_size = 0;
|
||||
int err = tbl_setup(env, &env->kvs[MAIN_DBI], &txn->dbs[MAIN_DBI]);
|
||||
if (unlikely(err != MDBX_SUCCESS)) {
|
||||
txn->dbi_state[MAIN_DBI] = DBI_LINDO;
|
||||
txn->flags |= MDBX_TXN_ERROR;
|
||||
env->flags |= ENV_FATAL_ERROR;
|
||||
return err;
|
||||
}
|
||||
env->dbs_flags[MAIN_DBI] = main_flags | DB_VALID;
|
||||
txn->dbi_seqs[MAIN_DBI] = atomic_store32(&env->dbi_seqs[MAIN_DBI], seq, mo_AcquireRelease);
|
||||
txn->dbi_state[MAIN_DBI] |= DBI_DIRTY;
|
||||
txn->flags |= MDBX_TXN_DIRTY;
|
||||
}
|
||||
|
||||
tASSERT(txn, env->kvs[MAIN_DBI].clc.k.cmp);
|
||||
|
||||
/* Is the DB already open? */
|
||||
size_t slot = env->n_dbi;
|
||||
for (size_t scan = CORE_DBS; scan < env->n_dbi; ++scan) {
|
||||
if ((env->dbs_flags[scan] & DB_VALID) == 0) {
|
||||
/* Remember this free slot */
|
||||
slot = (slot < scan) ? slot : scan;
|
||||
continue;
|
||||
}
|
||||
if (!env->kvs[MAIN_DBI].clc.k.cmp(&name, &env->kvs[scan].name)) {
|
||||
slot = scan;
|
||||
int err = dbi_check(txn, slot);
|
||||
if (err == MDBX_BAD_DBI && txn->dbi_state[slot] == (DBI_OLDEN | DBI_LINDO)) {
|
||||
/* хендл использовался, стал невалидным,
|
||||
* но теперь явно пере-открывается в этой транзакци */
|
||||
eASSERT(env, !txn->cursors[slot]);
|
||||
txn->dbi_state[slot] = DBI_LINDO;
|
||||
err = dbi_check(txn, slot);
|
||||
}
|
||||
if (err == MDBX_SUCCESS) {
|
||||
err = dbi_bind(txn, slot, user_flags, keycmp, datacmp);
|
||||
if (likely(err == MDBX_SUCCESS)) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fail, if no free slot and max hit */
|
||||
if (unlikely(slot >= env->max_dbi))
|
||||
return MDBX_DBS_FULL;
|
||||
|
||||
if (env->n_dbi == slot)
|
||||
eASSERT(env, !env->dbs_flags[slot] && !env->kvs[slot].name.iov_len && !env->kvs[slot].name.iov_base);
|
||||
|
||||
env->dbs_flags[slot] = DB_POISON;
|
||||
atomic_store32(&env->dbi_seqs[slot], dbi_seq_next(env, slot), mo_AcquireRelease);
|
||||
memset(&env->kvs[slot], 0, sizeof(env->kvs[slot]));
|
||||
if (env->n_dbi == slot)
|
||||
env->n_dbi = (unsigned)slot + 1;
|
||||
eASSERT(env, slot < env->n_dbi);
|
||||
|
||||
int err = dbi_check(txn, slot);
|
||||
eASSERT(env, err == MDBX_BAD_DBI);
|
||||
if (err != MDBX_BAD_DBI)
|
||||
return MDBX_PROBLEM;
|
||||
|
||||
/* Find the DB info */
|
||||
MDBX_val body;
|
||||
cursor_couple_t cx;
|
||||
int rc = cursor_init(&cx.outer, txn, MAIN_DBI);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
rc = cursor_seek(&cx.outer, &name, &body, MDBX_SET).err;
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
if (rc != MDBX_NOTFOUND || !(user_flags & MDBX_CREATE))
|
||||
return rc;
|
||||
} else {
|
||||
/* make sure this is actually a table */
|
||||
node_t *node = page_node(cx.outer.pg[cx.outer.top], cx.outer.ki[cx.outer.top]);
|
||||
if (unlikely((node_flags(node) & (N_DUP | N_TREE)) != N_TREE))
|
||||
return MDBX_INCOMPATIBLE;
|
||||
if (!MDBX_DISABLE_VALIDATION && unlikely(body.iov_len != sizeof(tree_t))) {
|
||||
ERROR("%s/%d: %s %zu", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid table node size", body.iov_len);
|
||||
return MDBX_CORRUPTED;
|
||||
}
|
||||
memcpy(&txn->dbs[slot], body.iov_base, sizeof(tree_t));
|
||||
}
|
||||
|
||||
/* Done here so we cannot fail after creating a new DB */
|
||||
defer_free_item_t *const clone = osal_malloc(dbi_namelen(name));
|
||||
if (unlikely(!clone))
|
||||
return MDBX_ENOMEM;
|
||||
memcpy(clone, name.iov_base, name.iov_len);
|
||||
name.iov_base = clone;
|
||||
|
||||
uint8_t dbi_state = DBI_LINDO | DBI_VALID | DBI_FRESH;
|
||||
if (unlikely(rc)) {
|
||||
/* MDBX_NOTFOUND and MDBX_CREATE: Create new DB */
|
||||
tASSERT(txn, rc == MDBX_NOTFOUND);
|
||||
body.iov_base = memset(&txn->dbs[slot], 0, body.iov_len = sizeof(tree_t));
|
||||
txn->dbs[slot].root = P_INVALID;
|
||||
txn->dbs[slot].mod_txnid = txn->txnid;
|
||||
txn->dbs[slot].flags = user_flags & DB_PERSISTENT_FLAGS;
|
||||
cx.outer.next = txn->cursors[MAIN_DBI];
|
||||
txn->cursors[MAIN_DBI] = &cx.outer;
|
||||
rc = cursor_put_checklen(&cx.outer, &name, &body, N_TREE | MDBX_NOOVERWRITE);
|
||||
txn->cursors[MAIN_DBI] = cx.outer.next;
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
|
||||
dbi_state |= DBI_DIRTY | DBI_CREAT;
|
||||
txn->flags |= MDBX_TXN_DIRTY;
|
||||
tASSERT(txn, (txn->dbi_state[MAIN_DBI] & DBI_DIRTY) != 0);
|
||||
}
|
||||
|
||||
/* Got info, register DBI in this txn */
|
||||
const uint32_t seq = dbi_seq_next(env, slot);
|
||||
eASSERT(env, env->dbs_flags[slot] == DB_POISON && !txn->cursors[slot] &&
|
||||
(txn->dbi_state[slot] & (DBI_LINDO | DBI_VALID)) == DBI_LINDO);
|
||||
txn->dbi_state[slot] = dbi_state;
|
||||
memcpy(&txn->dbs[slot], body.iov_base, sizeof(txn->dbs[slot]));
|
||||
env->dbs_flags[slot] = txn->dbs[slot].flags;
|
||||
rc = dbi_bind(txn, slot, user_flags, keycmp, datacmp);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
|
||||
env->kvs[slot].name = name;
|
||||
env->dbs_flags[slot] = txn->dbs[slot].flags | DB_VALID;
|
||||
txn->dbi_seqs[slot] = atomic_store32(&env->dbi_seqs[slot], seq, mo_AcquireRelease);
|
||||
|
||||
done:
|
||||
*dbi = (MDBX_dbi)slot;
|
||||
tASSERT(txn, slot < txn->n_dbi && (env->dbs_flags[slot] & DB_VALID) != 0);
|
||||
eASSERT(env, dbi_check(txn, slot) == MDBX_SUCCESS);
|
||||
return MDBX_SUCCESS;
|
||||
|
||||
bailout:
|
||||
eASSERT(env, !txn->cursors[slot] && !env->kvs[slot].name.iov_len && !env->kvs[slot].name.iov_base);
|
||||
txn->dbi_state[slot] &= DBI_LINDO | DBI_OLDEN;
|
||||
env->dbs_flags[slot] = 0;
|
||||
osal_free(clone);
|
||||
if (slot + 1 == env->n_dbi)
|
||||
txn->n_dbi = env->n_dbi = (unsigned)slot;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int dbi_open(MDBX_txn *txn, const MDBX_val *const name, unsigned user_flags, MDBX_dbi *dbi, MDBX_cmp_func *keycmp,
|
||||
MDBX_cmp_func *datacmp) {
|
||||
if (unlikely(!dbi))
|
||||
return MDBX_EINVAL;
|
||||
*dbi = 0;
|
||||
|
||||
if (user_flags != MDBX_ACCEDE && unlikely(!check_table_flags(user_flags & ~MDBX_CREATE)))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
if ((user_flags & MDBX_CREATE) && unlikely(txn->flags & MDBX_TXN_RDONLY))
|
||||
return MDBX_EACCESS;
|
||||
|
||||
/* main table? */
|
||||
if (unlikely(name == MDBX_CHK_MAIN || name->iov_base == MDBX_CHK_MAIN)) {
|
||||
rc = dbi_bind(txn, MAIN_DBI, user_flags, keycmp, datacmp);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
*dbi = MAIN_DBI;
|
||||
return rc;
|
||||
}
|
||||
if (unlikely(name == MDBX_CHK_GC || name->iov_base == MDBX_CHK_GC)) {
|
||||
rc = dbi_bind(txn, FREE_DBI, user_flags, keycmp, datacmp);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
*dbi = FREE_DBI;
|
||||
return rc;
|
||||
}
|
||||
if (unlikely(name == MDBX_CHK_META || name->iov_base == MDBX_CHK_META))
|
||||
return MDBX_EINVAL;
|
||||
if (unlikely(name->iov_len > txn->env->leaf_nodemax - NODESIZE - sizeof(tree_t)))
|
||||
return MDBX_EINVAL;
|
||||
|
||||
#if MDBX_ENABLE_DBI_LOCKFREE
|
||||
/* Is the DB already open? */
|
||||
const MDBX_env *const env = txn->env;
|
||||
size_t free_slot = env->n_dbi;
|
||||
for (size_t i = CORE_DBS; i < env->n_dbi; ++i) {
|
||||
retry:
|
||||
if ((env->dbs_flags[i] & DB_VALID) == 0) {
|
||||
free_slot = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t snap_seq = atomic_load32(&env->dbi_seqs[i], mo_AcquireRelease);
|
||||
const uint16_t snap_flags = env->dbs_flags[i];
|
||||
const MDBX_val snap_name = env->kvs[i].name;
|
||||
if (user_flags != MDBX_ACCEDE &&
|
||||
(((user_flags ^ snap_flags) & DB_PERSISTENT_FLAGS) || (keycmp && keycmp != env->kvs[i].clc.k.cmp) ||
|
||||
(datacmp && datacmp != env->kvs[i].clc.v.cmp)))
|
||||
continue;
|
||||
const uint32_t main_seq = atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease);
|
||||
MDBX_cmp_func *const snap_cmp = env->kvs[MAIN_DBI].clc.k.cmp;
|
||||
if (unlikely(!(snap_flags & DB_VALID) || !snap_name.iov_base || !snap_name.iov_len || !snap_cmp))
|
||||
continue;
|
||||
|
||||
const bool name_match = snap_cmp(&snap_name, name) == 0;
|
||||
osal_flush_incoherent_cpu_writeback();
|
||||
if (unlikely(snap_seq != atomic_load32(&env->dbi_seqs[i], mo_AcquireRelease) ||
|
||||
main_seq != atomic_load32(&env->dbi_seqs[MAIN_DBI], mo_AcquireRelease) ||
|
||||
snap_flags != env->dbs_flags[i] || snap_name.iov_base != env->kvs[i].name.iov_base ||
|
||||
snap_name.iov_len != env->kvs[i].name.iov_len))
|
||||
goto retry;
|
||||
if (name_match) {
|
||||
rc = dbi_check(txn, i);
|
||||
if (rc == MDBX_BAD_DBI && txn->dbi_state[i] == (DBI_OLDEN | DBI_LINDO)) {
|
||||
/* хендл использовался, стал невалидным,
|
||||
* но теперь явно пере-открывается в этой транзакци */
|
||||
eASSERT(env, !txn->cursors[i]);
|
||||
txn->dbi_state[i] = DBI_LINDO;
|
||||
rc = dbi_check(txn, i);
|
||||
}
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
rc = dbi_bind(txn, i, user_flags, keycmp, datacmp);
|
||||
if (likely(rc == MDBX_SUCCESS))
|
||||
*dbi = (MDBX_dbi)i;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fail, if no free slot and max hit */
|
||||
if (unlikely(free_slot >= env->max_dbi))
|
||||
return MDBX_DBS_FULL;
|
||||
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
|
||||
|
||||
rc = osal_fastmutex_acquire(&txn->env->dbi_lock);
|
||||
if (likely(rc == MDBX_SUCCESS)) {
|
||||
rc = dbi_open_locked(txn, user_flags, dbi, keycmp, datacmp, *name);
|
||||
ENSURE(txn->env, osal_fastmutex_release(&txn->env->dbi_lock) == MDBX_SUCCESS);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold struct dbi_rename_result dbi_rename_locked(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val new_name) {
|
||||
struct dbi_rename_result pair;
|
||||
pair.defer = nullptr;
|
||||
pair.err = dbi_check(txn, dbi);
|
||||
if (unlikely(pair.err != MDBX_SUCCESS))
|
||||
return pair;
|
||||
|
||||
MDBX_env *const env = txn->env;
|
||||
MDBX_val old_name = env->kvs[dbi].name;
|
||||
if (env->kvs[MAIN_DBI].clc.k.cmp(&new_name, &old_name) == 0 && MDBX_DEBUG == 0)
|
||||
return pair;
|
||||
|
||||
cursor_couple_t cx;
|
||||
pair.err = cursor_init(&cx.outer, txn, MAIN_DBI);
|
||||
if (unlikely(pair.err != MDBX_SUCCESS))
|
||||
return pair;
|
||||
pair.err = cursor_seek(&cx.outer, &new_name, nullptr, MDBX_SET).err;
|
||||
if (unlikely(pair.err != MDBX_NOTFOUND)) {
|
||||
pair.err = (pair.err == MDBX_SUCCESS) ? MDBX_KEYEXIST : pair.err;
|
||||
return pair;
|
||||
}
|
||||
|
||||
pair.defer = osal_malloc(dbi_namelen(new_name));
|
||||
if (unlikely(!pair.defer)) {
|
||||
pair.err = MDBX_ENOMEM;
|
||||
return pair;
|
||||
}
|
||||
new_name.iov_base = memcpy(pair.defer, new_name.iov_base, new_name.iov_len);
|
||||
|
||||
cx.outer.next = txn->cursors[MAIN_DBI];
|
||||
txn->cursors[MAIN_DBI] = &cx.outer;
|
||||
|
||||
MDBX_val data = {&txn->dbs[dbi], sizeof(tree_t)};
|
||||
pair.err = cursor_put_checklen(&cx.outer, &new_name, &data, N_TREE | MDBX_NOOVERWRITE);
|
||||
if (likely(pair.err == MDBX_SUCCESS)) {
|
||||
pair.err = cursor_seek(&cx.outer, &old_name, nullptr, MDBX_SET).err;
|
||||
if (likely(pair.err == MDBX_SUCCESS))
|
||||
pair.err = cursor_del(&cx.outer, N_TREE);
|
||||
if (likely(pair.err == MDBX_SUCCESS)) {
|
||||
pair.defer = env->kvs[dbi].name.iov_base;
|
||||
env->kvs[dbi].name = new_name;
|
||||
} else
|
||||
txn->flags |= MDBX_TXN_ERROR;
|
||||
}
|
||||
|
||||
txn->cursors[MAIN_DBI] = cx.outer.next;
|
||||
return pair;
|
||||
}
|
||||
|
||||
static defer_free_item_t *dbi_close_locked(MDBX_env *env, MDBX_dbi dbi) {
|
||||
eASSERT(env, dbi >= CORE_DBS);
|
||||
if (unlikely(dbi >= env->n_dbi))
|
||||
return nullptr;
|
||||
|
||||
const uint32_t seq = dbi_seq_next(env, dbi);
|
||||
defer_free_item_t *defer_item = env->kvs[dbi].name.iov_base;
|
||||
if (likely(defer_item)) {
|
||||
env->dbs_flags[dbi] = 0;
|
||||
env->kvs[dbi].name.iov_len = 0;
|
||||
env->kvs[dbi].name.iov_base = nullptr;
|
||||
atomic_store32(&env->dbi_seqs[dbi], seq, mo_AcquireRelease);
|
||||
osal_flush_incoherent_cpu_writeback();
|
||||
defer_item->next = nullptr;
|
||||
|
||||
if (env->n_dbi == dbi + 1) {
|
||||
size_t i = env->n_dbi;
|
||||
do {
|
||||
--i;
|
||||
eASSERT(env, i >= CORE_DBS);
|
||||
eASSERT(env, !env->dbs_flags[i] && !env->kvs[i].name.iov_len && !env->kvs[i].name.iov_base);
|
||||
} while (i > CORE_DBS && !env->kvs[i - 1].name.iov_base);
|
||||
env->n_dbi = (unsigned)i;
|
||||
}
|
||||
}
|
||||
|
||||
return defer_item;
|
||||
}
|
||||
|
||||
__cold const tree_t *dbi_dig(const MDBX_txn *txn, const size_t dbi, tree_t *fallback) {
|
||||
const MDBX_txn *dig = txn;
|
||||
do {
|
||||
tASSERT(txn, txn->n_dbi == dig->n_dbi);
|
||||
const uint8_t state = dbi_state(dig, dbi);
|
||||
if (state & DBI_LINDO)
|
||||
switch (state & (DBI_VALID | DBI_STALE | DBI_OLDEN)) {
|
||||
case DBI_VALID:
|
||||
case DBI_OLDEN:
|
||||
return dig->dbs + dbi;
|
||||
case 0:
|
||||
return fallback;
|
||||
case DBI_VALID | DBI_STALE:
|
||||
case DBI_OLDEN | DBI_STALE:
|
||||
break;
|
||||
default:
|
||||
tASSERT(txn, !!"unexpected dig->dbi_state[dbi]");
|
||||
}
|
||||
dig = dig->parent;
|
||||
} while (dig);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int dbi_close_release(MDBX_env *env, MDBX_dbi dbi) { return dbi_defer_release(env, dbi_close_locked(env, dbi)); }
|
147
src/dbi.h
Normal file
147
src/dbi.h
Normal file
@ -0,0 +1,147 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED MDBX_INTERNAL size_t dbi_bitmap_ctz_fallback(const MDBX_txn *txn,
|
||||
intptr_t bmi);
|
||||
|
||||
static inline size_t dbi_bitmap_ctz(const MDBX_txn *txn, intptr_t bmi) {
|
||||
tASSERT(txn, bmi > 0);
|
||||
STATIC_ASSERT(sizeof(bmi) >= sizeof(txn->dbi_sparse[0]));
|
||||
#if __GNUC_PREREQ(4, 1) || __has_builtin(__builtin_ctzl)
|
||||
if (sizeof(txn->dbi_sparse[0]) <= sizeof(int))
|
||||
return __builtin_ctz((int)bmi);
|
||||
if (sizeof(txn->dbi_sparse[0]) == sizeof(long))
|
||||
return __builtin_ctzl((long)bmi);
|
||||
#if (defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ == 8) || __has_builtin(__builtin_ctzll)
|
||||
return __builtin_ctzll(bmi);
|
||||
#endif /* have(long long) && long long == uint64_t */
|
||||
#endif /* GNU C */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long index;
|
||||
if (sizeof(txn->dbi_sparse[0]) > 4) {
|
||||
#if defined(_M_AMD64) || defined(_M_ARM64) || defined(_M_X64)
|
||||
_BitScanForward64(&index, bmi);
|
||||
return index;
|
||||
#else
|
||||
if (bmi > UINT32_MAX) {
|
||||
_BitScanForward(&index, (uint32_t)((uint64_t)bmi >> 32));
|
||||
return index;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
_BitScanForward(&index, (uint32_t)bmi);
|
||||
return index;
|
||||
#endif /* MSVC */
|
||||
|
||||
return dbi_bitmap_ctz_fallback(txn, bmi);
|
||||
}
|
||||
|
||||
static inline bool dbi_foreach_step(const MDBX_txn *const txn, size_t *bitmap_item, size_t *dbi) {
|
||||
const size_t bitmap_chunk = CHAR_BIT * sizeof(txn->dbi_sparse[0]);
|
||||
if (*bitmap_item & 1) {
|
||||
*bitmap_item >>= 1;
|
||||
return txn->dbi_state[*dbi] != 0;
|
||||
}
|
||||
if (*bitmap_item) {
|
||||
size_t bitmap_skip = dbi_bitmap_ctz(txn, *bitmap_item);
|
||||
*bitmap_item >>= bitmap_skip;
|
||||
*dbi += bitmap_skip - 1;
|
||||
} else {
|
||||
*dbi = (*dbi - 1) | (bitmap_chunk - 1);
|
||||
*bitmap_item = txn->dbi_sparse[(1 + *dbi) / bitmap_chunk];
|
||||
if (*bitmap_item == 0)
|
||||
*dbi += bitmap_chunk;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* LY: Макрос целенаправленно сделан с одним циклом, чтобы сохранить возможность
|
||||
* использования оператора break */
|
||||
#define TXN_FOREACH_DBI_FROM(TXN, I, FROM) \
|
||||
for (size_t bitmap_item = TXN->dbi_sparse[0] >> FROM, I = FROM; I < TXN->n_dbi; ++I) \
|
||||
if (dbi_foreach_step(TXN, &bitmap_item, &I))
|
||||
|
||||
#else
|
||||
|
||||
#define TXN_FOREACH_DBI_FROM(TXN, I, FROM) \
|
||||
for (size_t I = FROM; I < TXN->n_dbi; ++I) \
|
||||
if (TXN->dbi_state[I])
|
||||
|
||||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||||
|
||||
#define TXN_FOREACH_DBI_ALL(TXN, I) TXN_FOREACH_DBI_FROM(TXN, I, 0)
|
||||
#define TXN_FOREACH_DBI_USER(TXN, I) TXN_FOREACH_DBI_FROM(TXN, I, CORE_DBS)
|
||||
|
||||
MDBX_INTERNAL int dbi_import(MDBX_txn *txn, const size_t dbi);
|
||||
|
||||
struct dbi_snap_result {
|
||||
uint32_t sequence;
|
||||
unsigned flags;
|
||||
};
|
||||
MDBX_INTERNAL struct dbi_snap_result dbi_snap(const MDBX_env *env, const size_t dbi);
|
||||
|
||||
MDBX_INTERNAL int dbi_update(MDBX_txn *txn, bool keep);
|
||||
|
||||
static inline uint8_t dbi_state(const MDBX_txn *txn, const size_t dbi) {
|
||||
STATIC_ASSERT((int)DBI_DIRTY == MDBX_DBI_DIRTY && (int)DBI_STALE == MDBX_DBI_STALE &&
|
||||
(int)DBI_FRESH == MDBX_DBI_FRESH && (int)DBI_CREAT == MDBX_DBI_CREAT);
|
||||
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
const size_t bitmap_chunk = CHAR_BIT * sizeof(txn->dbi_sparse[0]);
|
||||
const size_t bitmap_indx = dbi / bitmap_chunk;
|
||||
const size_t bitmap_mask = (size_t)1 << dbi % bitmap_chunk;
|
||||
return likely(dbi < txn->n_dbi && (txn->dbi_sparse[bitmap_indx] & bitmap_mask) != 0) ? txn->dbi_state[dbi] : 0;
|
||||
#else
|
||||
return likely(dbi < txn->n_dbi) ? txn->dbi_state[dbi] : 0;
|
||||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||||
}
|
||||
|
||||
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 unlikely(snap_seq != txn->dbi_seqs[dbi]);
|
||||
}
|
||||
|
||||
static inline int dbi_check(const MDBX_txn *txn, const size_t dbi) {
|
||||
const uint8_t state = dbi_state(txn, dbi);
|
||||
if (likely((state & DBI_LINDO) != 0 && !dbi_changed(txn, dbi)))
|
||||
return (state & DBI_VALID) ? MDBX_SUCCESS : MDBX_BAD_DBI;
|
||||
|
||||
/* Медленный путь: ленивая до-инициализацяи и импорт */
|
||||
return dbi_import((MDBX_txn *)txn, dbi);
|
||||
}
|
||||
|
||||
static inline uint32_t dbi_seq_next(const MDBX_env *const env, size_t dbi) {
|
||||
uint32_t v = atomic_load32(&env->dbi_seqs[dbi], mo_AcquireRelease) + 1;
|
||||
return v ? v : 1;
|
||||
}
|
||||
|
||||
MDBX_INTERNAL int dbi_open(MDBX_txn *txn, const MDBX_val *const name, unsigned user_flags, MDBX_dbi *dbi,
|
||||
MDBX_cmp_func *keycmp, MDBX_cmp_func *datacmp);
|
||||
|
||||
MDBX_INTERNAL int dbi_bind(MDBX_txn *txn, const size_t dbi, unsigned user_flags, MDBX_cmp_func *keycmp,
|
||||
MDBX_cmp_func *datacmp);
|
||||
|
||||
typedef struct defer_free_item {
|
||||
struct defer_free_item *next;
|
||||
uint64_t timestamp;
|
||||
} defer_free_item_t;
|
||||
|
||||
MDBX_INTERNAL int dbi_defer_release(MDBX_env *const env, defer_free_item_t *const chain);
|
||||
MDBX_INTERNAL int dbi_close_release(MDBX_env *env, MDBX_dbi dbi);
|
||||
MDBX_INTERNAL const tree_t *dbi_dig(const MDBX_txn *txn, const size_t dbi, tree_t *fallback);
|
||||
|
||||
struct dbi_rename_result {
|
||||
defer_free_item_t *defer;
|
||||
int err;
|
||||
};
|
||||
|
||||
MDBX_INTERNAL struct dbi_rename_result dbi_rename_locked(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val new_name);
|
36
src/debug_begin.h
Normal file
36
src/debug_begin.h
Normal file
@ -0,0 +1,36 @@
|
||||
#if defined(__GNUC__) && !defined(__LCC__)
|
||||
|
||||
#pragma push_macro("TRACE")
|
||||
#pragma push_macro("DEBUG")
|
||||
#pragma push_macro("VERBOSE")
|
||||
#pragma push_macro("NOTICE")
|
||||
#pragma push_macro("WARNING")
|
||||
#pragma push_macro("ERROR")
|
||||
#pragma push_macro("eASSERT")
|
||||
|
||||
#undef TRACE
|
||||
#define TRACE(fmt, ...) debug_log(MDBX_LOG_TRACE, __func__, __LINE__, fmt "\n", __VA_ARGS__)
|
||||
|
||||
#undef DEBUG
|
||||
#define DEBUG(fmt, ...) debug_log(MDBX_LOG_DEBUG, __func__, __LINE__, fmt "\n", __VA_ARGS__)
|
||||
|
||||
#undef VERBOSE
|
||||
#define VERBOSE(fmt, ...) debug_log(MDBX_LOG_VERBOSE, __func__, __LINE__, fmt "\n", __VA_ARGS__)
|
||||
|
||||
#undef NOTICE
|
||||
#define NOTICE(fmt, ...) debug_log(MDBX_LOG_NOTICE, __func__, __LINE__, fmt "\n", __VA_ARGS__)
|
||||
|
||||
#undef WARNING
|
||||
#define WARNING(fmt, ...) debug_log(MDBX_LOG_WARN, __func__, __LINE__, fmt "\n", __VA_ARGS__)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(fmt, ...) debug_log(MDBX_LOG_ERROR, __func__, __LINE__, fmt "\n", __VA_ARGS__)
|
||||
|
||||
#undef eASSERT
|
||||
#define eASSERT(env, expr) ENSURE(env, expr)
|
||||
|
||||
#if !defined(__clang__)
|
||||
#pragma GCC optimize("-Og")
|
||||
#endif
|
||||
|
||||
#endif /* GCC only */
|
15
src/debug_end.h
Normal file
15
src/debug_end.h
Normal file
@ -0,0 +1,15 @@
|
||||
#if defined(__GNUC__) && !defined(__LCC__)
|
||||
|
||||
#pragma pop_macro("TRACE")
|
||||
#pragma pop_macro("DEBUG")
|
||||
#pragma pop_macro("VERBOSE")
|
||||
#pragma pop_macro("NOTICE")
|
||||
#pragma pop_macro("WARNING")
|
||||
#pragma pop_macro("ERROR")
|
||||
#pragma pop_macro("eASSERT")
|
||||
|
||||
#if !defined(__clang__)
|
||||
#pragma GCC reset_options
|
||||
#endif
|
||||
|
||||
#endif /* GCC only */
|
428
src/defs.h
428
src/defs.h
@ -1,428 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2018 Leonid Yuriev <leo@yuriev.ru>
|
||||
* and other libmdbx authors: please see AUTHORS file.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
/* *INDENT-OFF* */
|
||||
/* clang-format off */
|
||||
|
||||
#ifndef __GNUC_PREREQ
|
||||
# if defined(__GNUC__) && defined(__GNUC_MINOR__)
|
||||
# define __GNUC_PREREQ(maj, min) \
|
||||
((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
|
||||
# else
|
||||
# define __GNUC_PREREQ(maj, min) (0)
|
||||
# endif
|
||||
#endif /* __GNUC_PREREQ */
|
||||
|
||||
#ifndef __CLANG_PREREQ
|
||||
# ifdef __clang__
|
||||
# define __CLANG_PREREQ(maj,min) \
|
||||
((__clang_major__ << 16) + __clang_minor__ >= ((maj) << 16) + (min))
|
||||
# else
|
||||
# define __CLANG_PREREQ(maj,min) (0)
|
||||
# endif
|
||||
#endif /* __CLANG_PREREQ */
|
||||
|
||||
#ifndef __GLIBC_PREREQ
|
||||
# if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
||||
# define __GLIBC_PREREQ(maj, min) \
|
||||
((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min))
|
||||
# else
|
||||
# define __GLIBC_PREREQ(maj, min) (0)
|
||||
# endif
|
||||
#endif /* __GLIBC_PREREQ */
|
||||
|
||||
#ifndef __has_attribute
|
||||
# define __has_attribute(x) (0)
|
||||
#endif
|
||||
|
||||
#ifndef __has_feature
|
||||
# define __has_feature(x) (0)
|
||||
#endif
|
||||
|
||||
#ifndef __has_extension
|
||||
# define __has_extension(x) (0)
|
||||
#endif
|
||||
|
||||
#ifndef __has_builtin
|
||||
# define __has_builtin(x) (0)
|
||||
#endif
|
||||
|
||||
#ifndef __has_warning
|
||||
# define __has_warning(x) (0)
|
||||
#endif
|
||||
|
||||
#ifndef __has_include
|
||||
# define __has_include(x) (0)
|
||||
#endif
|
||||
|
||||
#if __has_feature(thread_sanitizer)
|
||||
# define __SANITIZE_THREAD__ 1
|
||||
#endif
|
||||
|
||||
#if __has_feature(address_sanitizer)
|
||||
# define __SANITIZE_ADDRESS__ 1
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef __extern_C
|
||||
# ifdef __cplusplus
|
||||
# define __extern_C extern "C"
|
||||
# else
|
||||
# define __extern_C
|
||||
# endif
|
||||
#endif /* __extern_C */
|
||||
|
||||
#ifndef __cplusplus
|
||||
# ifndef bool
|
||||
# define bool _Bool
|
||||
# endif
|
||||
# ifndef true
|
||||
# define true (1)
|
||||
# endif
|
||||
# ifndef false
|
||||
# define false (0)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(nullptr) && !defined(__cplusplus) || (__cplusplus < 201103L && !defined(_MSC_VER))
|
||||
# define nullptr NULL
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef __alwaysinline
|
||||
# if defined(__GNUC__) || __has_attribute(always_inline)
|
||||
# define __alwaysinline __inline __attribute__((always_inline))
|
||||
# elif defined(_MSC_VER)
|
||||
# define __alwaysinline __forceinline
|
||||
# else
|
||||
# define __alwaysinline
|
||||
# endif
|
||||
#endif /* __alwaysinline */
|
||||
|
||||
#ifndef __noinline
|
||||
# if defined(__GNUC__) || __has_attribute(noinline)
|
||||
# define __noinline __attribute__((noinline))
|
||||
# elif defined(_MSC_VER)
|
||||
# define __noinline __declspec(noinline)
|
||||
# elif defined(__SUNPRO_C) || defined(__sun) || defined(sun)
|
||||
# define __noinline inline
|
||||
# elif !defined(__INTEL_COMPILER)
|
||||
# define __noinline /* FIXME ? */
|
||||
# endif
|
||||
#endif /* __noinline */
|
||||
|
||||
#ifndef __must_check_result
|
||||
# if defined(__GNUC__) || __has_attribute(warn_unused_result)
|
||||
# define __must_check_result __attribute__((warn_unused_result))
|
||||
# else
|
||||
# define __must_check_result
|
||||
# endif
|
||||
#endif /* __must_check_result */
|
||||
|
||||
#ifndef __deprecated
|
||||
# if defined(__GNUC__) || __has_attribute(deprecated)
|
||||
# define __deprecated __attribute__((deprecated))
|
||||
# elif defined(_MSC_VER)
|
||||
# define __deprecated __declspec(deprecated)
|
||||
# else
|
||||
# define __deprecated
|
||||
# endif
|
||||
#endif /* __deprecated */
|
||||
|
||||
#if !defined(__noop) && !defined(_MSC_VER)
|
||||
# ifdef __cplusplus
|
||||
static inline void __noop_consume_args() {}
|
||||
template <typename First, typename... Rest>
|
||||
static inline void
|
||||
__noop_consume_args(const First &first, const Rest &... rest) {
|
||||
(void) first; __noop_consume_args(rest...);
|
||||
}
|
||||
# define __noop(...) __noop_consume_args(__VA_ARGS__)
|
||||
# elif defined(__GNUC__) && (!defined(__STRICT_ANSI__) || !__STRICT_ANSI__)
|
||||
static __inline void __noop_consume_args(void* anchor, ...) {
|
||||
(void) anchor;
|
||||
}
|
||||
# define __noop(...) __noop_consume_args(0, ##__VA_ARGS__)
|
||||
# else
|
||||
# define __noop(...) do {} while(0)
|
||||
# endif
|
||||
#endif /* __noop */
|
||||
|
||||
#ifndef __fallthrough
|
||||
# if __GNUC_PREREQ(7, 0) || __has_attribute(fallthrough)
|
||||
# define __fallthrough __attribute__((fallthrough))
|
||||
# else
|
||||
# define __fallthrough __noop()
|
||||
# endif
|
||||
#endif /* __fallthrough */
|
||||
|
||||
#ifndef __unreachable
|
||||
# if __GNUC_PREREQ(4,5)
|
||||
# define __unreachable() __builtin_unreachable()
|
||||
# elif defined(_MSC_VER)
|
||||
# define __unreachable() __assume(0)
|
||||
# else
|
||||
# define __unreachable() __noop()
|
||||
# endif
|
||||
#endif /* __unreachable */
|
||||
|
||||
#ifndef __prefetch
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
# define __prefetch(ptr) __builtin_prefetch(ptr)
|
||||
# else
|
||||
# define __prefetch(ptr) __noop(ptr)
|
||||
# endif
|
||||
#endif /* __prefetch */
|
||||
|
||||
#ifndef __noreturn
|
||||
# if defined(__GNUC__) || __has_attribute(noreturn)
|
||||
# define __noreturn __attribute__((noreturn))
|
||||
# elif defined(_MSC_VER)
|
||||
# define __noreturn __declspec(noreturn)
|
||||
# else
|
||||
# define __noreturn
|
||||
# endif
|
||||
#endif /* __noreturn */
|
||||
|
||||
#ifndef __nothrow
|
||||
# if defined(__GNUC__) || __has_attribute(nothrow)
|
||||
# define __nothrow __attribute__((nothrow))
|
||||
# elif defined(_MSC_VER) && defined(__cplusplus)
|
||||
# define __nothrow __declspec(nothrow)
|
||||
# else
|
||||
# define __nothrow
|
||||
# endif
|
||||
#endif /* __nothrow */
|
||||
|
||||
#ifndef __pure_function
|
||||
/* Many functions have no effects except the return value and their
|
||||
* return value depends only on the parameters and/or global variables.
|
||||
* Such a function can be subject to common subexpression elimination
|
||||
* and loop optimization just as an arithmetic operator would be.
|
||||
* These functions should be declared with the attribute pure. */
|
||||
# if defined(__GNUC__) || __has_attribute(pure)
|
||||
# define __pure_function __attribute__((pure))
|
||||
# else
|
||||
# define __pure_function
|
||||
# endif
|
||||
#endif /* __pure_function */
|
||||
|
||||
#ifndef __const_function
|
||||
/* Many functions do not examine any values except their arguments,
|
||||
* and have no effects except the return value. Basically this is just
|
||||
* slightly more strict class than the PURE attribute, since function
|
||||
* is not allowed to read global memory.
|
||||
*
|
||||
* Note that a function that has pointer arguments and examines the
|
||||
* data pointed to must not be declared const. Likewise, a function
|
||||
* that calls a non-const function usually must not be const.
|
||||
* It does not make sense for a const function to return void. */
|
||||
# if defined(__GNUC__) || __has_attribute(const)
|
||||
# define __const_function __attribute__((const))
|
||||
# else
|
||||
# define __const_function
|
||||
# endif
|
||||
#endif /* __const_function */
|
||||
|
||||
#ifndef __dll_hidden
|
||||
# if defined(__GNUC__) || __has_attribute(visibility)
|
||||
# define __hidden __attribute__((visibility("hidden")))
|
||||
# else
|
||||
# define __hidden
|
||||
# endif
|
||||
#endif /* __dll_hidden */
|
||||
|
||||
#ifndef __optimize
|
||||
# if defined(__OPTIMIZE__)
|
||||
# if defined(__clang__) && !__has_attribute(optimize)
|
||||
# define __optimize(ops)
|
||||
# elif defined(__GNUC__) || __has_attribute(optimize)
|
||||
# define __optimize(ops) __attribute__((optimize(ops)))
|
||||
# else
|
||||
# define __optimize(ops)
|
||||
# endif
|
||||
# else
|
||||
# define __optimize(ops)
|
||||
# endif
|
||||
#endif /* __optimize */
|
||||
|
||||
#ifndef __hot
|
||||
# if defined(__OPTIMIZE__)
|
||||
# if defined(__e2k__)
|
||||
# define __hot __attribute__((hot)) __optimize(3)
|
||||
# elif defined(__clang__) && !__has_attribute(hot)
|
||||
/* just put frequently used functions in separate section */
|
||||
# define __hot __attribute__((section("text.hot"))) __optimize("O3")
|
||||
# elif defined(__GNUC__) || __has_attribute(hot)
|
||||
# define __hot __attribute__((hot)) __optimize("O3")
|
||||
# else
|
||||
# define __hot __optimize("O3")
|
||||
# endif
|
||||
# else
|
||||
# define __hot
|
||||
# endif
|
||||
#endif /* __hot */
|
||||
|
||||
#ifndef __cold
|
||||
# if defined(__OPTIMIZE__)
|
||||
# if defined(__e2k__)
|
||||
# define __cold __attribute__((cold)) __optimize(1)
|
||||
# elif defined(__clang__) && !__has_attribute(cold)
|
||||
/* just put infrequently used functions in separate section */
|
||||
# define __cold __attribute__((section("text.unlikely"))) __optimize("Os")
|
||||
# elif defined(__GNUC__) || __has_attribute(cold)
|
||||
# define __cold __attribute__((cold)) __optimize("Os")
|
||||
# else
|
||||
# define __cold __optimize("Os")
|
||||
# endif
|
||||
# else
|
||||
# define __cold
|
||||
# endif
|
||||
#endif /* __cold */
|
||||
|
||||
#ifndef __flatten
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) || __has_attribute(flatten))
|
||||
# define __flatten __attribute__((flatten))
|
||||
# else
|
||||
# define __flatten
|
||||
# endif
|
||||
#endif /* __flatten */
|
||||
|
||||
#ifndef likely
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
# define likely(cond) __builtin_expect(!!(cond), 1)
|
||||
# else
|
||||
# define likely(x) (x)
|
||||
# endif
|
||||
#endif /* likely */
|
||||
|
||||
#ifndef unlikely
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
# define unlikely(cond) __builtin_expect(!!(cond), 0)
|
||||
# else
|
||||
# define unlikely(x) (x)
|
||||
# endif
|
||||
#endif /* unlikely */
|
||||
|
||||
/* Wrapper around __func__, which is a C99 feature */
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||
# define mdbx_func_ __func__
|
||||
#elif (defined(__GNUC__) && __GNUC__ >= 2) || defined(__clang__) || defined(_MSC_VER)
|
||||
# define mdbx_func_ __FUNCTION__
|
||||
#else
|
||||
# define mdbx_func_ "<mdbx_unknown>"
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || __has_attribute(format)
|
||||
#define __printf_args(format_index, first_arg) \
|
||||
__attribute__((format(printf, format_index, first_arg)))
|
||||
#else
|
||||
#define __printf_args(format_index, first_arg)
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#if defined(USE_VALGRIND)
|
||||
# include <valgrind/memcheck.h>
|
||||
# ifndef VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE
|
||||
/* LY: available since Valgrind 3.10 */
|
||||
# define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
|
||||
# define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
|
||||
# endif
|
||||
#elif !defined(RUNNING_ON_VALGRIND)
|
||||
# define VALGRIND_CREATE_MEMPOOL(h,r,z)
|
||||
# define VALGRIND_DESTROY_MEMPOOL(h)
|
||||
# define VALGRIND_MEMPOOL_TRIM(h,a,s)
|
||||
# define VALGRIND_MEMPOOL_ALLOC(h,a,s)
|
||||
# define VALGRIND_MEMPOOL_FREE(h,a)
|
||||
# define VALGRIND_MEMPOOL_CHANGE(h,a,b,s)
|
||||
# define VALGRIND_MAKE_MEM_NOACCESS(a,s)
|
||||
# define VALGRIND_MAKE_MEM_DEFINED(a,s)
|
||||
# define VALGRIND_MAKE_MEM_UNDEFINED(a,s)
|
||||
# define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
|
||||
# define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
|
||||
# define VALGRIND_CHECK_MEM_IS_ADDRESSABLE(a,s) (0)
|
||||
# define VALGRIND_CHECK_MEM_IS_DEFINED(a,s) (0)
|
||||
# define RUNNING_ON_VALGRIND (0)
|
||||
#endif /* USE_VALGRIND */
|
||||
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
# include <sanitizer/asan_interface.h>
|
||||
#elif !defined(ASAN_POISON_MEMORY_REGION)
|
||||
# define ASAN_POISON_MEMORY_REGION(addr, size) \
|
||||
((void)(addr), (void)(size))
|
||||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
|
||||
((void)(addr), (void)(size))
|
||||
#endif /* __SANITIZE_ADDRESS__ */
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef ARRAY_LENGTH
|
||||
# ifdef __cplusplus
|
||||
template <typename T, size_t N>
|
||||
char (&__ArraySizeHelper(T (&array)[N]))[N];
|
||||
# define ARRAY_LENGTH(array) (sizeof(::__ArraySizeHelper(array)))
|
||||
# else
|
||||
# define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))
|
||||
# endif
|
||||
#endif /* ARRAY_LENGTH */
|
||||
|
||||
#ifndef ARRAY_END
|
||||
# define ARRAY_END(array) (&array[ARRAY_LENGTH(array)])
|
||||
#endif /* ARRAY_END */
|
||||
|
||||
#ifndef STRINGIFY
|
||||
# define STRINGIFY_HELPER(x) #x
|
||||
# define STRINGIFY(x) STRINGIFY_HELPER(x)
|
||||
#endif /* STRINGIFY */
|
||||
|
||||
#ifndef offsetof
|
||||
# define offsetof(type, member) __builtin_offsetof(type, member)
|
||||
#endif /* offsetof */
|
||||
|
||||
#ifndef container_of
|
||||
# define container_of(ptr, type, member) \
|
||||
((type *)((char *)(ptr) - offsetof(type, member)))
|
||||
#endif /* container_of */
|
||||
|
||||
#define MDBX_TETRAD(a, b, c, d) \
|
||||
((uint32_t)(a) << 24 | (uint32_t)(b) << 16 | (uint32_t)(c) << 8 | (d))
|
||||
|
||||
#define MDBX_STRING_TETRAD(str) MDBX_TETRAD(str[0], str[1], str[2], str[3])
|
||||
|
||||
#define FIXME "FIXME: " __FILE__ ", " STRINGIFY(__LINE__)
|
||||
|
||||
#ifndef STATIC_ASSERT_MSG
|
||||
# if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) \
|
||||
|| __has_feature(c_static_assert)
|
||||
# define STATIC_ASSERT_MSG(expr, msg) _Static_assert(expr, msg)
|
||||
# elif defined(static_assert)
|
||||
# define STATIC_ASSERT_MSG(expr, msg) static_assert(expr, msg)
|
||||
# elif defined(_MSC_VER)
|
||||
# include <crtdbg.h>
|
||||
# define STATIC_ASSERT_MSG(expr, msg) _STATIC_ASSERT(expr)
|
||||
# else
|
||||
# define STATIC_ASSERT_MSG(expr, msg) switch (0) {case 0:case (expr):;}
|
||||
# endif
|
||||
#endif /* STATIC_ASSERT */
|
||||
|
||||
#ifndef STATIC_ASSERT
|
||||
# define STATIC_ASSERT(expr) STATIC_ASSERT_MSG(expr, #expr)
|
||||
#endif
|
||||
|
||||
/* *INDENT-ON* */
|
||||
/* clang-format on */
|
488
src/dpl.c
Normal file
488
src/dpl.c
Normal file
@ -0,0 +1,488 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static inline size_t dpl_size2bytes(ptrdiff_t size) {
|
||||
assert(size > CURSOR_STACK_SIZE && (size_t)size <= PAGELIST_LIMIT);
|
||||
#if MDBX_DPL_PREALLOC_FOR_RADIXSORT
|
||||
size += size;
|
||||
#endif /* MDBX_DPL_PREALLOC_FOR_RADIXSORT */
|
||||
STATIC_ASSERT(MDBX_ASSUME_MALLOC_OVERHEAD + sizeof(dpl_t) +
|
||||
(PAGELIST_LIMIT * (MDBX_DPL_PREALLOC_FOR_RADIXSORT + 1)) * sizeof(dp_t) +
|
||||
MDBX_PNL_GRANULATE * sizeof(void *) * 2 <
|
||||
SIZE_MAX / 4 * 3);
|
||||
size_t bytes = ceil_powerof2(MDBX_ASSUME_MALLOC_OVERHEAD + sizeof(dpl_t) + size * sizeof(dp_t),
|
||||
MDBX_PNL_GRANULATE * sizeof(void *) * 2) -
|
||||
MDBX_ASSUME_MALLOC_OVERHEAD;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static inline size_t dpl_bytes2size(const ptrdiff_t bytes) {
|
||||
size_t size = (bytes - sizeof(dpl_t)) / sizeof(dp_t);
|
||||
#if MDBX_DPL_PREALLOC_FOR_RADIXSORT
|
||||
size >>= 1;
|
||||
#endif /* MDBX_DPL_PREALLOC_FOR_RADIXSORT */
|
||||
assert(size > CURSOR_STACK_SIZE && size <= PAGELIST_LIMIT + MDBX_PNL_GRANULATE);
|
||||
return size;
|
||||
}
|
||||
|
||||
void dpl_free(MDBX_txn *txn) {
|
||||
if (likely(txn->wr.dirtylist)) {
|
||||
osal_free(txn->wr.dirtylist);
|
||||
txn->wr.dirtylist = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
dpl_t *dpl_reserve(MDBX_txn *txn, size_t size) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
size_t bytes = dpl_size2bytes((size < PAGELIST_LIMIT) ? size : PAGELIST_LIMIT);
|
||||
dpl_t *const dl = osal_realloc(txn->wr.dirtylist, bytes);
|
||||
if (likely(dl)) {
|
||||
#ifdef osal_malloc_usable_size
|
||||
bytes = osal_malloc_usable_size(dl);
|
||||
#endif /* osal_malloc_usable_size */
|
||||
dl->detent = dpl_bytes2size(bytes);
|
||||
tASSERT(txn, txn->wr.dirtylist == nullptr || dl->length <= dl->detent);
|
||||
txn->wr.dirtylist = dl;
|
||||
}
|
||||
return dl;
|
||||
}
|
||||
|
||||
int dpl_alloc(MDBX_txn *txn) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
const size_t wanna = (txn->env->options.dp_initial < txn->geo.upper) ? txn->env->options.dp_initial : txn->geo.upper;
|
||||
#if MDBX_FORCE_ASSERTIONS || MDBX_DEBUG
|
||||
if (txn->wr.dirtylist)
|
||||
/* обнуляем чтобы не сработал ассерт внутри dpl_reserve() */
|
||||
txn->wr.dirtylist->sorted = txn->wr.dirtylist->length = 0;
|
||||
#endif /* asertions enabled */
|
||||
if (unlikely(!txn->wr.dirtylist || txn->wr.dirtylist->detent < wanna || txn->wr.dirtylist->detent > wanna + wanna) &&
|
||||
unlikely(!dpl_reserve(txn, wanna)))
|
||||
return MDBX_ENOMEM;
|
||||
|
||||
/* LY: wr.dirtylist не может быть nullptr, так как либо уже выделен, либо будет выделен в dpl_reserve(). */
|
||||
/* coverity[var_deref_model] */
|
||||
dpl_clear(txn->wr.dirtylist);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
#define MDBX_DPL_EXTRACT_KEY(ptr) ((ptr)->pgno)
|
||||
RADIXSORT_IMPL(dp, dp_t, MDBX_DPL_EXTRACT_KEY, MDBX_DPL_PREALLOC_FOR_RADIXSORT, 1)
|
||||
|
||||
#define DP_SORT_CMP(first, last) ((first).pgno < (last).pgno)
|
||||
SORT_IMPL(dp_sort, false, dp_t, DP_SORT_CMP)
|
||||
|
||||
__hot __noinline dpl_t *dpl_sort_slowpath(const MDBX_txn *txn) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
const size_t unsorted = dl->length - dl->sorted;
|
||||
if (likely(unsorted < MDBX_RADIXSORT_THRESHOLD) || unlikely(!dp_radixsort(dl->items + 1, dl->length))) {
|
||||
if (dl->sorted > unsorted / 4 + 4 &&
|
||||
(MDBX_DPL_PREALLOC_FOR_RADIXSORT || dl->length + unsorted < dl->detent + dpl_gap_mergesort)) {
|
||||
dp_t *const sorted_begin = dl->items + 1;
|
||||
dp_t *const sorted_end = sorted_begin + dl->sorted;
|
||||
dp_t *const end =
|
||||
dl->items + (MDBX_DPL_PREALLOC_FOR_RADIXSORT ? dl->length + dl->length + 1 : dl->detent + dpl_reserve_gap);
|
||||
dp_t *const tmp = end - unsorted;
|
||||
assert(dl->items + dl->length + 1 < tmp);
|
||||
/* copy unsorted to the end of allocated space and sort it */
|
||||
memcpy(tmp, sorted_end, unsorted * sizeof(dp_t));
|
||||
dp_sort(tmp, tmp + unsorted);
|
||||
/* merge two parts from end to begin */
|
||||
dp_t *__restrict w = dl->items + dl->length;
|
||||
dp_t *__restrict l = dl->items + dl->sorted;
|
||||
dp_t *__restrict r = end - 1;
|
||||
do {
|
||||
const bool cmp = expect_with_probability(l->pgno > r->pgno, 0, .5);
|
||||
#if defined(__LCC__) || __CLANG_PREREQ(13, 0) || !MDBX_HAVE_CMOV
|
||||
*w = cmp ? *l-- : *r--;
|
||||
#else
|
||||
*w = cmp ? *l : *r;
|
||||
l -= cmp;
|
||||
r += (ptrdiff_t)cmp - 1;
|
||||
#endif
|
||||
} while (likely(--w > l));
|
||||
assert(r == tmp - 1);
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
if (ASSERT_ENABLED())
|
||||
for (size_t i = 0; i <= dl->length; ++i)
|
||||
assert(dl->items[i].pgno < dl->items[i + 1].pgno);
|
||||
} else {
|
||||
dp_sort(dl->items + 1, dl->items + dl->length + 1);
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
}
|
||||
} else {
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
}
|
||||
dl->sorted = dl->length;
|
||||
return dl;
|
||||
}
|
||||
|
||||
/* Returns the index of the first dirty-page whose pgno
|
||||
* member is greater than or equal to id. */
|
||||
#define DP_SEARCH_CMP(dp, id) ((dp).pgno < (id))
|
||||
SEARCH_IMPL(dp_bsearch, dp_t, pgno_t, DP_SEARCH_CMP)
|
||||
|
||||
__hot __noinline size_t dpl_search(const MDBX_txn *txn, pgno_t pgno) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
if (AUDIT_ENABLED()) {
|
||||
for (const dp_t *ptr = dl->items + dl->sorted; --ptr > dl->items;) {
|
||||
assert(ptr[0].pgno < ptr[1].pgno);
|
||||
assert(ptr[0].pgno >= NUM_METAS);
|
||||
}
|
||||
}
|
||||
|
||||
switch (dl->length - dl->sorted) {
|
||||
default:
|
||||
/* sort a whole */
|
||||
dpl_sort_slowpath(txn);
|
||||
break;
|
||||
case 0:
|
||||
/* whole sorted cases */
|
||||
break;
|
||||
|
||||
#define LINEAR_SEARCH_CASE(N) \
|
||||
case N: \
|
||||
if (dl->items[dl->length - N + 1].pgno == pgno) \
|
||||
return dl->length - N + 1; \
|
||||
__fallthrough
|
||||
|
||||
/* use linear scan until the threshold */
|
||||
LINEAR_SEARCH_CASE(7); /* fall through */
|
||||
LINEAR_SEARCH_CASE(6); /* fall through */
|
||||
LINEAR_SEARCH_CASE(5); /* fall through */
|
||||
LINEAR_SEARCH_CASE(4); /* fall through */
|
||||
LINEAR_SEARCH_CASE(3); /* fall through */
|
||||
LINEAR_SEARCH_CASE(2); /* fall through */
|
||||
case 1:
|
||||
if (dl->items[dl->length].pgno == pgno)
|
||||
return dl->length;
|
||||
/* continue bsearch on the sorted part */
|
||||
break;
|
||||
}
|
||||
return dp_bsearch(dl->items + 1, dl->sorted, pgno) - dl->items;
|
||||
}
|
||||
|
||||
const page_t *debug_dpl_find(const MDBX_txn *txn, const pgno_t pgno) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
const dpl_t *dl = txn->wr.dirtylist;
|
||||
if (dl) {
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
for (size_t i = dl->length; i > dl->sorted; --i)
|
||||
if (dl->items[i].pgno == pgno)
|
||||
return dl->items[i].ptr;
|
||||
|
||||
if (dl->sorted) {
|
||||
const size_t i = dp_bsearch(dl->items + 1, dl->sorted, pgno) - dl->items;
|
||||
if (dl->items[i].pgno == pgno)
|
||||
return dl->items[i].ptr;
|
||||
}
|
||||
} else {
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) != 0 && !MDBX_AVOID_MSYNC);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void dpl_remove_ex(const MDBX_txn *txn, size_t i, size_t npages) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
assert((intptr_t)i > 0 && i <= dl->length);
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
dl->pages_including_loose -= npages;
|
||||
dl->sorted -= dl->sorted >= i;
|
||||
dl->length -= 1;
|
||||
memmove(dl->items + i, dl->items + i + 1, (dl->length - i + 2) * sizeof(dl->items[0]));
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
}
|
||||
|
||||
int __must_check_result dpl_append(MDBX_txn *txn, pgno_t pgno, page_t *page, size_t npages) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
const dp_t dp = {page, pgno, (pgno_t)npages};
|
||||
if ((txn->flags & MDBX_WRITEMAP) == 0) {
|
||||
size_t *const ptr = ptr_disp(page, -(ptrdiff_t)sizeof(size_t));
|
||||
*ptr = txn->wr.dirtylru;
|
||||
}
|
||||
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
tASSERT(txn, dl->length <= PAGELIST_LIMIT + MDBX_PNL_GRANULATE);
|
||||
tASSERT(txn, dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
if (AUDIT_ENABLED()) {
|
||||
for (size_t i = dl->length; i > 0; --i) {
|
||||
assert(dl->items[i].pgno != dp.pgno);
|
||||
if (unlikely(dl->items[i].pgno == dp.pgno)) {
|
||||
ERROR("Page %u already exist in the DPL at %zu", dp.pgno, i);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(dl->length == dl->detent)) {
|
||||
if (unlikely(dl->detent >= PAGELIST_LIMIT)) {
|
||||
ERROR("DPL is full (PAGELIST_LIMIT %zu)", PAGELIST_LIMIT);
|
||||
return MDBX_TXN_FULL;
|
||||
}
|
||||
const size_t size = (dl->detent < MDBX_PNL_INITIAL * 42) ? dl->detent + dl->detent : dl->detent + dl->detent / 2;
|
||||
dl = dpl_reserve(txn, size);
|
||||
if (unlikely(!dl))
|
||||
return MDBX_ENOMEM;
|
||||
tASSERT(txn, dl->length < dl->detent);
|
||||
}
|
||||
|
||||
/* Сортировка нужна для быстрого поиска, используем несколько тактик:
|
||||
* 1) Сохраняем упорядоченность при естественной вставке в нужном порядке.
|
||||
* 2) Добавляем в не-сортированный хвост, который сортируем и сливаем
|
||||
* с отсортированной головой по необходимости, а пока хвост короткий
|
||||
* ищем в нём сканированием, избегая большой пересортировки.
|
||||
* 3) Если не-сортированный хвост короткий, а добавляемый элемент близок
|
||||
* к концу отсортированной головы, то выгоднее сразу вставить элемент
|
||||
* в нужное место.
|
||||
*
|
||||
* Алгоритмически:
|
||||
* - добавлять в не-сортированный хвост следует только если вставка сильно
|
||||
* дорогая, т.е. если целевая позиция элемента сильно далека от конца;
|
||||
* - для быстрой проверки достаточно сравнить добавляемый элемент с отстоящим
|
||||
* от конца на максимально-приемлемое расстояние;
|
||||
* - если список короче, либо элемент в этой позиции меньше вставляемого,
|
||||
* то следует перемещать элементы и вставлять в отсортированную голову;
|
||||
* - если не-сортированный хвост длиннее, либо элемент в этой позиции больше,
|
||||
* то следует добавлять в не-сортированный хвост. */
|
||||
|
||||
dl->pages_including_loose += npages;
|
||||
dp_t *i = dl->items + dl->length;
|
||||
|
||||
const ptrdiff_t pivot = (ptrdiff_t)dl->length - dpl_insertion_threshold;
|
||||
#if MDBX_HAVE_CMOV
|
||||
const pgno_t pivot_pgno =
|
||||
dl->items[(dl->length < dpl_insertion_threshold) ? 0 : dl->length - dpl_insertion_threshold].pgno;
|
||||
#endif /* MDBX_HAVE_CMOV */
|
||||
|
||||
/* copy the stub beyond the end */
|
||||
i[2] = i[1];
|
||||
dl->length += 1;
|
||||
|
||||
if (likely(pivot <= (ptrdiff_t)dl->sorted) &&
|
||||
#if MDBX_HAVE_CMOV
|
||||
pivot_pgno < dp.pgno) {
|
||||
#else
|
||||
(pivot <= 0 || dl->items[pivot].pgno < dp.pgno)) {
|
||||
#endif /* MDBX_HAVE_CMOV */
|
||||
dl->sorted += 1;
|
||||
|
||||
/* сдвигаем несортированный хвост */
|
||||
while (i >= dl->items + dl->sorted) {
|
||||
#if !defined(__GNUC__) /* пытаемся избежать вызова memmove() */
|
||||
i[1] = *i;
|
||||
#elif MDBX_WORDBITS == 64 && (defined(__SIZEOF_INT128__) || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128))
|
||||
STATIC_ASSERT(sizeof(dp) == sizeof(__uint128_t));
|
||||
((__uint128_t *)i)[1] = *(volatile __uint128_t *)i;
|
||||
#else
|
||||
i[1].ptr = i->ptr;
|
||||
i[1].pgno = i->pgno;
|
||||
i[1].npages = i->npages;
|
||||
#endif
|
||||
--i;
|
||||
}
|
||||
/* ищем нужную позицию сдвигая отсортированные элементы */
|
||||
while (i->pgno > pgno) {
|
||||
tASSERT(txn, i > dl->items);
|
||||
i[1] = *i;
|
||||
--i;
|
||||
}
|
||||
tASSERT(txn, i->pgno < dp.pgno);
|
||||
}
|
||||
|
||||
i[1] = dp;
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
assert(dl->sorted <= dl->length);
|
||||
return MDBX_SUCCESS;
|
||||
}
|
||||
|
||||
__cold bool dpl_check(MDBX_txn *txn) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
const dpl_t *const dl = txn->wr.dirtylist;
|
||||
if (!dl) {
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) != 0 && !MDBX_AVOID_MSYNC);
|
||||
return true;
|
||||
}
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
tASSERT(txn,
|
||||
txn->wr.dirtyroom + dl->length == (txn->parent ? txn->parent->wr.dirtyroom : txn->env->options.dp_limit));
|
||||
|
||||
if (!AUDIT_ENABLED())
|
||||
return true;
|
||||
|
||||
size_t loose = 0, pages = 0;
|
||||
for (size_t i = dl->length; i > 0; --i) {
|
||||
const page_t *const dp = dl->items[i].ptr;
|
||||
if (!dp)
|
||||
continue;
|
||||
|
||||
tASSERT(txn, dp->pgno == dl->items[i].pgno);
|
||||
if (unlikely(dp->pgno != dl->items[i].pgno))
|
||||
return false;
|
||||
|
||||
if ((txn->flags & MDBX_WRITEMAP) == 0) {
|
||||
const uint32_t age = dpl_age(txn, i);
|
||||
tASSERT(txn, age < UINT32_MAX / 3);
|
||||
if (unlikely(age > UINT32_MAX / 3))
|
||||
return false;
|
||||
}
|
||||
|
||||
tASSERT(txn, dp->flags == P_LOOSE || is_modifable(txn, dp));
|
||||
if (dp->flags == P_LOOSE) {
|
||||
loose += 1;
|
||||
} else if (unlikely(!is_modifable(txn, dp)))
|
||||
return false;
|
||||
|
||||
const unsigned num = dpl_npages(dl, i);
|
||||
pages += num;
|
||||
tASSERT(txn, txn->geo.first_unallocated >= dp->pgno + num);
|
||||
if (unlikely(txn->geo.first_unallocated < dp->pgno + num))
|
||||
return false;
|
||||
|
||||
if (i < dl->sorted) {
|
||||
tASSERT(txn, dl->items[i + 1].pgno >= dp->pgno + num);
|
||||
if (unlikely(dl->items[i + 1].pgno < dp->pgno + num))
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t rpa = pnl_search(txn->wr.repnl, dp->pgno, txn->geo.first_unallocated);
|
||||
tASSERT(txn, rpa > MDBX_PNL_GETSIZE(txn->wr.repnl) || txn->wr.repnl[rpa] != dp->pgno);
|
||||
if (rpa <= MDBX_PNL_GETSIZE(txn->wr.repnl) && unlikely(txn->wr.repnl[rpa] == dp->pgno))
|
||||
return false;
|
||||
if (num > 1) {
|
||||
const size_t rpb = pnl_search(txn->wr.repnl, dp->pgno + num - 1, txn->geo.first_unallocated);
|
||||
tASSERT(txn, rpa == rpb);
|
||||
if (unlikely(rpa != rpb))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tASSERT(txn, loose == txn->wr.loose_count);
|
||||
if (unlikely(loose != txn->wr.loose_count))
|
||||
return false;
|
||||
|
||||
tASSERT(txn, pages == dl->pages_including_loose);
|
||||
if (unlikely(pages != dl->pages_including_loose))
|
||||
return false;
|
||||
|
||||
for (size_t i = 1; i <= MDBX_PNL_GETSIZE(txn->wr.retired_pages); ++i) {
|
||||
const page_t *const dp = debug_dpl_find(txn, txn->wr.retired_pages[i]);
|
||||
tASSERT(txn, !dp);
|
||||
if (unlikely(dp))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
__noinline void dpl_lru_reduce(MDBX_txn *txn) {
|
||||
VERBOSE("lru-reduce %u -> %u", txn->wr.dirtylru, txn->wr.dirtylru >> 1);
|
||||
tASSERT(txn, (txn->flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP)) == 0);
|
||||
do {
|
||||
txn->wr.dirtylru >>= 1;
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
for (size_t i = 1; i <= dl->length; ++i) {
|
||||
size_t *const ptr = ptr_disp(dl->items[i].ptr, -(ptrdiff_t)sizeof(size_t));
|
||||
*ptr >>= 1;
|
||||
}
|
||||
txn = txn->parent;
|
||||
} while (txn);
|
||||
}
|
||||
|
||||
void dpl_sift(MDBX_txn *const txn, pnl_t pl, const bool spilled) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
if (MDBX_PNL_GETSIZE(pl) && txn->wr.dirtylist->length) {
|
||||
tASSERT(txn, pnl_check_allocated(pl, (size_t)txn->geo.first_unallocated << spilled));
|
||||
dpl_t *dl = dpl_sort(txn);
|
||||
|
||||
/* Scanning in ascend order */
|
||||
const intptr_t step = MDBX_PNL_ASCENDING ? 1 : -1;
|
||||
const intptr_t begin = MDBX_PNL_ASCENDING ? 1 : MDBX_PNL_GETSIZE(pl);
|
||||
const intptr_t end = MDBX_PNL_ASCENDING ? MDBX_PNL_GETSIZE(pl) + 1 : 0;
|
||||
tASSERT(txn, pl[begin] <= pl[end - step]);
|
||||
|
||||
size_t w, r = dpl_search(txn, pl[begin] >> spilled);
|
||||
tASSERT(txn, dl->sorted == dl->length);
|
||||
for (intptr_t i = begin; r <= dl->length;) { /* scan loop */
|
||||
assert(i != end);
|
||||
tASSERT(txn, !spilled || (pl[i] & 1) == 0);
|
||||
pgno_t pl_pgno = pl[i] >> spilled;
|
||||
pgno_t dp_pgno = dl->items[r].pgno;
|
||||
if (likely(dp_pgno != pl_pgno)) {
|
||||
const bool cmp = dp_pgno < pl_pgno;
|
||||
r += cmp;
|
||||
i += cmp ? 0 : step;
|
||||
if (likely(i != end))
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/* update loop */
|
||||
unsigned npages;
|
||||
w = r;
|
||||
remove_dl:
|
||||
npages = dpl_npages(dl, r);
|
||||
dl->pages_including_loose -= npages;
|
||||
if (!MDBX_AVOID_MSYNC || !(txn->flags & MDBX_WRITEMAP))
|
||||
page_shadow_release(txn->env, dl->items[r].ptr, npages);
|
||||
++r;
|
||||
next_i:
|
||||
i += step;
|
||||
if (unlikely(i == end)) {
|
||||
while (r <= dl->length)
|
||||
dl->items[w++] = dl->items[r++];
|
||||
} else {
|
||||
while (r <= dl->length) {
|
||||
assert(i != end);
|
||||
tASSERT(txn, !spilled || (pl[i] & 1) == 0);
|
||||
pl_pgno = pl[i] >> spilled;
|
||||
dp_pgno = dl->items[r].pgno;
|
||||
if (dp_pgno < pl_pgno)
|
||||
dl->items[w++] = dl->items[r++];
|
||||
else if (dp_pgno > pl_pgno)
|
||||
goto next_i;
|
||||
else
|
||||
goto remove_dl;
|
||||
}
|
||||
}
|
||||
dl->sorted = dpl_setlen(dl, w - 1);
|
||||
txn->wr.dirtyroom += r - w;
|
||||
tASSERT(txn, txn->wr.dirtyroom + txn->wr.dirtylist->length ==
|
||||
(txn->parent ? txn->parent->wr.dirtyroom : txn->env->options.dp_limit));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dpl_release_shadows(MDBX_txn *txn) {
|
||||
tASSERT(txn, (txn->flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP)) == 0);
|
||||
MDBX_env *env = txn->env;
|
||||
dpl_t *const dl = txn->wr.dirtylist;
|
||||
|
||||
for (size_t i = 1; i <= dl->length; i++)
|
||||
page_shadow_release(env, dl->items[i].ptr, dpl_npages(dl, i));
|
||||
|
||||
dpl_clear(dl);
|
||||
}
|
134
src/dpl.h
Normal file
134
src/dpl.h
Normal file
@ -0,0 +1,134 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
static inline size_t dpl_setlen(dpl_t *dl, size_t len) {
|
||||
static const page_t dpl_stub_pageE = {INVALID_TXNID,
|
||||
0,
|
||||
P_BAD,
|
||||
{0},
|
||||
/* pgno */ ~(pgno_t)0};
|
||||
assert(dpl_stub_pageE.flags == P_BAD && dpl_stub_pageE.pgno == P_INVALID);
|
||||
dl->length = len;
|
||||
dl->items[len + 1].ptr = (page_t *)&dpl_stub_pageE;
|
||||
dl->items[len + 1].pgno = P_INVALID;
|
||||
dl->items[len + 1].npages = 1;
|
||||
return len;
|
||||
}
|
||||
|
||||
static inline void dpl_clear(dpl_t *dl) {
|
||||
static const page_t dpl_stub_pageB = {INVALID_TXNID,
|
||||
0,
|
||||
P_BAD,
|
||||
{0},
|
||||
/* pgno */ 0};
|
||||
assert(dpl_stub_pageB.flags == P_BAD && dpl_stub_pageB.pgno == 0);
|
||||
dl->sorted = dpl_setlen(dl, 0);
|
||||
dl->pages_including_loose = 0;
|
||||
dl->items[0].ptr = (page_t *)&dpl_stub_pageB;
|
||||
dl->items[0].pgno = 0;
|
||||
dl->items[0].npages = 1;
|
||||
assert(dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
}
|
||||
|
||||
MDBX_INTERNAL int __must_check_result dpl_alloc(MDBX_txn *txn);
|
||||
|
||||
MDBX_INTERNAL void dpl_free(MDBX_txn *txn);
|
||||
|
||||
MDBX_INTERNAL dpl_t *dpl_reserve(MDBX_txn *txn, size_t size);
|
||||
|
||||
MDBX_INTERNAL __noinline dpl_t *dpl_sort_slowpath(const MDBX_txn *txn);
|
||||
|
||||
static inline dpl_t *dpl_sort(const MDBX_txn *txn) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
tASSERT(txn, dl->length <= PAGELIST_LIMIT);
|
||||
tASSERT(txn, dl->sorted <= dl->length);
|
||||
tASSERT(txn, dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
return likely(dl->sorted == dl->length) ? dl : dpl_sort_slowpath(txn);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL __noinline size_t dpl_search(const MDBX_txn *txn, pgno_t pgno);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL const page_t *debug_dpl_find(const MDBX_txn *txn, const pgno_t pgno);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline unsigned dpl_npages(const dpl_t *dl, size_t i) {
|
||||
assert(0 <= (intptr_t)i && i <= dl->length);
|
||||
unsigned n = dl->items[i].npages;
|
||||
assert(n == (is_largepage(dl->items[i].ptr) ? dl->items[i].ptr->pages : 1));
|
||||
return n;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t dpl_endpgno(const dpl_t *dl, size_t i) {
|
||||
return dpl_npages(dl, i) + dl->items[i].pgno;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline bool dpl_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npages) {
|
||||
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
tASSERT(txn, dl->sorted == dl->length);
|
||||
tASSERT(txn, dl->items[0].pgno == 0 && dl->items[dl->length + 1].pgno == P_INVALID);
|
||||
size_t const n = dpl_search(txn, pgno);
|
||||
tASSERT(txn, n >= 1 && n <= dl->length + 1);
|
||||
tASSERT(txn, pgno <= dl->items[n].pgno);
|
||||
tASSERT(txn, pgno > dl->items[n - 1].pgno);
|
||||
const bool rc =
|
||||
/* intersection with founded */ pgno + npages > dl->items[n].pgno ||
|
||||
/* intersection with prev */ dpl_endpgno(dl, n - 1) > pgno;
|
||||
if (ASSERT_ENABLED()) {
|
||||
bool check = false;
|
||||
for (size_t i = 1; i <= dl->length; ++i) {
|
||||
const page_t *const dp = dl->items[i].ptr;
|
||||
if (!(dp->pgno /* begin */ >= /* end */ pgno + npages || dpl_endpgno(dl, i) /* end */ <= /* begin */ pgno))
|
||||
check |= true;
|
||||
}
|
||||
tASSERT(txn, check == rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t dpl_exist(const MDBX_txn *txn, pgno_t pgno) {
|
||||
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
|
||||
dpl_t *dl = txn->wr.dirtylist;
|
||||
size_t i = dpl_search(txn, pgno);
|
||||
tASSERT(txn, (int)i > 0);
|
||||
return (dl->items[i].pgno == pgno) ? i : 0;
|
||||
}
|
||||
|
||||
MDBX_INTERNAL void dpl_remove_ex(const MDBX_txn *txn, size_t i, size_t npages);
|
||||
|
||||
static inline void dpl_remove(const MDBX_txn *txn, size_t i) {
|
||||
dpl_remove_ex(txn, i, dpl_npages(txn->wr.dirtylist, i));
|
||||
}
|
||||
|
||||
MDBX_INTERNAL int __must_check_result dpl_append(MDBX_txn *txn, pgno_t pgno, page_t *page, size_t npages);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL bool dpl_check(MDBX_txn *txn);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline uint32_t dpl_age(const MDBX_txn *txn, size_t i) {
|
||||
tASSERT(txn, (txn->flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP)) == 0);
|
||||
const dpl_t *dl = txn->wr.dirtylist;
|
||||
assert((intptr_t)i > 0 && i <= dl->length);
|
||||
size_t *const ptr = ptr_disp(dl->items[i].ptr, -(ptrdiff_t)sizeof(size_t));
|
||||
return txn->wr.dirtylru - (uint32_t)*ptr;
|
||||
}
|
||||
|
||||
MDBX_INTERNAL void dpl_lru_reduce(MDBX_txn *txn);
|
||||
|
||||
static inline uint32_t dpl_lru_turn(MDBX_txn *txn) {
|
||||
txn->wr.dirtylru += 1;
|
||||
if (unlikely(txn->wr.dirtylru > UINT32_MAX / 3) && (txn->flags & MDBX_WRITEMAP) == 0)
|
||||
dpl_lru_reduce(txn);
|
||||
return txn->wr.dirtylru;
|
||||
}
|
||||
|
||||
MDBX_INTERNAL void dpl_sift(MDBX_txn *const txn, pnl_t pl, const bool spilled);
|
||||
|
||||
MDBX_INTERNAL void dpl_release_shadows(MDBX_txn *txn);
|
602
src/env.c
Normal file
602
src/env.c
Normal file
@ -0,0 +1,602 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
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) {
|
||||
const int err = env->page_auxbuf
|
||||
? MDBX_SUCCESS
|
||||
: osal_memalign_alloc(globals.sys_pagesize, env->ps * (size_t)NUM_METAS, &env->page_auxbuf);
|
||||
if (likely(err == MDBX_SUCCESS)) {
|
||||
memset(env->page_auxbuf, -1, env->ps * (size_t)2);
|
||||
memset(ptr_disp(env->page_auxbuf, env->ps * (size_t)2), 0, env->ps);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
__cold unsigned env_setup_pagesize(MDBX_env *env, const size_t pagesize) {
|
||||
STATIC_ASSERT(PTRDIFF_MAX > MAX_MAPSIZE);
|
||||
STATIC_ASSERT(MDBX_MIN_PAGESIZE > sizeof(page_t) + sizeof(meta_t));
|
||||
ENSURE(env, is_powerof2(pagesize));
|
||||
ENSURE(env, pagesize >= MDBX_MIN_PAGESIZE);
|
||||
ENSURE(env, pagesize <= MDBX_MAX_PAGESIZE);
|
||||
ENSURE(env, !env->page_auxbuf && env->ps != pagesize);
|
||||
env->ps = (unsigned)pagesize;
|
||||
|
||||
STATIC_ASSERT(MAX_GC1OVPAGE(MDBX_MIN_PAGESIZE) > 4);
|
||||
STATIC_ASSERT(MAX_GC1OVPAGE(MDBX_MAX_PAGESIZE) < PAGELIST_LIMIT);
|
||||
const intptr_t maxgc_ov1page = (pagesize - PAGEHDRSZ) / sizeof(pgno_t) - 1;
|
||||
ENSURE(env, maxgc_ov1page > 42 && maxgc_ov1page < (intptr_t)PAGELIST_LIMIT / 4);
|
||||
env->maxgc_large1page = (unsigned)maxgc_ov1page;
|
||||
env->maxgc_per_branch = (unsigned)((pagesize - PAGEHDRSZ) / (sizeof(indx_t) + sizeof(node_t) + sizeof(txnid_t)));
|
||||
|
||||
STATIC_ASSERT(LEAF_NODE_MAX(MDBX_MIN_PAGESIZE) > sizeof(tree_t) + NODESIZE + 42);
|
||||
STATIC_ASSERT(LEAF_NODE_MAX(MDBX_MAX_PAGESIZE) < UINT16_MAX);
|
||||
STATIC_ASSERT(LEAF_NODE_MAX(MDBX_MIN_PAGESIZE) >= BRANCH_NODE_MAX(MDBX_MIN_PAGESIZE));
|
||||
STATIC_ASSERT(BRANCH_NODE_MAX(MDBX_MAX_PAGESIZE) > NODESIZE + 42);
|
||||
STATIC_ASSERT(BRANCH_NODE_MAX(MDBX_MAX_PAGESIZE) < UINT16_MAX);
|
||||
const intptr_t branch_nodemax = BRANCH_NODE_MAX(pagesize);
|
||||
const intptr_t leaf_nodemax = LEAF_NODE_MAX(pagesize);
|
||||
ENSURE(env, branch_nodemax > (intptr_t)(NODESIZE + 42) && branch_nodemax % 2 == 0 &&
|
||||
leaf_nodemax > (intptr_t)(sizeof(tree_t) + NODESIZE + 42) && leaf_nodemax >= branch_nodemax &&
|
||||
leaf_nodemax < (int)UINT16_MAX && leaf_nodemax % 2 == 0);
|
||||
env->leaf_nodemax = (uint16_t)leaf_nodemax;
|
||||
env->branch_nodemax = (uint16_t)branch_nodemax;
|
||||
env->ps2ln = (uint8_t)log2n_powerof2(pagesize);
|
||||
eASSERT(env, pgno2bytes(env, 1) == pagesize);
|
||||
eASSERT(env, bytes2pgno(env, pagesize + pagesize) == 2);
|
||||
recalculate_merge_thresholds(env);
|
||||
recalculate_subpage_thresholds(env);
|
||||
env_options_adjust_dp_limit(env);
|
||||
return env->ps;
|
||||
}
|
||||
|
||||
__cold int env_sync(MDBX_env *env, bool force, bool nonblock) {
|
||||
if (unlikely(env->flags & MDBX_RDONLY))
|
||||
return MDBX_EACCESS;
|
||||
|
||||
MDBX_txn *const txn_owned = env_owned_wrtxn(env);
|
||||
bool should_unlock = false;
|
||||
int rc = MDBX_RESULT_TRUE /* means "nothing to sync" */;
|
||||
|
||||
retry:;
|
||||
unsigned flags = env->flags & ~(MDBX_NOMETASYNC | txn_shrink_allowed);
|
||||
if (unlikely((flags & (ENV_FATAL_ERROR | ENV_ACTIVE)) != ENV_ACTIVE)) {
|
||||
rc = (flags & ENV_FATAL_ERROR) ? MDBX_PANIC : MDBX_EPERM;
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
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) {
|
||||
const uint32_t synched_meta_txnid_u32 = atomic_load32(&env->lck->meta_sync_txnid, mo_Relaxed);
|
||||
if (synched_meta_txnid_u32 == (uint32_t)head.txnid && head.is_steady)
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
if (should_unlock && (env->flags & MDBX_WRITEMAP) &&
|
||||
unlikely(head.ptr_c->geometry.first_unallocated > bytes2pgno(env, env->dxb_mmap.current))) {
|
||||
|
||||
if (unlikely(env->stuck_meta >= 0) && troika.recent != (uint8_t)env->stuck_meta) {
|
||||
NOTICE("skip %s since wagering meta-page (%u) is mispatch the recent "
|
||||
"meta-page (%u)",
|
||||
"sync datafile", env->stuck_meta, troika.recent);
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
} else {
|
||||
rc = dxb_resize(env, head.ptr_c->geometry.first_unallocated, head.ptr_c->geometry.now, head.ptr_c->geometry.upper,
|
||||
implicit_grow);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t autosync_threshold = atomic_load32(&env->lck->autosync_threshold, mo_Relaxed);
|
||||
const uint64_t autosync_period = atomic_load64(&env->lck->autosync_period, mo_Relaxed);
|
||||
uint64_t eoos_timestamp;
|
||||
if (force || (autosync_threshold && unsynced_pages >= autosync_threshold) ||
|
||||
(autosync_period && (eoos_timestamp = atomic_load64(&env->lck->eoos_timestamp, mo_Relaxed)) &&
|
||||
osal_monotime() - eoos_timestamp >= autosync_period))
|
||||
flags &= MDBX_WRITEMAP /* clear flags for full steady sync */;
|
||||
|
||||
if (!txn_owned) {
|
||||
if (!should_unlock) {
|
||||
#if MDBX_ENABLE_PGOP_STAT
|
||||
unsigned wops = 0;
|
||||
#endif /* MDBX_ENABLE_PGOP_STAT */
|
||||
|
||||
int err;
|
||||
/* pre-sync to avoid latency for writer */
|
||||
if (unsynced_pages > /* FIXME: define threshold */ 42 && (flags & MDBX_SAFE_NOSYNC) == 0) {
|
||||
eASSERT(env, ((flags ^ env->flags) & MDBX_WRITEMAP) == 0);
|
||||
if (flags & MDBX_WRITEMAP) {
|
||||
/* Acquire guard to avoid collision with remap */
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
imports.srwl_AcquireShared(&env->remap_guard);
|
||||
#else
|
||||
err = osal_fastmutex_acquire(&env->remap_guard);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
#endif
|
||||
const size_t usedbytes = pgno_align2os_bytes(env, head.ptr_c->geometry.first_unallocated);
|
||||
err = osal_msync(&env->dxb_mmap, 0, usedbytes, MDBX_SYNC_DATA);
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
imports.srwl_ReleaseShared(&env->remap_guard);
|
||||
#else
|
||||
int unlock_err = osal_fastmutex_release(&env->remap_guard);
|
||||
if (unlikely(unlock_err != MDBX_SUCCESS) && err == MDBX_SUCCESS)
|
||||
err = unlock_err;
|
||||
#endif
|
||||
} else
|
||||
err = osal_fsync(env->lazy_fd, MDBX_SYNC_DATA);
|
||||
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
|
||||
#if MDBX_ENABLE_PGOP_STAT
|
||||
wops = 1;
|
||||
#endif /* MDBX_ENABLE_PGOP_STAT */
|
||||
/* pre-sync done */
|
||||
rc = MDBX_SUCCESS /* means "some data was synced" */;
|
||||
}
|
||||
|
||||
err = lck_txn_lock(env, nonblock);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
|
||||
should_unlock = true;
|
||||
#if MDBX_ENABLE_PGOP_STAT
|
||||
env->lck->pgops.wops.weak += wops;
|
||||
#endif /* MDBX_ENABLE_PGOP_STAT */
|
||||
env->basal_txn->wr.troika = meta_tap(env);
|
||||
eASSERT(env, !env->txn && !env->basal_txn->nested);
|
||||
goto retry;
|
||||
}
|
||||
eASSERT(env, head.txnid == recent_committed_txnid(env));
|
||||
env->basal_txn->txnid = head.txnid;
|
||||
txn_gc_detent(env->basal_txn);
|
||||
flags |= txn_shrink_allowed;
|
||||
}
|
||||
|
||||
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 "
|
||||
"meta-page (%u)",
|
||||
"sync datafile", env->stuck_meta, troika.recent);
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
goto bailout;
|
||||
}
|
||||
if (!head.is_steady || ((flags & MDBX_SAFE_NOSYNC) == 0 && unsynced_pages)) {
|
||||
DEBUG("meta-head %" PRIaPGNO ", %s, sync_pending %" PRIu64, data_page(head.ptr_c)->pgno,
|
||||
durable_caption(head.ptr_c), unsynced_pages);
|
||||
meta_t meta = *head.ptr_c;
|
||||
rc = dxb_sync_locked(env, flags, &meta, &env->basal_txn->wr.troika);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
/* LY: sync meta-pages if MDBX_NOMETASYNC enabled
|
||||
* and someone was not synced above. */
|
||||
if (atomic_load32(&env->lck->meta_sync_txnid, mo_Relaxed) != (uint32_t)head.txnid)
|
||||
rc = meta_sync(env, head);
|
||||
|
||||
bailout:
|
||||
if (should_unlock)
|
||||
lck_txn_unlock(env);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold int env_open(MDBX_env *env, mdbx_mode_t mode) {
|
||||
/* Использование O_DSYNC или FILE_FLAG_WRITE_THROUGH:
|
||||
*
|
||||
* 0) Если размер страниц БД меньше системной страницы ОЗУ, то ядру ОС
|
||||
* придется чаще обновлять страницы в unified page cache.
|
||||
*
|
||||
* Однако, O_DSYNC не предполагает отключение unified page cache,
|
||||
* поэтому подобные затруднения будем считать проблемой ОС и/или
|
||||
* ожидаемым пенальти из-за использования мелких страниц БД.
|
||||
*
|
||||
* 1) В режиме MDBX_SYNC_DURABLE - O_DSYNC для записи как данных,
|
||||
* так и мета-страниц. Однако, на Linux отказ от O_DSYNC с последующим
|
||||
* fdatasync() может быть выгоднее при использовании HDD, так как
|
||||
* позволяет io-scheduler переупорядочить запись с учетом актуального
|
||||
* расположения файла БД на носителе.
|
||||
*
|
||||
* 2) В режиме MDBX_NOMETASYNC - O_DSYNC можно использовать для данных,
|
||||
* но в этом может не быть смысла, так как fdatasync() всё равно
|
||||
* требуется для гарантии фиксации мета после предыдущей транзакции.
|
||||
*
|
||||
* В итоге на нормальных системах (не Windows) есть два варианта:
|
||||
* - при возможности O_DIRECT и/или io_ring для данных, скорее всего,
|
||||
* есть смысл вызвать fdatasync() перед записью данных, а затем
|
||||
* использовать O_DSYNC;
|
||||
* - не использовать O_DSYNC и вызывать fdatasync() после записи данных.
|
||||
*
|
||||
* На Windows же следует минимизировать использование FlushFileBuffers()
|
||||
* из-за проблем с производительностью. Поэтому на Windows в режиме
|
||||
* MDBX_NOMETASYNC:
|
||||
* - мета обновляется через дескриптор без FILE_FLAG_WRITE_THROUGH;
|
||||
* - перед началом записи данных вызывается FlushFileBuffers(), если
|
||||
* meta_sync_txnid не совпадает с последней записанной мета;
|
||||
* - данные записываются через дескриптор с FILE_FLAG_WRITE_THROUGH.
|
||||
*
|
||||
* 3) В режиме MDBX_SAFE_NOSYNC - O_DSYNC нет смысла использовать, пока не
|
||||
* будет реализована возможность полностью асинхронной "догоняющей"
|
||||
* записи в выделенном процессе-сервере с io-ring очередями внутри.
|
||||
*
|
||||
* -----
|
||||
*
|
||||
* Использование O_DIRECT или FILE_FLAG_NO_BUFFERING:
|
||||
*
|
||||
* Назначение этих флагов в отключении файлового дескриптора от
|
||||
* unified page cache, т.е. от отображенных в память данных в случае
|
||||
* libmdbx.
|
||||
*
|
||||
* Поэтому, использование direct i/o в libmdbx без MDBX_WRITEMAP лишено
|
||||
* смысла и контр-продуктивно, ибо так мы провоцируем ядро ОС на
|
||||
* не-когерентность отображения в память с содержимым файла на носителе,
|
||||
* либо требуем дополнительных проверок и действий направленных на
|
||||
* фактическое отключение O_DIRECT для отображенных в память данных.
|
||||
*
|
||||
* В режиме MDBX_WRITEMAP когерентность отображенных данных обеспечивается
|
||||
* физически. Поэтому использование direct i/o может иметь смысл, если у
|
||||
* ядра ОС есть какие-то проблемы с msync(), в том числе с
|
||||
* производительностью:
|
||||
* - использование io_ring или gather-write может быть дешевле, чем
|
||||
* просмотр PTE ядром и запись измененных/грязных;
|
||||
* - но проблема в том, что записываемые из user mode страницы либо не
|
||||
* будут помечены чистыми (и соответственно будут записаны ядром
|
||||
* еще раз), либо ядру необходимо искать и чистить PTE при получении
|
||||
* запроса на запись.
|
||||
*
|
||||
* Поэтому O_DIRECT или FILE_FLAG_NO_BUFFERING используется:
|
||||
* - только в режиме MDBX_SYNC_DURABLE с MDBX_WRITEMAP;
|
||||
* - когда ps >= me_os_psize;
|
||||
* - опция сборки MDBX_AVOID_MSYNC != 0, которая по-умолчанию включена
|
||||
* только на Windows (см ниже).
|
||||
*
|
||||
* -----
|
||||
*
|
||||
* Использование FILE_FLAG_OVERLAPPED на Windows:
|
||||
*
|
||||
* У Windows очень плохо с I/O (за исключением прямых постраничных
|
||||
* scatter/gather, которые работают в обход проблемного unified page
|
||||
* cache и поэтому почти бесполезны в libmdbx).
|
||||
*
|
||||
* При этом всё еще хуже при использовании FlushFileBuffers(), что также
|
||||
* требуется после FlushViewOfFile() в режиме MDBX_WRITEMAP. Поэтому
|
||||
* на Windows вместо FlushViewOfFile() и FlushFileBuffers() следует
|
||||
* использовать запись через дескриптор с FILE_FLAG_WRITE_THROUGH.
|
||||
*
|
||||
* В свою очередь, запись с FILE_FLAG_WRITE_THROUGH дешевле/быстрее
|
||||
* при использовании FILE_FLAG_OVERLAPPED. В результате, на Windows
|
||||
* в durable-режимах запись данных всегда в overlapped-режиме,
|
||||
* при этом для записи мета требуется отдельный не-overlapped дескриптор.
|
||||
*/
|
||||
|
||||
env->pid = osal_getpid();
|
||||
int rc = osal_openfile((env->flags & MDBX_RDONLY) ? MDBX_OPEN_DXB_READ : MDBX_OPEN_DXB_LAZY, env, env->pathname.dxb,
|
||||
&env->lazy_fd, mode);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
|
||||
#if MDBX_LOCKING == MDBX_LOCKING_SYSV
|
||||
env->me_sysv_ipc.key = ftok(env->pathname.dxb, 42);
|
||||
if (unlikely(env->me_sysv_ipc.key == -1))
|
||||
return errno;
|
||||
#endif /* MDBX_LOCKING */
|
||||
|
||||
/* Set the position in files outside of the data to avoid corruption
|
||||
* due to erroneous use of file descriptors in the application code. */
|
||||
const uint64_t safe_parking_lot_offset = UINT64_C(0x7fffFFFF80000000);
|
||||
osal_fseek(env->lazy_fd, safe_parking_lot_offset);
|
||||
|
||||
env->fd4meta = env->lazy_fd;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
eASSERT(env, env->ioring.overlapped_fd == 0);
|
||||
bool ior_direct = false;
|
||||
if (!(env->flags & (MDBX_RDONLY | MDBX_SAFE_NOSYNC | MDBX_NOMETASYNC | MDBX_EXCLUSIVE))) {
|
||||
if (MDBX_AVOID_MSYNC && (env->flags & MDBX_WRITEMAP)) {
|
||||
/* Запрошен режим MDBX_SYNC_DURABLE | MDBX_WRITEMAP при активной опции
|
||||
* MDBX_AVOID_MSYNC.
|
||||
*
|
||||
* 1) В этой комбинации наиболее выгодно использовать WriteFileGather(),
|
||||
* но для этого необходимо открыть файл с флагом FILE_FLAG_NO_BUFFERING и
|
||||
* после обеспечивать выравнивание адресов и размера данных на границу
|
||||
* системной страницы, что в свою очередь возможно если размер страницы БД
|
||||
* не меньше размера системной страницы ОЗУ. Поэтому для открытия файла в
|
||||
* нужном режиме требуется знать размер страницы БД.
|
||||
*
|
||||
* 2) Кроме этого, в Windows запись в заблокированный регион файла
|
||||
* возможно только через тот-же дескриптор. Поэтому изначальный захват
|
||||
* блокировок посредством lck_seize(), захват/освобождение блокировок
|
||||
* во время пишущих транзакций и запись данных должны выполнятся через
|
||||
* один дескриптор.
|
||||
*
|
||||
* Таким образом, требуется прочитать волатильный заголовок БД, чтобы
|
||||
* узнать размер страницы, чтобы открыть дескриптор файла в режиме нужном
|
||||
* для записи данных, чтобы использовать именно этот дескриптор для
|
||||
* изначального захвата блокировок. */
|
||||
meta_t header;
|
||||
uint64_t dxb_filesize;
|
||||
int err = dxb_read_header(env, &header, MDBX_SUCCESS, true);
|
||||
if ((err == MDBX_SUCCESS && header.pagesize >= globals.sys_pagesize) ||
|
||||
(err == MDBX_ENODATA && mode && env->ps >= globals.sys_pagesize &&
|
||||
osal_filesize(env->lazy_fd, &dxb_filesize) == MDBX_SUCCESS && dxb_filesize == 0))
|
||||
/* Может быть коллизия, если два процесса пытаются одновременно создать
|
||||
* БД с разным размером страницы, который у одного меньше системной
|
||||
* страницы, а у другого НЕ меньше. Эта допустимая, но очень странная
|
||||
* ситуация. Поэтому считаем её ошибочной и не пытаемся разрешить. */
|
||||
ior_direct = true;
|
||||
}
|
||||
|
||||
rc = osal_openfile(ior_direct ? MDBX_OPEN_DXB_OVERLAPPED_DIRECT : MDBX_OPEN_DXB_OVERLAPPED, env, env->pathname.dxb,
|
||||
&env->ioring.overlapped_fd, 0);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
return rc;
|
||||
env->dxb_lock_event = CreateEventW(nullptr, true, false, nullptr);
|
||||
if (unlikely(!env->dxb_lock_event))
|
||||
return (int)GetLastError();
|
||||
osal_fseek(env->ioring.overlapped_fd, safe_parking_lot_offset);
|
||||
}
|
||||
#else
|
||||
if (mode == 0) {
|
||||
/* pickup mode for lck-file */
|
||||
struct stat st;
|
||||
if (unlikely(fstat(env->lazy_fd, &st)))
|
||||
return errno;
|
||||
mode = st.st_mode;
|
||||
}
|
||||
mode = (/* inherit read permissions for group and others */ mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) |
|
||||
/* always add read/write for owner */ S_IRUSR | S_IWUSR |
|
||||
((mode & S_IRGRP) ? /* +write if readable by group */ S_IWGRP : 0) |
|
||||
((mode & S_IROTH) ? /* +write if readable by others */ S_IWOTH : 0);
|
||||
#endif /* !Windows */
|
||||
const int lck_rc = lck_setup(env, mode);
|
||||
if (unlikely(MDBX_IS_ERROR(lck_rc)))
|
||||
return lck_rc;
|
||||
if (env->lck_mmap.fd != INVALID_HANDLE_VALUE)
|
||||
osal_fseek(env->lck_mmap.fd, safe_parking_lot_offset);
|
||||
|
||||
eASSERT(env, env->dsync_fd == INVALID_HANDLE_VALUE);
|
||||
if (!(env->flags & (MDBX_RDONLY | MDBX_SAFE_NOSYNC | DEPRECATED_MAPASYNC
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
| MDBX_EXCLUSIVE
|
||||
#endif /* !Windows */
|
||||
))) {
|
||||
rc = osal_openfile(MDBX_OPEN_DXB_DSYNC, env, env->pathname.dxb, &env->dsync_fd, 0);
|
||||
if (unlikely(MDBX_IS_ERROR(rc)))
|
||||
return rc;
|
||||
if (env->dsync_fd != INVALID_HANDLE_VALUE) {
|
||||
if ((env->flags & MDBX_NOMETASYNC) == 0)
|
||||
env->fd4meta = env->dsync_fd;
|
||||
osal_fseek(env->dsync_fd, safe_parking_lot_offset);
|
||||
}
|
||||
}
|
||||
|
||||
const MDBX_env_flags_t lazy_flags = MDBX_SAFE_NOSYNC | MDBX_UTTERLY_NOSYNC | MDBX_NOMETASYNC;
|
||||
const MDBX_env_flags_t mode_flags = lazy_flags | MDBX_LIFORECLAIM | MDBX_NORDAHEAD | MDBX_RDONLY | MDBX_WRITEMAP;
|
||||
|
||||
lck_t *const lck = env->lck_mmap.lck;
|
||||
if (lck && lck_rc != MDBX_RESULT_TRUE && (env->flags & MDBX_RDONLY) == 0) {
|
||||
MDBX_env_flags_t snap_flags;
|
||||
while ((snap_flags = atomic_load32(&lck->envmode, mo_AcquireRelease)) == MDBX_RDONLY) {
|
||||
if (atomic_cas32(&lck->envmode, MDBX_RDONLY, (snap_flags = (env->flags & mode_flags)))) {
|
||||
/* The case:
|
||||
* - let's assume that for some reason the DB file is smaller
|
||||
* than it should be according to the geometry,
|
||||
* but not smaller than the last page used;
|
||||
* - the first process that opens the database (lck_rc == RESULT_TRUE)
|
||||
* does this in readonly mode and therefore cannot bring
|
||||
* the file size back to normal;
|
||||
* - some next process (lck_rc != RESULT_TRUE) opens the DB in
|
||||
* read-write mode and now is here.
|
||||
*
|
||||
* FIXME: Should we re-check and set the size of DB-file right here? */
|
||||
break;
|
||||
}
|
||||
atomic_yield();
|
||||
}
|
||||
|
||||
if (env->flags & MDBX_ACCEDE) {
|
||||
/* Pickup current mode-flags (MDBX_LIFORECLAIM, MDBX_NORDAHEAD, etc). */
|
||||
const MDBX_env_flags_t diff =
|
||||
(snap_flags ^ env->flags) & ((snap_flags & lazy_flags) ? mode_flags : mode_flags & ~MDBX_WRITEMAP);
|
||||
env->flags ^= diff;
|
||||
NOTICE("accede mode-flags: 0x%X, 0x%X -> 0x%X", diff, env->flags ^ diff, env->flags);
|
||||
}
|
||||
|
||||
/* Ранее упущенный не очевидный момент: При работе БД в режимах
|
||||
* не-синхронной/отложенной фиксации на диске, все процессы-писатели должны
|
||||
* иметь одинаковый режим MDBX_WRITEMAP.
|
||||
*
|
||||
* В противном случае, сброс на диск следует выполнять дважды: сначала
|
||||
* msync(), затем fdatasync(). При этом msync() не обязан отрабатывать
|
||||
* в процессах без MDBX_WRITEMAP, так как файл в память отображен только
|
||||
* для чтения. Поэтому, в общем случае, различия по MDBX_WRITEMAP не
|
||||
* позволяют выполнить фиксацию данных на диск, после их изменения в другом
|
||||
* процессе.
|
||||
*
|
||||
* В режиме MDBX_UTTERLY_NOSYNC позволять совместную работу с MDBX_WRITEMAP
|
||||
* также не следует, поскольку никакой процесс (в том числе последний) не
|
||||
* может гарантированно сбросить данные на диск, а следовательно не должен
|
||||
* помечать какую-либо транзакцию как steady.
|
||||
*
|
||||
* В результате, требуется либо запретить совместную работу процессам с
|
||||
* разным MDBX_WRITEMAP в режиме отложенной записи, либо отслеживать такое
|
||||
* смешивание и блокировать steady-пометки - что контрпродуктивно. */
|
||||
const MDBX_env_flags_t rigorous_flags = (snap_flags & lazy_flags)
|
||||
? MDBX_SAFE_NOSYNC | MDBX_UTTERLY_NOSYNC | MDBX_WRITEMAP
|
||||
: MDBX_SAFE_NOSYNC | MDBX_UTTERLY_NOSYNC;
|
||||
const MDBX_env_flags_t rigorous_diff = (snap_flags ^ env->flags) & rigorous_flags;
|
||||
if (rigorous_diff) {
|
||||
ERROR("current mode/flags 0x%X incompatible with requested 0x%X, "
|
||||
"rigorous diff 0x%X",
|
||||
env->flags, snap_flags, rigorous_diff);
|
||||
return MDBX_INCOMPATIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
mincore_clean_cache(env);
|
||||
const int dxb_rc = dxb_setup(env, lck_rc, mode);
|
||||
if (MDBX_IS_ERROR(dxb_rc))
|
||||
return dxb_rc;
|
||||
|
||||
rc = osal_check_fs_incore(env->lazy_fd);
|
||||
env->incore = false;
|
||||
if (rc == MDBX_RESULT_TRUE) {
|
||||
env->incore = true;
|
||||
NOTICE("%s", "in-core database");
|
||||
rc = MDBX_SUCCESS;
|
||||
} else if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
ERROR("check_fs_incore(), err %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (unlikely(/* recovery mode */ env->stuck_meta >= 0) &&
|
||||
(lck_rc != /* exclusive */ MDBX_RESULT_TRUE || (env->flags & MDBX_EXCLUSIVE) == 0)) {
|
||||
ERROR("%s", "recovery requires exclusive mode");
|
||||
return MDBX_BUSY;
|
||||
}
|
||||
|
||||
DEBUG("opened dbenv %p", (void *)env);
|
||||
env->flags |= ENV_ACTIVE;
|
||||
if (!lck || lck_rc == MDBX_RESULT_TRUE) {
|
||||
env->lck->envmode.weak = env->flags & mode_flags;
|
||||
env->lck->meta_sync_txnid.weak = (uint32_t)recent_committed_txnid(env);
|
||||
env->lck->readers_check_timestamp.weak = osal_monotime();
|
||||
}
|
||||
if (lck) {
|
||||
if (lck_rc == MDBX_RESULT_TRUE) {
|
||||
rc = lck_downgrade(env);
|
||||
DEBUG("lck-downgrade-%s: rc %i", (env->flags & MDBX_EXCLUSIVE) ? "partial" : "full", rc);
|
||||
if (rc != MDBX_SUCCESS)
|
||||
return rc;
|
||||
} else {
|
||||
rc = mvcc_cleanup_dead(env, false, nullptr);
|
||||
if (MDBX_IS_ERROR(rc))
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
rc = (env->flags & MDBX_RDONLY) ? MDBX_SUCCESS
|
||||
: osal_ioring_create(&env->ioring
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
,
|
||||
ior_direct, env->ioring.overlapped_fd
|
||||
#endif /* Windows */
|
||||
);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold int env_close(MDBX_env *env, bool resurrect_after_fork) {
|
||||
const unsigned flags = env->flags;
|
||||
env->flags &= ~ENV_INTERNAL_FLAGS;
|
||||
if (flags & ENV_TXKEY) {
|
||||
thread_key_delete(env->me_txkey);
|
||||
env->me_txkey = 0;
|
||||
}
|
||||
|
||||
if (env->lck)
|
||||
munlock_all(env);
|
||||
|
||||
rthc_lock();
|
||||
int rc = rthc_remove(env);
|
||||
rthc_unlock();
|
||||
|
||||
#if MDBX_ENABLE_DBI_LOCKFREE
|
||||
for (defer_free_item_t *next, *ptr = env->defer_free; ptr; ptr = next) {
|
||||
next = ptr->next;
|
||||
osal_free(ptr);
|
||||
}
|
||||
env->defer_free = nullptr;
|
||||
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
|
||||
|
||||
if ((env->flags & MDBX_RDONLY) == 0)
|
||||
osal_ioring_destroy(&env->ioring);
|
||||
|
||||
env->lck = nullptr;
|
||||
if (env->lck_mmap.lck)
|
||||
osal_munmap(&env->lck_mmap);
|
||||
|
||||
if (env->dxb_mmap.base) {
|
||||
osal_munmap(&env->dxb_mmap);
|
||||
#ifdef ENABLE_MEMCHECK
|
||||
VALGRIND_DISCARD(env->valgrind_handle);
|
||||
env->valgrind_handle = -1;
|
||||
#endif /* ENABLE_MEMCHECK */
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
eASSERT(env, !env->ioring.overlapped_fd || env->ioring.overlapped_fd == INVALID_HANDLE_VALUE);
|
||||
if (env->dxb_lock_event != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(env->dxb_lock_event);
|
||||
env->dxb_lock_event = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
eASSERT(env, !resurrect_after_fork);
|
||||
if (env->pathname_char) {
|
||||
osal_free(env->pathname_char);
|
||||
env->pathname_char = nullptr;
|
||||
}
|
||||
#endif /* Windows */
|
||||
|
||||
if (env->dsync_fd != INVALID_HANDLE_VALUE) {
|
||||
(void)osal_closefile(env->dsync_fd);
|
||||
env->dsync_fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
if (env->lazy_fd != INVALID_HANDLE_VALUE) {
|
||||
(void)osal_closefile(env->lazy_fd);
|
||||
env->lazy_fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
if (env->lck_mmap.fd != INVALID_HANDLE_VALUE) {
|
||||
(void)osal_closefile(env->lck_mmap.fd);
|
||||
env->lck_mmap.fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
if (!resurrect_after_fork) {
|
||||
if (env->kvs) {
|
||||
for (size_t i = CORE_DBS; i < env->n_dbi; ++i)
|
||||
if (env->kvs[i].name.iov_len)
|
||||
osal_free(env->kvs[i].name.iov_base);
|
||||
osal_free(env->kvs);
|
||||
env->n_dbi = CORE_DBS;
|
||||
env->kvs = nullptr;
|
||||
}
|
||||
if (env->page_auxbuf) {
|
||||
osal_memalign_free(env->page_auxbuf);
|
||||
env->page_auxbuf = nullptr;
|
||||
}
|
||||
if (env->dbi_seqs) {
|
||||
osal_free(env->dbi_seqs);
|
||||
env->dbi_seqs = nullptr;
|
||||
}
|
||||
if (env->dbs_flags) {
|
||||
osal_free(env->dbs_flags);
|
||||
env->dbs_flags = nullptr;
|
||||
}
|
||||
if (env->pathname.buffer) {
|
||||
osal_free(env->pathname.buffer);
|
||||
env->pathname.buffer = nullptr;
|
||||
}
|
||||
if (env->basal_txn) {
|
||||
txn_basal_destroy(env->basal_txn);
|
||||
env->basal_txn = nullptr;
|
||||
}
|
||||
}
|
||||
env->stuck_meta = -1;
|
||||
return rc;
|
||||
}
|
125
src/essentials.h
Normal file
125
src/essentials.h
Normal file
@ -0,0 +1,125 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#define LIBMDBX_INTERNALS
|
||||
#define MDBX_DEPRECATED
|
||||
|
||||
#ifdef MDBX_CONFIG_H
|
||||
#include MDBX_CONFIG_H
|
||||
#endif
|
||||
|
||||
#include "preface.h"
|
||||
|
||||
#ifdef xMDBX_ALLOY
|
||||
/* Amalgamated build */
|
||||
#define MDBX_INTERNAL static
|
||||
#else
|
||||
/* Non-amalgamated build */
|
||||
#define MDBX_INTERNAL
|
||||
#endif /* xMDBX_ALLOY */
|
||||
|
||||
#include "../mdbx.h"
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Basic constants and types */
|
||||
|
||||
typedef struct iov_ctx iov_ctx_t;
|
||||
#include "osal.h"
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#include "atomics-types.h"
|
||||
|
||||
#include "layout-dxb.h"
|
||||
#include "layout-lck.h"
|
||||
|
||||
#define MIN_MAPSIZE (MDBX_MIN_PAGESIZE * MIN_PAGENO)
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define MAX_MAPSIZE32 UINT32_C(0x38000000)
|
||||
#else
|
||||
#define MAX_MAPSIZE32 UINT32_C(0x7f000000)
|
||||
#endif
|
||||
#define MAX_MAPSIZE64 ((MAX_PAGENO + 1) * (uint64_t)MDBX_MAX_PAGESIZE)
|
||||
|
||||
#if MDBX_WORDBITS >= 64
|
||||
#define MAX_MAPSIZE MAX_MAPSIZE64
|
||||
#define PAGELIST_LIMIT ((size_t)MAX_PAGENO)
|
||||
#else
|
||||
#define MAX_MAPSIZE MAX_MAPSIZE32
|
||||
#define PAGELIST_LIMIT (MAX_MAPSIZE32 / MDBX_MIN_PAGESIZE)
|
||||
#endif /* MDBX_WORDBITS */
|
||||
|
||||
#define MDBX_GOLD_RATIO_DBL 1.6180339887498948482
|
||||
#define MEGABYTE ((size_t)1 << 20)
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
union logger_union {
|
||||
void *ptr;
|
||||
MDBX_debug_func *fmt;
|
||||
MDBX_debug_func_nofmt *nofmt;
|
||||
};
|
||||
|
||||
struct libmdbx_globals {
|
||||
bin128_t bootid;
|
||||
unsigned sys_pagesize, sys_allocation_granularity;
|
||||
uint8_t sys_pagesize_ln2;
|
||||
uint8_t runtime_flags;
|
||||
uint8_t loglevel;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
bool running_under_Wine;
|
||||
#elif defined(__linux__) || defined(__gnu_linux__)
|
||||
bool running_on_WSL1 /* Windows Subsystem 1 for Linux */;
|
||||
uint32_t linux_kernel_version;
|
||||
#endif /* Linux */
|
||||
union logger_union logger;
|
||||
osal_fastmutex_t debug_lock;
|
||||
size_t logger_buffer_size;
|
||||
char *logger_buffer;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
extern struct libmdbx_globals globals;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
extern struct libmdbx_imports imports;
|
||||
#endif /* Windows */
|
||||
|
||||
#include "logging_and_debug.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include "pnl.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#define mdbx_sourcery_anchor XCONCAT(mdbx_sourcery_, MDBX_BUILD_SOURCERY)
|
||||
#if defined(xMDBX_TOOLS)
|
||||
extern LIBMDBX_API const char *const mdbx_sourcery_anchor;
|
||||
#endif
|
||||
|
||||
#define MDBX_IS_ERROR(rc) ((rc) != MDBX_RESULT_TRUE && (rc) != MDBX_RESULT_FALSE)
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t int64pgno(int64_t i64) {
|
||||
if (likely(i64 >= (int64_t)MIN_PAGENO && i64 <= (int64_t)MAX_PAGENO + 1))
|
||||
return (pgno_t)i64;
|
||||
return (i64 < (int64_t)MIN_PAGENO) ? MIN_PAGENO : MAX_PAGENO;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t pgno_add(size_t base, size_t augend) {
|
||||
assert(base <= MAX_PAGENO + 1 && augend < MAX_PAGENO);
|
||||
return int64pgno((int64_t)base + (int64_t)augend);
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t pgno_sub(size_t base, size_t subtrahend) {
|
||||
assert(base >= MIN_PAGENO && base <= MAX_PAGENO + 1 && subtrahend < MAX_PAGENO);
|
||||
return int64pgno((int64_t)base - (int64_t)subtrahend);
|
||||
}
|
1367
src/gc-get.c
Normal file
1367
src/gc-get.c
Normal file
File diff suppressed because it is too large
Load Diff
1513
src/gc-put.c
Normal file
1513
src/gc-put.c
Normal file
File diff suppressed because it is too large
Load Diff
80
src/gc.h
Normal file
80
src/gc.h
Normal file
@ -0,0 +1,80 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
/* Гистограмма решения нарезки фрагментов для ситуации нехватки идентификаторов/слотов. */
|
||||
typedef struct gc_dense_histogram {
|
||||
/* Размер массива одновременно задаёт максимальный размер последовательностей,
|
||||
* с которыми решается задача распределения.
|
||||
*
|
||||
* Использование длинных последовательностей контрпродуктивно, так как такие последовательности будут
|
||||
* создавать/воспроизводить/повторять аналогичные затруднения при последующей переработке. Однако,
|
||||
* в редких ситуациях это может быть единственным выходом. */
|
||||
unsigned end;
|
||||
pgno_t array[31];
|
||||
} gc_dense_histogram_t;
|
||||
|
||||
typedef struct gc_update_context {
|
||||
unsigned loop;
|
||||
unsigned goodchunk;
|
||||
bool dense;
|
||||
pgno_t prev_first_unallocated;
|
||||
size_t retired_stored;
|
||||
size_t return_reserved_lo, return_reserved_hi;
|
||||
txnid_t gc_first;
|
||||
intptr_t return_left;
|
||||
#ifndef MDBX_DEBUG_GCU
|
||||
#define MDBX_DEBUG_GCU 0
|
||||
#endif
|
||||
#if MDBX_DEBUG_GCU
|
||||
struct {
|
||||
txnid_t prev;
|
||||
unsigned n;
|
||||
} dbg;
|
||||
#endif /* MDBX_DEBUG_GCU */
|
||||
rkl_t ready4reuse, sequel;
|
||||
#if MDBX_ENABLE_BIGFOOT
|
||||
txnid_t bigfoot;
|
||||
#endif /* MDBX_ENABLE_BIGFOOT */
|
||||
union {
|
||||
MDBX_cursor cursor;
|
||||
cursor_couple_t couple;
|
||||
};
|
||||
gc_dense_histogram_t dense_histogram;
|
||||
} gcu_t;
|
||||
|
||||
MDBX_INTERNAL int gc_put_init(MDBX_txn *txn, gcu_t *ctx);
|
||||
MDBX_INTERNAL void gc_put_destroy(gcu_t *ctx);
|
||||
|
||||
#define ALLOC_DEFAULT 0 /* штатное/обычное выделение страниц */
|
||||
#define ALLOC_UNIMPORTANT 1 /* запрос неважен, невозможность выделения не приведет к ошибке транзакции */
|
||||
#define ALLOC_RESERVE 2 /* подготовка резерва для обновления GC, без аллокации */
|
||||
#define ALLOC_COALESCE 4 /* внутреннее состояние/флажок */
|
||||
#define ALLOC_SHOULD_SCAN 8 /* внутреннее состояние/флажок */
|
||||
#define ALLOC_LIFO 16 /* внутреннее состояние/флажок */
|
||||
|
||||
MDBX_INTERNAL pgr_t gc_alloc_ex(const MDBX_cursor *const mc, const size_t num, uint8_t flags);
|
||||
|
||||
MDBX_INTERNAL pgr_t gc_alloc_single(const MDBX_cursor *const mc);
|
||||
MDBX_INTERNAL int gc_update(MDBX_txn *txn, gcu_t *ctx);
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t gc_stockpile(const MDBX_txn *txn) {
|
||||
return MDBX_PNL_GETSIZE(txn->wr.repnl) + txn->wr.loose_count;
|
||||
}
|
||||
|
||||
MDBX_NOTHROW_PURE_FUNCTION static inline size_t gc_chunk_bytes(const size_t chunk) {
|
||||
return (chunk + 1) * sizeof(pgno_t);
|
||||
}
|
||||
|
||||
MDBX_INTERNAL bool gc_repnl_has_span(const MDBX_txn *txn, const size_t num);
|
||||
|
||||
static inline bool gc_is_reclaimed(const MDBX_txn *txn, const txnid_t id) {
|
||||
return rkl_contain(&txn->wr.gc.reclaimed, id) || rkl_contain(&txn->wr.gc.comeback, id);
|
||||
}
|
||||
|
||||
static inline txnid_t txnid_min(txnid_t a, txnid_t b) { return (a < b) ? a : b; }
|
||||
|
||||
static inline txnid_t txnid_max(txnid_t a, txnid_t b) { return (a > b) ? a : b; }
|
474
src/global.c
Normal file
474
src/global.c
Normal file
@ -0,0 +1,474 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static void mdbx_init(void);
|
||||
static void mdbx_fini(void);
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* mdbx constructor/destructor */
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
#if MDBX_BUILD_SHARED_LIBRARY
|
||||
#if MDBX_WITHOUT_MSVC_CRT && defined(NDEBUG)
|
||||
/* DEBUG/CHECKED builds still require MSVC's CRT for runtime checks.
|
||||
*
|
||||
* Define dll's entry point only for Release build when NDEBUG is defined and
|
||||
* MDBX_WITHOUT_MSVC_CRT=ON. if the entry point isn't defined then MSVC's will
|
||||
* automatically use DllMainCRTStartup() from CRT library, which also
|
||||
* automatically call DllMain() from our mdbx.dll */
|
||||
#pragma comment(linker, "/ENTRY:DllMain")
|
||||
#endif /* MDBX_WITHOUT_MSVC_CRT */
|
||||
|
||||
BOOL APIENTRY DllMain(HANDLE module, DWORD reason, LPVOID reserved)
|
||||
#else
|
||||
#if !MDBX_MANUAL_MODULE_HANDLER
|
||||
static
|
||||
#endif /* !MDBX_MANUAL_MODULE_HANDLER */
|
||||
void NTAPI
|
||||
mdbx_module_handler(PVOID module, DWORD reason, PVOID reserved)
|
||||
#endif /* MDBX_BUILD_SHARED_LIBRARY */
|
||||
{
|
||||
(void)reserved;
|
||||
switch (reason) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
windows_import();
|
||||
mdbx_init();
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
mdbx_fini();
|
||||
break;
|
||||
|
||||
case DLL_THREAD_ATTACH:
|
||||
break;
|
||||
case DLL_THREAD_DETACH:
|
||||
rthc_thread_dtor(module);
|
||||
break;
|
||||
}
|
||||
#if MDBX_BUILD_SHARED_LIBRARY
|
||||
return TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !MDBX_BUILD_SHARED_LIBRARY && !MDBX_MANUAL_MODULE_HANDLER
|
||||
/* *INDENT-OFF* */
|
||||
/* clang-format off */
|
||||
#if defined(_MSC_VER)
|
||||
# pragma const_seg(push)
|
||||
# pragma data_seg(push)
|
||||
|
||||
# ifndef _M_IX86
|
||||
/* kick a linker to create the TLS directory if not already done */
|
||||
# pragma comment(linker, "/INCLUDE:_tls_used")
|
||||
/* Force some symbol references. */
|
||||
# pragma comment(linker, "/INCLUDE:mdbx_tls_anchor")
|
||||
/* specific const-segment for WIN64 */
|
||||
# pragma const_seg(".CRT$XLB")
|
||||
const
|
||||
# else
|
||||
/* kick a linker to create the TLS directory if not already done */
|
||||
# pragma comment(linker, "/INCLUDE:__tls_used")
|
||||
/* Force some symbol references. */
|
||||
# pragma comment(linker, "/INCLUDE:_mdbx_tls_anchor")
|
||||
/* specific data-segment for WIN32 */
|
||||
# pragma data_seg(".CRT$XLB")
|
||||
# endif
|
||||
|
||||
__declspec(allocate(".CRT$XLB")) PIMAGE_TLS_CALLBACK mdbx_tls_anchor = mdbx_module_handler;
|
||||
# pragma data_seg(pop)
|
||||
# pragma const_seg(pop)
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
# ifndef _M_IX86
|
||||
const
|
||||
# endif
|
||||
PIMAGE_TLS_CALLBACK mdbx_tls_anchor __attribute__((__section__(".CRT$XLB"), used)) = mdbx_module_handler;
|
||||
#else
|
||||
# error FIXME
|
||||
#endif
|
||||
/* *INDENT-ON* */
|
||||
/* clang-format on */
|
||||
#endif /* !MDBX_BUILD_SHARED_LIBRARY && !MDBX_MANUAL_MODULE_HANDLER */
|
||||
|
||||
#else
|
||||
|
||||
#if defined(__linux__) || defined(__gnu_linux__)
|
||||
#include <sys/utsname.h>
|
||||
|
||||
MDBX_EXCLUDE_FOR_GPROF
|
||||
__cold static uint8_t probe_for_WSL(const char *tag) {
|
||||
const char *const WSL = strstr(tag, "WSL");
|
||||
if (WSL && WSL[3] >= '2' && WSL[3] <= '9')
|
||||
return WSL[3] - '0';
|
||||
const char *const wsl = strstr(tag, "wsl");
|
||||
if (wsl && wsl[3] >= '2' && wsl[3] <= '9')
|
||||
return wsl[3] - '0';
|
||||
if (WSL || wsl || strcasestr(tag, "Microsoft"))
|
||||
/* Expecting no new kernel within WSL1, either it will explicitly
|
||||
* marked by an appropriate WSL-version hint. */
|
||||
return (globals.linux_kernel_version < /* 4.19.x */ 0x04130000) ? 1 : 2;
|
||||
return 0;
|
||||
}
|
||||
#endif /* Linux */
|
||||
|
||||
#ifdef ENABLE_GPROF
|
||||
extern void _mcleanup(void);
|
||||
extern void monstartup(unsigned long, unsigned long);
|
||||
extern void _init(void);
|
||||
extern void _fini(void);
|
||||
extern void __gmon_start__(void) __attribute__((__weak__));
|
||||
#endif /* ENABLE_GPROF */
|
||||
|
||||
MDBX_EXCLUDE_FOR_GPROF
|
||||
__cold static __attribute__((__constructor__)) void mdbx_global_constructor(void) {
|
||||
#ifdef ENABLE_GPROF
|
||||
if (!&__gmon_start__)
|
||||
monstartup((uintptr_t)&_init, (uintptr_t)&_fini);
|
||||
#endif /* ENABLE_GPROF */
|
||||
|
||||
#if defined(__linux__) || defined(__gnu_linux__)
|
||||
struct utsname buffer;
|
||||
if (uname(&buffer) == 0) {
|
||||
int i = 0;
|
||||
char *p = buffer.release;
|
||||
while (*p && i < 4) {
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
long number = strtol(p, &p, 10);
|
||||
if (number > 0) {
|
||||
if (number > 255)
|
||||
number = 255;
|
||||
globals.linux_kernel_version += number << (24 - i * 8);
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
/* "Official" way of detecting WSL1 but not WSL2
|
||||
* https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
|
||||
*
|
||||
* WARNING: False negative detection of WSL1 will result in DATA LOSS!
|
||||
* So, the REQUIREMENTS for this code:
|
||||
* 1. MUST detect WSL1 without false-negatives.
|
||||
* 2. DESIRABLE detect WSL2 but without the risk of violating the first. */
|
||||
globals.running_on_WSL1 =
|
||||
probe_for_WSL(buffer.version) == 1 || probe_for_WSL(buffer.sysname) == 1 || probe_for_WSL(buffer.release) == 1;
|
||||
}
|
||||
#endif /* Linux */
|
||||
|
||||
mdbx_init();
|
||||
}
|
||||
|
||||
MDBX_EXCLUDE_FOR_GPROF
|
||||
__cold static __attribute__((__destructor__)) void mdbx_global_destructor(void) {
|
||||
mdbx_fini();
|
||||
#ifdef ENABLE_GPROF
|
||||
if (!&__gmon_start__)
|
||||
_mcleanup();
|
||||
#endif /* ENABLE_GPROF */
|
||||
}
|
||||
|
||||
#endif /* ! Windows */
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
struct libmdbx_globals globals;
|
||||
|
||||
__cold static void mdbx_init(void) {
|
||||
globals.runtime_flags = ((MDBX_DEBUG) > 0) * MDBX_DBG_ASSERT + ((MDBX_DEBUG) > 1) * MDBX_DBG_AUDIT;
|
||||
globals.loglevel = MDBX_LOG_FATAL;
|
||||
ENSURE(nullptr, osal_fastmutex_init(&globals.debug_lock) == 0);
|
||||
osal_ctor();
|
||||
assert(globals.sys_pagesize > 0 && (globals.sys_pagesize & (globals.sys_pagesize - 1)) == 0);
|
||||
rthc_ctor();
|
||||
#if MDBX_DEBUG
|
||||
ENSURE(nullptr, troika_verify_fsm());
|
||||
ENSURE(nullptr, pv2pages_verify());
|
||||
#endif /* MDBX_DEBUG*/
|
||||
}
|
||||
|
||||
MDBX_EXCLUDE_FOR_GPROF
|
||||
__cold static void mdbx_fini(void) {
|
||||
const uint32_t current_pid = osal_getpid();
|
||||
TRACE(">> pid %d", current_pid);
|
||||
rthc_dtor(current_pid);
|
||||
osal_dtor();
|
||||
TRACE("<< pid %d\n", current_pid);
|
||||
ENSURE(nullptr, osal_fastmutex_destroy(&globals.debug_lock) == 0);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
/* clang-format off */
|
||||
|
||||
__dll_export
|
||||
#ifdef __attribute_used__
|
||||
__attribute_used__
|
||||
#elif defined(__GNUC__) || __has_attribute(__used__)
|
||||
__attribute__((__used__))
|
||||
#endif
|
||||
#ifdef __attribute_externally_visible__
|
||||
__attribute_externally_visible__
|
||||
#elif (defined(__GNUC__) && !defined(__clang__)) || \
|
||||
__has_attribute(__externally_visible__)
|
||||
__attribute__((__externally_visible__))
|
||||
#endif
|
||||
const struct MDBX_build_info mdbx_build = {
|
||||
#ifdef MDBX_BUILD_TIMESTAMP
|
||||
MDBX_BUILD_TIMESTAMP
|
||||
#else
|
||||
"\"" __DATE__ " " __TIME__ "\""
|
||||
#endif /* MDBX_BUILD_TIMESTAMP */
|
||||
|
||||
,
|
||||
#ifdef MDBX_BUILD_TARGET
|
||||
MDBX_BUILD_TARGET
|
||||
#else
|
||||
#if defined(__ANDROID_API__)
|
||||
"Android" MDBX_STRINGIFY(__ANDROID_API__)
|
||||
#elif defined(__linux__) || defined(__gnu_linux__)
|
||||
"Linux"
|
||||
#elif defined(EMSCRIPTEN) || defined(__EMSCRIPTEN__)
|
||||
"webassembly"
|
||||
#elif defined(__CYGWIN__)
|
||||
"CYGWIN"
|
||||
#elif defined(_WIN64) || defined(_WIN32) || defined(__TOS_WIN__) \
|
||||
|| defined(__WINDOWS__)
|
||||
"Windows"
|
||||
#elif defined(__APPLE__)
|
||||
#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) \
|
||||
|| (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR)
|
||||
"iOS"
|
||||
#else
|
||||
"MacOS"
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
"FreeBSD"
|
||||
#elif defined(__DragonFly__)
|
||||
"DragonFlyBSD"
|
||||
#elif defined(__NetBSD__)
|
||||
"NetBSD"
|
||||
#elif defined(__OpenBSD__)
|
||||
"OpenBSD"
|
||||
#elif defined(__bsdi__)
|
||||
"UnixBSDI"
|
||||
#elif defined(__MACH__)
|
||||
"MACH"
|
||||
#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC))
|
||||
"HPUX"
|
||||
#elif defined(_AIX)
|
||||
"AIX"
|
||||
#elif defined(__sun) && defined(__SVR4)
|
||||
"Solaris"
|
||||
#elif defined(__BSD__) || defined(BSD)
|
||||
"UnixBSD"
|
||||
#elif defined(__unix__) || defined(UNIX) || defined(__unix) \
|
||||
|| defined(__UNIX) || defined(__UNIX__)
|
||||
"UNIX"
|
||||
#elif defined(_POSIX_VERSION)
|
||||
"POSIX" MDBX_STRINGIFY(_POSIX_VERSION)
|
||||
#else
|
||||
"UnknownOS"
|
||||
#endif /* Target OS */
|
||||
|
||||
"-"
|
||||
|
||||
#if defined(__amd64__)
|
||||
"AMD64"
|
||||
#elif defined(__ia32__)
|
||||
"IA32"
|
||||
#elif defined(__e2k__) || defined(__elbrus__)
|
||||
"Elbrus"
|
||||
#elif defined(__alpha__) || defined(__alpha) || defined(_M_ALPHA)
|
||||
"Alpha"
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
"ARM64"
|
||||
#elif defined(__arm__) || defined(__thumb__) || defined(__TARGET_ARCH_ARM) \
|
||||
|| defined(__TARGET_ARCH_THUMB) || defined(_ARM) || defined(_M_ARM) \
|
||||
|| defined(_M_ARMT) || defined(__arm)
|
||||
"ARM"
|
||||
#elif defined(__mips64) || defined(__mips64__) || (defined(__mips) && (__mips >= 64))
|
||||
"MIPS64"
|
||||
#elif defined(__mips__) || defined(__mips) || defined(_R4000) || defined(__MIPS__)
|
||||
"MIPS"
|
||||
#elif defined(__hppa64__) || defined(__HPPA64__) || defined(__hppa64)
|
||||
"PARISC64"
|
||||
#elif defined(__hppa__) || defined(__HPPA__) || defined(__hppa)
|
||||
"PARISC"
|
||||
#elif defined(__ia64__) || defined(__ia64) || defined(_IA64) \
|
||||
|| defined(__IA64__) || defined(_M_IA64) || defined(__itanium__)
|
||||
"Itanium"
|
||||
#elif defined(__powerpc64__) || defined(__ppc64__) || defined(__ppc64) \
|
||||
|| defined(__powerpc64) || defined(_ARCH_PPC64)
|
||||
"PowerPC64"
|
||||
#elif defined(__powerpc__) || defined(__ppc__) || defined(__powerpc) \
|
||||
|| defined(__ppc) || defined(_ARCH_PPC) || defined(__PPC__) || defined(__POWERPC__)
|
||||
"PowerPC"
|
||||
#elif defined(__sparc64__) || defined(__sparc64)
|
||||
"SPARC64"
|
||||
#elif defined(__sparc__) || defined(__sparc)
|
||||
"SPARC"
|
||||
#elif defined(__s390__) || defined(__s390) || defined(__zarch__) || defined(__zarch)
|
||||
"S390"
|
||||
#else
|
||||
"UnknownARCH"
|
||||
#endif
|
||||
#endif /* MDBX_BUILD_TARGET */
|
||||
|
||||
#ifdef MDBX_BUILD_TYPE
|
||||
# if defined(_MSC_VER)
|
||||
# pragma message("Configuration-depended MDBX_BUILD_TYPE: " MDBX_BUILD_TYPE)
|
||||
# endif
|
||||
"-" MDBX_BUILD_TYPE
|
||||
#endif /* MDBX_BUILD_TYPE */
|
||||
,
|
||||
"MDBX_DEBUG=" MDBX_STRINGIFY(MDBX_DEBUG)
|
||||
#ifdef ENABLE_GPROF
|
||||
" ENABLE_GPROF"
|
||||
#endif /* ENABLE_GPROF */
|
||||
" MDBX_WORDBITS=" MDBX_STRINGIFY(MDBX_WORDBITS)
|
||||
" BYTE_ORDER="
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
"LITTLE_ENDIAN"
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
"BIG_ENDIAN"
|
||||
#else
|
||||
#error "FIXME: Unsupported byte order"
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
" MDBX_ENABLE_BIGFOOT=" MDBX_STRINGIFY(MDBX_ENABLE_BIGFOOT)
|
||||
" MDBX_ENV_CHECKPID=" MDBX_ENV_CHECKPID_CONFIG
|
||||
" MDBX_TXN_CHECKOWNER=" MDBX_TXN_CHECKOWNER_CONFIG
|
||||
" MDBX_64BIT_ATOMIC=" MDBX_64BIT_ATOMIC_CONFIG
|
||||
" MDBX_64BIT_CAS=" MDBX_64BIT_CAS_CONFIG
|
||||
" MDBX_TRUST_RTC=" MDBX_TRUST_RTC_CONFIG
|
||||
" MDBX_AVOID_MSYNC=" MDBX_STRINGIFY(MDBX_AVOID_MSYNC)
|
||||
" MDBX_ENABLE_REFUND=" MDBX_STRINGIFY(MDBX_ENABLE_REFUND)
|
||||
" MDBX_USE_MINCORE=" MDBX_STRINGIFY(MDBX_USE_MINCORE)
|
||||
" MDBX_ENABLE_PGOP_STAT=" MDBX_STRINGIFY(MDBX_ENABLE_PGOP_STAT)
|
||||
" MDBX_ENABLE_PROFGC=" MDBX_STRINGIFY(MDBX_ENABLE_PROFGC)
|
||||
#if MDBX_DISABLE_VALIDATION
|
||||
" MDBX_DISABLE_VALIDATION=YES"
|
||||
#endif /* MDBX_DISABLE_VALIDATION */
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
" SANITIZE_ADDRESS=YES"
|
||||
#endif /* __SANITIZE_ADDRESS__ */
|
||||
#ifdef ENABLE_MEMCHECK
|
||||
" ENABLE_MEMCHECK=YES"
|
||||
#endif /* ENABLE_MEMCHECK */
|
||||
#if MDBX_FORCE_ASSERTIONS
|
||||
" MDBX_FORCE_ASSERTIONS=YES"
|
||||
#endif /* MDBX_FORCE_ASSERTIONS */
|
||||
#ifdef _GNU_SOURCE
|
||||
" _GNU_SOURCE=YES"
|
||||
#else
|
||||
" _GNU_SOURCE=NO"
|
||||
#endif /* _GNU_SOURCE */
|
||||
#ifdef __APPLE__
|
||||
" MDBX_APPLE_SPEED_INSTEADOF_DURABILITY=" MDBX_STRINGIFY(MDBX_APPLE_SPEED_INSTEADOF_DURABILITY)
|
||||
#endif /* MacOS */
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
" MDBX_WITHOUT_MSVC_CRT=" MDBX_STRINGIFY(MDBX_WITHOUT_MSVC_CRT)
|
||||
" MDBX_BUILD_SHARED_LIBRARY=" MDBX_STRINGIFY(MDBX_BUILD_SHARED_LIBRARY)
|
||||
#if !MDBX_BUILD_SHARED_LIBRARY
|
||||
" MDBX_MANUAL_MODULE_HANDLER=" MDBX_STRINGIFY(MDBX_MANUAL_MODULE_HANDLER)
|
||||
#endif
|
||||
" WINVER=" MDBX_STRINGIFY(WINVER)
|
||||
#else /* Windows */
|
||||
" MDBX_LOCKING=" MDBX_LOCKING_CONFIG
|
||||
" MDBX_USE_OFDLOCKS=" MDBX_USE_OFDLOCKS_CONFIG
|
||||
#endif /* !Windows */
|
||||
" MDBX_CACHELINE_SIZE=" MDBX_STRINGIFY(MDBX_CACHELINE_SIZE)
|
||||
" MDBX_CPU_WRITEBACK_INCOHERENT=" MDBX_STRINGIFY(MDBX_CPU_WRITEBACK_INCOHERENT)
|
||||
" MDBX_MMAP_INCOHERENT_CPU_CACHE=" MDBX_STRINGIFY(MDBX_MMAP_INCOHERENT_CPU_CACHE)
|
||||
" MDBX_MMAP_INCOHERENT_FILE_WRITE=" MDBX_STRINGIFY(MDBX_MMAP_INCOHERENT_FILE_WRITE)
|
||||
" MDBX_UNALIGNED_OK=" MDBX_STRINGIFY(MDBX_UNALIGNED_OK)
|
||||
" MDBX_PNL_ASCENDING=" MDBX_STRINGIFY(MDBX_PNL_ASCENDING)
|
||||
,
|
||||
#ifdef MDBX_BUILD_COMPILER
|
||||
MDBX_BUILD_COMPILER
|
||||
#else
|
||||
#ifdef __INTEL_COMPILER
|
||||
"Intel C/C++ " MDBX_STRINGIFY(__INTEL_COMPILER)
|
||||
#elif defined(__apple_build_version__)
|
||||
"Apple clang " MDBX_STRINGIFY(__apple_build_version__)
|
||||
#elif defined(__ibmxl__)
|
||||
"IBM clang C " MDBX_STRINGIFY(__ibmxl_version__) "." MDBX_STRINGIFY(__ibmxl_release__)
|
||||
"." MDBX_STRINGIFY(__ibmxl_modification__) "." MDBX_STRINGIFY(__ibmxl_ptf_fix_level__)
|
||||
#elif defined(__clang__)
|
||||
"clang " MDBX_STRINGIFY(__clang_version__)
|
||||
#elif defined(__MINGW64__)
|
||||
"MINGW-64 " MDBX_STRINGIFY(__MINGW64_MAJOR_VERSION) "." MDBX_STRINGIFY(__MINGW64_MINOR_VERSION)
|
||||
#elif defined(__MINGW32__)
|
||||
"MINGW-32 " MDBX_STRINGIFY(__MINGW32_MAJOR_VERSION) "." MDBX_STRINGIFY(__MINGW32_MINOR_VERSION)
|
||||
#elif defined(__MINGW__)
|
||||
"MINGW " MDBX_STRINGIFY(__MINGW_MAJOR_VERSION) "." MDBX_STRINGIFY(__MINGW_MINOR_VERSION)
|
||||
#elif defined(__IBMC__)
|
||||
"IBM C " MDBX_STRINGIFY(__IBMC__)
|
||||
#elif defined(__GNUC__)
|
||||
"GNU C/C++ "
|
||||
#ifdef __VERSION__
|
||||
__VERSION__
|
||||
#else
|
||||
MDBX_STRINGIFY(__GNUC__) "." MDBX_STRINGIFY(__GNUC_MINOR__) "." MDBX_STRINGIFY(__GNUC_PATCHLEVEL__)
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
"MSVC " MDBX_STRINGIFY(_MSC_FULL_VER) "-" MDBX_STRINGIFY(_MSC_BUILD)
|
||||
#else
|
||||
"Unknown compiler"
|
||||
#endif
|
||||
#endif /* MDBX_BUILD_COMPILER */
|
||||
,
|
||||
#ifdef MDBX_BUILD_FLAGS_CONFIG
|
||||
MDBX_BUILD_FLAGS_CONFIG
|
||||
#endif /* MDBX_BUILD_FLAGS_CONFIG */
|
||||
#if defined(MDBX_BUILD_FLAGS_CONFIG) && defined(MDBX_BUILD_FLAGS)
|
||||
" "
|
||||
#endif
|
||||
#ifdef MDBX_BUILD_FLAGS
|
||||
MDBX_BUILD_FLAGS
|
||||
#endif /* MDBX_BUILD_FLAGS */
|
||||
#if !(defined(MDBX_BUILD_FLAGS_CONFIG) || defined(MDBX_BUILD_FLAGS))
|
||||
"undefined (please use correct build script)"
|
||||
#ifdef _MSC_VER
|
||||
#pragma message("warning: Build flags undefined. Please use correct build script")
|
||||
#else
|
||||
#warning "Build flags undefined. Please use correct build script"
|
||||
#endif // _MSC_VER
|
||||
#endif
|
||||
, MDBX_BUILD_METADATA
|
||||
};
|
||||
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
#if !defined(_MSC_VER) || __has_attribute(weak)
|
||||
LIBMDBX_API __attribute__((__weak__))
|
||||
#endif
|
||||
const char *__asan_default_options(void) {
|
||||
return "symbolize=1:allow_addr2line=1:"
|
||||
#if MDBX_DEBUG
|
||||
"debug=1:"
|
||||
"verbosity=2:"
|
||||
#endif /* MDBX_DEBUG */
|
||||
"log_threads=1:"
|
||||
"report_globals=1:"
|
||||
"replace_str=1:replace_intrin=1:"
|
||||
"malloc_context_size=9:"
|
||||
#if !defined(__APPLE__)
|
||||
"detect_leaks=1:"
|
||||
#endif
|
||||
"check_printf=1:"
|
||||
"detect_deadlocks=1:"
|
||||
#ifndef LTO_ENABLED
|
||||
"check_initialization_order=1:"
|
||||
#endif
|
||||
"detect_stack_use_after_return=1:"
|
||||
"intercept_tls_get_addr=1:"
|
||||
"decorate_proc_maps=1:"
|
||||
"abort_on_error=1";
|
||||
}
|
||||
#endif /* __SANITIZE_ADDRESS__ */
|
||||
|
||||
/* *INDENT-ON* */
|
||||
/* clang-format on */
|
587
src/internals.h
Normal file
587
src/internals.h
Normal file
@ -0,0 +1,587 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \note Please refer to the COPYRIGHT file for explanations license change,
|
||||
/// credits and acknowledgments.
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
typedef struct dp dp_t;
|
||||
typedef struct dpl dpl_t;
|
||||
typedef struct kvx kvx_t;
|
||||
typedef struct meta_ptr meta_ptr_t;
|
||||
typedef struct inner_cursor subcur_t;
|
||||
typedef struct cursor_couple cursor_couple_t;
|
||||
typedef struct defer_free_item defer_free_item_t;
|
||||
|
||||
typedef struct troika {
|
||||
uint8_t fsm, recent, prefer_steady, tail_and_flags;
|
||||
#if MDBX_WORDBITS > 32 /* Workaround for false-positives from Valgrind */
|
||||
uint32_t unused_pad;
|
||||
#endif
|
||||
#define TROIKA_HAVE_STEADY(troika) ((troika)->fsm & 7u)
|
||||
#define TROIKA_STRICT_VALID(troika) ((troika)->tail_and_flags & 64u)
|
||||
#define TROIKA_VALID(troika) ((troika)->tail_and_flags & 128u)
|
||||
#define TROIKA_TAIL(troika) ((troika)->tail_and_flags & 3u)
|
||||
txnid_t txnid[NUM_METAS];
|
||||
} troika_t;
|
||||
|
||||
typedef struct page_get_result {
|
||||
page_t *page;
|
||||
int err;
|
||||
} pgr_t;
|
||||
|
||||
typedef struct node_search_result {
|
||||
node_t *node;
|
||||
bool exact;
|
||||
} nsr_t;
|
||||
|
||||
typedef struct bind_reader_slot_result {
|
||||
int err;
|
||||
reader_slot_t *slot;
|
||||
} bsr_t;
|
||||
|
||||
#include "atomics-ops.h"
|
||||
#include "proto.h"
|
||||
#include "rkl.h"
|
||||
#include "txl.h"
|
||||
#include "unaligned.h"
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include "windows-import.h"
|
||||
#endif /* Windows */
|
||||
|
||||
enum signatures {
|
||||
env_signature = INT32_C(0x1A899641),
|
||||
txn_signature = INT32_C(0x13D53A31),
|
||||
cur_signature_live = INT32_C(0x7E05D5B1),
|
||||
cur_signature_ready4dispose = INT32_C(0x2817A047),
|
||||
cur_signature_wait4eot = INT32_C(0x10E297A7)
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/* An dirty-page list item is an pgno/pointer pair. */
|
||||
struct dp {
|
||||
page_t *ptr;
|
||||
pgno_t pgno, npages;
|
||||
};
|
||||
|
||||
enum dpl_rules {
|
||||
dpl_gap_edging = 2,
|
||||
dpl_gap_mergesort = 16,
|
||||
dpl_reserve_gap = dpl_gap_mergesort + dpl_gap_edging,
|
||||
dpl_insertion_threshold = 42
|
||||
};
|
||||
|
||||
/* An DPL (dirty-page list) is a lazy-sorted array of MDBX_DPs. */
|
||||
struct dpl {
|
||||
size_t sorted;
|
||||
size_t length;
|
||||
/* number of pages, but not an entries. */
|
||||
size_t pages_including_loose;
|
||||
/* allocated size excluding the dpl_reserve_gap */
|
||||
size_t detent;
|
||||
/* dynamic size with holes at zero and after the last */
|
||||
dp_t items[dpl_reserve_gap];
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Internal structures */
|
||||
|
||||
/* Comparing/ordering and length constraints */
|
||||
typedef struct clc {
|
||||
MDBX_cmp_func *cmp; /* comparator */
|
||||
size_t lmin, lmax; /* min/max length constraints */
|
||||
} clc_t;
|
||||
|
||||
/* Вспомогательная информация о table.
|
||||
*
|
||||
* Совокупность потребностей:
|
||||
* 1. Для транзакций и основного курсора нужны все поля.
|
||||
* 2. Для вложенного dupsort-курсора нужен компаратор значений, который изнутри
|
||||
* курсора будет выглядеть как компаратор ключей. Плюс заглушка компаратора
|
||||
* значений, которая не должна использоваться в штатных ситуациях, но
|
||||
* требуется хотя-бы для отслеживания таких обращений.
|
||||
* 3. Использование компараторов для курсора и вложенного dupsort-курсора
|
||||
* должно выглядеть одинаково.
|
||||
* 4. Желательно минимизировать объём данных размещаемых внутри вложенного
|
||||
* dupsort-курсора.
|
||||
* 5. Желательно чтобы объем всей структуры был степенью двойки.
|
||||
*
|
||||
* Решение:
|
||||
* - не храним в dupsort-курсоре ничего лишнего, а только tree;
|
||||
* - в курсоры помещаем только указатель на clc_t, который будет указывать
|
||||
* на соответствующее clc-поле в общей kvx-таблице привязанной к env;
|
||||
* - компаратор размещаем в начале clc_t, в kvx_t сначала размещаем clc
|
||||
* для ключей, потом для значений, а имя БД в конце kvx_t.
|
||||
* - тогда в курсоре clc[0] будет содержать информацию для ключей,
|
||||
* а clc[1] для значений, причем компаратор значений для dupsort-курсора
|
||||
* будет попадать на MDBX_val с именем, что приведет к SIGSEGV при попытке
|
||||
* использования такого компаратора.
|
||||
* - размер kvx_t становится равным 8 словам.
|
||||
*
|
||||
* Трюки и прочая экономия на спичках:
|
||||
* - не храним dbi внутри курсора, вместо этого вычисляем его как разницу между
|
||||
* dbi_state курсора и началом таблицы dbi_state в транзакции. Смысл тут в
|
||||
* экономии кол-ва полей при инициализации курсора. Затрат это не создает,
|
||||
* так как dbi требуется для последующего доступа к массивам в транзакции,
|
||||
* т.е. при вычислении dbi разыменовывается тот-же указатель на txn
|
||||
* и читается та же кэш-линия с указателями. */
|
||||
typedef struct clc2 {
|
||||
clc_t k; /* для ключей */
|
||||
clc_t v; /* для значений */
|
||||
} clc2_t;
|
||||
|
||||
struct kvx {
|
||||
clc2_t clc;
|
||||
MDBX_val name; /* имя table */
|
||||
};
|
||||
|
||||
/* Non-shared DBI state flags inside transaction */
|
||||
enum dbi_state {
|
||||
DBI_DIRTY = 0x01 /* DB was written in this txn */,
|
||||
DBI_STALE = 0x02 /* Named-DB record is older than txnID */,
|
||||
DBI_FRESH = 0x04 /* Named-DB handle opened in this txn */,
|
||||
DBI_CREAT = 0x08 /* Named-DB handle created in this txn */,
|
||||
DBI_VALID = 0x10 /* Handle is valid, see also DB_VALID */,
|
||||
DBI_OLDEN = 0x40 /* Handle was closed/reopened outside txn */,
|
||||
DBI_LINDO = 0x80 /* Lazy initialization done for DBI-slot */,
|
||||
};
|
||||
|
||||
enum txn_flags {
|
||||
txn_ro_begin_flags = MDBX_TXN_RDONLY | MDBX_TXN_RDONLY_PREPARE,
|
||||
txn_rw_begin_flags = MDBX_TXN_NOMETASYNC | MDBX_TXN_NOSYNC | MDBX_TXN_TRY,
|
||||
txn_shrink_allowed = UINT32_C(0x40000000),
|
||||
txn_parked = MDBX_TXN_PARKED,
|
||||
txn_gc_drained = 0x80 /* GC was depleted up to oldest reader */,
|
||||
txn_may_have_cursors = 0x100,
|
||||
txn_state_flags = MDBX_TXN_FINISHED | MDBX_TXN_ERROR | MDBX_TXN_DIRTY | MDBX_TXN_SPILLS | MDBX_TXN_HAS_CHILD |
|
||||
MDBX_TXN_INVALID | txn_gc_drained
|
||||
};
|
||||
|
||||
/* A database transaction.
|
||||
* Every operation requires a transaction handle. */
|
||||
struct MDBX_txn {
|
||||
int32_t signature;
|
||||
uint32_t flags; /* Transaction Flags */
|
||||
size_t n_dbi;
|
||||
size_t owner; /* thread ID that owns this transaction */
|
||||
|
||||
MDBX_txn *parent; /* parent of a nested txn */
|
||||
MDBX_txn *nested; /* nested txn under this txn,
|
||||
set together with MDBX_TXN_HAS_CHILD */
|
||||
geo_t geo;
|
||||
|
||||
/* The ID of this transaction. IDs are integers incrementing from
|
||||
* INITIAL_TXNID. Only committed write transactions increment the ID. If a
|
||||
* transaction aborts, the ID may be re-used by the next writer. */
|
||||
txnid_t txnid, front_txnid;
|
||||
|
||||
MDBX_env *env; /* the DB environment */
|
||||
tree_t *dbs; /* Array of tree_t records for each known DB */
|
||||
|
||||
#if MDBX_ENABLE_DBI_SPARSE
|
||||
unsigned *__restrict dbi_sparse;
|
||||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||||
|
||||
/* Array of non-shared txn's flags of DBI.
|
||||
* Модификатор __restrict тут полезен и безопасен в текущем понимании,
|
||||
* так как пересечение возможно только с dbi_state курсоров,
|
||||
* и происходит по-чтению до последующего изменения/записи. */
|
||||
uint8_t *__restrict dbi_state;
|
||||
|
||||
/* Array of sequence numbers for each DB handle. */
|
||||
uint32_t *__restrict dbi_seqs;
|
||||
|
||||
/* Массив с головами односвязных списков отслеживания курсоров. */
|
||||
MDBX_cursor **cursors;
|
||||
|
||||
/* "Канареечные" маркеры/счетчики */
|
||||
MDBX_canary canary;
|
||||
|
||||
/* User-settable context */
|
||||
void *userctx;
|
||||
|
||||
union {
|
||||
struct {
|
||||
/* For read txns: This thread/txn's slot table slot, or nullptr. */
|
||||
reader_slot_t *slot;
|
||||
} ro;
|
||||
struct {
|
||||
troika_t troika;
|
||||
pnl_t __restrict repnl; /* Reclaimed GC pages */
|
||||
struct {
|
||||
rkl_t reclaimed; /* The list of reclaimed txn-ids from GC */
|
||||
uint64_t spent; /* Time spent reading and searching GC */
|
||||
rkl_t comeback; /* The list of ids of records returned into GC during commit, etc */
|
||||
} gc;
|
||||
bool prefault_write_activated;
|
||||
#if MDBX_ENABLE_REFUND
|
||||
pgno_t loose_refund_wl /* FIXME: describe */;
|
||||
#endif /* MDBX_ENABLE_REFUND */
|
||||
/* a sequence to spilling dirty page with LRU policy */
|
||||
unsigned dirtylru;
|
||||
/* dirtylist room: Dirty array size - dirty pages visible to this txn.
|
||||
* Includes ancestor txns' dirty pages not hidden by other txns'
|
||||
* dirty/spilled pages. Thus commit(nested txn) has room to merge
|
||||
* dirtylist into parent after freeing hidden parent pages. */
|
||||
size_t dirtyroom;
|
||||
/* For write txns: Modified pages. Sorted when not MDBX_WRITEMAP. */
|
||||
dpl_t *__restrict dirtylist;
|
||||
/* The list of pages that became unused during this transaction. */
|
||||
pnl_t __restrict retired_pages;
|
||||
/* The list of loose pages that became unused and may be reused
|
||||
* in this transaction, linked through `page_next()`. */
|
||||
page_t *__restrict loose_pages;
|
||||
/* Number of loose pages (wr.loose_pages) */
|
||||
size_t loose_count;
|
||||
union {
|
||||
struct {
|
||||
size_t least_removed;
|
||||
/* The sorted list of dirty pages we temporarily wrote to disk
|
||||
* because the dirty list was full. page numbers in here are
|
||||
* shifted left by 1, deleted slots have the LSB set. */
|
||||
pnl_t __restrict list;
|
||||
} spilled;
|
||||
size_t writemap_dirty_npages;
|
||||
size_t writemap_spilled_npages;
|
||||
};
|
||||
/* In write txns, next is located the array of cursors for each DB */
|
||||
} wr;
|
||||
};
|
||||
};
|
||||
|
||||
#define CURSOR_STACK_SIZE (16 + MDBX_WORDBITS / 4)
|
||||
|
||||
struct MDBX_cursor {
|
||||
int32_t signature;
|
||||
union {
|
||||
/* Тут некоторые трюки/заморочки с тем чтобы во всех основных сценариях
|
||||
* проверять состояние курсора одной простой операцией сравнения,
|
||||
* и при этом ни на каплю не усложнять код итерации стека курсора.
|
||||
*
|
||||
* Поэтому решение такое:
|
||||
* - поля flags и top сделаны знаковыми, а их отрицательные значения
|
||||
* используются для обозначения не-установленного/не-инициализированного
|
||||
* состояния курсора;
|
||||
* - для инвалидации/сброса курсора достаточно записать отрицательное
|
||||
* значение в объединенное поле top_and_flags;
|
||||
* - все проверки состояния сводятся к сравнению одного из полей
|
||||
* flags/snum/snum_and_flags, которые в зависимости от сценария,
|
||||
* трактуются либо как знаковые, либо как безнаковые. */
|
||||
__anonymous_struct_extension__ struct {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
int8_t flags;
|
||||
/* индекс вершины стека, меньше нуля для не-инициализированного курсора */
|
||||
int8_t top;
|
||||
#else
|
||||
int8_t top;
|
||||
int8_t flags;
|
||||
#endif
|
||||
};
|
||||
int16_t top_and_flags;
|
||||
};
|
||||
/* флаги проверки, в том числе биты для проверки типа листовых страниц. */
|
||||
uint8_t checking;
|
||||
uint8_t pad;
|
||||
|
||||
/* Указывает на txn->dbi_state[] для DBI этого курсора.
|
||||
* Модификатор __restrict тут полезен и безопасен в текущем понимании,
|
||||
* так как пересечение возможно только с dbi_state транзакции,
|
||||
* и происходит по-чтению до последующего изменения/записи. */
|
||||
uint8_t *__restrict dbi_state;
|
||||
/* Связь списка отслеживания курсоров в транзакции. */
|
||||
MDBX_txn *txn;
|
||||
/* Указывает на tree->dbs[] для DBI этого курсора. */
|
||||
tree_t *tree;
|
||||
/* Указывает на env->kvs[] для DBI этого курсора. */
|
||||
clc2_t *clc;
|
||||
subcur_t *__restrict subcur;
|
||||
page_t *pg[CURSOR_STACK_SIZE]; /* stack of pushed pages */
|
||||
indx_t ki[CURSOR_STACK_SIZE]; /* stack of page indices */
|
||||
MDBX_cursor *next;
|
||||
/* Состояние на момент старта вложенной транзакции */
|
||||
MDBX_cursor *backup;
|
||||
};
|
||||
|
||||
struct inner_cursor {
|
||||
MDBX_cursor cursor;
|
||||
tree_t nested_tree;
|
||||
};
|
||||
|
||||
struct cursor_couple {
|
||||
MDBX_cursor outer;
|
||||
void *userctx; /* User-settable context */
|
||||
subcur_t inner;
|
||||
};
|
||||
|
||||
enum env_flags {
|
||||
/* Failed to update the meta page. Probably an I/O error. */
|
||||
ENV_FATAL_ERROR = INT32_MIN /* 0x80000000 */,
|
||||
/* Some fields are initialized. */
|
||||
ENV_ACTIVE = UINT32_C(0x20000000),
|
||||
/* me_txkey is set */
|
||||
ENV_TXKEY = UINT32_C(0x10000000),
|
||||
/* Legacy MDBX_MAPASYNC (prior v0.9) */
|
||||
DEPRECATED_MAPASYNC = UINT32_C(0x100000),
|
||||
/* Legacy MDBX_COALESCE (prior v0.12) */
|
||||
DEPRECATED_COALESCE = UINT32_C(0x2000000),
|
||||
ENV_INTERNAL_FLAGS = ENV_FATAL_ERROR | ENV_ACTIVE | ENV_TXKEY,
|
||||
/* Only a subset of the mdbx_env flags can be changed
|
||||
* at runtime. Changing other flags requires closing the
|
||||
* environment and re-opening it with the new flags. */
|
||||
ENV_CHANGEABLE_FLAGS = MDBX_SAFE_NOSYNC | MDBX_NOMETASYNC | DEPRECATED_MAPASYNC | MDBX_NOMEMINIT |
|
||||
DEPRECATED_COALESCE | MDBX_PAGEPERTURB | MDBX_ACCEDE | MDBX_VALIDATION,
|
||||
ENV_CHANGELESS_FLAGS = MDBX_NOSUBDIR | MDBX_RDONLY | MDBX_WRITEMAP | MDBX_NOSTICKYTHREADS | MDBX_NORDAHEAD |
|
||||
MDBX_LIFORECLAIM | MDBX_EXCLUSIVE,
|
||||
ENV_USABLE_FLAGS = ENV_CHANGEABLE_FLAGS | ENV_CHANGELESS_FLAGS
|
||||
};
|
||||
|
||||
/* The database environment. */
|
||||
struct MDBX_env {
|
||||
/* ----------------------------------------------------- mostly static part */
|
||||
mdbx_atomic_uint32_t signature;
|
||||
uint32_t flags;
|
||||
unsigned ps; /* DB page size, initialized from me_os_psize */
|
||||
osal_mmap_t dxb_mmap; /* The main data file */
|
||||
#define lazy_fd dxb_mmap.fd
|
||||
mdbx_filehandle_t dsync_fd, fd4meta;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
HANDLE dxb_lock_event;
|
||||
#endif /* Windows */
|
||||
osal_mmap_t lck_mmap; /* The lock file */
|
||||
lck_t *lck;
|
||||
|
||||
uint16_t leaf_nodemax; /* max size of a leaf-node */
|
||||
uint16_t branch_nodemax; /* max size of a branch-node */
|
||||
uint16_t subpage_limit;
|
||||
uint16_t subpage_room_threshold;
|
||||
uint16_t subpage_reserve_prereq;
|
||||
uint16_t subpage_reserve_limit;
|
||||
atomic_pgno_t mlocked_pgno;
|
||||
uint8_t ps2ln; /* log2 of DB page size */
|
||||
int8_t stuck_meta; /* recovery-only: target meta page or less that zero */
|
||||
uint16_t merge_threshold; /* pages emptier than this are candidates for merging */
|
||||
unsigned max_readers; /* size of the reader table */
|
||||
MDBX_dbi max_dbi; /* size of the DB table */
|
||||
uint32_t pid; /* process ID of this env */
|
||||
osal_thread_key_t me_txkey; /* thread-key for readers */
|
||||
struct { /* path to the DB files */
|
||||
pathchar_t *lck, *dxb, *specified;
|
||||
void *buffer;
|
||||
} pathname;
|
||||
void *page_auxbuf; /* scratch area for DUPSORT put() */
|
||||
MDBX_txn *basal_txn; /* preallocated write transaction */
|
||||
kvx_t *kvs; /* array of auxiliary key-value properties */
|
||||
uint8_t *__restrict dbs_flags; /* array of flags from tree_t.flags */
|
||||
mdbx_atomic_uint32_t *dbi_seqs; /* array of dbi sequence numbers */
|
||||
unsigned maxgc_large1page; /* Number of pgno_t fit in a single large page */
|
||||
unsigned maxgc_per_branch;
|
||||
uint32_t registered_reader_pid; /* have liveness lock in reader table */
|
||||
void *userctx; /* User-settable context */
|
||||
MDBX_hsr_func *hsr_callback; /* Callback for kicking laggard readers */
|
||||
size_t madv_threshold;
|
||||
|
||||
struct {
|
||||
unsigned dp_reserve_limit;
|
||||
unsigned rp_augment_limit;
|
||||
unsigned dp_limit;
|
||||
unsigned dp_initial;
|
||||
uint64_t gc_time_limit;
|
||||
uint8_t dp_loose_limit;
|
||||
uint8_t spill_max_denominator;
|
||||
uint8_t spill_min_denominator;
|
||||
uint8_t spill_parent4child_denominator;
|
||||
unsigned merge_threshold_16dot16_percent;
|
||||
#if !(defined(_WIN32) || defined(_WIN64))
|
||||
unsigned writethrough_threshold;
|
||||
#endif /* Windows */
|
||||
bool prefault_write;
|
||||
bool prefer_waf_insteadof_balance; /* Strive to minimize WAF instead of
|
||||
balancing pages fullment */
|
||||
bool need_dp_limit_adjust;
|
||||
struct {
|
||||
uint16_t limit;
|
||||
uint16_t room_threshold;
|
||||
uint16_t reserve_prereq;
|
||||
uint16_t reserve_limit;
|
||||
} subpage;
|
||||
|
||||
union {
|
||||
unsigned all;
|
||||
/* tracks options with non-auto values but tuned by user */
|
||||
struct {
|
||||
unsigned dp_limit : 1;
|
||||
unsigned rp_augment_limit : 1;
|
||||
unsigned prefault_write : 1;
|
||||
} non_auto;
|
||||
} flags;
|
||||
} options;
|
||||
|
||||
/* struct geo_in_bytes used for accepting db-geo params from user for the new
|
||||
* database creation, i.e. when mdbx_env_set_geometry() was called before
|
||||
* mdbx_env_open(). */
|
||||
struct {
|
||||
size_t lower; /* minimal size of datafile */
|
||||
size_t upper; /* maximal size of datafile */
|
||||
size_t now; /* current size of datafile */
|
||||
size_t grow; /* step to grow datafile */
|
||||
size_t shrink; /* threshold to shrink datafile */
|
||||
} geo_in_bytes;
|
||||
|
||||
#if MDBX_LOCKING == MDBX_LOCKING_SYSV
|
||||
union {
|
||||
key_t key;
|
||||
int semid;
|
||||
} me_sysv_ipc;
|
||||
#endif /* MDBX_LOCKING == MDBX_LOCKING_SYSV */
|
||||
bool incore;
|
||||
|
||||
#if MDBX_ENABLE_DBI_LOCKFREE
|
||||
defer_free_item_t *defer_free;
|
||||
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
|
||||
|
||||
/* -------------------------------------------------------------- debugging */
|
||||
|
||||
#if MDBX_DEBUG
|
||||
MDBX_assert_func *assert_func; /* Callback for assertion failures */
|
||||
#endif
|
||||
#ifdef ENABLE_MEMCHECK
|
||||
int valgrind_handle;
|
||||
#endif
|
||||
#if defined(ENABLE_MEMCHECK) || defined(__SANITIZE_ADDRESS__)
|
||||
pgno_t poison_edge;
|
||||
#endif /* ENABLE_MEMCHECK || __SANITIZE_ADDRESS__ */
|
||||
|
||||
#ifndef xMDBX_DEBUG_SPILLING
|
||||
#define xMDBX_DEBUG_SPILLING 0
|
||||
#endif
|
||||
#if xMDBX_DEBUG_SPILLING == 2
|
||||
size_t debug_dirtied_est, debug_dirtied_act;
|
||||
#endif /* xMDBX_DEBUG_SPILLING */
|
||||
|
||||
/* --------------------------------------------------- mostly volatile part */
|
||||
|
||||
MDBX_txn *txn; /* current write transaction */
|
||||
struct {
|
||||
txnid_t detent;
|
||||
} gc;
|
||||
osal_fastmutex_t dbi_lock;
|
||||
unsigned n_dbi; /* number of DBs opened */
|
||||
|
||||
unsigned shadow_reserve_len;
|
||||
page_t *__restrict shadow_reserve; /* list of malloc'ed blocks for re-use */
|
||||
|
||||
osal_ioring_t ioring;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
osal_srwlock_t remap_guard;
|
||||
/* Workaround for LockFileEx and WriteFile multithread bug */
|
||||
CRITICAL_SECTION windowsbug_lock;
|
||||
char *pathname_char; /* cache of multi-byte representation of pathname
|
||||
to the DB files */
|
||||
#else
|
||||
osal_fastmutex_t remap_guard;
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------- stub for lck-less mode */
|
||||
mdbx_atomic_uint64_t lckless_placeholder[(sizeof(lck_t) + MDBX_CACHELINE_SIZE - 1) / sizeof(mdbx_atomic_uint64_t)];
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/* pseudo-error code, not exposed outside libmdbx */
|
||||
#define MDBX_NO_ROOT (MDBX_LAST_ADDED_ERRCODE + 33)
|
||||
|
||||
/* Number of slots in the reader table.
|
||||
* This value was chosen somewhat arbitrarily. The 61 is a prime number,
|
||||
* and such readers plus a couple mutexes fit into single 4KB page.
|
||||
* Applications should set the table size using mdbx_env_set_maxreaders(). */
|
||||
#define DEFAULT_READERS 61
|
||||
|
||||
enum db_flags {
|
||||
DB_PERSISTENT_FLAGS =
|
||||
MDBX_REVERSEKEY | MDBX_DUPSORT | MDBX_INTEGERKEY | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP,
|
||||
|
||||
/* mdbx_dbi_open() flags */
|
||||
DB_USABLE_FLAGS = DB_PERSISTENT_FLAGS | MDBX_CREATE | MDBX_DB_ACCEDE,
|
||||
|
||||
DB_VALID = 0x80u /* DB handle is valid, for dbs_flags */,
|
||||
DB_POISON = 0x7fu /* update pending */,
|
||||
DB_INTERNAL_FLAGS = DB_VALID
|
||||
};
|
||||
|
||||
#if !defined(__cplusplus) || CONSTEXPR_ENUM_FLAGS_OPERATIONS
|
||||
MDBX_MAYBE_UNUSED static void static_checks(void) {
|
||||
STATIC_ASSERT(MDBX_WORDBITS == sizeof(void *) * CHAR_BIT);
|
||||
STATIC_ASSERT(UINT64_C(0x80000000) == (uint32_t)ENV_FATAL_ERROR);
|
||||
STATIC_ASSERT_MSG(INT16_MAX - CORE_DBS == MDBX_MAX_DBI, "Oops, MDBX_MAX_DBI or CORE_DBS?");
|
||||
STATIC_ASSERT_MSG((unsigned)(MDBX_DB_ACCEDE | MDBX_CREATE) ==
|
||||
((DB_USABLE_FLAGS | DB_INTERNAL_FLAGS) & (ENV_USABLE_FLAGS | ENV_INTERNAL_FLAGS)),
|
||||
"Oops, some flags overlapped or wrong");
|
||||
STATIC_ASSERT_MSG((DB_INTERNAL_FLAGS & DB_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong");
|
||||
STATIC_ASSERT_MSG((DB_PERSISTENT_FLAGS & ~DB_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong");
|
||||
STATIC_ASSERT(DB_PERSISTENT_FLAGS <= UINT8_MAX);
|
||||
STATIC_ASSERT_MSG((ENV_INTERNAL_FLAGS & ENV_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong");
|
||||
|
||||
STATIC_ASSERT_MSG((txn_state_flags & (txn_rw_begin_flags | txn_ro_begin_flags)) == 0,
|
||||
"Oops, some txn flags overlapped or wrong");
|
||||
STATIC_ASSERT_MSG(((txn_rw_begin_flags | txn_ro_begin_flags | txn_state_flags) & txn_shrink_allowed) == 0,
|
||||
"Oops, some txn flags overlapped or wrong");
|
||||
|
||||
STATIC_ASSERT(sizeof(reader_slot_t) == 32);
|
||||
#if MDBX_LOCKING > 0
|
||||
STATIC_ASSERT(offsetof(lck_t, wrt_lock) % MDBX_CACHELINE_SIZE == 0);
|
||||
STATIC_ASSERT(offsetof(lck_t, rdt_lock) % MDBX_CACHELINE_SIZE == 0);
|
||||
#else
|
||||
STATIC_ASSERT(offsetof(lck_t, cached_oldest) % MDBX_CACHELINE_SIZE == 0);
|
||||
STATIC_ASSERT(offsetof(lck_t, rdt_length) % MDBX_CACHELINE_SIZE == 0);
|
||||
#endif /* MDBX_LOCKING */
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
STATIC_ASSERT(offsetof(lck_t, rdt) % MDBX_CACHELINE_SIZE == 0);
|
||||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||||
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
STATIC_ASSERT(NODESIZE == offsetof(node_t, payload));
|
||||
STATIC_ASSERT(PAGEHDRSZ == offsetof(page_t, entries));
|
||||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||||
STATIC_ASSERT(sizeof(clc_t) == 3 * sizeof(void *));
|
||||
STATIC_ASSERT(sizeof(kvx_t) == 8 * sizeof(void *));
|
||||
|
||||
#define KVX_SIZE_LN2 MDBX_WORDBITS_LN2
|
||||
STATIC_ASSERT(sizeof(kvx_t) == (1u << KVX_SIZE_LN2));
|
||||
}
|
||||
#endif /* Disabled for MSVC 19.0 (VisualStudio 2015) */
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#include "node.h"
|
||||
|
||||
#include "dbi.h"
|
||||
|
||||
#include "cogs.h"
|
||||
|
||||
#include "cursor.h"
|
||||
|
||||
#include "dpl.h"
|
||||
|
||||
#include "gc.h"
|
||||
|
||||
#include "lck.h"
|
||||
|
||||
#include "meta.h"
|
||||
|
||||
#include "page-iov.h"
|
||||
|
||||
#include "spill.h"
|
||||
|
||||
#include "page-ops.h"
|
||||
|
||||
#include "tls.h"
|
||||
|
||||
#include "walk.h"
|
||||
|
||||
#include "sort.h"
|
288
src/layout-dxb.h
Normal file
288
src/layout-dxb.h
Normal file
@ -0,0 +1,288 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \note Please refer to the COPYRIGHT file for explanations license change,
|
||||
/// credits and acknowledgments.
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
#pragma pack(push, 4)
|
||||
|
||||
/* A stamp that identifies a file as an MDBX file.
|
||||
* There's nothing special about this value other than that it is easily
|
||||
* recognizable, and it will reflect any byte order mismatches. */
|
||||
#define MDBX_MAGIC UINT64_C(/* 56-bit prime */ 0x59659DBDEF4C11)
|
||||
|
||||
/* FROZEN: The version number for a database's datafile format. */
|
||||
#define MDBX_DATA_VERSION 3
|
||||
|
||||
#define MDBX_DATA_MAGIC ((MDBX_MAGIC << 8) + MDBX_PNL_ASCENDING * 64 + MDBX_DATA_VERSION)
|
||||
#define MDBX_DATA_MAGIC_LEGACY_COMPAT ((MDBX_MAGIC << 8) + MDBX_PNL_ASCENDING * 64 + 2)
|
||||
#define MDBX_DATA_MAGIC_LEGACY_DEVEL ((MDBX_MAGIC << 8) + 255)
|
||||
|
||||
/* handle for the DB used to track free pages. */
|
||||
#define FREE_DBI 0
|
||||
/* handle for the default DB. */
|
||||
#define MAIN_DBI 1
|
||||
/* Number of DBs in metapage (free and main) - also hardcoded elsewhere */
|
||||
#define CORE_DBS 2
|
||||
|
||||
/* Number of meta pages - also hardcoded elsewhere */
|
||||
#define NUM_METAS 3
|
||||
|
||||
/* A page number in the database.
|
||||
*
|
||||
* MDBX uses 32 bit for page numbers. This limits database
|
||||
* size up to 2^44 bytes, in case of 4K pages. */
|
||||
typedef uint32_t pgno_t;
|
||||
typedef mdbx_atomic_uint32_t atomic_pgno_t;
|
||||
#define PRIaPGNO PRIu32
|
||||
#define MAX_PAGENO UINT32_C(0x7FFFffff)
|
||||
#define MIN_PAGENO NUM_METAS
|
||||
|
||||
/* An invalid page number.
|
||||
* Mainly used to denote an empty tree. */
|
||||
#define P_INVALID (~(pgno_t)0)
|
||||
|
||||
/* A transaction ID. */
|
||||
typedef uint64_t txnid_t;
|
||||
typedef mdbx_atomic_uint64_t atomic_txnid_t;
|
||||
#define PRIaTXN PRIi64
|
||||
#define MIN_TXNID UINT64_C(1)
|
||||
#define MAX_TXNID (SAFE64_INVALID_THRESHOLD - 1)
|
||||
#define INITIAL_TXNID (MIN_TXNID + NUM_METAS - 1)
|
||||
#define INVALID_TXNID UINT64_MAX
|
||||
|
||||
/* Used for offsets within a single page. */
|
||||
typedef uint16_t indx_t;
|
||||
|
||||
typedef struct tree {
|
||||
uint16_t flags; /* see mdbx_dbi_open */
|
||||
uint16_t height; /* height of this tree */
|
||||
uint32_t dupfix_size; /* key-size for MDBX_DUPFIXED (DUPFIX pages) */
|
||||
pgno_t root; /* the root page of this tree */
|
||||
pgno_t branch_pages; /* number of branch pages */
|
||||
pgno_t leaf_pages; /* number of leaf pages */
|
||||
pgno_t large_pages; /* number of large pages */
|
||||
uint64_t sequence; /* table sequence counter */
|
||||
uint64_t items; /* number of data items */
|
||||
uint64_t mod_txnid; /* txnid of last committed modification */
|
||||
} tree_t;
|
||||
|
||||
/* database size-related parameters */
|
||||
typedef struct geo {
|
||||
uint16_t grow_pv; /* datafile growth step as a 16-bit packed (exponential
|
||||
quantized) value */
|
||||
uint16_t shrink_pv; /* datafile shrink threshold as a 16-bit packed
|
||||
(exponential quantized) value */
|
||||
pgno_t lower; /* minimal size of datafile in pages */
|
||||
pgno_t upper; /* maximal size of datafile in pages */
|
||||
union {
|
||||
pgno_t now; /* current size of datafile in pages */
|
||||
pgno_t end_pgno;
|
||||
};
|
||||
union {
|
||||
pgno_t first_unallocated; /* first unused page in the datafile,
|
||||
but actually the file may be shorter. */
|
||||
pgno_t next_pgno;
|
||||
};
|
||||
} geo_t;
|
||||
|
||||
/* Meta page content.
|
||||
* A meta page is the start point for accessing a database snapshot.
|
||||
* Pages 0-2 are meta pages. */
|
||||
typedef struct meta {
|
||||
/* Stamp identifying this as an MDBX file.
|
||||
* It must be set to MDBX_MAGIC with MDBX_DATA_VERSION. */
|
||||
uint32_t magic_and_version[2];
|
||||
|
||||
/* txnid that committed this meta, the first of a two-phase-update pair */
|
||||
union {
|
||||
mdbx_atomic_uint32_t txnid_a[2];
|
||||
uint64_t unsafe_txnid;
|
||||
};
|
||||
|
||||
uint16_t reserve16; /* extra flags, zero (nothing) for now */
|
||||
uint8_t validator_id; /* ID of checksum and page validation method,
|
||||
* zero (nothing) for now */
|
||||
int8_t extra_pagehdr; /* extra bytes in the page header,
|
||||
* zero (nothing) for now */
|
||||
|
||||
geo_t geometry; /* database size-related parameters */
|
||||
|
||||
union {
|
||||
struct {
|
||||
tree_t gc, main;
|
||||
} trees;
|
||||
__anonymous_struct_extension__ struct {
|
||||
uint16_t gc_flags;
|
||||
uint16_t gc_height;
|
||||
uint32_t pagesize;
|
||||
};
|
||||
};
|
||||
|
||||
MDBX_canary canary;
|
||||
|
||||
#define DATASIGN_NONE 0u
|
||||
#define DATASIGN_WEAK 1u
|
||||
#define SIGN_IS_STEADY(sign) ((sign) > DATASIGN_WEAK)
|
||||
union {
|
||||
uint32_t sign[2];
|
||||
uint64_t unsafe_sign;
|
||||
};
|
||||
|
||||
/* txnid that committed this meta, the second of a two-phase-update pair */
|
||||
mdbx_atomic_uint32_t txnid_b[2];
|
||||
|
||||
/* Number of non-meta pages which were put in GC after COW. May be 0 in case
|
||||
* DB was previously handled by libmdbx without corresponding feature.
|
||||
* This value in couple with reader.snapshot_pages_retired allows fast
|
||||
* estimation of "how much reader is restraining GC recycling". */
|
||||
uint32_t pages_retired[2];
|
||||
|
||||
/* The analogue /proc/sys/kernel/random/boot_id or similar to determine
|
||||
* whether the system was rebooted after the last use of the database files.
|
||||
* If there was no reboot, but there is no need to rollback to the last
|
||||
* steady sync point. Zeros mean that no relevant information is available
|
||||
* from the system. */
|
||||
bin128_t bootid;
|
||||
|
||||
/* GUID базы данных, начиная с v0.13.1 */
|
||||
bin128_t dxbid;
|
||||
} meta_t;
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
typedef enum page_type {
|
||||
P_BRANCH = 0x01u /* branch page */,
|
||||
P_LEAF = 0x02u /* leaf page */,
|
||||
P_LARGE = 0x04u /* large/overflow page */,
|
||||
P_META = 0x08u /* meta page */,
|
||||
P_LEGACY_DIRTY = 0x10u /* legacy P_DIRTY flag prior to v0.10 958fd5b9 */,
|
||||
P_BAD = P_LEGACY_DIRTY /* explicit flag for invalid/bad page */,
|
||||
P_DUPFIX = 0x20u /* for MDBX_DUPFIXED records */,
|
||||
P_SUBP = 0x40u /* for MDBX_DUPSORT sub-pages */,
|
||||
P_SPILLED = 0x2000u /* spilled in parent txn */,
|
||||
P_LOOSE = 0x4000u /* page was dirtied then freed, can be reused */,
|
||||
P_FROZEN = 0x8000u /* used for retire page with known status */,
|
||||
P_ILL_BITS = (uint16_t)~(P_BRANCH | P_LEAF | P_DUPFIX | P_LARGE | P_SPILLED),
|
||||
|
||||
page_broken = 0,
|
||||
page_large = P_LARGE,
|
||||
page_branch = P_BRANCH,
|
||||
page_leaf = P_LEAF,
|
||||
page_dupfix_leaf = P_DUPFIX,
|
||||
page_sub_leaf = P_SUBP | P_LEAF,
|
||||
page_sub_dupfix_leaf = P_SUBP | P_DUPFIX,
|
||||
page_sub_broken = P_SUBP,
|
||||
} page_type_t;
|
||||
|
||||
/* Common header for all page types. The page type depends on flags.
|
||||
*
|
||||
* P_BRANCH and P_LEAF pages have unsorted 'node_t's at the end, with
|
||||
* sorted entries[] entries referring to them. Exception: P_DUPFIX pages
|
||||
* omit entries and pack sorted MDBX_DUPFIXED values after the page header.
|
||||
*
|
||||
* P_LARGE records occupy one or more contiguous pages where only the
|
||||
* first has a page header. They hold the real data of N_BIG nodes.
|
||||
*
|
||||
* P_SUBP sub-pages are small leaf "pages" with duplicate data.
|
||||
* A node with flag N_DUP but not N_TREE contains a sub-page.
|
||||
* (Duplicate data can also go in tables, which use normal pages.)
|
||||
*
|
||||
* P_META pages contain meta_t, the start point of an MDBX snapshot.
|
||||
*
|
||||
* Each non-metapage up to meta_t.mm_last_pg is reachable exactly once
|
||||
* in the snapshot: Either used by a database or listed in a GC record. */
|
||||
typedef struct page {
|
||||
uint64_t txnid; /* txnid which created page, maybe zero in legacy DB */
|
||||
uint16_t dupfix_ksize; /* key size if this is a DUPFIX page */
|
||||
uint16_t flags;
|
||||
union {
|
||||
uint32_t pages; /* number of overflow pages */
|
||||
__anonymous_struct_extension__ struct {
|
||||
indx_t lower; /* lower bound of free space */
|
||||
indx_t upper; /* upper bound of free space */
|
||||
};
|
||||
};
|
||||
pgno_t pgno; /* page number */
|
||||
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
indx_t entries[] /* dynamic size */;
|
||||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||||
} page_t;
|
||||
|
||||
/* Size of the page header, excluding dynamic data at the end */
|
||||
#define PAGEHDRSZ 20u
|
||||
|
||||
/* Header for a single key/data pair within a page.
|
||||
* Used in pages of type P_BRANCH and P_LEAF without P_DUPFIX.
|
||||
* We guarantee 2-byte alignment for 'node_t's.
|
||||
*
|
||||
* Leaf node flags describe node contents. N_BIG says the node's
|
||||
* data part is the page number of an overflow page with actual data.
|
||||
* N_DUP and N_TREE can be combined giving duplicate data in
|
||||
* a sub-page/table, and named databases (just N_TREE). */
|
||||
typedef struct node {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
union {
|
||||
uint32_t dsize;
|
||||
uint32_t child_pgno;
|
||||
};
|
||||
uint8_t flags; /* see node_flags */
|
||||
uint8_t extra;
|
||||
uint16_t ksize; /* key size */
|
||||
#else
|
||||
uint16_t ksize; /* key size */
|
||||
uint8_t extra;
|
||||
uint8_t flags; /* see node_flags */
|
||||
union {
|
||||
uint32_t child_pgno;
|
||||
uint32_t dsize;
|
||||
};
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
uint8_t payload[] /* key and data are appended here */;
|
||||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||||
} node_t;
|
||||
|
||||
/* Size of the node header, excluding dynamic data at the end */
|
||||
#define NODESIZE 8u
|
||||
|
||||
typedef enum node_flags {
|
||||
N_BIG = 0x01 /* data put on large page */,
|
||||
N_TREE = 0x02 /* data is a b-tree */,
|
||||
N_DUP = 0x04 /* data has duplicates */
|
||||
} node_flags_t;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint8_t page_type(const page_t *mp) { return mp->flags; }
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline uint8_t page_type_compat(const page_t *mp) {
|
||||
/* Drop legacy P_DIRTY flag for sub-pages for compatilibity,
|
||||
* for assertions only. */
|
||||
return unlikely(mp->flags & P_SUBP) ? mp->flags & ~(P_SUBP | P_LEGACY_DIRTY) : mp->flags;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_leaf(const page_t *mp) {
|
||||
return (mp->flags & P_LEAF) != 0;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_dupfix_leaf(const page_t *mp) {
|
||||
return (mp->flags & P_DUPFIX) != 0;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_branch(const page_t *mp) {
|
||||
return (mp->flags & P_BRANCH) != 0;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_largepage(const page_t *mp) {
|
||||
return (mp->flags & P_LARGE) != 0;
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_NOTHROW_PURE_FUNCTION static inline bool is_subpage(const page_t *mp) {
|
||||
return (mp->flags & P_SUBP) != 0;
|
||||
}
|
289
src/layout-lck.h
Normal file
289
src/layout-lck.h
Normal file
@ -0,0 +1,289 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \note Please refer to the COPYRIGHT file for explanations license change,
|
||||
/// credits and acknowledgments.
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
/* The version number for a database's lockfile format. */
|
||||
#define MDBX_LOCK_VERSION 6
|
||||
|
||||
#if MDBX_LOCKING == MDBX_LOCKING_WIN32FILES
|
||||
|
||||
#define MDBX_LCK_SIGN UINT32_C(0xF10C)
|
||||
typedef void osal_ipclock_t;
|
||||
#elif MDBX_LOCKING == MDBX_LOCKING_SYSV
|
||||
|
||||
#define MDBX_LCK_SIGN UINT32_C(0xF18D)
|
||||
typedef mdbx_pid_t osal_ipclock_t;
|
||||
|
||||
#elif MDBX_LOCKING == MDBX_LOCKING_POSIX2001 || MDBX_LOCKING == MDBX_LOCKING_POSIX2008
|
||||
|
||||
#define MDBX_LCK_SIGN UINT32_C(0x8017)
|
||||
typedef pthread_mutex_t osal_ipclock_t;
|
||||
|
||||
#elif MDBX_LOCKING == MDBX_LOCKING_POSIX1988
|
||||
|
||||
#define MDBX_LCK_SIGN UINT32_C(0xFC29)
|
||||
typedef sem_t osal_ipclock_t;
|
||||
|
||||
#else
|
||||
#error "FIXME"
|
||||
#endif /* MDBX_LOCKING */
|
||||
|
||||
/* Статистика профилирования работы GC */
|
||||
typedef struct gc_prof_stat {
|
||||
/* Монотонное время по "настенным часам"
|
||||
* затраченное на чтение и поиск внутри GC */
|
||||
uint64_t rtime_monotonic;
|
||||
/* Процессорное время в режим пользователя
|
||||
* на подготовку страниц извлекаемых из GC, включая подкачку с диска. */
|
||||
uint64_t xtime_cpu;
|
||||
/* Количество итераций чтения-поиска внутри GC при выделении страниц */
|
||||
uint32_t rsteps;
|
||||
/* Количество запросов на выделение последовательностей страниц,
|
||||
* т.е. когда запрашивает выделение больше одной страницы */
|
||||
uint32_t xpages;
|
||||
/* Счетчик выполнения по медленному пути (slow path execution count) */
|
||||
uint32_t spe_counter;
|
||||
/* page faults (hard page faults) */
|
||||
uint32_t majflt;
|
||||
/* Для разборок с pnl_merge() */
|
||||
struct {
|
||||
uint64_t time;
|
||||
uint64_t volume;
|
||||
uint32_t calls;
|
||||
} pnl_merge;
|
||||
} gc_prof_stat_t;
|
||||
|
||||
/* Statistics of pages operations for all transactions,
|
||||
* including incomplete and aborted. */
|
||||
typedef struct pgops {
|
||||
mdbx_atomic_uint64_t newly; /* Quantity of a new pages added */
|
||||
mdbx_atomic_uint64_t cow; /* Quantity of pages copied for update */
|
||||
mdbx_atomic_uint64_t clone; /* Quantity of parent's dirty pages clones
|
||||
for nested transactions */
|
||||
mdbx_atomic_uint64_t split; /* Page splits */
|
||||
mdbx_atomic_uint64_t merge; /* Page merges */
|
||||
mdbx_atomic_uint64_t spill; /* Quantity of spilled dirty pages */
|
||||
mdbx_atomic_uint64_t unspill; /* Quantity of unspilled/reloaded pages */
|
||||
mdbx_atomic_uint64_t wops; /* Number of explicit write operations (not a pages) to a disk */
|
||||
mdbx_atomic_uint64_t msync; /* Number of explicit msync/flush-to-disk operations */
|
||||
mdbx_atomic_uint64_t fsync; /* Number of explicit fsync/flush-to-disk operations */
|
||||
|
||||
mdbx_atomic_uint64_t prefault; /* Number of prefault write operations */
|
||||
mdbx_atomic_uint64_t mincore; /* Number of mincore() calls */
|
||||
|
||||
mdbx_atomic_uint32_t incoherence; /* number of https://libmdbx.dqdkfa.ru/dead-github/issues/269
|
||||
caught */
|
||||
mdbx_atomic_uint32_t reserved;
|
||||
|
||||
/* Статистика для профилирования GC.
|
||||
* Логически эти данные, возможно, стоит вынести в другую структуру,
|
||||
* но разница будет сугубо косметическая. */
|
||||
struct {
|
||||
/* Затраты на поддержку данных пользователя */
|
||||
gc_prof_stat_t work;
|
||||
/* Затраты на поддержку и обновления самой GC */
|
||||
gc_prof_stat_t self;
|
||||
/* Итераций обновления GC,
|
||||
* больше 1 если были повторы/перезапуски */
|
||||
uint32_t wloops;
|
||||
/* Итерации слияния записей GC */
|
||||
uint32_t coalescences;
|
||||
/* Уничтожения steady-точек фиксации в MDBX_UTTERLY_NOSYNC */
|
||||
uint32_t wipes;
|
||||
/* Сбросы данные на диск вне MDBX_UTTERLY_NOSYNC */
|
||||
uint32_t flushes;
|
||||
/* Попытки пнуть тормозящих читателей */
|
||||
uint32_t kicks;
|
||||
} gc_prof;
|
||||
} pgop_stat_t;
|
||||
|
||||
/* Reader Lock Table
|
||||
*
|
||||
* Readers don't acquire any locks for their data access. Instead, they
|
||||
* simply record their transaction ID in the reader table. The reader
|
||||
* mutex is needed just to find an empty slot in the reader table. The
|
||||
* slot's address is saved in thread-specific data so that subsequent
|
||||
* read transactions started by the same thread need no further locking to
|
||||
* proceed.
|
||||
*
|
||||
* If MDBX_NOSTICKYTHREADS is set, the slot address is not saved in
|
||||
* thread-specific data. No reader table is used if the database is on a
|
||||
* read-only filesystem.
|
||||
*
|
||||
* Since the database uses multi-version concurrency control, readers don't
|
||||
* actually need any locking. This table is used to keep track of which
|
||||
* readers are using data from which old transactions, so that we'll know
|
||||
* when a particular old transaction is no longer in use. Old transactions
|
||||
* that have discarded any data pages can then have those pages reclaimed
|
||||
* for use by a later write transaction.
|
||||
*
|
||||
* The lock table is constructed such that reader slots are aligned with the
|
||||
* processor's cache line size. Any slot is only ever used by one thread.
|
||||
* This alignment guarantees that there will be no contention or cache
|
||||
* thrashing as threads update their own slot info, and also eliminates
|
||||
* any need for locking when accessing a slot.
|
||||
*
|
||||
* A writer thread will scan every slot in the table to determine the oldest
|
||||
* outstanding reader transaction. Any freed pages older than this will be
|
||||
* reclaimed by the writer. The writer doesn't use any locks when scanning
|
||||
* this table. This means that there's no guarantee that the writer will
|
||||
* see the most up-to-date reader info, but that's not required for correct
|
||||
* operation - all we need is to know the upper bound on the oldest reader,
|
||||
* we don't care at all about the newest reader. So the only consequence of
|
||||
* reading stale information here is that old pages might hang around a
|
||||
* while longer before being reclaimed. That's actually good anyway, because
|
||||
* the longer we delay reclaiming old pages, the more likely it is that a
|
||||
* string of contiguous pages can be found after coalescing old pages from
|
||||
* many old transactions together. */
|
||||
|
||||
/* The actual reader record, with cacheline padding. */
|
||||
typedef struct reader_slot {
|
||||
/* Current Transaction ID when this transaction began, or INVALID_TXNID.
|
||||
* Multiple readers that start at the same time will probably have the
|
||||
* same ID here. Again, it's not important to exclude them from
|
||||
* anything; all we need to know is which version of the DB they
|
||||
* started from so we can avoid overwriting any data used in that
|
||||
* particular version. */
|
||||
atomic_txnid_t txnid;
|
||||
|
||||
/* The information we store in a single slot of the reader table.
|
||||
* In addition to a transaction ID, we also record the process and
|
||||
* thread ID that owns a slot, so that we can detect stale information,
|
||||
* e.g. threads or processes that went away without cleaning up.
|
||||
*
|
||||
* NOTE: We currently don't check for stale records.
|
||||
* We simply re-init the table when we know that we're the only process
|
||||
* opening the lock file. */
|
||||
|
||||
/* Псевдо thread_id для пометки вытесненных читающих транзакций. */
|
||||
#define MDBX_TID_TXN_OUSTED (UINT64_MAX - 1)
|
||||
|
||||
/* Псевдо thread_id для пометки припаркованных читающих транзакций. */
|
||||
#define MDBX_TID_TXN_PARKED UINT64_MAX
|
||||
|
||||
/* The thread ID of the thread owning this txn. */
|
||||
mdbx_atomic_uint64_t tid;
|
||||
|
||||
/* The process ID of the process owning this reader txn. */
|
||||
mdbx_atomic_uint32_t pid;
|
||||
|
||||
/* The number of pages used in the reader's MVCC snapshot,
|
||||
* i.e. the value of meta->geometry.first_unallocated and
|
||||
* txn->geo.first_unallocated */
|
||||
atomic_pgno_t snapshot_pages_used;
|
||||
/* Number of retired pages at the time this reader starts transaction. So,
|
||||
* at any time the difference meta.pages_retired -
|
||||
* reader.snapshot_pages_retired will give the number of pages which this
|
||||
* reader restraining from reuse. */
|
||||
mdbx_atomic_uint64_t snapshot_pages_retired;
|
||||
} reader_slot_t;
|
||||
|
||||
/* The header for the reader table (a memory-mapped lock file). */
|
||||
typedef struct shared_lck {
|
||||
/* Stamp identifying this as an MDBX file.
|
||||
* It must be set to MDBX_MAGIC with MDBX_LOCK_VERSION. */
|
||||
uint64_t magic_and_version;
|
||||
|
||||
/* Format of this lock file. Must be set to MDBX_LOCK_FORMAT. */
|
||||
uint32_t os_and_format;
|
||||
|
||||
/* Flags which environment was opened. */
|
||||
mdbx_atomic_uint32_t envmode;
|
||||
|
||||
/* Threshold of un-synced-with-disk pages for auto-sync feature,
|
||||
* zero means no-threshold, i.e. auto-sync is disabled. */
|
||||
atomic_pgno_t autosync_threshold;
|
||||
|
||||
/* Low 32-bit of txnid with which meta-pages was synced,
|
||||
* i.e. for sync-polling in the MDBX_NOMETASYNC mode. */
|
||||
#define MDBX_NOMETASYNC_LAZY_UNK (UINT32_MAX / 3)
|
||||
#define MDBX_NOMETASYNC_LAZY_FD (MDBX_NOMETASYNC_LAZY_UNK + UINT32_MAX / 8)
|
||||
#define MDBX_NOMETASYNC_LAZY_WRITEMAP (MDBX_NOMETASYNC_LAZY_UNK - UINT32_MAX / 8)
|
||||
mdbx_atomic_uint32_t meta_sync_txnid;
|
||||
|
||||
/* Period for timed auto-sync feature, i.e. at the every steady checkpoint
|
||||
* the mti_unsynced_timeout sets to the current_time + autosync_period.
|
||||
* The time value is represented in a suitable system-dependent form, for
|
||||
* example clock_gettime(CLOCK_BOOTTIME) or clock_gettime(CLOCK_MONOTONIC).
|
||||
* Zero means timed auto-sync is disabled. */
|
||||
mdbx_atomic_uint64_t autosync_period;
|
||||
|
||||
/* Marker to distinguish uniqueness of DB/CLK. */
|
||||
mdbx_atomic_uint64_t bait_uniqueness;
|
||||
|
||||
/* Paired counter of processes that have mlock()ed part of mmapped DB.
|
||||
* The (mlcnt[0] - mlcnt[1]) > 0 means at least one process
|
||||
* lock at least one page, so therefore madvise() could return EINVAL. */
|
||||
mdbx_atomic_uint32_t mlcnt[2];
|
||||
|
||||
MDBX_ALIGNAS(MDBX_CACHELINE_SIZE) /* cacheline ----------------------------*/
|
||||
|
||||
/* Statistics of costly ops of all (running, completed and aborted)
|
||||
* transactions */
|
||||
pgop_stat_t pgops;
|
||||
|
||||
MDBX_ALIGNAS(MDBX_CACHELINE_SIZE) /* cacheline ----------------------------*/
|
||||
|
||||
#if MDBX_LOCKING > 0
|
||||
/* Write transaction lock. */
|
||||
osal_ipclock_t wrt_lock;
|
||||
#endif /* MDBX_LOCKING > 0 */
|
||||
|
||||
atomic_txnid_t cached_oldest;
|
||||
|
||||
/* Timestamp of entering an out-of-sync state. Value is represented in a
|
||||
* suitable system-dependent form, for example clock_gettime(CLOCK_BOOTTIME)
|
||||
* or clock_gettime(CLOCK_MONOTONIC). */
|
||||
mdbx_atomic_uint64_t eoos_timestamp;
|
||||
|
||||
/* Number un-synced-with-disk pages for auto-sync feature. */
|
||||
mdbx_atomic_uint64_t unsynced_pages;
|
||||
|
||||
/* Timestamp of the last readers check. */
|
||||
mdbx_atomic_uint64_t readers_check_timestamp;
|
||||
|
||||
/* Number of page which was discarded last time by madvise(DONTNEED). */
|
||||
atomic_pgno_t discarded_tail;
|
||||
|
||||
/* Shared anchor for tracking readahead edge and enabled/disabled status. */
|
||||
pgno_t readahead_anchor;
|
||||
|
||||
/* Shared cache for mincore() results */
|
||||
struct {
|
||||
pgno_t begin[4];
|
||||
uint64_t mask[4];
|
||||
} mincore_cache;
|
||||
|
||||
MDBX_ALIGNAS(MDBX_CACHELINE_SIZE) /* cacheline ----------------------------*/
|
||||
|
||||
#if MDBX_LOCKING > 0
|
||||
/* Readeaders table lock. */
|
||||
osal_ipclock_t rdt_lock;
|
||||
#endif /* MDBX_LOCKING > 0 */
|
||||
|
||||
/* The number of slots that have been used in the reader table.
|
||||
* This always records the maximum count, it is not decremented
|
||||
* when readers release their slots. */
|
||||
mdbx_atomic_uint32_t rdt_length;
|
||||
mdbx_atomic_uint32_t rdt_refresh_flag;
|
||||
|
||||
#if FLEXIBLE_ARRAY_MEMBERS
|
||||
MDBX_ALIGNAS(MDBX_CACHELINE_SIZE) /* cacheline ----------------------------*/
|
||||
reader_slot_t rdt[] /* dynamic size */;
|
||||
|
||||
/* Lockfile format signature: version, features and field layout */
|
||||
#define MDBX_LOCK_FORMAT \
|
||||
(MDBX_LCK_SIGN * 27733 + (unsigned)sizeof(reader_slot_t) * 13 + \
|
||||
(unsigned)offsetof(reader_slot_t, snapshot_pages_used) * 251 + (unsigned)offsetof(lck_t, cached_oldest) * 83 + \
|
||||
(unsigned)offsetof(lck_t, rdt_length) * 37 + (unsigned)offsetof(lck_t, rdt) * 29)
|
||||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||||
} lck_t;
|
||||
|
||||
#define MDBX_LOCK_MAGIC ((MDBX_MAGIC << 8) + MDBX_LOCK_VERSION)
|
||||
|
||||
#define MDBX_READERS_LIMIT 32767
|
1017
src/lck-posix.c
1017
src/lck-posix.c
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
174
src/lck.c
Normal file
174
src/lck.c
Normal file
@ -0,0 +1,174 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
__cold static int lck_setup_locked(MDBX_env *env) {
|
||||
int err = rthc_register(env);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
|
||||
int lck_seize_rc = lck_seize(env);
|
||||
if (unlikely(MDBX_IS_ERROR(lck_seize_rc)))
|
||||
return lck_seize_rc;
|
||||
|
||||
if (env->lck_mmap.fd == INVALID_HANDLE_VALUE) {
|
||||
env->lck = lckless_stub(env);
|
||||
env->max_readers = UINT_MAX;
|
||||
DEBUG("lck-setup:%s%s%s", " lck-less", (env->flags & MDBX_RDONLY) ? " readonly" : "",
|
||||
(lck_seize_rc == MDBX_RESULT_TRUE) ? " exclusive" : " cooperative");
|
||||
return lck_seize_rc;
|
||||
}
|
||||
|
||||
DEBUG("lck-setup:%s%s%s", " with-lck", (env->flags & MDBX_RDONLY) ? " readonly" : "",
|
||||
(lck_seize_rc == MDBX_RESULT_TRUE) ? " exclusive" : " cooperative");
|
||||
|
||||
MDBX_env *inprocess_neighbor = nullptr;
|
||||
err = rthc_uniq_check(&env->lck_mmap, &inprocess_neighbor);
|
||||
if (unlikely(MDBX_IS_ERROR(err)))
|
||||
return err;
|
||||
if (inprocess_neighbor) {
|
||||
if ((globals.runtime_flags & MDBX_DBG_LEGACY_MULTIOPEN) == 0 || (inprocess_neighbor->flags & MDBX_EXCLUSIVE) != 0)
|
||||
return MDBX_BUSY;
|
||||
if (lck_seize_rc == MDBX_RESULT_TRUE) {
|
||||
err = lck_downgrade(env);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
lck_seize_rc = MDBX_RESULT_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t size = 0;
|
||||
err = osal_filesize(env->lck_mmap.fd, &size);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
|
||||
if (lck_seize_rc == MDBX_RESULT_TRUE) {
|
||||
size = ceil_powerof2(env->max_readers * sizeof(reader_slot_t) + sizeof(lck_t), globals.sys_pagesize);
|
||||
jitter4testing(false);
|
||||
} else {
|
||||
if (env->flags & MDBX_EXCLUSIVE)
|
||||
return MDBX_BUSY;
|
||||
if (size > INT_MAX || (size & (globals.sys_pagesize - 1)) != 0 || size < globals.sys_pagesize) {
|
||||
ERROR("lck-file has invalid size %" PRIu64 " bytes", size);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t maxreaders = ((size_t)size - sizeof(lck_t)) / sizeof(reader_slot_t);
|
||||
if (maxreaders < 4) {
|
||||
ERROR("lck-size too small (up to %" PRIuPTR " readers)", maxreaders);
|
||||
return MDBX_PROBLEM;
|
||||
}
|
||||
env->max_readers = (maxreaders <= MDBX_READERS_LIMIT) ? (unsigned)maxreaders : (unsigned)MDBX_READERS_LIMIT;
|
||||
|
||||
err =
|
||||
osal_mmap((env->flags & MDBX_EXCLUSIVE) | MDBX_WRITEMAP, &env->lck_mmap, (size_t)size, (size_t)size,
|
||||
lck_seize_rc ? MMAP_OPTION_TRUNCATE | MMAP_OPTION_SEMAPHORE : MMAP_OPTION_SEMAPHORE, env->pathname.lck);
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
return err;
|
||||
|
||||
#ifdef MADV_DODUMP
|
||||
err = madvise(env->lck_mmap.lck, size, MADV_DODUMP) ? ignore_enosys_and_eagain(errno) : MDBX_SUCCESS;
|
||||
if (unlikely(MDBX_IS_ERROR(err)))
|
||||
return err;
|
||||
#endif /* MADV_DODUMP */
|
||||
|
||||
#ifdef MADV_WILLNEED
|
||||
err = madvise(env->lck_mmap.lck, size, MADV_WILLNEED) ? ignore_enosys_and_eagain(errno) : MDBX_SUCCESS;
|
||||
if (unlikely(MDBX_IS_ERROR(err)))
|
||||
return err;
|
||||
#elif defined(POSIX_MADV_WILLNEED)
|
||||
err = ignore_enosys(posix_madvise(env->lck_mmap.lck, size, POSIX_MADV_WILLNEED));
|
||||
if (unlikely(MDBX_IS_ERROR(err)))
|
||||
return err;
|
||||
#endif /* MADV_WILLNEED */
|
||||
|
||||
lck_t *lck = env->lck_mmap.lck;
|
||||
if (lck_seize_rc == MDBX_RESULT_TRUE) {
|
||||
/* If we succeed got exclusive lock, then nobody is using the lock region
|
||||
* and we should initialize it. */
|
||||
memset(lck, 0, (size_t)size);
|
||||
jitter4testing(false);
|
||||
lck->magic_and_version = MDBX_LOCK_MAGIC;
|
||||
lck->os_and_format = MDBX_LOCK_FORMAT;
|
||||
#if MDBX_ENABLE_PGOP_STAT
|
||||
lck->pgops.wops.weak = 1;
|
||||
#endif /* MDBX_ENABLE_PGOP_STAT */
|
||||
err = osal_msync(&env->lck_mmap, 0, (size_t)size, MDBX_SYNC_DATA | MDBX_SYNC_SIZE);
|
||||
if (unlikely(err != MDBX_SUCCESS)) {
|
||||
ERROR("initial-%s for lck-file failed, err %d", "msync/fsync", err);
|
||||
eASSERT(env, MDBX_IS_ERROR(err));
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
if (lck->magic_and_version != MDBX_LOCK_MAGIC) {
|
||||
const bool invalid = (lck->magic_and_version >> 8) != MDBX_MAGIC;
|
||||
ERROR("lock region has %s", invalid ? "invalid magic"
|
||||
: "incompatible version (only applications with nearly or the "
|
||||
"same versions of libmdbx can share the same database)");
|
||||
return invalid ? MDBX_INVALID : MDBX_VERSION_MISMATCH;
|
||||
}
|
||||
if (lck->os_and_format != MDBX_LOCK_FORMAT) {
|
||||
ERROR("lock region has os/format signature 0x%" PRIx32 ", expected 0x%" PRIx32, lck->os_and_format,
|
||||
MDBX_LOCK_FORMAT);
|
||||
return MDBX_VERSION_MISMATCH;
|
||||
}
|
||||
}
|
||||
|
||||
err = lck_init(env, inprocess_neighbor, lck_seize_rc);
|
||||
if (unlikely(err != MDBX_SUCCESS)) {
|
||||
eASSERT(env, MDBX_IS_ERROR(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
env->lck = lck;
|
||||
eASSERT(env, !MDBX_IS_ERROR(lck_seize_rc));
|
||||
return lck_seize_rc;
|
||||
}
|
||||
|
||||
__cold int lck_setup(MDBX_env *env, mdbx_mode_t mode) {
|
||||
eASSERT(env, env->lazy_fd != INVALID_HANDLE_VALUE);
|
||||
eASSERT(env, env->lck_mmap.fd == INVALID_HANDLE_VALUE);
|
||||
|
||||
int err = osal_openfile(MDBX_OPEN_LCK, env, env->pathname.lck, &env->lck_mmap.fd, mode);
|
||||
if (err != MDBX_SUCCESS) {
|
||||
switch (err) {
|
||||
case MDBX_EACCESS:
|
||||
case MDBX_EPERM:
|
||||
if (F_ISSET(env->flags, MDBX_RDONLY | MDBX_EXCLUSIVE))
|
||||
break;
|
||||
__fallthrough /* fall through */;
|
||||
case MDBX_ENOFILE:
|
||||
case MDBX_EROFS:
|
||||
if (env->flags & MDBX_RDONLY) {
|
||||
/* ENSURE the file system is read-only */
|
||||
int err_rofs = osal_check_fs_rdonly(env->lazy_fd, env->pathname.lck, err);
|
||||
if (err_rofs == MDBX_SUCCESS ||
|
||||
/* ignore ERROR_NOT_SUPPORTED for exclusive mode */
|
||||
(err_rofs == MDBX_ENOSYS && (env->flags & MDBX_EXCLUSIVE)))
|
||||
break;
|
||||
if (err_rofs != MDBX_ENOSYS)
|
||||
err = err_rofs;
|
||||
}
|
||||
__fallthrough /* fall through */;
|
||||
default:
|
||||
ERROR("unable to open lck-file %" MDBX_PRIsPATH ", env-flags 0x%X, err %d", env->pathname.lck, env->flags, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* LY: without-lck mode (e.g. exclusive or on read-only filesystem) */
|
||||
env->lck_mmap.fd = INVALID_HANDLE_VALUE;
|
||||
NOTICE("continue %" MDBX_PRIsPATH " within without-lck mode, env-flags 0x%X, lck-error %d", env->pathname.dxb,
|
||||
env->flags, err);
|
||||
}
|
||||
|
||||
rthc_lock();
|
||||
err = lck_setup_locked(env);
|
||||
rthc_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
void mincore_clean_cache(const MDBX_env *const env) {
|
||||
memset(env->lck->mincore_cache.begin, -1, sizeof(env->lck->mincore_cache.begin));
|
||||
}
|
110
src/lck.h
Normal file
110
src/lck.h
Normal file
@ -0,0 +1,110 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
MDBX_INTERNAL int lck_setup(MDBX_env *env, mdbx_mode_t mode);
|
||||
#if MDBX_LOCKING > MDBX_LOCKING_SYSV
|
||||
MDBX_INTERNAL int lck_ipclock_stubinit(osal_ipclock_t *ipc);
|
||||
MDBX_INTERNAL int lck_ipclock_destroy(osal_ipclock_t *ipc);
|
||||
#endif /* MDBX_LOCKING > MDBX_LOCKING_SYSV */
|
||||
|
||||
/// \brief Initialization of synchronization primitives linked with MDBX_env
|
||||
/// instance both in LCK-file and within the current process.
|
||||
/// \param
|
||||
/// global_uniqueness_flag = true - denotes that there are no other processes
|
||||
/// working with DB and LCK-file. Thus the function MUST initialize
|
||||
/// shared synchronization objects in memory-mapped LCK-file.
|
||||
/// global_uniqueness_flag = false - denotes that at least one process is
|
||||
/// already working with DB and LCK-file, including the case when DB
|
||||
/// has already been opened in the current process. Thus the function
|
||||
/// MUST NOT initialize shared synchronization objects in memory-mapped
|
||||
/// LCK-file that are already in use.
|
||||
/// \return Error code or zero on success.
|
||||
MDBX_INTERNAL int lck_init(MDBX_env *env, MDBX_env *inprocess_neighbor, int global_uniqueness_flag);
|
||||
|
||||
/// \brief Disconnects from shared interprocess objects and destructs
|
||||
/// synchronization objects linked with MDBX_env instance
|
||||
/// within the current process.
|
||||
/// \param
|
||||
/// inprocess_neighbor = nullptr - if the current process does not have other
|
||||
/// instances of MDBX_env linked with the DB being closed.
|
||||
/// Thus the function MUST check for other processes working with DB or
|
||||
/// LCK-file, and keep or destroy shared synchronization objects in
|
||||
/// memory-mapped LCK-file depending on the result.
|
||||
/// inprocess_neighbor = not-nullptr - pointer to another instance of MDBX_env
|
||||
/// (anyone of there is several) working with DB or LCK-file within the
|
||||
/// current process. Thus the function MUST NOT try to acquire exclusive
|
||||
/// lock and/or try to destruct shared synchronization objects linked with
|
||||
/// DB or LCK-file. Moreover, the implementation MUST ensure correct work
|
||||
/// of other instances of MDBX_env within the current process, e.g.
|
||||
/// restore POSIX-fcntl locks after the closing of file descriptors.
|
||||
/// \return Error code (MDBX_PANIC) or zero on success.
|
||||
MDBX_INTERNAL int lck_destroy(MDBX_env *env, MDBX_env *inprocess_neighbor, const uint32_t current_pid);
|
||||
|
||||
/// \brief Connects to shared interprocess locking objects and tries to acquire
|
||||
/// the maximum lock level (shared if exclusive is not available)
|
||||
/// Depending on implementation or/and platform (Windows) this function may
|
||||
/// acquire the non-OS super-level lock (e.g. for shared synchronization
|
||||
/// objects initialization), which will be downgraded to OS-exclusive or
|
||||
/// shared via explicit calling of lck_downgrade().
|
||||
/// \return
|
||||
/// MDBX_RESULT_TRUE (-1) - if an exclusive lock was acquired and thus
|
||||
/// the current process is the first and only after the last use of DB.
|
||||
/// MDBX_RESULT_FALSE (0) - if a shared lock was acquired and thus
|
||||
/// DB has already been opened and now is used by other processes.
|
||||
/// Otherwise (not 0 and not -1) - error code.
|
||||
MDBX_INTERNAL int lck_seize(MDBX_env *env);
|
||||
|
||||
/// \brief Downgrades the level of initially acquired lock to
|
||||
/// operational level specified by argument. The reason for such downgrade:
|
||||
/// - unblocking of other processes that are waiting for access, i.e.
|
||||
/// if (env->flags & MDBX_EXCLUSIVE) != 0, then other processes
|
||||
/// should be made aware that access is unavailable rather than
|
||||
/// wait for it.
|
||||
/// - freeing locks that interfere file operation (especially for Windows)
|
||||
/// (env->flags & MDBX_EXCLUSIVE) == 0 - downgrade to shared lock.
|
||||
/// (env->flags & MDBX_EXCLUSIVE) != 0 - downgrade to exclusive
|
||||
/// operational lock.
|
||||
/// \return Error code or zero on success
|
||||
MDBX_INTERNAL int lck_downgrade(MDBX_env *env);
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL int lck_upgrade(MDBX_env *env, bool dont_wait);
|
||||
|
||||
/// \brief Locks LCK-file or/and table of readers for (de)registering.
|
||||
/// \return Error code or zero on success
|
||||
MDBX_INTERNAL int lck_rdt_lock(MDBX_env *env);
|
||||
|
||||
/// \brief Unlocks LCK-file or/and table of readers after (de)registering.
|
||||
MDBX_INTERNAL void lck_rdt_unlock(MDBX_env *env);
|
||||
|
||||
/// \brief Acquires write-transaction lock.
|
||||
/// \return Error code or zero on success
|
||||
MDBX_INTERNAL int lck_txn_lock(MDBX_env *env, bool dont_wait);
|
||||
|
||||
/// \brief Releases write-transaction lock..
|
||||
MDBX_INTERNAL void lck_txn_unlock(MDBX_env *env);
|
||||
|
||||
/// \brief Sets alive-flag of reader presence (indicative lock) for PID of
|
||||
/// the current process. The function does no more than needed for
|
||||
/// the correct working of lck_rpid_check() in other processes.
|
||||
/// \return Error code or zero on success
|
||||
MDBX_INTERNAL int lck_rpid_set(MDBX_env *env);
|
||||
|
||||
/// \brief Resets alive-flag of reader presence (indicative lock)
|
||||
/// for PID of the current process. The function does no more than needed
|
||||
/// for the correct working of lck_rpid_check() in other processes.
|
||||
/// \return Error code or zero on success
|
||||
MDBX_INTERNAL int lck_rpid_clear(MDBX_env *env);
|
||||
|
||||
/// \brief Checks for reading process status with the given pid with help of
|
||||
/// alive-flag of presence (indicative lock) or using another way.
|
||||
/// \return
|
||||
/// MDBX_RESULT_TRUE (-1) - if the reader process with the given PID is alive
|
||||
/// and working with DB (indicative lock is present).
|
||||
/// MDBX_RESULT_FALSE (0) - if the reader process with the given PID is absent
|
||||
/// or not working with DB (indicative lock is not present).
|
||||
/// Otherwise (not 0 and not -1) - error code.
|
||||
MDBX_INTERNAL int lck_rpid_check(MDBX_env *env, uint32_t pid);
|
250
src/logging_and_debug.c
Normal file
250
src/logging_and_debug.c
Normal file
@ -0,0 +1,250 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
__cold void debug_log_va(int level, const char *function, int line, const char *fmt, va_list args) {
|
||||
ENSURE(nullptr, osal_fastmutex_acquire(&globals.debug_lock) == 0);
|
||||
if (globals.logger.ptr) {
|
||||
if (globals.logger_buffer == nullptr)
|
||||
globals.logger.fmt(level, function, line, fmt, args);
|
||||
else {
|
||||
const int len = vsnprintf(globals.logger_buffer, globals.logger_buffer_size, fmt, args);
|
||||
if (len > 0)
|
||||
globals.logger.nofmt(level, function, line, globals.logger_buffer, len);
|
||||
}
|
||||
} else {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if (IsDebuggerPresent()) {
|
||||
int prefix_len = 0;
|
||||
char *prefix = nullptr;
|
||||
if (function && line > 0)
|
||||
prefix_len = osal_asprintf(&prefix, "%s:%d ", function, line);
|
||||
else if (function)
|
||||
prefix_len = osal_asprintf(&prefix, "%s: ", function);
|
||||
else if (line > 0)
|
||||
prefix_len = osal_asprintf(&prefix, "%d: ", line);
|
||||
if (prefix_len > 0 && prefix) {
|
||||
OutputDebugStringA(prefix);
|
||||
osal_free(prefix);
|
||||
}
|
||||
char *msg = nullptr;
|
||||
int msg_len = osal_vasprintf(&msg, fmt, args);
|
||||
if (msg_len > 0 && msg) {
|
||||
OutputDebugStringA(msg);
|
||||
osal_free(msg);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (function && line > 0)
|
||||
fprintf(stderr, "%s:%d ", function, line);
|
||||
else if (function)
|
||||
fprintf(stderr, "%s: ", function);
|
||||
else if (line > 0)
|
||||
fprintf(stderr, "%d: ", line);
|
||||
vfprintf(stderr, fmt, args);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
}
|
||||
ENSURE(nullptr, osal_fastmutex_release(&globals.debug_lock) == 0);
|
||||
}
|
||||
|
||||
__cold void debug_log(int level, const char *function, int line, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
debug_log_va(level, function, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
__cold void log_error(const int err, const char *func, unsigned line) {
|
||||
assert(err != MDBX_SUCCESS);
|
||||
if (unlikely(globals.loglevel >= MDBX_LOG_DEBUG)) {
|
||||
const bool is_error = err != MDBX_RESULT_TRUE && err != MDBX_NOTFOUND;
|
||||
char buf[256];
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump a val in ascii or hexadecimal. */
|
||||
__cold const char *mdbx_dump_val(const MDBX_val *val, char *const buf, const size_t bufsize) {
|
||||
if (!val)
|
||||
return "<null>";
|
||||
if (!val->iov_len)
|
||||
return "<empty>";
|
||||
if (!buf || bufsize < 4)
|
||||
return nullptr;
|
||||
|
||||
if (!val->iov_base) {
|
||||
int len = snprintf(buf, bufsize, "<nullptr.%zu>", val->iov_len);
|
||||
assert(len > 0 && (size_t)len < bufsize);
|
||||
(void)len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool is_ascii = true;
|
||||
const uint8_t *const data = val->iov_base;
|
||||
for (size_t i = 0; i < val->iov_len; i++)
|
||||
if (data[i] < ' ' || data[i] > '~') {
|
||||
is_ascii = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_ascii) {
|
||||
int len = snprintf(buf, bufsize, "%.*s", (val->iov_len > INT_MAX) ? INT_MAX : (int)val->iov_len, data);
|
||||
assert(len > 0 && (size_t)len < bufsize);
|
||||
(void)len;
|
||||
} else {
|
||||
char *const detent = buf + bufsize - 2;
|
||||
char *ptr = buf;
|
||||
*ptr++ = '<';
|
||||
for (size_t i = 0; i < val->iov_len && ptr < detent; i++) {
|
||||
const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
*ptr++ = hex[data[i] >> 4];
|
||||
*ptr++ = hex[data[i] & 15];
|
||||
}
|
||||
if (ptr < detent)
|
||||
*ptr++ = '>';
|
||||
*ptr = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
LY: debug stuff */
|
||||
|
||||
__cold const char *pagetype_caption(const uint8_t type, char buf4unknown[16]) {
|
||||
switch (type) {
|
||||
case P_BRANCH:
|
||||
return "branch";
|
||||
case P_LEAF:
|
||||
return "leaf";
|
||||
case P_LEAF | P_SUBP:
|
||||
return "subleaf";
|
||||
case P_LEAF | P_DUPFIX:
|
||||
return "dupfix-leaf";
|
||||
case P_LEAF | P_DUPFIX | P_SUBP:
|
||||
return "dupfix-subleaf";
|
||||
case P_LEAF | P_DUPFIX | P_SUBP | P_LEGACY_DIRTY:
|
||||
return "dupfix-subleaf.legacy-dirty";
|
||||
case P_LARGE:
|
||||
return "large";
|
||||
default:
|
||||
snprintf(buf4unknown, 16, "unknown_0x%x", type);
|
||||
return buf4unknown;
|
||||
}
|
||||
}
|
||||
|
||||
__cold static const char *leafnode_type(node_t *n) {
|
||||
static const char *const tp[2][2] = {{"", ": DB"}, {": sub-page", ": sub-DB"}};
|
||||
return (node_flags(n) & N_BIG) ? ": large page" : tp[!!(node_flags(n) & N_DUP)][!!(node_flags(n) & N_TREE)];
|
||||
}
|
||||
|
||||
/* Display all the keys in the page. */
|
||||
__cold void page_list(page_t *mp) {
|
||||
pgno_t pgno = mp->pgno;
|
||||
const char *type;
|
||||
node_t *node;
|
||||
size_t i, nkeys, nsize, total = 0;
|
||||
MDBX_val key;
|
||||
DKBUF;
|
||||
|
||||
switch (page_type(mp)) {
|
||||
case P_BRANCH:
|
||||
type = "Branch page";
|
||||
break;
|
||||
case P_LEAF:
|
||||
type = "Leaf page";
|
||||
break;
|
||||
case P_LEAF | P_SUBP:
|
||||
type = "Leaf sub-page";
|
||||
break;
|
||||
case P_LEAF | P_DUPFIX:
|
||||
type = "Leaf2 page";
|
||||
break;
|
||||
case P_LEAF | P_DUPFIX | P_SUBP:
|
||||
type = "Leaf2 sub-page";
|
||||
break;
|
||||
case P_LARGE:
|
||||
VERBOSE("Overflow page %" PRIaPGNO " pages %u\n", pgno, mp->pages);
|
||||
return;
|
||||
case P_META:
|
||||
VERBOSE("Meta-page %" PRIaPGNO " txnid %" PRIu64 "\n", pgno, unaligned_peek_u64(4, page_meta(mp)->txnid_a));
|
||||
return;
|
||||
default:
|
||||
VERBOSE("Bad page %" PRIaPGNO " flags 0x%X\n", pgno, mp->flags);
|
||||
return;
|
||||
}
|
||||
|
||||
nkeys = page_numkeys(mp);
|
||||
VERBOSE("%s %" PRIaPGNO " numkeys %zu\n", type, pgno, nkeys);
|
||||
|
||||
for (i = 0; i < nkeys; i++) {
|
||||
if (is_dupfix_leaf(mp)) { /* DUPFIX pages have no entries[] or node headers */
|
||||
key = page_dupfix_key(mp, i, nsize = mp->dupfix_ksize);
|
||||
total += nsize;
|
||||
VERBOSE("key %zu: nsize %zu, %s\n", i, nsize, DKEY(&key));
|
||||
continue;
|
||||
}
|
||||
node = page_node(mp, i);
|
||||
key.iov_len = node_ks(node);
|
||||
key.iov_base = node->payload;
|
||||
nsize = NODESIZE + key.iov_len;
|
||||
if (is_branch(mp)) {
|
||||
VERBOSE("key %zu: page %" PRIaPGNO ", %s\n", i, node_pgno(node), DKEY(&key));
|
||||
total += nsize;
|
||||
} else {
|
||||
if (node_flags(node) & N_BIG)
|
||||
nsize += sizeof(pgno_t);
|
||||
else
|
||||
nsize += node_ds(node);
|
||||
total += nsize;
|
||||
nsize += sizeof(indx_t);
|
||||
VERBOSE("key %zu: nsize %zu, %s%s\n", i, nsize, DKEY(&key), leafnode_type(node));
|
||||
}
|
||||
total = EVEN_CEIL(total);
|
||||
}
|
||||
VERBOSE("Total: header %u + contents %zu + unused %zu\n", is_dupfix_leaf(mp) ? PAGEHDRSZ : PAGEHDRSZ + mp->lower,
|
||||
total, page_room(mp));
|
||||
}
|
||||
|
||||
__cold static int setup_debug(MDBX_log_level_t level, MDBX_debug_flags_t flags, union logger_union logger, char *buffer,
|
||||
size_t buffer_size) {
|
||||
ENSURE(nullptr, osal_fastmutex_acquire(&globals.debug_lock) == 0);
|
||||
|
||||
const int rc = globals.runtime_flags | (globals.loglevel << 16);
|
||||
if (level != MDBX_LOG_DONTCHANGE)
|
||||
globals.loglevel = (uint8_t)level;
|
||||
|
||||
if (flags != MDBX_DBG_DONTCHANGE) {
|
||||
flags &=
|
||||
#if MDBX_DEBUG
|
||||
MDBX_DBG_ASSERT | MDBX_DBG_AUDIT | MDBX_DBG_JITTER |
|
||||
#endif
|
||||
MDBX_DBG_DUMP | MDBX_DBG_LEGACY_MULTIOPEN | MDBX_DBG_LEGACY_OVERLAP | MDBX_DBG_DONT_UPGRADE;
|
||||
globals.runtime_flags = (uint8_t)flags;
|
||||
}
|
||||
|
||||
assert(MDBX_LOGGER_DONTCHANGE == ((MDBX_debug_func *)(intptr_t)-1));
|
||||
if (logger.ptr != (void *)((intptr_t)-1)) {
|
||||
globals.logger.ptr = logger.ptr;
|
||||
globals.logger_buffer = buffer;
|
||||
globals.logger_buffer_size = buffer_size;
|
||||
}
|
||||
|
||||
ENSURE(nullptr, osal_fastmutex_release(&globals.debug_lock) == 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
__cold int mdbx_setup_debug_nofmt(MDBX_log_level_t level, MDBX_debug_flags_t flags, MDBX_debug_func_nofmt *logger,
|
||||
char *buffer, size_t buffer_size) {
|
||||
union logger_union thunk;
|
||||
thunk.nofmt = (logger && buffer && buffer_size) ? logger : MDBX_LOGGER_NOFMT_DONTCHANGE;
|
||||
return setup_debug(level, flags, thunk, buffer, buffer_size);
|
||||
}
|
||||
|
||||
__cold int mdbx_setup_debug(MDBX_log_level_t level, MDBX_debug_flags_t flags, MDBX_debug_func *logger) {
|
||||
union logger_union thunk;
|
||||
thunk.fmt = logger;
|
||||
return setup_debug(level, flags, thunk, nullptr, 0);
|
||||
}
|
159
src/logging_and_debug.h
Normal file
159
src/logging_and_debug.h
Normal file
@ -0,0 +1,159 @@
|
||||
/// \copyright SPDX-License-Identifier: Apache-2.0
|
||||
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2025
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "essentials.h"
|
||||
|
||||
#ifndef __Wpedantic_format_voidptr
|
||||
MDBX_MAYBE_UNUSED static inline const void *__Wpedantic_format_voidptr(const void *ptr) { return ptr; }
|
||||
#define __Wpedantic_format_voidptr(ARG) __Wpedantic_format_voidptr(ARG)
|
||||
#endif /* __Wpedantic_format_voidptr */
|
||||
|
||||
MDBX_INTERNAL void MDBX_PRINTF_ARGS(4, 5) debug_log(int level, const char *function, int line, const char *fmt, ...)
|
||||
MDBX_PRINTF_ARGS(4, 5);
|
||||
MDBX_INTERNAL void debug_log_va(int level, const char *function, int line, const char *fmt, va_list args);
|
||||
|
||||
#if MDBX_DEBUG
|
||||
#define LOG_ENABLED(LVL) unlikely(LVL <= globals.loglevel)
|
||||
#define AUDIT_ENABLED() unlikely((globals.runtime_flags & (unsigned)MDBX_DBG_AUDIT))
|
||||
#else /* MDBX_DEBUG */
|
||||
#define LOG_ENABLED(LVL) (LVL < MDBX_LOG_VERBOSE && LVL <= globals.loglevel)
|
||||
#define AUDIT_ENABLED() (0)
|
||||
#endif /* LOG_ENABLED() & AUDIT_ENABLED() */
|
||||
|
||||
#if MDBX_FORCE_ASSERTIONS
|
||||
#define ASSERT_ENABLED() (1)
|
||||
#elif MDBX_DEBUG
|
||||
#define ASSERT_ENABLED() likely((globals.runtime_flags & (unsigned)MDBX_DBG_ASSERT))
|
||||
#else
|
||||
#define ASSERT_ENABLED() (0)
|
||||
#endif /* ASSERT_ENABLED() */
|
||||
|
||||
#define DEBUG_EXTRA(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_EXTRA)) \
|
||||
debug_log(MDBX_LOG_EXTRA, __func__, __LINE__, fmt, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define DEBUG_EXTRA_PRINT(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_EXTRA)) \
|
||||
debug_log(MDBX_LOG_EXTRA, nullptr, 0, fmt, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define TRACE(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_TRACE)) \
|
||||
debug_log(MDBX_LOG_TRACE, __func__, __LINE__, fmt "\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define DEBUG(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_DEBUG)) \
|
||||
debug_log(MDBX_LOG_DEBUG, __func__, __LINE__, fmt "\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define VERBOSE(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_VERBOSE)) \
|
||||
debug_log(MDBX_LOG_VERBOSE, __func__, __LINE__, fmt "\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define NOTICE(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_NOTICE)) \
|
||||
debug_log(MDBX_LOG_NOTICE, __func__, __LINE__, fmt "\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define WARNING(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_WARN)) \
|
||||
debug_log(MDBX_LOG_WARN, __func__, __LINE__, fmt "\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#undef ERROR /* wingdi.h \
|
||||
Yeah, morons from M$ put such definition to the public header. */
|
||||
|
||||
#define ERROR(fmt, ...) \
|
||||
do { \
|
||||
if (LOG_ENABLED(MDBX_LOG_ERROR)) \
|
||||
debug_log(MDBX_LOG_ERROR, __func__, __LINE__, fmt "\n", __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define FATAL(fmt, ...) debug_log(MDBX_LOG_FATAL, __func__, __LINE__, fmt "\n", __VA_ARGS__);
|
||||
|
||||
#if MDBX_DEBUG
|
||||
#define ASSERT_FAIL(env, msg, func, line) mdbx_assert_fail(env, msg, func, line)
|
||||
#else /* MDBX_DEBUG */
|
||||
MDBX_NORETURN __cold void assert_fail(const char *msg, const char *func, unsigned line);
|
||||
#define ASSERT_FAIL(env, msg, func, line) \
|
||||
do { \
|
||||
(void)(env); \
|
||||
assert_fail(msg, func, line); \
|
||||
} while (0)
|
||||
#endif /* MDBX_DEBUG */
|
||||
|
||||
#define ENSURE_MSG(env, expr, msg) \
|
||||
do { \
|
||||
if (unlikely(!(expr))) \
|
||||
ASSERT_FAIL(env, msg, __func__, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
#define ENSURE(env, expr) ENSURE_MSG(env, expr, #expr)
|
||||
|
||||
/* assert(3) variant in environment context */
|
||||
#define eASSERT(env, expr) \
|
||||
do { \
|
||||
if (ASSERT_ENABLED()) \
|
||||
ENSURE(env, expr); \
|
||||
} while (0)
|
||||
|
||||
/* assert(3) variant in cursor context */
|
||||
#define cASSERT(mc, expr) eASSERT((mc)->txn->env, expr)
|
||||
|
||||
/* assert(3) variant in transaction context */
|
||||
#define tASSERT(txn, expr) eASSERT((txn)->env, expr)
|
||||
|
||||
#ifndef xMDBX_TOOLS /* Avoid using internal eASSERT() */
|
||||
#undef assert
|
||||
#define assert(expr) eASSERT(nullptr, expr)
|
||||
#endif
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline void jitter4testing(bool tiny) {
|
||||
#if MDBX_DEBUG
|
||||
if (globals.runtime_flags & (unsigned)MDBX_DBG_JITTER)
|
||||
osal_jitter(tiny);
|
||||
#else
|
||||
(void)tiny;
|
||||
#endif
|
||||
}
|
||||
|
||||
MDBX_MAYBE_UNUSED MDBX_INTERNAL void page_list(page_t *mp);
|
||||
|
||||
MDBX_INTERNAL const char *pagetype_caption(const uint8_t type, char buf4unknown[16]);
|
||||
/* Key size which fits in a DKBUF (debug key buffer). */
|
||||
#define DKBUF_MAX 127
|
||||
#define DKBUF char dbg_kbuf[DKBUF_MAX * 4 + 2]
|
||||
#define DKEY(x) mdbx_dump_val(x, dbg_kbuf, DKBUF_MAX * 2 + 1)
|
||||
#define DVAL(x) mdbx_dump_val(x, dbg_kbuf + DKBUF_MAX * 2 + 1, DKBUF_MAX * 2 + 1)
|
||||
|
||||
#if MDBX_DEBUG
|
||||
#define DKBUF_DEBUG DKBUF
|
||||
#define DKEY_DEBUG(x) DKEY(x)
|
||||
#define DVAL_DEBUG(x) DVAL(x)
|
||||
#else
|
||||
#define DKBUF_DEBUG ((void)(0))
|
||||
#define DKEY_DEBUG(x) ("-")
|
||||
#define DVAL_DEBUG(x) ("-")
|
||||
#endif
|
||||
|
||||
MDBX_INTERNAL void log_error(const int err, const char *func, unsigned line);
|
||||
|
||||
MDBX_MAYBE_UNUSED static inline int log_if_error(const int err, const char *func, unsigned line) {
|
||||
if (unlikely(err != MDBX_SUCCESS))
|
||||
log_error(err, func, line);
|
||||
return err;
|
||||
}
|
||||
|
||||
#define LOG_IFERR(err) log_if_error((err), __func__, __LINE__)
|
106
src/man1/mdbx_chk.1
Normal file
106
src/man1/mdbx_chk.1
Normal file
@ -0,0 +1,106 @@
|
||||
.\" Copyright 2015-2025 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDBX_CHK 1 "2025-01-14" "MDBX 0.14"
|
||||
.SH NAME
|
||||
mdbx_chk \- MDBX checking tool
|
||||
.SH SYNOPSIS
|
||||
.B mdbx_chk
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-v [ v [ v ]]]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-q ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-w ]
|
||||
[\c
|
||||
.BR \-d ]
|
||||
[\c
|
||||
.BR \-i ]
|
||||
[\c
|
||||
.BI \-s \ table\fR]
|
||||
.BR \ dbpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdbx_chk
|
||||
utility is intended to check an MDBX database file.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-v
|
||||
Produce verbose output, including summarize space and page usage statistics.
|
||||
If \fB\-vv\fP is given, be more verbose, show summarized B-tree info
|
||||
and space allocation.
|
||||
If \fB\-vvv\fP is given, be more verbose, include summarized statistics
|
||||
of leaf B-tree pages.
|
||||
If \fB\-vvvv\fP is given, be even more verbose, show info of each page
|
||||
during B-tree traversal and basic info of each GC record.
|
||||
If \fB\-vvvvv\fP is given, turn maximal verbosity, display the full list
|
||||
of page IDs in the GC records and size of each key-value pair of database(s).
|
||||
.TP
|
||||
.BR \-q
|
||||
Be quiet; do not output anything even if an error was detected.
|
||||
.TP
|
||||
.BR \-c
|
||||
Force using cooperative mode while opening environment, i.e. don't try to open
|
||||
in exclusive/monopolistic mode. Only exclusive/monopolistic mode allow complete
|
||||
check, including full check of all meta-pages and actual size of database file.
|
||||
.TP
|
||||
.BR \-w
|
||||
Open environment in read-write mode and lock for writing while checking.
|
||||
This could be impossible if environment already used by another process(s)
|
||||
in an incompatible read-write mode. This allows rollback to last steady commit
|
||||
(in case environment was not closed properly) and then check transaction IDs
|
||||
of meta-pages. Otherwise, without \fB\-w\fP option environment will be
|
||||
opened in read-only mode.
|
||||
.TP
|
||||
.BR \-d
|
||||
Disable page-by-page traversal of B-tree. In this case, without B-tree
|
||||
traversal, it is unable to check for lost-unused pages nor for double-used
|
||||
pages.
|
||||
.TP
|
||||
.BR \-i
|
||||
Ignore wrong order errors, which will likely false-positive if custom
|
||||
comparator(s) was used.
|
||||
.TP
|
||||
.BR \-s \ table
|
||||
Verify and show info only for a specific table.
|
||||
.TP
|
||||
.BR \-0 | \-1 | \-2
|
||||
Using specific meta-page 0, or 2 for checking.
|
||||
.TP
|
||||
.BR \-t
|
||||
Turn to a specified meta-page on successful check.
|
||||
.TP
|
||||
.BR \-T
|
||||
Turn to a specified meta-page EVEN ON UNSUCCESSFUL CHECK!
|
||||
.TP
|
||||
.BR \-u
|
||||
Warms up the DB before checking via notifying OS kernel of subsequent access to the database pages.
|
||||
.TP
|
||||
.BR \-U
|
||||
Warms up the DB before checking, notifying the OS kernel of subsequent access to the database pages,
|
||||
then forcibly loads ones by sequential access and tries to lock database pages in memory.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open MDBX environment(s) which do not use subdirectories.
|
||||
This is a legacy option. For now MDBX handles this automatically.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur. Errors result in a non-zero exit status
|
||||
and a diagnostic message being written to standard error
|
||||
if no quiet mode was requested.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdbx_stat (1),
|
||||
.BR mdbx_copy (1),
|
||||
.BR mdbx_dump (1),
|
||||
.BR mdbx_load (1)
|
||||
.BR mdbx_drop (1)
|
||||
.SH AUTHOR
|
||||
Leonid Yuriev <https://gitflic.ru/user/erthink>
|
95
src/man1/mdbx_copy.1
Normal file
95
src/man1/mdbx_copy.1
Normal file
@ -0,0 +1,95 @@
|
||||
.\" Copyright 2015-2025 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copyright 2012-2015 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDBX_COPY 1 "2025-01-14" "MDBX 0.14"
|
||||
.SH NAME
|
||||
mdbx_copy \- MDBX environment copy tool
|
||||
.SH SYNOPSIS
|
||||
.B mdbx_copy
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-q ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-d ]
|
||||
[\c
|
||||
.BR \-p ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.B src_path
|
||||
[\c
|
||||
.BR dest_path ]
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdbx_copy
|
||||
utility copies an MDBX environment. The environment can
|
||||
be copied regardless of whether it is currently in use.
|
||||
No lockfile is created, since it gets recreated at need.
|
||||
|
||||
If
|
||||
.I dest_path
|
||||
is specified it must be the path of an empty directory
|
||||
for storing the backup. Otherwise, the backup will be
|
||||
written to stdout.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-q
|
||||
Be quiet.
|
||||
.TP
|
||||
.BR \-c
|
||||
Compact while copying. Only current data pages will be copied; freed
|
||||
or unused pages will be omitted from the copy. This option will
|
||||
slow down the backup process as it is more CPU-intensive.
|
||||
Currently it fails if the environment has suffered a page leak.
|
||||
.TP
|
||||
.BR \-d
|
||||
Alters geometry to enforce the copy to be a dynamic size DB,
|
||||
which could be growth and shrink by reasonable steps on the fly.
|
||||
.TP
|
||||
.BR \-p
|
||||
Use read transaction parking/ousting during copying MVCC-snapshot.
|
||||
This allows the writing transaction to oust the read
|
||||
transaction used to copy the database if copying takes so long
|
||||
that it will interfere with the recycling old MVCC snapshots
|
||||
and may lead to an overflow of the database.
|
||||
However, if the reading transaction is ousted the copy will
|
||||
be aborted until successful completion. Thus, this option
|
||||
allows copy the database without interfering with write
|
||||
transactions and a threat of database overflow, but at the cost
|
||||
that copying will be aborted to prevent such conditions.
|
||||
.TP
|
||||
.BR \-u
|
||||
Warms up the DB before copying via notifying OS kernel of subsequent access to the database pages.
|
||||
.TP
|
||||
.BR \-U
|
||||
Warms up the DB before copying, notifying the OS kernel of subsequent access to the database pages,
|
||||
then forcibly loads ones by sequential access and tries to lock database pages in memory.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open MDBX environment(s) which do not use subdirectories.
|
||||
This is legacy option. For now MDBX handles this automatically.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH CAVEATS
|
||||
This utility can trigger significant file size growth if run
|
||||
in parallel with write transactions, because pages which they
|
||||
free during copying cannot be reused until the copy is done.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdbx_dump (1),
|
||||
.BR mdbx_chk (1),
|
||||
.BR mdbx_stat (1),
|
||||
.BR mdbx_load (1)
|
||||
.BR mdbx_drop (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>,
|
||||
Leonid Yuriev <https://gitflic.ru/user/erthink>
|
48
src/man1/mdbx_drop.1
Normal file
48
src/man1/mdbx_drop.1
Normal file
@ -0,0 +1,48 @@
|
||||
.\" Copyright 2021-2025 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDBX_DROP 1 "2025-01-14" "MDBX 0.14"
|
||||
.SH NAME
|
||||
mdbx_drop \- MDBX database delete tool
|
||||
.SH SYNOPSIS
|
||||
.B mdbx_drop
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-d ]
|
||||
[\c
|
||||
.BI \-s \ table\fR]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.BR \ dbpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdbx_drop
|
||||
utility empties or deletes a database in the specified
|
||||
environment.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-d
|
||||
Delete the specified database, don't just empty it.
|
||||
.TP
|
||||
.BR \-s \ table
|
||||
Operate on a specific table. If no table is specified, only the main table is dropped.
|
||||
.TP
|
||||
.BR \-n
|
||||
Dump an MDBX database which does not use subdirectories.
|
||||
This is legacy option. For now MDBX handles this automatically.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdbx_load (1),
|
||||
.BR mdbx_copy (1),
|
||||
.BR mdbx_chk (1),
|
||||
.BR mdbx_stat (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user