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

Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new b9d20cf337e [fix](profile) avoid out-of-bounds when backends report 
uneven pipeline counts (#66283)
b9d20cf337e is described below

commit b9d20cf337e0bba2c792e5abc36721913b4a1ec0
Author: York Cao <[email protected]>
AuthorDate: Tue Aug 4 15:31:30 2026 +0800

    [fix](profile) avoid out-of-bounds when backends report uneven pipeline 
counts (#66283)
    
    ### What problem does this PR solve?
    
    Issue Number: close #66282
    
    Problem Summary:
    A fragment's pipeline count is built at runtime per backend
    (`add_pipeline` reacts to data distribution and local-exchange
    injection), so backends of one fragment can legitimately report
    different pipeline counts. `ExecutionProfile.getMultiBeProfile()` takes
    `pipelineSize` as the max across backends, then indexed every backend at
    that position, throwing `IndexOutOfBoundsException` on the backends with
    fewer pipelines and dropping the entire merged profile for the query.
    This adds a bounds check so backends with fewer pipelines are skipped at
    the higher indices.
    
    ### Release note
    
    Fix IndexOutOfBoundsException in merged profile generation when backends
    of a fragment report uneven pipeline counts.
    
    ### Check List (For Author)
    
    - [x] Test <!-- At least one of them must be included. -->
        - [x] Regression test
        - [x] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    
    - [x] Behavior changed:
        - [x] No.
    
    - [x] Does this need documentation?
        - [x] No.
    
    <!-- ExecutionProfileTest.testAggregatedProfileUnevenPipelineCounts
    reproduces the crash (Index 1 out of bounds for length 1) before the fix
    and passes after. -->
---
 .../doris/common/profile/ExecutionProfile.java     |  6 ++
 .../doris/common/profile/ExecutionProfileTest.java | 71 ++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
 
b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
index 47d6fce03ac..067c695e1d8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
@@ -134,6 +134,12 @@ public class ExecutionProfile {
             for (int pipelineIdx = 0; pipelineIdx < pipelineSize; 
pipelineIdx++) {
                 List<RuntimeProfile> allPipelineTask = new 
ArrayList<RuntimeProfile>();
                 for (List<RuntimeProfile> profileSingleBE : 
multiPipeline.values()) {
+                    // BEs may report different pipeline counts (pipelineSize 
is the max
+                    // above); a BE with fewer pipelines simply has no profile 
at this
+                    // index, so skip it instead of indexing out of bounds.
+                    if (pipelineIdx >= profileSingleBE.size()) {
+                        continue;
+                    }
                     RuntimeProfile pipeline = profileSingleBE.get(pipelineIdx);
                     for (Pair<RuntimeProfile, Boolean> pipelineTaskProfile : 
pipeline.getChildList()) {
                         allPipelineTask.add(pipelineTaskProfile.first);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/common/profile/ExecutionProfileTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/common/profile/ExecutionProfileTest.java
new file mode 100644
index 00000000000..bd8310148bd
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/common/profile/ExecutionProfileTest.java
@@ -0,0 +1,71 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.common.profile;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.thrift.TNetworkAddress;
+import org.apache.doris.thrift.TUniqueId;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+public class ExecutionProfileTest {
+
+    // A fragment whose backends report different pipeline counts must not 
blow up the
+    // merge. pipelineSize is taken as the max across backends, so a backend 
with fewer
+    // pipelines has no profile at the higher indices; before the fix, 
indexing it threw
+    // IndexOutOfBoundsException and getPipelineAggregatedProfile lost the 
whole tree.
+    @Test
+    public void testAggregatedProfileUnevenPipelineCounts() {
+        TUniqueId queryId = new TUniqueId(1L, 2L);
+        ExecutionProfile executionProfile = new ExecutionProfile(queryId, 
Lists.newArrayList(0));
+
+        // Backend A has two pipelines, backend B only one.
+        TNetworkAddress beA = new TNetworkAddress("127.0.0.1", 9050);
+        TNetworkAddress beB = new TNetworkAddress("127.0.0.2", 9050);
+        executionProfile.setMultiBeProfile(0, beA, Lists.newArrayList(
+                pipelineWithTasks("Pipeline 0(host=" + beA + ")", 2),
+                pipelineWithTasks("Pipeline 1(host=" + beA + ")", 2)));
+        executionProfile.setMultiBeProfile(0, beB, Lists.newArrayList(
+                pipelineWithTasks("Pipeline 0(host=" + beB + ")", 2)));
+
+        // Must not throw despite the uneven pipeline counts.
+        RuntimeProfile result = 
executionProfile.getPipelineAggregatedProfile(Maps.newHashMap());
+
+        Assert.assertNotNull(result);
+        Assert.assertEquals("Fragments", result.getName());
+        // The max pipeline count (2) is used, so both pipelines are 
represented.
+        List<Pair<RuntimeProfile, Boolean>> fragments = result.getChildList();
+        Assert.assertEquals(1, fragments.size());
+        RuntimeProfile fragment0 = fragments.get(0).first;
+        Assert.assertEquals("Fragment 0", fragment0.getName());
+        Assert.assertEquals(2, fragment0.getChildList().size());
+    }
+
+    private RuntimeProfile pipelineWithTasks(String name, int taskNum) {
+        RuntimeProfile pipeline = new RuntimeProfile(name);
+        for (int i = 0; i < taskNum; i++) {
+            pipeline.addChild(new RuntimeProfile(name + "-task" + i), true);
+        }
+        return pipeline;
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to