github-actions[bot] commented on code in PR #67066:
URL: https://github.com/apache/doris/pull/67066#discussion_r3842692189


##########
be/src/common/config.cpp:
##########
@@ -333,9 +333,9 @@ DEFINE_Int32(task_executor_max_concurrency_per_task, "-1");
 DEFINE_Int32(task_executor_initial_max_concurrency_per_task, "-1");
 
 // Enable task executor in internal table scan.
-DEFINE_Bool(enable_task_executor_in_internal_table, "false");
+DEFINE_Bool(enable_task_executor_in_internal_table, "true");

Review Comment:
   [P1] Do not default to TaskExecutor while submit failures can strand splits 
— Initial scanner admission increments the Context's in-flight count, but 
`_start_split()` only logs `_do_submit()` failures and `enqueue_splits()` still 
returns success. Capacity rejection leaves the split marked running with no 
executor queue owner; a failed first worker creation leaves it queued in an 
executor that can have zero workers because `init()` ignores minimum-worker 
creation failures. In either case nothing releases the slot or wakes the 
dependency, so the scan can hang. Please propagate/roll back pre-publication 
rejection and guarantee progress for retained work, with deterministic 
ScannerContext coverage, before enabling this path by default.



##########
be/src/common/config.cpp:
##########
@@ -333,9 +333,9 @@ DEFINE_Int32(task_executor_max_concurrency_per_task, "-1");
 DEFINE_Int32(task_executor_initial_max_concurrency_per_task, "-1");
 
 // Enable task executor in internal table scan.
-DEFINE_Bool(enable_task_executor_in_internal_table, "false");
+DEFINE_Bool(enable_task_executor_in_internal_table, "true");

Review Comment:
   [P1] Fix the TaskExecutor shutdown wakeup before enabling it by default — A 
scanner submission can reserve the only pending worker and enter 
`_create_thread()` outside the lock while concurrent scheduler `stop()` clears 
the queue and waits for live+pending to become zero. If creation fails, 
`_do_submit()` decrements the final pending count and returns without notifying 
`_no_threads_cond`; no dispatch worker exists to send the other notification, 
so WorkloadGroup shutdown can wait forever. Please notify this transition and 
add deterministic stop/create-failure coverage before switching the defaults.



##########
be/src/util/threadpool.cpp:
##########
@@ -454,12 +454,10 @@ Status ThreadPool::do_submit(std::shared_ptr<Runnable> r, 
ThreadPoolToken* token
     // We assume that each current inactive thread will grab one item from the
     // queue.  If it seems like we'll need another thread, we create one.
     //
-    // Rather than creating an additional thread here, while holding the lock,
-    // we defer it to down below. This is because thread creation can be rather
-    // slow (hundreds of milliseconds in some cases) and we'd like to allow the
-    // existing threads to continue to process tasks while we do so. The first
-    // thread is an exception: it must be created before publishing the task so
-    // a failed submission cannot leave a runnable in a pool with no workers.
+    // Rather than creating the thread here, while holding the lock, we defer

Review Comment:
   [P1] Keep failed submissions from retaining the runnable — With a 
zero-worker pool, this now publishes the task before `create_thread()`. If 
`pthread_create` fails, the branch below returns non-OK without removing the 
token/queue entry, so a later successful submit can execute a callback its 
caller already treated as rejected. That is unsafe for generic callers: for 
example, `S3FileWriter::close()` performs the close and fulfills its promise 
synchronously after a failed submit, while other callers capture stack/object 
state by reference. Please create the first worker before publication (or fully 
roll back the task) so a non-OK submit guarantees the runnable cannot run, and 
retain the deleted regression coverage.



##########
be/src/util/threadpool.cpp:
##########
@@ -582,12 +563,7 @@ void ThreadPool::dispatch_thread() {
             break;
         }
 
-        // A runtime shrink may leave more live + pending threads than 
_max_threads. Excess threads
-        // retire here, but the last live worker must stay while tasks are 
queued: a pending
-        // replacement may still fail to start, and a pool with queued tasks 
and no worker can only
-        // recover on the next submit. The excess thread retires once the 
queue drains.
-        if (_num_threads + _num_threads_pending_start > _max_threads &&
-            (_num_threads > 1 || _queue.empty())) {
+        if (_num_threads + _num_threads_pending_start > _max_threads) {

Review Comment:
   [P1] Keep the last live worker while work is queued — A submit can publish 
task B and count one pending replacement while the current worker is active. If 
the pool is concurrently shrunk from 2 to 1, this condition retires that sole 
live worker because `1 live + 1 pending > 1`; if the replacement then fails to 
start, B is stranded with no worker and waits cannot complete. Please preserve 
the last live worker while `_queue` is nonempty until another worker has 
actually started, and retain the deleted shrink/failure regression test.



##########
be/src/common/config.cpp:
##########
@@ -333,9 +333,9 @@ DEFINE_Int32(task_executor_max_concurrency_per_task, "-1");
 DEFINE_Int32(task_executor_initial_max_concurrency_per_task, "-1");
 
 // Enable task executor in internal table scan.
-DEFINE_Bool(enable_task_executor_in_internal_table, "false");
+DEFINE_Bool(enable_task_executor_in_internal_table, "true");
 // Enable task executor in external table scan.
-DEFINE_Bool(enable_task_executor_in_external_table, "false");
+DEFINE_Bool(enable_task_executor_in_external_table, "true");

Review Comment:
   [P1] Preserve a live TaskExecutor worker across runtime shrink — External 
WorkloadGroup schedulers can shrink from max 2 to 1 while one worker is active, 
task B is queued, and a replacement is only pending. This exit condition then 
retires the sole live worker; if the replacement's thread creation fails, B 
remains queued with no worker and the scanner dependency cannot complete. 
Please keep the last live worker while queued work relies only on a fallible 
pending replacement, and cover resize plus injected creation failure before 
making this executor the default.



##########
be/src/common/config.cpp:
##########
@@ -333,9 +333,9 @@ DEFINE_Int32(task_executor_max_concurrency_per_task, "-1");
 DEFINE_Int32(task_executor_initial_max_concurrency_per_task, "-1");
 
 // Enable task executor in internal table scan.
-DEFINE_Bool(enable_task_executor_in_internal_table, "false");
+DEFINE_Bool(enable_task_executor_in_internal_table, "true");

Review Comment:
   [P1] Honor the configured per-task maximum on the new default path — 
Production constructs TimeSharing with `enable_concurrency_control=false`, so 
direct scanner `enqueue_splits()` polls against the handle's initial target (at 
least 48) and never consults `task_executor_max_concurrency_per_task`; that 
executor-wide cap is checked only on a later entrant path. Setting the 
documented maximum to 1 can therefore still promote the Context's normal 4 
internal or 16 external splits. Please enforce the maximum even when adaptive 
control is disabled and cover the production constructor mode before enabling 
it by default.



##########
be/src/common/config.cpp:
##########
@@ -333,9 +333,9 @@ DEFINE_Int32(task_executor_max_concurrency_per_task, "-1");
 DEFINE_Int32(task_executor_initial_max_concurrency_per_task, "-1");
 
 // Enable task executor in internal table scan.
-DEFINE_Bool(enable_task_executor_in_internal_table, "false");
+DEFINE_Bool(enable_task_executor_in_internal_table, "true");
 // Enable task executor in external table scan.
-DEFINE_Bool(enable_task_executor_in_external_table, "false");
+DEFINE_Bool(enable_task_executor_in_external_table, "true");

Review Comment:
   [P1] Preserve the low-memory occupancy cap on the new default path — 
TaskExecutor's low-memory clamp subtracts only in-flight tasks, although 
completed blocks still occupy scanner memory and concurrency slots. After four 
tasks complete, consuming one can leave three completed and admit four more 
when the scheduler margin is positive, producing seven occupied tasks despite 
the documented cap of four; the ThreadPool path correctly compares completed 
plus in-flight. Please cap total occupied concurrency and add this 
completed-block state to TaskExecutor coverage before enabling it by default 
for external scans.



##########
be/src/util/threadpool.cpp:
##########
@@ -750,9 +717,6 @@ Status ThreadPool::set_min_threads(int min_threads) {
 
 Status ThreadPool::set_max_threads(int max_threads) {
     std::lock_guard<std::mutex> l(_lock);
-    if (max_threads <= 0) {

Review Comment:
   [P2] Reject a zero runtime maximum — When `min_threads` is zero, removing 
this check lets `set_max_threads(0)` succeed even though the builder requires a 
positive maximum. Existing workers then retire; queued work can trip the 
last-worker `CHECK`, and an idle pool can later accept a task without ever 
satisfying the condition that creates a worker. Please retain the 
positive-maximum invariant and its deleted regression test.



##########
be/src/exec/scan/scanner_context.cpp:
##########
@@ -885,6 +840,13 @@ std::shared_ptr<ScanTask> 
ScannerContext::_pull_next_scan_task(
     }
 
     if (!_pending_tasks.empty()) {
+        // 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() &&

Review Comment:
   [P1] Apply the exhausted-LIMIT guard to the current scanner too — After a 
peer exhausts the shared LIMIT, consuming scanner A's non-EOS block can leave 
scanner B completed or in flight, so the Context is not terminal yet. This 
function returns A before reaching this new guard; with a positive minimum and 
a saturated default TaskExecutor, the unnecessary re-enqueue is rejected and 
`TOO_MANY_TASKS` replaces an already-complete LIMIT result even though B 
guarantees progress. Please check the exhausted-LIMIT/progress condition before 
selecting `current_scan_task` and cover this consumed-block/re-enqueue case.



##########
be/src/exec/scan/scanner_scheduler.cpp:
##########
@@ -185,10 +185,6 @@ void 
ScannerScheduler::_scanner_scan(std::shared_ptr<ScannerContext> ctx,
 
     ASSIGN_STATUS_IF_CATCH_EXCEPTION(
             RuntimeState* state = ctx->state(); DCHECK(nullptr != state);
-            // Do not suppress admission when shared LIMIT is exhausted. A 
queued scanner still
-            // needs to complete as EOS so push_completed_scan_task() releases 
its in-flight slot
-            // and the pipeline can observe completion instead of waiting 
indefinitely.
-            if (ctx->is_shared_scan_limit_exhausted()) { eos = true; }

Review Comment:
   [P1] Check shared LIMIT before preparing or opening an admitted scanner — 
Scanner A can satisfy the shared LIMIT while scanner B is already admitted but 
has not started. Without this guard, B reaches `prepare()`/`open()` before 
`Scanner::get_block()` notices the exhausted counter, so an otherwise unused 
JDBC/tablet/file split can publish an open error and fail the query before A's 
valid final block is consumed. Please retain the pre-open EOS check (while 
still publishing B to release its in-flight slot) and cover a second scanner 
whose open would fail after a peer produces the final row.



##########
be/src/exec/scan/simplified_scan_scheduler.cpp:
##########
@@ -58,43 +58,32 @@ Status 
ThreadPoolSimplifiedScanScheduler::schedule_scan_task(
         return Status::OK();
     }
 
-    // transfer_lock prevents another producer from submitting concurrently. 
The worker callback
-    // also waits for this lock, so it cannot run between successful 
submission and marking queued.
     if (_is_stop) {
-        // Shutdown must surface: progressing tasks may never complete on a 
stopped pool.
-        return Status::InternalError<false>("scanner pool {} is shutdown.", 
_sched_name);
+        Status failure = Status::InternalError<false>("scanner pool {} is 
shutdown.", _sched_name);
+        scanner_ctx->set_context_failure(failure, transfer_lock);
+        return failure;
     }
+
+    // ThreadPool::submit_func() may return an error after retaining the 
runnable. Set the marker
+    // before submission so either outcome is safe: a retained callback clears 
it, while a truly
+    // rejected callback leaves a terminal Context that no longer needs 
rescheduling.
+    scanner_ctx->set_context_queued(true, transfer_lock);
     Status status =
             _scan_thread_pool->submit_func([this, scanner_ctx] { 
_run_context(scanner_ctx); });
     if (status.ok()) {
-        // Start the Context wait interval only after submission succeeds. 
This excludes failed
-        // submit_func() calls, which never waited for a worker and must not 
affect the profile.
-        scanner_ctx->set_context_queued(true, transfer_lock);
-        return Status::OK();
-    }
-    // No worker can dequeue a rejected runnable. The Context remains 
unqueued, so a later
-    // scheduling attempt can submit it again without clearing state or 
accounting queue time.
-    LOG(WARNING) << fmt::format("Failed to submit scanner context {}, reason: 
{}",
-                                scanner_ctx->debug_string(), 
status.to_string());
-    if (scanner_ctx->has_progressing_task(transfer_lock) ||
-        scanner_ctx->is_shared_scan_limit_exhausted()) {
-        // Someone will retry: a progressing task completes, the operator 
consumes its result and
-        // reschedules; after shared LIMIT is exhausted, 
get_block_from_queue() finishes the
-        // Context on its next call. Pool saturation must not fail a query 
that still progresses.
         return Status::OK();
     }
-    // Nothing will retry this Context. Surface the failure, normalized like
-    // ScannerScheduler::submit() so both schedulers report saturation as 
TOO_MANY_TASKS.
-    return Status::TooManyTasks("Failed to submit scanner context {} to 
scanner pool, reason: {}",
-                                scanner_ctx->ctx_id, status.msg());
+    Status failure =
+            Status::TooManyTasks("Failed to submit scanner context {} to 
scanner pool, reason: {}",
+                                 scanner_ctx->ctx_id, status.msg());
+    scanner_ctx->set_context_failure(failure, transfer_lock);

Review Comment:
   [P2] Do not abort when an admitted scanner guarantees retry progress — This 
path also handles ordinary queue-capacity rejection, which occurs before 
ThreadPool retains the callback. `_run_context()` has already admitted scanner 
A and is only pre-queuing the next Context; executing A would publish a 
result/EOS, wake the operator, and provide a safe retry point. Marking the 
Context terminal instead turns transient backpressure into a query failure. 
Please preserve retry-on-progress for unambiguous pre-enqueue rejection while 
fixing the separate generic post-publication failure contract.



##########
be/src/util/threadpool.cpp:
##########
@@ -537,16 +521,13 @@ Status ThreadPool::do_submit(std::shared_ptr<Runnable> r, 
ThreadPoolToken* token
             l.lock();
             _num_threads_pending_start--;
             if (_num_threads + _num_threads_pending_start == 0) {
-                _no_threads_cond.notify_all();
-            }
-            if (_pool_status.ok()) {
-                // A published task either already ran or still has a live or 
pending worker.
-                // The last-worker guard in dispatch_thread() keeps this true; 
a violation is a
-                // pool logic error.
-                DORIS_CHECK(_queue.empty() || _num_threads + 
_num_threads_pending_start > 0);
-                LOG(WARNING) << "Thread pool " << _name
-                             << " failed to create thread: " << 
status.to_string();
+                // If we have no threads, we can't do any work.
+                return status;

Review Comment:
   [P1] Wake shutdown when the final worker creation fails — `shutdown()` can 
clear the queues and wait on `_no_threads_cond` while this submitter is outside 
the lock creating the only pending worker. If creation fails, this branch 
decrements live+pending to zero and returns without notifying, and there is no 
dispatch thread left to send the other notification. The concurrent shutdown 
can therefore wait forever. Please notify `_no_threads_cond` whenever this 
failure transition reaches zero and add deterministic concurrent-shutdown 
coverage.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to