stinger1206 opened a new issue, #67719:
URL: https://github.com/apache/doris/issues/67719

   ## Version
   
   4.1.3 (branch-4.1; local build based on Doris commit 13a108d04b)
   
   ## What's Wrong?
   
   Long-running BE nodes crash intermittently with SIGSEGV under concurrent 
query/compaction load. Observed innermost frames:
   
   - `SegmentFooterPB::ByteSizeLong` ← `Segment::get_metadata_size` (reader 
side)
   - `~SegmentFooterPB` ← `LRUCache::prune_if` (release side)
   
   plus crashes at unrelated, seemingly random sites. Every observed crash is 
healed by a BE restart without repair, and the implicated segment files remain 
byte-identical. This argues against persistent on-disk corruption and is 
consistent with process-memory corruption. Full `be.out` stacks from the 
incident can be attached in a comment if needed.
   
   Root cause is a data race on `std::weak_ptr<SegmentFooterPB> 
Segment::_footer_pb` (`be/src/storage/segment/segment.h`):
   
   - **Writer** — `Segment::_get_segment_footer()` 
(`be/src/storage/segment/segment.cpp`) rebinds the weak_ptr (`_footer_pb = 
footer_pb_shared`) from query/compaction threads whenever the previously cached 
footer has expired (its `StoragePageCache` entry pruned/erased). It also reads 
the weak_ptr via `_footer_pb.lock()`.
   - **Reader** — `Segment::get_metadata_size()` calls `_footer_pb.lock()` from 
the Daemon `memory_maintenance_thread` 
(`MemoryProfile::refresh_memory_overview_profile` → 
`MetadataAdder<Segment>::get_all_segments_estimate_size()`), with no 
synchronization against the writers.
   
   Concurrent assignment to `_footer_pb` and `lock()` on the same non-atomic 
`std::weak_ptr` object is a data race and therefore undefined behavior. The 
exact failure mechanism is implementation-dependent; the observed crashes are 
consistent with process-memory corruption caused by this undefined behavior.
   
   The same unsynchronized pattern is present in both current `master` and 
`branch-4.1`.
   
   ## What You Expected?
   
   Concurrent footer-cache expiry (query/compaction) and periodic 
memory-maintenance size estimation should be safe; BE should not crash.
   
   ## How to Reproduce?
   
   Hard to reproduce via SQL alone: it requires segment footer cache entries 
expiring while the memory maintenance thread's refresh runs (a matter of timing 
under sustained load). It reproduces intermittently in production on 
long-running BEs under mixed query + compaction traffic.
   
   The exact C++ pattern reproduces reliably under ThreadSanitizer with a 
standalone, dependency-free program that mimics the two access paths 
(`_get_segment_footer` rebind vs `get_metadata_size` lock):
   
   ```cpp
   // repro_weakptr_race.cpp — micro-repro of the Segment::_footer_pb pattern
   // clang++ -std=c++17 -O1 -g -fsanitize=thread repro_weakptr_race.cpp -o 
repro_tsan && ./repro_tsan
   #include <chrono>
   #include <cstdio>
   #include <memory>
   #include <thread>
   #include <vector>
   
   struct FooterPB {
       char payload[64];
       size_t ByteSizeLong() const { return sizeof(FooterPB); }
   };
   
   std::weak_ptr<FooterPB> g_footer_pb;  // == Segment::_footer_pb
   
   // "Segment::_get_segment_footer" writer (query / compaction thread):
   // parse footer, insert into footer cache, rebind the weak_ptr.
   void writer_loop() {
       uint64_t n = 0;
       while (true) {
           auto footer = std::make_shared<FooterPB>();
           footer->payload[0] = static_cast<char>(n++ & 0xff);
           g_footer_pb = footer;  // unlocked rebind
           std::this_thread::sleep_for(std::chrono::microseconds(50));
       }
   }
   
   // "Segment::get_metadata_size" reader (Daemon memory_maintenance_thread):
   void reader_loop() {
       while (true) {
           auto footer = g_footer_pb.lock();  // unlocked lock()
           if (footer) {
               volatile size_t sz = footer->ByteSizeLong();
               (void)sz;
           }
           std::this_thread::sleep_for(std::chrono::microseconds(50));
       }
   }
   
   int main() {
       std::vector<std::thread> ts;
       for (int i = 0; i < 4; i++) ts.emplace_back(writer_loop);
       for (int i = 0; i < 4; i++) ts.emplace_back(reader_loop);
       for (auto& t : ts) t.join();  // never reached; Ctrl-C after the first 
TSAN report
       return 0;
   }
   ```
   
   In our test TSAN reports the race within seconds, with one access in 
`std::weak_ptr::operator=` and the other in `std::weak_ptr::lock`.
   
   ## Anything Else?
   
   Existing issue #64826 reports an overlapping SegmentFooterPB crash symptom 
during page-cache capacity adjustment, but does not report or identify the 
`Segment::_footer_pb` data race described here.
   
   Fix: serialize every `_footer_pb` access with a dedicated `mutable 
std::mutex _footer_pb_lock` in `Segment` — read in `get_metadata_size()`, read 
+ rebind in `_get_segment_footer()`. The mutex is `mutable` because 
`get_metadata_size()` is `const`; the `shared_ptr` is copied under the lock and 
`ByteSizeLong()` runs outside it, so the critical sections are a few 
instructions. No behavior change. Validated on a production canary BE (crash 
family absent after deploy; previously recurring).
   
   - [x] I had searched in the issues and found no similar issues.
   - [x] Yes I am willing to submit a PR!
   - [x] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct).
   


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