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

Gabriel39 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 d86ad920d37 [fix](http) Clamp range reads to file size (#67634)
d86ad920d37 is described below

commit d86ad920d37c62c0adc494f9bd23c394bbbfb919
Author: Gabriel <[email protected]>
AuthorDate: Wed Sep 9 09:48:47 2026 +0800

    [fix](http) Clamp range reads to file size (#67634)
    
    ### What problem does this PR solve?
    
    Issue Number: DORIS-28543
    
    Related PR: None
    
    Problem Summary:
    
    `HttpFileReader` expands small reads to a 1 MiB read-ahead request. Near
    EOF, the generated byte range can extend beyond the file size. Some
    Range-capable object stores respond to that overlong range with HTTP 200
    and the complete object, so Doris incorrectly reports that Range support
    changed after it was successfully detected during open.
    
    This change clamps speculative read-ahead to the known file boundary. It
    also adds a local HTTP server unit test that reproduces the 206-at-open
    followed by 200-on-overlong-range behavior.
    
    ### Release note
    
    Fix HTTP range reads near EOF for servers that return the complete
    object for ranges extending past EOF.
    
    ### Check List (For Author)
    
    - Test
        - [ ] Regression test
        - [x] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason
    
    - Behavior changed:
        - [ ] No.
        - [x] Yes. HTTP read-ahead now respects the known EOF boundary.
    
    - Does this need documentation?
        - [x] No.
        - [ ] Yes.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 be/src/io/fs/http_file_reader.cpp       |  5 ++
 be/test/io/fs/http_file_reader_test.cpp | 84 +++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)

diff --git a/be/src/io/fs/http_file_reader.cpp 
b/be/src/io/fs/http_file_reader.cpp
index dc676e4a9f4..7ae9fc82a3f 100644
--- a/be/src/io/fs/http_file_reader.cpp
+++ b/be/src/io/fs/http_file_reader.cpp
@@ -280,6 +280,11 @@ Status HttpFileReader::read_at_impl(size_t offset, Slice 
result, size_t* bytes_r
         remaining = std::min<uint64_t>(to_read, left);
     }
     size_t req_len = (remaining > READ_BUFFER_SIZE) ? remaining : 
READ_BUFFER_SIZE;
+    if (_size_known) {
+        // Some servers return the entire object when an otherwise valid range 
crosses EOF, so the
+        // advertised file size must remain a hard boundary for speculative 
read-ahead.
+        req_len = std::min(req_len, _file_size - offset);
+    }
 
     VLOG(2) << "Issuing HTTP GET request: offset=" << offset << " req_len=" << 
req_len
             << " with_range=" << _range_supported;
diff --git a/be/test/io/fs/http_file_reader_test.cpp 
b/be/test/io/fs/http_file_reader_test.cpp
index 08241d5d5c5..1c63ce3fd21 100644
--- a/be/test/io/fs/http_file_reader_test.cpp
+++ b/be/test/io/fs/http_file_reader_test.cpp
@@ -19,9 +19,68 @@
 
 #include <gtest/gtest.h>
 
+#include <cstdio>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+
 #include "io/file_factory.h"
+#include "service/http/ev_http_server.h"
+#include "service/http/http_channel.h"
+#include "service/http/http_handler.h"
+#include "service/http/http_headers.h"
+#include "service/http/http_request.h"
 
 namespace doris::io {
+namespace {
+
+class EofSensitiveRangeHandler final : public HttpHandler {
+public:
+    explicit EofSensitiveRangeHandler(std::string content) : 
_content(std::move(content)) {}
+
+    void handle(HttpRequest* req) override {
+        if (req->method() == HttpMethod::HEAD) {
+            req->add_output_header(HttpHeaders::CONTENT_LENGTH,
+                                   std::to_string(_content.size()).c_str());
+            HttpChannel::send_reply(req);
+            return;
+        }
+
+        const std::string range = req->header(HttpHeaders::RANGE);
+        {
+            std::lock_guard<std::mutex> lock(_mutex);
+            _ranges.push_back(range);
+        }
+
+        unsigned long long begin = 0;
+        unsigned long long end = 0;
+        if (std::sscanf(range.c_str(), "bytes=%llu-%llu", &begin, &end) != 2 
|| begin > end ||
+            end >= _content.size()) {
+            HttpChannel::send_reply(req, _content);
+            return;
+        }
+
+        const std::string content_range = "bytes " + std::to_string(begin) + 
"-" +
+                                          std::to_string(end) + "/" +
+                                          std::to_string(_content.size());
+        req->add_output_header(HttpHeaders::CONTENT_RANGE, 
content_range.c_str());
+        HttpChannel::send_reply(req, HttpStatus::PARTIAL_CONTENT,
+                                _content.substr(begin, end - begin + 1));
+    }
+
+    std::vector<std::string> ranges() {
+        std::lock_guard<std::mutex> lock(_mutex);
+        return _ranges;
+    }
+
+private:
+    std::string _content;
+    std::mutex _mutex;
+    std::vector<std::string> _ranges;
+};
+
+} // namespace
 
 TEST(HttpFileReaderTest, ChunkResponseDisablesFileCache) {
     FileSystemProperties properties;
@@ -38,4 +97,29 @@ TEST(HttpFileReaderTest, ChunkResponseDisablesFileCache) {
     EXPECT_NE(std::dynamic_pointer_cast<HttpFileReader>(reader.value()), 
nullptr);
 }
 
+TEST(HttpFileReaderTest, RangeReadDoesNotCrossKnownEof) {
+    const std::string content = 
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
+    EofSensitiveRangeHandler handler(content);
+    EvHttpServer server(0);
+    ASSERT_TRUE(server.register_handler(HEAD, "/file", &handler));
+    ASSERT_TRUE(server.register_handler(GET, "/file", &handler));
+    server.start();
+    ASSERT_NE(server.get_real_port(), 0);
+
+    const std::string url = "http://127.0.0.1:"; + 
std::to_string(server.get_real_port()) + "/file";
+    auto reader = HttpFileReader::create(url, {}, FileReaderOptions::DEFAULT, 
nullptr);
+    ASSERT_TRUE(reader.has_value()) << reader.error();
+
+    constexpr size_t tail_size = 16;
+    char output[tail_size];
+    size_t bytes_read = 0;
+    Status status = reader.value()->read_at(content.size() - tail_size, 
Slice(output, tail_size),
+                                            &bytes_read);
+
+    ASSERT_TRUE(status.ok()) << status;
+    EXPECT_EQ(bytes_read, tail_size);
+    EXPECT_EQ(std::string(output, bytes_read), content.substr(content.size() - 
tail_size));
+    EXPECT_EQ(handler.ranges(), (std::vector<std::string> {"bytes=0-0", 
"bytes=48-63"}));
+}
+
 } // namespace doris::io


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

Reply via email to