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(); }
