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


##########
be/src/exec/rowid_fetcher.cpp:
##########
@@ -855,14 +876,91 @@ Status 
RowIdStorageReader::read_external_row_from_file_mapping(
                 file_read_times_counter->value(), 
file_read_times_counter->type());
     }
 
-    semaphore.release();
-    if (++producer_count == scan_rows_count) {
-        std::lock_guard<std::mutex> lock(mtx);
-        cv.notify_one();
-    }
     return Status::OK();
 }
 
+std::string RowIdStorageReader::source_column_key(const SlotDescriptor& slot, 
uint32_t column_idx) {
+    fmt::memory_buffer key;
+    // Length-prefix each component so distinct sequences cannot alias, e.g.
+    // paths ["a", "b"] -> "1:a1:b" while ["a:b"] -> "3:a:b".
+    auto append = [&key](std::string_view component) {
+        fmt::format_to(key, "{}:", component.size());
+        key.append(component.data(), component.data() + component.size());
+    };
+    append(slot.col_name());
+    append(std::to_string(column_idx));
+    append(std::to_string(slot.col_unique_id()));
+    append(std::to_string(slot.column_paths().size()));
+    for (const auto& path : slot.column_paths()) {
+        append(path);
+    }
+    append(std::to_string(slot.all_access_paths().size()));
+    // Encode each optional sub-path's presence bit separately from its element
+    // count so an absent path ("0") never aliases a present-but-empty path
+    // ("1" + size "0").
+    auto append_optional_path = [&append](bool is_set, const 
std::vector<std::string>& items) {
+        append(is_set ? "1" : "0");
+        if (is_set) {
+            append(std::to_string(items.size()));
+            for (const auto& item : items) {
+                append(item);
+            }
+        }
+    };
+    for (const auto& path : slot.all_access_paths()) {
+        append(fmt::format("{}", path.type));
+        append_optional_path(path.__isset.data_access_path, 
path.data_access_path.path);
+        append_optional_path(path.__isset.meta_access_path, 
path.meta_access_path.path);
+    }
+    return fmt::to_string(key);
+}
+
+Status RowIdStorageReader::submit_external_scan_tasks(
+        ScannerScheduler* scheduler, std::counting_semaphore<>& semaphore, 
size_t task_count,
+        const std::function<std::string(size_t)>& make_task_id,
+        const std::function<Status(size_t)>& run_task) {
+    // `completed_count` is a plain counter guarded by `mtx`; the same mutex 
guards
+    // the wait predicate below, so a worker can never notify between the 
waiter's
+    // predicate check and its wait.
+    AtomicStatus scan_status;
+    std::condition_variable cv;
+    std::mutex mtx;
+    size_t completed_count = 0;
+
+    // Only tasks the scheduler actually accepted are waited for. If a 
submission
+    // fails we stop submitting, but still wait for the already-accepted tasks 
so
+    // their workers cannot outlive the locals they capture by reference.
+    size_t submitted_count = 0;
+    for (size_t idx = 0; idx < task_count; ++idx) {
+        semaphore.acquire();
+        Status submit_st =
+                scheduler->submit_scan_task(SimplifiedScanTask(
+                                                    [&, idx]() -> bool {
+                                                        Defer complete([&] {
+                                                            
std::lock_guard<std::mutex> lock(mtx);
+                                                            ++completed_count;
+                                                            cv.notify_one();
+                                                        });
+                                                        
scan_status.update(run_task(idx));

Review Comment:
   [P1] Publish worker exceptions before signaling completion
   
   If `run_task(idx)` throws, this call never reaches `AtomicStatus::update`, 
but the completion defer still increments `completed_count` and wakes the 
waiter (and the inner scan defer releases its permit). The task executor 
catches `doris::Exception` only into the split's private future, which this 
helper never observes, so the RPC path can proceed with an OK `scan_status`, 
lose the original reader error, and skip the scheduler wrapper's task-removal 
path. Catch and convert the repository-standard exception set inside this 
callback, publish it to `scan_status`, and still return `true`; a 
throwing-worker unit test should cover this path.



##########
be/src/exec/rowid_fetcher.cpp:
##########
@@ -993,42 +1119,69 @@ Status RowIdStorageReader::read_batch_external_row(
     int64_t scan_running_time = 0;
     RETURN_IF_ERROR(scope_timer_run(
             [&]() -> Status {
-                // Make sure to insert data into result_block only after all 
scan tasks have been executed.
-                std::atomic<int> producer_count {0};
-                std::condition_variable cv;
-                std::mutex mtx;
-
                 //semaphore: Limit the number of scan tasks submitted at one 
time
                 std::counting_semaphore semaphore {max_file_scanners};
 
-                size_t idx = 0;
+                std::vector<std::pair<std::multimap<segment_v2::rowid_t, 
size_t>,
+                                      std::shared_ptr<FileMapping>>>
+                        scan_info_list;
+                scan_info_list.reserve(scan_rows.size());
                 for (const auto& [_, scan_info] : scan_rows) {
-                    semaphore.acquire();
-                    RETURN_IF_ERROR(remote_scan_sched->submit_scan_task(
-                            SimplifiedScanTask(
-                                    [&, idx, scan_info]() -> Status {
-                                        const auto& [row_ids, file_mapping] = 
scan_info;
-                                        return 
read_external_row_from_file_mapping(
-                                                idx, row_ids, file_mapping, 
slots, query_id,
-                                                runtime_state, scan_blocks, 
row_id_block_idx,
-                                                fetch_statistics, 
rpc_scan_params,
-                                                colname_to_slot_id, 
producer_count,
-                                                scan_rows.size(), semaphore, 
cv, mtx, tuple_desc);
-                                    },
-                                    nullptr, nullptr),
-                            fmt::format("{}-read_batch_external_row-{}", 
print_id(query_id), idx)));
-                    idx++;
+                    scan_info_list.emplace_back(scan_info);
                 }
 
-                {
-                    std::unique_lock<std::mutex> lock(mtx);
-                    cv.wait(lock, [&] { return producer_count == 
scan_rows.size(); });
-                }
-                return Status::OK();
+                return submit_external_scan_tasks(
+                        remote_scan_sched, semaphore, scan_rows.size(),
+                        [&](size_t idx) {
+                            return 
fmt::format("{}-read_batch_external_row-{}", print_id(query_id),
+                                               idx);
+                        },
+                        [&](size_t idx) -> Status {
+                            const auto& [row_ids, file_mapping] = 
scan_info_list[idx];
+                            return read_external_row_from_file_mapping(
+                                    idx, row_ids, file_mapping, scan_slots, 
query_id, runtime_state,
+                                    scan_blocks, row_id_block_idx, 
fetch_statistics,
+                                    rpc_scan_params, colname_to_slot_id, 
semaphore, tuple_desc);
+                        });
             },
             &scan_running_time));
 
-    scatter_scan_blocks_to_result_block(row_id_block_idx, scan_blocks, 
result_block);
+    // Insert the read data into result_block. Use insert_indices_from() 
instead of
+    // scatter_scan_blocks_to_result_block()/insert_from_multi_column(), 
because
+    // scan_blocks may have fewer columns than result_block when duplicate 
physical columns
+    // are deduplicated, and insert_from_multi_column() cannot handle 
ColumnString
+    // cross-type (32/64) copies safely.
+    uint32_t scan_position = 0;
+    for (size_t column_id = 0; column_id < result_block.get_columns().size(); 
column_id++) {

Review Comment:
   [P2] Use constant-time column counts in the scatter loop
   
   `Block::get_columns()` allocates a vector and calls 
`convert_to_full_column_if_const()` for every column. Calling it in this loop 
condition makes a C-column projection do O(C^2) column visits, and the inner 
DCHECK repeats the same full traversal per output cell in checked builds. These 
checks only need the count, so use `result_block.columns()` here and 
`scan_blocks[pos_block].columns()` below.



##########
regression-test/suites/external_table_p0/hive/test_hive_topn_lazy_mat.groovy:
##########
@@ -34,6 +34,15 @@ suite("test_hive_topn_lazy_mat", "p0,external") {
             qt_3 """ select score, value, active,name  from ${table} order by 
id,file_id limit 10; """
             qt_4 """ select value,name,id,file_id  from ${table} order by name 
limit 10; """
 
+            // Duplicate projected column - same column twice (the core bug 
from rowid_fetcher fix)
+            qt_dup_col_twice """ select name a, name b from ${table} order by 
id limit 10; """

Review Comment:
   [P2] Prove this duplicate projection exercises phase 2
   
   This result is identical when the suite later disables 
`topn_lazy_materialization_threshold`, and the existing EXPLAIN assertions 
cover different projection shapes. If duplicate aliases make this exact query 
fall back to eager scanning, the new regression stays green without entering 
the changed row-id dedup/remap path. Add an enabled-path EXPLAIN for this 
duplicate query (and the analogous TVF shape) that requires `VMaterializeNode`, 
the row-id slot, and the expected duplicate descriptor/index layout.



-- 
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