This is an automated email from the ASF dual-hosted git repository.
HappenLee 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 fb19e0da0c0 [fix](be) Finish pending scanners after shared limit
(#65814)
fb19e0da0c0 is described below
commit fb19e0da0c0db23c0b643caa4710b80b9055e9dd
Author: Mryange <[email protected]>
AuthorDate: Mon Jul 27 15:11:39 2026 +0800
[fix](be) Finish pending scanners after shared limit (#65814)
### What problem does this PR solve?
Queries with a pushed-down shared scan limit could hang when multiple
pipeline tasks shared the same limit. After one scanner exhausted the
limit, a `ScannerContext` could still own pending scan tasks while
having no completed or in-flight task. The scheduler returned without
submitting a pending task, so that context never received EOS and its
pipeline task remained blocked.
Root cause: `_pull_next_scan_task()` treated an exhausted shared limit
as sufficient reason to skip every pending scanner, even when no other
task could make progress or wake the pipeline dependency.
This change skips pending scanners only while a completed or in-flight
task can still make progress. If neither exists, it submits a pending
scanner so the scanner can observe the exhausted limit, return EOS, and
wake the pipeline task. The shared limit continues to be consumed and
enforced in the scanner block-processing path; this change only fixes
the scheduling decision after the limit has been exhausted. It also
centralizes the
---
be/src/exec/scan/scanner_context.cpp | 18 ++-
be/src/exec/scan/scanner_context.h | 1 +
.../test_shared_scan_limit_pending_tasks.out | 7 +
.../test_shared_scan_limit_pending_tasks.groovy | 175 +++++++++++++++++++++
4 files changed, 196 insertions(+), 5 deletions(-)
diff --git a/be/src/exec/scan/scanner_context.cpp
b/be/src/exec/scan/scanner_context.cpp
index d6c7347e4a5..dfa5569fcb5 100644
--- a/be/src/exec/scan/scanner_context.cpp
+++ b/be/src/exec/scan/scanner_context.cpp
@@ -404,7 +404,7 @@ Status ScannerContext::get_block_from_queue(RuntimeState*
state, Block* block, b
if (_completed_tasks.empty() &&
(_num_finished_scanners == _all_scanners.size() ||
- (_shared_scan_limit->load(std::memory_order_acquire) == 0 &&
_in_flight_tasks_num == 0))) {
+ (_is_shared_scan_limit_exhausted() && _in_flight_tasks_num == 0))) {
_set_scanner_done();
_is_finished = true;
}
@@ -545,6 +545,10 @@ void ScannerContext::_set_scanner_done() {
_dependency->set_always_ready();
}
+bool ScannerContext::_is_shared_scan_limit_exhausted() const {
+ return limit >= 0 && _shared_scan_limit->load(std::memory_order_acquire)
<= 0;
+}
+
void ScannerContext::update_peak_running_scanner(int num) {
#ifndef BE_TEST
_local_state->_peak_running_scanner->add(num);
@@ -661,9 +665,11 @@ Status
ScannerContext::schedule_scan_task(std::shared_ptr<ScanTask> current_scan
if (first_pull) {
task_to_run = _pull_next_scan_task(current_scan_task,
current_concurrency);
if (task_to_run == nullptr) {
- // In two situations we will get nullptr.
+ // In three situations we will get nullptr.
// 1. current_concurrency already reached
_max_scan_concurrency.
// 2. all scanners are finished.
+ // 3. The shared LIMIT is exhausted while completed or
in-flight tasks can still
+ // make progress.
if (current_scan_task) {
DCHECK(current_scan_task->cached_block == nullptr);
DCHECK(!current_scan_task->is_eos());
@@ -735,9 +741,11 @@ std::shared_ptr<ScanTask>
ScannerContext::_pull_next_scan_task(
}
if (!_pending_tasks.empty()) {
- // Skip submitting more pending scanners once the LIMIT budget is
- // exhausted; they would only open and immediately EOF.
- if (_shared_scan_limit->load(std::memory_order_acquire) == 0) {
+ // Do not submit more pending scanners after the shared LIMIT is
exhausted while
+ // completed or in-flight tasks can still make progress. If neither
exists, allow pending
+ // scanners to be submitted so they can report EOS and wake the
pipeline task.
+ if (_is_shared_scan_limit_exhausted() &&
+ (_in_flight_tasks_num != 0 || !_completed_tasks.empty())) {
return nullptr;
}
std::shared_ptr<ScanTask> next_scan_task;
diff --git a/be/src/exec/scan/scanner_context.h
b/be/src/exec/scan/scanner_context.h
index 0f97e82126d..013deef3544 100644
--- a/be/src/exec/scan/scanner_context.h
+++ b/be/src/exec/scan/scanner_context.h
@@ -267,6 +267,7 @@ protected:
/// 3. `_free_blocks_memory_usage` < `_max_bytes_in_queue`, remains enough
memory to scale up
/// 4. At most scale up `MAX_SCALE_UP_RATIO` times to `_max_thread_num`
void _set_scanner_done();
+ bool _is_shared_scan_limit_exhausted() const;
RuntimeState* _state = nullptr;
ScanLocalStateBase* _local_state = nullptr;
diff --git
a/regression-test/data/correctness_p0/test_shared_scan_limit_pending_tasks.out
b/regression-test/data/correctness_p0/test_shared_scan_limit_pending_tasks.out
new file mode 100644
index 00000000000..ae79c1d44f4
--- /dev/null
+++
b/regression-test/data/correctness_p0/test_shared_scan_limit_pending_tasks.out
@@ -0,0 +1,7 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !shared_scan_limit_parallel_2 --
+
+-- !shared_scan_limit_parallel_6 --
+
+-- !shared_scan_limit_parallel_8 --
+
diff --git
a/regression-test/suites/correctness_p0/test_shared_scan_limit_pending_tasks.groovy
b/regression-test/suites/correctness_p0/test_shared_scan_limit_pending_tasks.groovy
new file mode 100644
index 00000000000..72f47c16f66
--- /dev/null
+++
b/regression-test/suites/correctness_p0/test_shared_scan_limit_pending_tasks.groovy
@@ -0,0 +1,175 @@
+// 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.
+
+suite("test_shared_scan_limit_pending_tasks") {
+ sql "DROP TABLE IF EXISTS shared_scan_limit_t1"
+ sql "DROP TABLE IF EXISTS shared_scan_limit_t3"
+ sql "DROP TABLE IF EXISTS shared_scan_limit_t4"
+ sql "DROP TABLE IF EXISTS shared_scan_limit_t5"
+
+ sql """
+ CREATE TABLE shared_scan_limit_t1 (
+ pk INT,
+ v1 BIGINT,
+ v2 BIGINT
+ ) ENGINE=OLAP
+ DUPLICATE KEY(pk)
+ DISTRIBUTED BY HASH(pk) BUCKETS 10
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ CREATE TABLE shared_scan_limit_t3 (
+ pk INT,
+ v1 BIGINT,
+ v2 BIGINT
+ ) ENGINE=OLAP
+ DUPLICATE KEY(pk)
+ DISTRIBUTED BY HASH(pk) BUCKETS 10
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ CREATE TABLE shared_scan_limit_t4 (
+ pk INT,
+ v1 BIGINT,
+ v2 BIGINT
+ ) ENGINE=OLAP
+ DUPLICATE KEY(pk)
+ DISTRIBUTED BY HASH(pk) BUCKETS 10
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ CREATE TABLE shared_scan_limit_t5 (
+ pk INT,
+ v1 BIGINT,
+ v2 BIGINT
+ ) ENGINE=OLAP
+ DUPLICATE KEY(pk)
+ DISTRIBUTED BY HASH(pk) BUCKETS 10
+ PROPERTIES ("replication_num" = "1")
+ """
+
+ sql """
+ INSERT INTO shared_scan_limit_t1 VALUES
+ (0,70,32163), (1,-71,NULL), (2,-85,-222125), (3,26,22867),
+ (4,17,NULL), (5,NULL,6236439), (6,-16907,-24720), (7,26877,1893),
+ (8,30405,9), (9,5873343,66), (10,-17,-47), (11,NULL,20987),
+ (12,NULL,-4672), (13,NULL,3551614), (14,28521,-1372),
(15,NULL,NULL),
+ (16,38,NULL), (17,NULL,-54), (18,NULL,-4686447), (19,NULL,-8420)
+ """
+ sql """
+ INSERT INTO shared_scan_limit_t3 VALUES
+ (0,28862,-25225), (1,5516858,-4609390), (2,-7815300,NULL),
+ (3,NULL,-7685824), (4,22293,26373), (5,NULL,NULL), (6,1237,-31),
+ (7,1606,3318374), (8,NULL,-21355), (9,NULL,-20127),
(10,5413995,-3),
+ (11,-4718995,NULL), (12,3179854,3733421), (13,18867,76),
+ (14,15517,-4932405), (15,-30778,NULL), (16,NULL,-7258),
+ (17,NULL,7520313), (18,6936629,NULL), (19,-45,6231596)
+ """
+ sql """
+ INSERT INTO shared_scan_limit_t4 VALUES
+ (0,5649134,4812327), (1,8249417,4670054), (2,106,6714155),
+ (3,NULL,NULL), (4,-52850,-5113499), (5,-23911,-9637), (6,-26168,9),
+ (7,-47,-28398), (8,-29518,-2365317), (9,20685,22956),
(10,97,25099),
+ (11,-32617,6143808), (12,NULL,NULL), (13,-52,NULL),
(14,7680925,NULL),
+ (15,10848,NULL), (16,64,-3394), (17,113,-12488), (18,-87,-12093),
+ (19,NULL,22)
+ """
+ sql """
+ INSERT INTO shared_scan_limit_t5 VALUES
+ (0,-3018453,-5763927), (1,-60,-6233027), (2,78,-5304), (3,63,NULL),
+ (4,23287,NULL), (5,98,7989209), (6,-61,-30493),
(7,-6781665,-22321),
+ (8,-5165806,-75), (9,NULL,31290), (10,1311226,7754904),
(11,-72,54),
+ (12,114,3741372), (13,NULL,NULL), (14,-40,2014555),
(15,69,-4555977),
+ (16,118,-8708), (17,NULL,NULL), (18,-123,NULL), (19,NULL,19843)
+ """
+ sql "sync"
+
+ sql "SET enable_sql_cache = false"
+ sql "SET experimental_enable_local_shuffle = false"
+ sql "SET query_timeout = 60"
+
+ // A shared LIMIT can be exhausted while another ScannerContext still owns
pending tasks.
+ // Every context must still reach EOS instead of leaving its pipeline task
blocked forever.
+ sql "SET parallel_pipeline_task_num = 2"
+ qt_shared_scan_limit_parallel_2 """
+ SELECT v1, pk
+ FROM shared_scan_limit_t1 AS t1
+ WHERE v2 > (
+ SELECT SUM(v1)
+ FROM shared_scan_limit_t5 AS t3
+ WHERE EXISTS (
+ SELECT pk
+ FROM shared_scan_limit_t3 AS t4
+ WHERE NOT EXISTS (
+ SELECT pk, v1, v1, v2, v1
+ FROM shared_scan_limit_t4 AS t5
+ WHERE v2 NOT IN (
+ SELECT pk FROM shared_scan_limit_t1 AS t6 ORDER BY pk
+ )
+ ) AND v1 < 7
+ )
+ )
+ ORDER BY v1, pk DESC
+ LIMIT 6
+ """
+
+ sql "SET parallel_pipeline_task_num = 6"
+ qt_shared_scan_limit_parallel_6 """
+ SELECT v1, pk
+ FROM shared_scan_limit_t1 AS t1
+ WHERE v2 > (
+ SELECT SUM(v1)
+ FROM shared_scan_limit_t5 AS t3
+ WHERE EXISTS (
+ SELECT pk
+ FROM shared_scan_limit_t3 AS t4
+ WHERE NOT EXISTS (
+ SELECT pk, v1, v1, v2, v1
+ FROM shared_scan_limit_t4 AS t5
+ WHERE v2 NOT IN (
+ SELECT pk FROM shared_scan_limit_t1 AS t6 ORDER BY pk
+ )
+ ) AND v1 < 7
+ )
+ )
+ ORDER BY v1, pk DESC
+ LIMIT 6
+ """
+
+ sql "SET parallel_pipeline_task_num = 8"
+ qt_shared_scan_limit_parallel_8 """
+ SELECT v1, pk
+ FROM shared_scan_limit_t1 AS t1
+ WHERE v2 > (
+ SELECT SUM(v1)
+ FROM shared_scan_limit_t5 AS t3
+ WHERE EXISTS (
+ SELECT pk
+ FROM shared_scan_limit_t3 AS t4
+ WHERE NOT EXISTS (
+ SELECT pk, v1, v1, v2, v1
+ FROM shared_scan_limit_t4 AS t5
+ WHERE v2 NOT IN (
+ SELECT pk FROM shared_scan_limit_t1 AS t6 ORDER BY pk
+ )
+ ) AND v1 < 7
+ )
+ )
+ ORDER BY v1, pk DESC
+ LIMIT 6
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]