From 5dfe3433a8f446d5c63a032bcccff011c6cd492a 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: Thu, 20 Mar 2025 01:21:02 +0300
Subject: [PATCH] =?UTF-8?q?mdbx:=20=D1=83=D1=81=D1=82=D1=80=D0=B0=D0=BD?=
 =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B3=D0=BE=D0=BD=D0=BA=D0=B8=20?=
 =?UTF-8?q?=D0=B2=20`tbl=5Fsetup(MDBX=5FDUPFIXED=20|=20MDBX=5FINTEGERDUP)`?=
 =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B5=20?=
 =?UTF-8?q?=D0=B2=20=D1=80=D0=B0=D0=B7=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE?=
 =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=B0=D1=85=20(backport).?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Проблема была в том, что в случаях фиксированного размера значений
clc.lmin/clc.lmax устанавливались в env->kvs[], а затем корректировались
по актуальному размеру данных в БД. Поэтому при конкурентном вызове из
разных потоков, один поток мог выполнять инициализацию, а второй
прочитать временные/промежуточные значения lmin/lmax.

В результате, при конкурентном старте транзакций в разных потоках при
использовании только-что открытого dbi-хендла, проверка допустимости
длины значения могла заканчиваться ложной ошибкой MDBX_BAD_VALSIZE.
---
 src/cogs.h   |  4 ++++
 src/cursor.c |  6 ++----
 src/proto.h  |  2 +-
 src/table.c  | 40 ++++++++++++++++++++++++++--------------
 4 files changed, 33 insertions(+), 19 deletions(-)

diff --git a/src/cogs.h b/src/cogs.h
index 11d7fe1d..7e814e7b 100644
--- a/src/cogs.h
+++ b/src/cogs.h
@@ -200,6 +200,10 @@ static inline bool check_table_flags(unsigned flags) {
   }
 }
 
+static inline int tbl_setup_ifneed(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db) {
+  return likely(kvx->clc.v.lmax) ? MDBX_SUCCESS : tbl_setup(env, kvx, db);
+}
+
 /*----------------------------------------------------------------------------*/
 
 MDBX_NOTHROW_PURE_FUNCTION static inline size_t pgno2bytes(const MDBX_env *env, size_t pgno) {
diff --git a/src/cursor.c b/src/cursor.c
index ff2c67e8..01fc8a56 100644
--- a/src/cursor.c
+++ b/src/cursor.c
@@ -298,10 +298,7 @@ static __always_inline int couple_init(cursor_couple_t *couple, const MDBX_txn *
   if (unlikely(*dbi_state & DBI_STALE))
     return tbl_fetch(couple->outer.txn, cursor_dbi(&couple->outer));
 
-  if (unlikely(kvx->clc.k.lmax == 0))
-    return tbl_setup(txn->env, kvx, tree);
-
-  return MDBX_SUCCESS;
+  return tbl_setup_ifneed(txn->env, kvx, tree);
 }
 
 __cold int cursor_init4walk(cursor_couple_t *couple, const MDBX_txn *const txn, tree_t *const tree, kvx_t *const kvx) {
@@ -387,6 +384,7 @@ int cursor_dupsort_setup(MDBX_cursor *mc, const node_t *node, const page_t *mp)
     }
     mc->tree->dupfix_size = mx->nested_tree.dupfix_size;
     mc->clc->v.lmin = mc->clc->v.lmax = mx->nested_tree.dupfix_size;
+    cASSERT(mc, mc->clc->v.lmax >= mc->clc->v.lmin);
   }
 
   DEBUG("Sub-db dbi -%zu root page %" PRIaPGNO, cursor_dbi(&mx->cursor), mx->nested_tree.root);
diff --git a/src/proto.h b/src/proto.h
index f0a6657f..92bbce6d 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -95,7 +95,7 @@ MDBX_INTERNAL void recalculate_subpage_thresholds(MDBX_env *env);
 
 /* table.c */
 MDBX_INTERNAL int __must_check_result tbl_fetch(MDBX_txn *txn, size_t dbi);
-MDBX_INTERNAL int __must_check_result tbl_setup(const MDBX_env *env, kvx_t *const kvx, const tree_t *const db);
+MDBX_INTERNAL int __must_check_result tbl_setup(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db);
 
 /* coherency.c */
 MDBX_INTERNAL bool coherency_check_meta(const MDBX_env *env, const volatile meta_t *meta, bool report);
diff --git a/src/table.c b/src/table.c
index faa44da1..5b3e441a 100644
--- a/src/table.c
+++ b/src/table.c
@@ -3,28 +3,37 @@
 
 #include "internals.h"
 
-int tbl_setup(const MDBX_env *env, kvx_t *const kvx, const tree_t *const db) {
+int tbl_setup(const MDBX_env *env, volatile kvx_t *const kvx, const tree_t *const db) {
+  osal_memory_fence(mo_AcquireRelease, false);
+
   if (unlikely(!check_table_flags(db->flags))) {
     ERROR("incompatible or invalid db.flags (0x%x) ", db->flags);
     return MDBX_INCOMPATIBLE;
   }
-  if (unlikely(!kvx->clc.k.cmp)) {
-    kvx->clc.k.cmp = builtin_keycmp(db->flags);
-    kvx->clc.v.cmp = builtin_datacmp(db->flags);
+
+  size_t v_lmin = valsize_min(db->flags);
+  size_t v_lmax = env_valsize_max(env, db->flags);
+  if ((db->flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->dupfix_size) {
+    if (!MDBX_DISABLE_VALIDATION && unlikely(db->dupfix_size < v_lmin || db->dupfix_size > v_lmax)) {
+      ERROR("db.dupfix_size (%u) <> min/max value-length (%zu/%zu)", db->dupfix_size, v_lmin, v_lmax);
+      return MDBX_CORRUPTED;
+    }
+    v_lmin = v_lmax = db->dupfix_size;
   }
 
   kvx->clc.k.lmin = keysize_min(db->flags);
   kvx->clc.k.lmax = env_keysize_max(env, db->flags);
-  kvx->clc.v.lmin = valsize_min(db->flags);
-  kvx->clc.v.lmax = env_valsize_max(env, db->flags);
-
-  if ((db->flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->dupfix_size) {
-    if (!MDBX_DISABLE_VALIDATION && unlikely(db->dupfix_size < kvx->clc.v.lmin || db->dupfix_size > kvx->clc.v.lmax)) {
-      ERROR("db.dupfix_size (%u) <> min/max value-length (%zu/%zu)", db->dupfix_size, kvx->clc.v.lmin, kvx->clc.v.lmax);
-      return MDBX_CORRUPTED;
-    }
-    kvx->clc.v.lmin = kvx->clc.v.lmax = db->dupfix_size;
+  if (unlikely(!kvx->clc.k.cmp)) {
+    kvx->clc.v.cmp = builtin_datacmp(db->flags);
+    kvx->clc.k.cmp = builtin_keycmp(db->flags);
   }
+  kvx->clc.v.lmin = v_lmin;
+  osal_memory_fence(mo_Relaxed, true);
+  kvx->clc.v.lmax = v_lmax;
+  osal_memory_fence(mo_AcquireRelease, true);
+
+  eASSERT(env, kvx->clc.k.lmax >= kvx->clc.k.lmin);
+  eASSERT(env, kvx->clc.v.lmax >= kvx->clc.v.lmin);
   return MDBX_SUCCESS;
 }
 
@@ -86,10 +95,13 @@ int tbl_fetch(MDBX_txn *txn, size_t dbi) {
     return MDBX_CORRUPTED;
   }
 #endif /* !MDBX_DISABLE_VALIDATION */
-  rc = tbl_setup(txn->env, kvx, db);
+  rc = tbl_setup_ifneed(txn->env, kvx, db);
   if (unlikely(rc != MDBX_SUCCESS))
     return rc;
 
+  if (unlikely(dbi_changed(txn, dbi)))
+    return MDBX_BAD_DBI;
+
   txn->dbi_state[dbi] &= ~DBI_STALE;
   return MDBX_SUCCESS;
 }