You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by yo...@apache.org on 2026/05/25 03:56:27 UTC

(iotdb) branch fix/npe-stale-fragment-instance-context created (now 58055340971)

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

CRZbulabula pushed a change to branch fix/npe-stale-fragment-instance-context
in repository https://gitbox.apache.org/repos/asf/iotdb.git


      at 58055340971 Fix NPE when retried query reuses stale FragmentInstanceContext

This branch includes the following new commits:

     new 58055340971 Fix NPE when retried query reuses stale FragmentInstanceContext

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(iotdb) 01/01: Fix NPE when retried query reuses stale FragmentInstanceContext

Posted by yo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

CRZbulabula pushed a commit to branch fix/npe-stale-fragment-instance-context
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 5805534097103ebe890348c4701919515ccf40ea
Author: Yongzao <53...@qq.com>
AuthorDate: Mon May 25 11:56:11 2026 +0800

    Fix NPE when retried query reuses stale FragmentInstanceContext
    
    When QueryExecution.retry() re-plans a query, doDistributionPlan()
    creates fresh PlanFragmentId objects with nextFragmentInstanceId reset
    to 0. Because the queryId is unchanged, retry generates fragment
    instance IDs identical to the first execution (e.g. queryId_11.0).
    
    FragmentInstanceManager.instanceContext retains completed contexts for
    5 minutes for statistics caching. When a retry dispatches the same FI
    ID, instanceContext.computeIfAbsent() returns the stale old context
    whose releaseResource() has already been called, setting dataRegion to
    null. New drivers then NPE at dataRegion.tryReadLock() inside
    FragmentInstanceContext.initQueryDataSource().
    
    Fix: replace computeIfAbsent() with compute() in
    execDataQueryFragmentInstance() so that a released context
    (dataRegion == null) is atomically replaced with a fresh one carrying
    the new dataRegion reference.
    
    Defensive fix: add a null guard for dataRegion in
    getSharedQueryDataSource() that returns null (treated by DataDriver as
    an aborted FI) instead of propagating NPE.
---
 .../fragment/FragmentInstanceContext.java          |  4 +++
 .../fragment/FragmentInstanceManager.java          | 30 ++++++++++++++--------
 2 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
index 2a0373cf6fd..ee177dde02e 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
@@ -781,6 +781,10 @@ public class FragmentInstanceContext extends QueryContext {
 
   public synchronized IQueryDataSource getSharedQueryDataSource() throws QueryProcessException {
     if (sharedQueryDataSource == null) {
+      if (dataRegion == null) {
+        // Context was released (releaseResource() already ran). Signal aborted to the driver.
+        return null;
+      }
       switch (queryDataSourceType) {
         case SERIES_SCAN:
           if (initQueryDataSource(sourcePaths)) {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceManager.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceManager.java
index 37e5c6c0858..520d98c3f81 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceManager.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceManager.java
@@ -151,19 +151,27 @@ public class FragmentInstanceManager {
                 DataNodeQueryContext dataNodeQueryContext =
                     getOrCreateDataNodeQueryContext(instanceId.getQueryId(), dataNodeFINum);
 
+                // Use compute() instead of computeIfAbsent() to handle the retry scenario:
+                // QueryExecution.retry() re-creates the distribution plan with PlanFragmentId
+                // counters reset to 0, generating fragment instance IDs identical to the first
+                // execution. instanceContext retains released contexts (dataRegion == null) for
+                // statistics caching. Without this check, a retried FI reuses the stale context
+                // and NPEs at dataRegion.tryReadLock().
                 FragmentInstanceContext context =
-                    instanceContext.computeIfAbsent(
+                    instanceContext.compute(
                         instanceId,
-                        fragmentInstanceId ->
-                            createFragmentInstanceContext(
-                                fragmentInstanceId,
-                                stateMachine,
-                                instance.getSessionInfo(),
-                                dataRegion,
-                                instance.getGlobalTimePredicate(),
-                                dataNodeQueryContextMap,
-                                instance.isDebug(),
-                                instance.isVerbose()));
+                        (fiId, existingContext) ->
+                            (existingContext == null || existingContext.getDataRegion() == null)
+                                ? createFragmentInstanceContext(
+                                    fiId,
+                                    stateMachine,
+                                    instance.getSessionInfo(),
+                                    dataRegion,
+                                    instance.getGlobalTimePredicate(),
+                                    dataNodeQueryContextMap,
+                                    instance.isDebug(),
+                                    instance.isVerbose())
+                                : existingContext);
                 context.setHighestPriority(instance.isHighestPriority());
 
                 try {