[DNM][VL] Add regression tests for rss-sort shuffle reader EOS handling - #12983
[DNM][VL] Add regression tests for rss-sort shuffle reader EOS handling#12983kecookier wants to merge 3 commits into
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
arrow::io::Readable::Read(int64_t) is pure virtual; without an override FakeInputStream is abstract and make_shared fails to compile. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Adds a new Velox shuffle reader regression test suite targeting rss_sort EOS handling and boundary conditions, with coverage for empty streams, mid-page truncation, negative reads, multi-window page parsing, and checksum paths.
Changes:
- Introduce
VeloxShuffleReaderTestwith a controllableFakeInputStreamto probe EOS/Read edge cases via the public deserializer API. - Add regression scenarios for multi-window uncompressed pages (nested structs, header refills), zero-copy single-window pages, and
GlutenByteInputStream::nextViewbehavior. - Wire the new test binary into the Velox tests CMake target list.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| cpp/velox/tests/VeloxShuffleReaderTest.cc | Adds a new gtest suite with fake Arrow streams to regress rss_sort reader EOS and window-boundary behaviors. |
| cpp/velox/tests/CMakeLists.txt | Registers the new velox_shuffle_reader_test binary in the test build. |
Suppressed comments (1)
cpp/velox/tests/VeloxShuffleReaderTest.cc:1
- These two tests are explicitly described as expected to fail on current main. If this PR is ever merged without the accompanying fix, it will cause the test suite to fail. To keep the regression coverage without breaking main, mark the tests as disabled (
DISABLED_EosMidPageTerminates,DISABLED_NegativeReadThrows) orGTEST_SKIP()them behind a runtime/compile-time gate until the fix is present.
/*
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| add_velox_test(velox_shuffle_writer_test SOURCES VeloxShuffleWriterTest.cc) | ||
|
|
||
| add_velox_test(velox_shuffle_reader_test SOURCES VeloxShuffleReaderTest.cc) |
| auto deserializer = makeDeserializer( | ||
| std::make_shared<FakeInputStream>(std::vector<uint8_t>{}, /*negativeRead=*/true)); | ||
| VELOX_ASSERT_THROW(deserializer->next(), "Read returned negative value"); | ||
| } |
| // Throw a plain C++ exception: the reader wraps Read() in | ||
| // arrow::Result and drops arrow errors via .ValueOr(0), so an | ||
| // arrow::Status::IOError would be swallowed and the loop would spin on. | ||
| throw std::runtime_error( | ||
| "possible infinite loop: Read() returned 0 for " + std::to_string(kMaxConsecutiveEosReads) + | ||
| " consecutive calls"); |
| auto rowVectorA = makeNestedArraysRowVector(kArraysA, kElementsPerArray); | ||
| auto rowVectorB = makeNestedArraysRowVector(kArraysB, kElementsPerArray); | ||
| auto pageA = serializePage(rowVectorA); | ||
| auto pageB = serializePage(rowVectorB); |
| std::vector<uint8_t> payload = pageA; | ||
| payload.insert(payload.end(), pageB.begin(), pageB.end()); | ||
| // First window holds all of A plus only 10 bytes of B's header. | ||
| const int64_t firstReadLimit = static_cast<int64_t>(pageA.size()) + 10; |
| template <typename T> | ||
| void appendLe(std::vector<uint8_t>& out, T value) { | ||
| T v = value; | ||
| const auto* p = reinterpret_cast<const uint8_t*>(&v); | ||
| out.insert(out.end(), p, p + sizeof(T)); | ||
| } |
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| TEST_F(VeloxShuffleReaderTest, EosMidPageTerminates) { | ||
| auto payload = buildTruncatedCompressedPage(/*compressedSize=*/1000, /*bodyBytes=*/8); | ||
| auto deserializer = makeDeserializer(std::make_shared<FakeInputStream>(std::move(payload))); | ||
|
|
||
| VELOX_ASSERT_THROW( | ||
| deserializer->next(), "Reading past end of VeloxRssSortShuffleReaderDeserializer::VeloxInputStream"); | ||
| } |
| TEST_F(VeloxShuffleReaderTest, NegativeReadThrows) { | ||
| auto deserializer = makeDeserializer( | ||
| std::make_shared<FakeInputStream>(std::vector<uint8_t>{}, /*negativeRead=*/true)); | ||
| VELOX_ASSERT_THROW(deserializer->next(), "Read returned negative value"); | ||
| } |
| arrow::Result<int64_t> Read(int64_t nbytes, void* out) override { | ||
| if (negativeRead_) { | ||
| return static_cast<int64_t>(-1); | ||
| } | ||
| int64_t toRead = std::min<int64_t>(nbytes, static_cast<int64_t>(payload_.size()) - pos_); | ||
| if (firstRead_ && firstReadLimit_ >= 0) { | ||
| toRead = std::min<int64_t>(toRead, firstReadLimit_); | ||
| firstRead_ = false; | ||
| } | ||
| if (toRead > 0) { | ||
| std::memcpy(out, payload_.data() + pos_, toRead); | ||
| pos_ += toRead; | ||
| consecutiveEosReads_ = 0; | ||
| } else if (++consecutiveEosReads_ > kMaxConsecutiveEosReads) { | ||
| // Throw a plain C++ exception: the reader wraps Read() in | ||
| // arrow::Result and drops arrow errors via .ValueOr(0), so an | ||
| // arrow::Status::IOError would be swallowed and the loop would spin on. | ||
| throw std::runtime_error( | ||
| "possible infinite loop: Read() returned 0 for " + std::to_string(kMaxConsecutiveEosReads) + | ||
| " consecutive calls"); | ||
| } | ||
| return toRead; // 0 == EOS when payload exhausted | ||
| } |
| arrow::Result<std::shared_ptr<arrow::Buffer>> Read(int64_t nbytes) override { | ||
| ARROW_ASSIGN_OR_RAISE(auto buffer, arrow::AllocateResizableBuffer(nbytes)); | ||
| ARROW_ASSIGN_OR_RAISE(int64_t bytesRead, Read(nbytes, buffer->mutable_data())); | ||
| ARROW_RETURN_NOT_OK(buffer->Resize(bytesRead, false)); | ||
| buffer->ZeroPadding(); | ||
| return std::move(buffer); | ||
| } |
|
|
||
| auto rowVector = makeNestedArraysRowVector(kNumArrays, kElementsPerArray); | ||
| auto payload = serializePage(rowVector); | ||
| // The page must be larger than the reader's read window (~1MB) to exercise | ||
| // the multi-window slow path. | ||
| ASSERT_GT(payload.size(), 1 << 20); |
| // Small page: header + payload well under 1MB -> zero-copy fast path. | ||
| ASSERT_LT(payload.size(), 1 << 20); |
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
|
|
||
| #include <cstdint> | ||
| #include <cstring> | ||
| #include <memory> | ||
| #include <sstream> | ||
| #include <stdexcept> |
| arrow::Result<std::shared_ptr<arrow::Buffer>> Read(int64_t nbytes) override { | ||
| ARROW_ASSIGN_OR_RAISE(auto buffer, arrow::AllocateResizableBuffer(nbytes)); | ||
| ARROW_ASSIGN_OR_RAISE(int64_t bytesRead, Read(nbytes, buffer->mutable_data())); | ||
| ARROW_RETURN_NOT_OK(buffer->Resize(bytesRead, false)); | ||
| buffer->ZeroPadding(); | ||
| return std::move(buffer); | ||
| } |
| template <typename T> | ||
| void appendLe(std::vector<uint8_t>& out, T value) { | ||
| T v = value; | ||
| const auto* p = reinterpret_cast<const uint8_t*>(&v); | ||
| out.insert(out.end(), p, p + sizeof(T)); | ||
| } |
| constexpr vector_size_t kNumArrays = 20000; | ||
| constexpr vector_size_t kElementsPerArray = 30; | ||
|
|
||
| auto rowVector = makeNestedArraysRowVector(kNumArrays, kElementsPerArray); | ||
| auto payload = serializePage(rowVector); | ||
| // The page must be larger than the reader's read window (~1MB) to exercise | ||
| // the multi-window slow path. | ||
| ASSERT_GT(payload.size(), 1 << 20); |
| VELOX_ASSERT_THROW( | ||
| deserializer->next(), "Reading past end of VeloxRssSortShuffleReaderDeserializer::VeloxInputStream"); |
|
VeloxShuffleReaderTest.EosMidPageTerminates VeloxShuffleReaderTest.NegativeReadThrows VeloxShuffleReaderTest.UncompressedNestedStructPageSpansWindows |
What changes are proposed in this pull request?
Backgroud
The two bugs probed by these tests were discovered in our internal production environment: shuffle read tasks hit an infinite loop or deserialization failures with Celeborn (rss_sort + hash partitioning).
This PR runs these tests against the latest upstream gluten to check whether the issues still reproduce; if they do, I'll port the fix in a follow-up.
What
Adds VeloxShuffleReaderTest (velox_shuffle_reader_test): regression tests for the rss_sort shuffle reader, driven through the public VeloxRssSortShuffleReaderDeserializer API via fake arrow::io::InputStreams.
Covers graceful EOS on empty streams, uncompressed Presto pages spanning multiple read windows (nested struct pre-scan, header crossing a refill boundary, checksummed pages), the single-window zero-copy fast path, and GlutenByteInputStream::nextView contract.
Note
Two probe cases are expected to fail on current main, exposing real bugs that will be fixed in a follow-up PR: