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


##########
be/src/cloud/cloud_internal_service.cpp:
##########
@@ -765,103 +861,46 @@ Status handle_peer_file_cache_block_request(const 
PFetchPeerDataRequest* request
                     int sleep_ms = dp->param<int>("sleep_ms", 300);
                     bthread_usleep(sleep_ms * 1000);
                 });
-        auto get_or_set_start = std::chrono::steady_clock::now();
-        auto holder = cache->get_or_set(hash, offset, size, ctx);
-        g_file_cache_get_by_peer_get_or_set_latency << 
elapsed_us(get_or_set_start);
-        ++get_or_set_calls;
-        get_or_set_blocks += holder.file_blocks.size();
-
-        for (auto& fb : holder.file_blocks) {
-            auto fb_state = fb->state();
-            if (fb_state == io::FileBlock::State::DOWNLOADING) {
-                if (do_fill) {
-                    // Only peer fill requests should wait longer here. Plain 
peer-cache reads keep
-                    // the short wait semantics so they can fail fast and let 
the client race S3.
-                    [[maybe_unused]] const bool completed = 
wait_for_file_block_state(
-                            fb, config::peer_server_cache_fill_timeout_ms);
-                    fb_state = fb->state();
-                } else {
-                    // Wait for in-progress download to complete using the 
normal short timeout.
-                    fb_state = fb->wait();
-                }
-            }
-            if (fb_state == io::FileBlock::State::EMPTY) {
-                if (!do_fill) {
-                    const std::string msg =
-                            fmt::format("cache block not downloaded, {}",
-                                        
format_peer_cache_block_context(request, cb_req, fb, hash,
-                                                                        
file_size, do_fill));
-                    g_file_cache_get_by_peer_failed_num << 1;
-                    g_file_cache_get_by_peer_not_downloaded_block_num << 1;
-                    LOG(WARNING) << msg;
-                    // Use NOT_FOUND so the client can distinguish "block not 
cached"
-                    // from an actual RPC/server error.  On NOT_FOUND the 
client rotates
-                    // the candidate to the end of its list (trying another CG 
next time)
-                    // rather than incrementing the RPC-failure eviction 
counter.
-                    response->mutable_status()->add_error_msgs(msg);
-                    
response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
-                    return Status::NotFound<false>(msg);
-                }
-                // Server-side fill: request.path already carries the full 
remote path.
-                auto fill_st = trigger_peer_server_fill(
-                        fb, get_rowset_meta_tablet_id_from_request(request), 
cache_key_path,
-                        get_rowset_meta_resource_id_from_request(request), 
path,
-                        request->has_file_size() ? request->file_size() : -1,
-                        static_cast<int64_t>(fb->range().left),
-                        static_cast<int64_t>(fb->range().size()),
-                        config::peer_server_cache_fill_timeout_ms);
-                if (!fill_st.ok()) {
-                    g_file_cache_get_by_peer_failed_num << 1;
-                    g_file_cache_get_by_peer_not_downloaded_block_num << 1;
-                    if (fill_st.is<ErrorCode::TOO_MANY_TASKS>()) {
-                        // Server slot exhausted: healthy but overloaded. 
Client must not rotate
-                        // or evict — just fall back to S3 and retry same 
candidate next time.
-                        
response->mutable_status()->add_error_msgs(std::string(fill_st.msg()));
-                        
response->mutable_status()->set_status_code(TStatusCode::TOO_MANY_TASKS);
-                    } else if (fill_st.is<ErrorCode::NOT_FOUND>()) {
-                        // Soft miss (fill incomplete, timeout, unexpected 
state) — client rotates,
-                        // not evicts.
-                        
response->mutable_status()->add_error_msgs(std::string(fill_st.msg()));
-                        
response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
-                    } else {
-                        LOG(WARNING) << "cache block fill failed, status=" << 
fill_st << ", "
-                                     << 
format_peer_cache_block_context(request, cb_req, fb, hash,
-                                                                        
file_size, do_fill);
-                        set_error_response(response, "cache block not ready");
-                    }
-                    return fill_st;
-                }
-                fb_state = io::FileBlock::State::DOWNLOADED;
-            }
-            if (fb_state != io::FileBlock::State::DOWNLOADED) {
-                // A concurrent download was in progress (DOWNLOADING at 
request time) but its
-                // wait() returned a non-DOWNLOADED state (e.g., timed-out 
while still
-                // DOWNLOADING, or some other non-EMPTY intermediate state).  
The server is
-                // healthy; the block just isn't available yet.  Return 
NOT_FOUND so the client
-                // rotates the candidate instead of evicting it.
-                const std::string msg =
-                        fmt::format("cache block not ready after wait, {}",
-                                    format_peer_cache_block_context(request, 
cb_req, fb, hash,
-                                                                    file_size, 
do_fill));
-                g_file_cache_get_by_peer_failed_num << 1;
-                g_file_cache_get_by_peer_not_downloaded_block_num << 1;
-                LOG(WARNING) << msg;
-                response->mutable_status()->add_error_msgs(msg);
-                
response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
-                return Status::NotFound<false>(msg);
+        if (do_fill) {
+            // Pull-through fill needs the EMPTY cell from get_or_set: the 
fill download lands in
+            // that cell, and this handler reads it back once the download 
finishes.
+            auto get_or_set_start = std::chrono::steady_clock::now();
+            auto holder = cache->get_or_set(hash, offset, size, ctx);
+            g_file_cache_get_by_peer_get_or_set_latency << 
elapsed_us(get_or_set_start);
+            ++get_or_set_calls;
+            get_or_set_blocks += holder.file_blocks.size();
+            for (auto& fb : holder.file_blocks) {
+                RETURN_IF_ERROR(serve_block(cb_req, fb));
             }
+            continue;
+        }
 
-            g_file_cache_get_by_peer_blocks_num << 1;
-            doris::CacheBlockPB* out = response->add_datas();
-            // In attachment mode, metadata order must match attachment append 
order because the
-            // client consumes attachment payload sequentially using 
resp.datas() order.
-            Status read_status = read_file_block(
-                    fb, file_size, out, use_attachment ? 
&cntl->response_attachment() : nullptr);
-            if (!read_status.ok()) {
-                set_error_response(response, "read cache file error");
-                return read_status;
+        // A plain peer read only looks up cached blocks, so probe() serves it 
without inserting
+        // EMPTY cells or taking downloader ownership. probe() asserts 
block-aligned offsets, and
+        // this offset comes from another BE whose cache block size may differ.
+        if (offset % cache->max_file_block_size() != 0) {
+            return not_found(fmt::format(
+                    "unaligned peer cache block request, req_block=[offset={}, 
size={}], "
+                    "max_file_block_size={}, {}",
+                    cb_req.block_offset(), cb_req.block_size(), 
cache->max_file_block_size(),
+                    format_peer_request_context(request, hash, file_size)));
+        }
+        auto probe_start = std::chrono::steady_clock::now();
+        auto probe_result = cache->probe(hash, offset, size, ctx);

Review Comment:
   [P1] Handle fragmented cached layouts before calling probe
   
   The offset guard does not establish probe()'s cached-layout preconditions. 
With a 4 MiB server cache block size and a file larger than 4 MiB, a 1 MiB 
client's pull-through fill calls the retained get_or_set(hash, 0, 1 MiB), 
publishing a short [0, 1 MiB) cell. An ordinary peer request for [0, 4 MiB), 
during or after that fill, passes the modulo check, but probe() encounters this 
cell and fails DORIS_CHECK(file_block->range().right >= expected_range.right) 
before checking its state. The fill downloader's aligned get_or_set preserves 
existing short cells, so this can also persist after fill completes. The failed 
check escapes the peer-fetch worker without an exception boundary and 
terminates the BE; the previous get_or_set path could serve the split layout. 
Use a non-mutating lookup that tolerates existing fragmented cells, or reject 
incompatible layouts with NOT_FOUND, and cover a smaller-block fill followed by 
an aligned plain read.



##########
be/test/io/cache/cached_remote_file_reader_peer_test.cpp:
##########
@@ -3501,6 +3671,38 @@ TEST_F(CrossCGWinnerRaceTest, 
race_peer_stops_when_s3_wins) {
     EXPECT_EQ(miss_service2.rpc_count.load(), 0) << "candidate 2 should be 
skipped (S3 won)";
 }
 
+// ----------------------------------------------------------------
+// Test: an S3 task that starts after the peer has won skips its GET.
+//
+// Holding S3 at s3_task_start models a task that waited in 
PeerRaceS3ThreadPool while the peer
+// won. The task must return before counting or issuing the S3 read.
+// ----------------------------------------------------------------
+TEST_F(CrossCGWinnerRaceTest, s3_task_skips_read_when_peer_won_while_queued) {
+    uint64_t s3_reads = 0;
+    uint64_t s3_skips = 0;
+    run_peer_win_with_s3_held_at("s3_skip_queued",
+                                 
"CachedRemoteFileReader::_execute_winner_race::s3_task_start",
+                                 &s3_reads, &s3_skips);
+    EXPECT_EQ(s3_reads, 0);
+    EXPECT_EQ(s3_skips, 1);
+}
+
+// ----------------------------------------------------------------
+// Test: an S3 task that started before the peer won still reads.
+//
+// s3_before_read is after the queue check, so the held task has already 
passed it and keeps
+// the existing read-then-discard behavior.
+// ----------------------------------------------------------------
+TEST_F(CrossCGWinnerRaceTest, s3_task_reads_when_started_before_peer_won) {
+    uint64_t s3_reads = 0;
+    uint64_t s3_skips = 0;
+    run_peer_win_with_s3_held_at("s3_read_started",
+                                 
"CachedRemoteFileReader::_execute_winner_race::s3_before_read",

Review Comment:
   [P2] Order peer completion after the S3 task passes its queue check
   
   Holding s3_before_read only delays S3 after the new winner check; it does 
not ensure the task reaches that point before the peer wins. 
_execute_winner_race starts the peer bthread before submitting S3, and this 
fixture's pool starts with zero worker threads, so the mock peer can publish 
winner == 0 before the worker runs. The task then correctly skips before 
invoking this callback, producing s3_reads == 0 and s3_skips == 1, contrary to 
the assertions below. This makes the new already-started test fail under a 
valid scheduling order. Gate the peer response with an explicit handshake until 
S3 reaches s3_before_read, then allow the peer to win.



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