- Ripgrep 15.2.0 की x86_64-unknown-linux-musl बाइनरी बड़े file tree को high concurrency के साथ सर्च करते समय बीच-बीच में
SIGSEGVके साथ बंद हो जाती है - क्रैश
opendirद्वारा कॉल किए गएcallocके अंदर होता है, और stack trace के सबसे ऊपर musl mallocng के heap metadata integrity check का बिंदु दिखाई देता है - reproduction environment लगभग 20GiB·18 लाख files वाले tree का है, और
rgसे मौजूद न होने वाली string को बार-बार सर्च किया जाता है - 24-core system पर अगर इतना RAM हो कि search tree kernel block cache में आ जाए, तो आम तौर पर लगभग 1 मिनट में समस्या दिख जाती है
- OpenAI Codex में शामिल
rgके अलावा, आधिकारिक release के byte-for-byte समान binary में भी यह अलग से reproduce हुआ, जिससे पुष्टि होती है कि यह Codex dependency से असंबंधित समस्या है
होने का environment
- उपयोग किया गया version ripgrep 15.2.0 rev
e89fff8है और इसमें+pcre2feature शामिल है- compile समय SIMD:
+SSE2,-SSSE3,-AVX2 - run समय SIMD:
+SSE2,+SSSE3,+AVX2 - PCRE2 10.45 और JIT उपलब्ध हैं
- compile समय SIMD:
- operating system OpenSUSE Tumbleweed Linux x86_64 है
- सबसे पहले पाया गया OpenAI Codex bundled
rg, आधिकारिक x86_64-unknown-linux-musl release के byte-for-byte समान है - Codex से अलग, आधिकारिक binary में भी इसे reproduce किया गया, और analysis के लिए binary को debug symbols सहित इस कमांड से build किया गया
CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl
reproduction steps
- generate_repro_tree.py उस repository के stats की नकल करने वाला random file tree बनाता है जिसमें मूल समस्या हुई थी
- यह प्रोग्राम LLM द्वारा लिखा गया है
- generated output लगभग 20GiB, 18 लाख files के आकार का है
- generated tree के root पर मौजूद न होने वाली random string को बार-बार सर्च किया जाता है
while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
- देखा गया कि reproduce करने के लिए पर्याप्त बड़ा search tree ज़रूरी है
- 24-core system पर, यदि पर्याप्त free RAM हो ताकि पूरा tree kernel block cache में आ जाए, तो आम तौर पर लगभग 1 मिनट बाद क्रैश होता है
crash point
- वास्तविक परिणाम core dump छोड़ने वाला
SIGSEGVहै - stack trace के सबसे ऊपर musl mallocng का
get_metaहै, और क्रैश heap metadata integrity check बिंदु पर होता है - call flow में
opendirद्वाराcallocकॉल होता है, और इसके बाद Rust standard library की directory traversal तथा ripgrepignore::walkworker आते हैंget_meta→__malloc_allzerop→calloc→opendirstd::fs::read_dir→ignore::walk::Work::read_dir→ignore::walk::Worker::run
- analysis material के रूप में core dump और संबंधित rg binary संलग्न हैं
expected behavior और वर्तमान स्थिति
- expected behavior यह है कि बड़े पैमाने और high-concurrency search में भी यह segmentation fault के बिना चले
- दिए गए विवरण में कारण की पुष्टि, fix, review result, या अंतिम समाधान की स्थिति शामिल नहीं है
1 टिप्पणियां
Hacker News की राय
kernel patch में एक दिलचस्प हिस्सा है: https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...
2 साल पहले ऐसी analysis को community के लिए उदार समय-दान माना जाता, लेकिन अब जब पता है कि इसका source और token cost सिर्फ $0.06 है, तो पढ़ने का मन ही नहीं होता। आगे क्या होने वाला है, इसका संकेत भी इसमें शामिल है
कुछ साल बाद इंसानों द्वारा खुद bug की तह तक जाना अंतिम उपाय बन जाएगा, और दूसरी AI agents report पढ़कर fixes को verify करेंगी। जैसे compiler-generated assembly और सार्वजनिक जगहों पर मोबाइल फ़ोन इस्तेमाल करने वालों को नज़रअंदाज़ करना सीख लिया गया, वैसे ही हम मज़ाक उड़ाने के चरण से उदासीनता के चरण में चले जाएँगे
musl के default allocator को सुविधा के लिए वैसे ही इस्तेमाल करना समझ में आता है, लेकिन जहाँ application का उद्देश्य ही speed हो वहाँ उसे किसी तेज allocator से न बदलना अजीब है
mallocng multi-thread contention के प्रति कमज़ोर है। जो application सामान्यतः I/O bottleneck था, उसे musl के साथ build करने पर सिर्फ 8 threads पर
mallocbottleneck आ गया, और mimalloc पर बदलते ही performance 20 गुना बढ़ गई, जिससे वह glibc default configuration के बहुत क़रीब आ गई, हालाँकि glibc+mimalloc से थोड़ी धीमी रहीसचमुच यहाँ एक दिलचस्प समस्या है, लेकिन यह मूल रूप से इस तरह सामने आनी ही नहीं चाहिए थी
opendirमें हो रहा है। Rust का allocator replacement तरीका पूरे process का allocator नहीं बदलता, बल्कि सिर्फ वही allocator बदलता है जिसे Rust code call करता हैफिर भी अगर global lock इस्तेमाल करने वाले allocator से होकर जाना पड़ता है, तो बेहतर होगा कि ripgrep libc के
opendirके उपयोग से बचेअगर आप HPC cluster पर बड़े cluster file system के खिलाफ ripgrep चला रहे हैं, तो तुरंत रुकिए और अपना workflow फिर से डिज़ाइन कीजिए। ऐसा काम बहुत बड़ी मात्रा में छोटे I/O operations पैदा करता है, और यही बड़े cluster file systems की Achilles' heel है
इससे वह काम, जो cluster की high-bandwidth memory hierarchy में होना चाहिए, file system की metadata layer पर धकेल दिया जाता है, और अगर कुछ ही users इसे साथ में चलाएँ तो पूरा high-bandwidth file system ठप हो सकता है
अगर https://isolveproblems.substack.com/p/how-microsoft-vaporize... में लिखा थोड़ा भी सही है, तो Azure की file system abstraction में सिर्फ एक unoptimized path भी usage spike को बहुत बड़े blast radius में बदल सकता है
kernel bug analysis को सीधे link करना शायद बेहतर होगा: https://github.com/dfoxfranke/ripgrep-3494-analysis
Headlineparagraph पर ही छोड़ दिया“नई page fault वाली anonymous page में thread ने जो value store की थी, वह लगभग 10 instructions बाद उसी thread की re-read में गायब हो जाती है, और function execution के दौरान page का backing बदल जाता है”, “fault के समय pagemap पढ़ने पर backing kernel की zero page होती है”, “mechanism को per-VMA lock के anonymous fault fast path और concurrent
munmapके TLB shootdown के interaction तक localize किया जा सकता है” — ऐसी पंक्तियाँ लगातार आती हैंpage का
backingक्या है,freshly-faultedया “लगभग 10 instructions बाद” का क्या मतलब है, और mechanism कैसे “localize” होता है — यह समझना मुश्किल है। तकनीकी विवरण से ज़्यादा यह शब्दों को जोड़कर लिखा गया पाठ लगता हैठीक-ठाक technical document का उदाहरण यह है: https://yifan.lu/2019/01/11/the-first-f00d-exploit/
निष्कर्ष मोटे तौर पर यह लगता है कि Linux 7.0 और musl 1.2.5 का संयोजन समस्या पैदा कर रहा है, लेकिन reproduction अभी भी उसी भौतिक Threadripper CPU पर भारी load देने पर ही कभी-कभी सफल होता है, इसलिए hardware issue को ख़ारिज नहीं किया जा सका
यह शायद CPU migration के असहज समय पर होने से पैदा हुई कोई जटिल race condition है, या page table removal path में ऐसा bug है जिसने अस्थायी रूप से गलत PTE दिखा दिया। मुझे यह भी नहीं लगता कि zero page का PFN 0 होता है
मेरा अंदाज़ा है कि सीधे page table removal की प्रक्रिया में CPU को ऊपरी paging structures की cached entries के ज़रिए पहले से free होकर reuse हो चुकी table पढ़ने की अनुमति मिल गई होगी। मैंने पहले ऐसे issue debug किए हैं, और वे सचमुच भयानक थे
bug सिर्फ musl libc में ही क्यों दिखता है, किसी और libc में क्यों नहीं?
सामान्यतः मैं musl के thread stack size पर शक करता, लेकिन क्या अब यह पक्का हो गया है कि यह kernel bug ही है?