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

shuwenwei pushed a commit to branch fix-external-tsfile-resource-race
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit dcb71a20498e50284de69ee5c0f801a9504290c5
Author: shuwenwei <[email protected]>
AuthorDate: Fri Jul 31 15:35:43 2026 +0800

    Fix race condition in ExternalTsFileQueryResource reference counting
    
    When multiple FragmentInstances share an ExternalTsFileQueryResource, a race
    condition can cause the resource to be closed prematurely:
    
    1. Fragment A initializes (retain, count=1) and finishes quickly,
       releasing its reference (count=0) which triggers close().
    2. Fragment B is still in the scheduling queue and has not called retain()
       yet. When it eventually calls retain(), it fails with
       'ExternalTsFileQueryResource has been closed'.
    
    Another path: QueryExecution cleanup runs while no FragmentInstance has
    initialized yet (count=0), closing the resource before scheduled FIs start.
    
    Fix: introduce a two-phase close mechanism.
    
    - closeByFragmentInstance(): only decrements the usage count. It no longer
      closes the resource on its own — it must wait for the QueryExecution 
signal.
    - closeByQueryExecution(): sets a wantsClose flag and closes only when the
      usage count is zero.
    - QueryExecution state listener: now calls 
releaseExternalTsFileQueryResources()
      for all terminal states (including FINISHED), not just error states, 
ensuring
      the resource is always eventually closed.
---
 .../apache/iotdb/db/queryengine/plan/execution/QueryExecution.java    | 4 ++++
 .../function/tvf/read_tsfile/ExternalTsFileQueryResource.java         | 4 +++-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/QueryExecution.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/QueryExecution.java
index 037ea8688bf..035bac7b5d8 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/QueryExecution.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/QueryExecution.java
@@ -158,6 +158,10 @@ public class QueryExecution implements IQueryExecution {
 
               this.cleanUpCoordinatorContextMapIfNeeded(cause);
             }
+            // Signal all ExternalTsFileQueryResource instances that the query 
has ended.
+            // Each resource closes only when its fragmentInstanceUsageCount 
reaches zero,
+            // preventing a premature close raced with late-scheduled 
FragmentInstances.
+            context.releaseExternalTsFileQueryResources();
             this.stop(cause);
           }
         });
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java
index 9a96145bc8a..3333d99e933 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/read_tsfile/ExternalTsFileQueryResource.java
@@ -101,6 +101,7 @@ public class ExternalTsFileQueryResource {
   // deleting temporary run files while drivers are still reading them.
   private int fragmentInstanceUsageCount;
   private boolean closed;
+  private boolean queryExecutionWantsToClose;
 
   public ExternalTsFileQueryResource(
       MPPQueryContext queryContext,
@@ -233,12 +234,13 @@ public class ExternalTsFileQueryResource {
       throw new IllegalStateException(
           
DataNodeQueryMessages.EXTERNAL_TSFILE_FRAGMENT_INSTANCE_USAGE_COUNT_CANNOT_BE_NEGATIVE);
     }
-    if (fragmentInstanceUsageCount == 0) {
+    if (fragmentInstanceUsageCount == 0 && queryExecutionWantsToClose) {
       close();
     }
   }
 
   public synchronized void closeByQueryExecution() {
+    queryExecutionWantsToClose = true;
     if (fragmentInstanceUsageCount == 0) {
       close();
     }

Reply via email to