This is an automated email from the ASF dual-hosted git repository.

shuwenwei pushed a commit to branch partial_tvlist_clone_dev_1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/partial_tvlist_clone_dev_1.3 
by this push:
     new 3c6f499ffd5 Fix race when concurrent queries replace working TVList 
during partial clone
3c6f499ffd5 is described below

commit 3c6f499ffd52091b996d0c83c6cc75687eceabb3
Author: shuwenwei <[email protected]>
AuthorDate: Tue Aug 4 19:10:20 2026 +0800

    Fix race when concurrent queries replace working TVList during partial clone
    
    Re-fetch and re-verify the working TVList under the memChunk lock after
    acquiring its queryListLock. The clone, column move and working-list swap
    now happen in the same memChunk critical section, so a concurrent query can
    never observe a working TVList whose uncloned columns were already moved
    away, and the fast paths never read a detached list.
---
 .../schemaregion/utils/ResourceByPathUtils.java    | 182 ++++++++++++---------
 1 file changed, 104 insertions(+), 78 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/ResourceByPathUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/ResourceByPathUtils.java
index adf2198019a..43b007dccca 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/ResourceByPathUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/ResourceByPathUtils.java
@@ -160,16 +160,22 @@ public abstract class ResourceByPathUtils {
     // TVList intrinsic lock (via synchronized methods like calculateRamSize, 
clone). So no AB-BA
     // deadlock is possible.
     while (true) {
-      // The working TVList may be replaced by another query. Always lock the 
actual list being used
-      // and verify it is still the current working list before touching 
query-owned fields.
-      TVList list = memChunk.getWorkingTVList();
-      list.lockQueryList();
+      // The working TVList may be replaced by a concurrent query via 
clone-and-swap
+      // (memChunk.setWorkingTVList(clone)). A queryListLock held on a 
detached candidate does
+      // not protect the current working TVList, so after acquiring the lock, 
re-verify it is
+      // still the current working list under the memChunk lock. If it was 
replaced while
+      // waiting for candidate's queryListLock, retry with the current one.
+      final TVList candidate = memChunk.getWorkingTVList();
+      candidate.lockQueryList();
       try {
-        if (list != memChunk.getWorkingTVList()) {
-          continue;
+        synchronized (memChunk) {
+          if (memChunk.getWorkingTVList() != candidate) {
+            continue;
+          }
         }
+
         if (copyTimeFilter != null
-            && !copyTimeFilter.satisfyStartEndTime(list.getMinTime(), 
list.getMaxTime())) {
+            && !copyTimeFilter.satisfyStartEndTime(candidate.getMinTime(), 
candidate.getMaxTime())) {
           return tvListQueryMap;
         }
 
@@ -188,15 +194,16 @@ public abstract class ResourceByPathUtils {
            * sorted or no active query is using it. Otherwise, Q2 should read 
from
            * workingListForFlush.
            */
-          boolean canUseListDirectly = list.isSorted() || 
list.getQueryContextSet().isEmpty();
+          boolean canUseListDirectly =
+              candidate.isSorted() || candidate.getQueryContextSet().isEmpty();
           LOGGER.debug(
               "Flushing MemTable - add current query context to mutable 
TVList's query list");
           if (canUseListDirectly) {
-            list.getQueryContextSet().add(context);
-            tvListQueryMap.put(list, list.rowCount());
+            candidate.getQueryContextSet().add(context);
+            tvListQueryMap.put(candidate, candidate.rowCount());
           } else {
             TVList workingListForFlushSort =
-                memChunk.initWorkingListForFlushIfNecessary(list, true);
+                memChunk.initWorkingListForFlushIfNecessary(candidate, true);
             /*
              * The query will read from workingListForFlushSort, but 
cloneForFlushSort() only clones
              * times and indices. The value arrays and bitmaps are still 
shared with the original
@@ -210,76 +217,96 @@ public abstract class ResourceByPathUtils {
              * Do not put the original list into tvListQueryMap here. The 
actual read path must use
              * workingListForFlushSort to avoid sorting the original list in 
place.
              */
-            list.getQueryContextSet().add(context);
-            context.addTVListToSet(Collections.singleton(list));
+            candidate.getQueryContextSet().add(context);
+            context.addTVListToSet(Collections.singleton(candidate));
             workingListForFlushSort.getQueryContextSet().add(context);
             tvListQueryMap.put(workingListForFlushSort, 
workingListForFlushSort.rowCount());
           }
-        } else {
-          if (list.isSorted() || list.getQueryContextSet().isEmpty()) {
-            LOGGER.debug(
-                "Working MemTable - add current query context to mutable 
TVList's query list when it's sorted or no other query on it");
-            list.getQueryContextSet().add(context);
-            tvListQueryMap.put(list, list.rowCount());
-
-            // columnIndexList is to track column-level access for 
AlignedTVList.
-            // For TVList (primitive time series), it remains null and column 
tracking is not
-            // needed.
-            if (columnIndexList != null && context instanceof 
FragmentInstanceContext) {
-              ((FragmentInstanceContext) context).putAccessedColumns(list, 
columnIndexList);
-            }
-          } else {
-            /*
-             * +----------------------+
-             * |      MemTable        |
-             * |                      |
-             * |    +------------+    |          +-----------------+
-             * |    |   TVList   |<---+--+   +---+  Previous Query |
-             * |    +-----^------+    |  |   |   +-----------------+
-             * |          |           |  |   |
-             * +----------+-----------+  |   |   +----------------+
-             *            | Clone        +---+---+  Current Query |
-             *      +-----+------+           |   +----------------+
-             *      |   TVList   | <---------+
-             *      +------------+
-             */
-            LOGGER.debug(
-                "Working MemTable - clone mutable TVList and replace old 
TVList in working MemTable");
-
-            Set<Integer> columnsToClone = getAccessedColumnsForQuery(list);
-            listRamInfo =
-                (columnsToClone == null)
-                    ? list.calculateRamSize()
-                    : ((AlignedTVList) list).calculateRamSize(columnsToClone);
-
-            // reserve query memory
-            QueryContext firstQuery = 
list.getQueryContextSet().iterator().next();
-            if (firstQuery instanceof FragmentInstanceContext) {
-              MemoryReservationManager memoryReservationManager =
-                  ((FragmentInstanceContext) 
firstQuery).getMemoryReservationContext();
-              
memoryReservationManager.reserveMemoryCumulatively(listRamInfo.getRamSize());
-              list.setReservedMemoryBytes(listRamInfo.getRamSize());
-            }
-            list.setOwnerQuery(firstQuery);
-
-            // clone TVList
-            TVList cloneList =
-                (columnsToClone == null)
-                    ? list.clone()
-                    : ((AlignedTVList) list).clone(columnsToClone);
-
-            cloneList.getQueryContextSet().add(context);
-            tvListQueryMap.put(cloneList, cloneList.rowCount());
-            if (columnIndexList != null && context instanceof 
FragmentInstanceContext) {
-              ((FragmentInstanceContext) 
context).putAccessedColumns(cloneList, columnIndexList);
-            }
+          return tvListQueryMap;
+        }
 
-            if (columnsToClone != null) {
-              ((AlignedTVList) list)
-                  .moveUnclonedColumnsTo((AlignedTVList) cloneList, 
columnsToClone);
-            }
-            memChunk.setWorkingTVList(cloneList);
+        if (candidate.isSorted() || candidate.getQueryContextSet().isEmpty()) {
+          LOGGER.debug(
+              "Working MemTable - add current query context to mutable 
TVList's query list when it's sorted or no other query on it");
+          candidate.getQueryContextSet().add(context);
+          tvListQueryMap.put(candidate, candidate.rowCount());
+
+          // columnIndexList is to track column-level access for AlignedTVList.
+          // For TVList (primitive time series), it remains null and column 
tracking is not needed.
+          if (columnIndexList != null && context instanceof 
FragmentInstanceContext) {
+            ((FragmentInstanceContext) context).putAccessedColumns(candidate, 
columnIndexList);
+          }
+          return tvListQueryMap;
+        }
+
+        /*
+         * +----------------------+
+         * |      MemTable        |
+         * |                      |
+         * |    +------------+    |          +-----------------+
+         * |    |   TVList   |<---+--+   +---+  Previous Query |
+         * |    +-----^------+    |  |   |   +-----------------+
+         * |          |           |  |   |
+         * +----------+-----------+  |   |   +----------------+
+         *            | Clone        +---+---+  Current Query |
+         *      +-----+------+           |   +----------------+
+         *      |   TVList   | <---------+
+         *      +------------+
+         */
+        LOGGER.debug(
+            "Working MemTable - clone mutable TVList and replace old TVList in 
working MemTable");
+
+        synchronized (memChunk) {
+          // Re-check defensively before cloning and publishing the 
replacement. The clone and the
+          // working-list swap must be done in the same memChunk critical 
section, so a concurrent
+          // query can never observe a working TVList whose columns have 
already been moved away.
+          if (memChunk.getWorkingTVList() != candidate) {
+            continue;
           }
+
+          // calculateRamSize (synchronized method on TVList) was previously 
called before
+          // lockQueryList to avoid deadlock concerns. For partial clone of 
AlignedTVList, however
+          // calculateRamSize must now be called inside the lockQueryList 
section because it depends
+          // on accessing columns on the AlignedTVList.
+          // This is safe because the lock ordering - queryListLock must 
always be acquired before
+          // the TVList intrinsic lock (via synchronized methods like 
calculateRamSize, clone). So
+          // no AB-BA deadlock is possible.
+          Set<Integer> columnsToClone = getAccessedColumnsForQuery(candidate);
+          listRamInfo =
+              (columnsToClone == null)
+                  ? candidate.calculateRamSize()
+                  : ((AlignedTVList) 
candidate).calculateRamSize(columnsToClone);
+
+          // reserve query memory
+          QueryContext firstQuery = 
candidate.getQueryContextSet().iterator().next();
+          if (firstQuery instanceof FragmentInstanceContext) {
+            MemoryReservationManager memoryReservationManager =
+                ((FragmentInstanceContext) 
firstQuery).getMemoryReservationContext();
+            
memoryReservationManager.reserveMemoryCumulatively(listRamInfo.getRamSize());
+            candidate.setReservedMemoryBytes(listRamInfo.getRamSize());
+          }
+          candidate.setOwnerQuery(firstQuery);
+
+          // clone TVList
+          TVList cloneList =
+              (columnsToClone == null)
+                  ? candidate.clone()
+                  : ((AlignedTVList) candidate).clone(columnsToClone);
+
+          cloneList.getQueryContextSet().add(context);
+          tvListQueryMap.put(cloneList, cloneList.rowCount());
+          if (columnIndexList != null && context instanceof 
FragmentInstanceContext) {
+            ((FragmentInstanceContext) context).putAccessedColumns(cloneList, 
columnIndexList);
+          }
+
+          // Move the uncloned columns and publish the clone in the same 
memChunk critical section,
+          // so a concurrent query never observes a working TVList whose 
columns were already moved.
+          if (columnsToClone != null) {
+            ((AlignedTVList) candidate)
+                .moveUnclonedColumnsTo((AlignedTVList) cloneList, 
columnsToClone);
+          }
+          memChunk.setWorkingTVList(cloneList);
+          return tvListQueryMap;
         }
       } catch (MemoryNotEnoughException ex) {
         if (listRamInfo != null) {
@@ -293,9 +320,8 @@ public abstract class ResourceByPathUtils {
         }
         throw ex;
       } finally {
-        list.unlockQueryList();
+        candidate.unlockQueryList();
       }
-      return tvListQueryMap;
     }
   }
 

Reply via email to