- Ripgrep 15.2.0'ın x86_64-unknown-linux-musl ikilisi, büyük dosya ağaçlarında yüksek eşzamanlılıkla arama yaparken aralıklı olarak
SIGSEGVile sonlanıyor - Çökme,
opendirtarafından çağrılancallociçinde meydana geliyor ve yığın izinin en üstünde musl mallocng'nin heap meta veri bütünlüğü denetim noktası görünüyor - Yeniden üretim ortamı, yaklaşık 20GiB ve 1,8 milyon dosyadan oluşan bir ağaçtan oluşuyor ve var olmayan bir dize
rgile tekrar tekrar aranıyor - 24 çekirdekli bir sistemde, arama ağacı çekirdeğin blok önbelleğine sığacak kadar RAM varsa sorun genellikle yaklaşık 1 dakika içinde ortaya çıkıyor
- Sorun, OpenAI Codex'e dahil edilen
rgile birlikte resmi sürümdeki bayt düzeyinde aynı ikilide de bağımsız olarak yeniden üretilebildiği için Codex bağımlılığıyla ilgisiz bir sorun olduğu doğrulandı
Ortam
- Kullanılan sürüm ripgrep 15.2.0 rev
e89fff8;+pcre2özelliğini içeriyor- Derleme sırasında SIMD:
+SSE2,-SSSE3,-AVX2 - Çalışma sırasında SIMD:
+SSE2,+SSSE3,+AVX2 - PCRE2 10.45 ve JIT kullanılabiliyor
- Derleme sırasında SIMD:
- İşletim sistemi OpenSUSE Tumbleweed Linux x86_64
- İlk fark edilen OpenAI Codex paketindeki
rg, resmi x86_64-unknown-linux-musl sürümüyle bayt düzeyinde aynı - Codex'ten bağımsız olarak resmi ikilide de yeniden üretildi; analiz için kullanılan ikili ise aşağıdaki komutla debug sembolleri dahil edilerek derlendi
CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl
Yeniden üretim adımları
- generate_repro_tree.py, sorunun ilk ortaya çıktığı deponun istatistiklerini taklit eden rastgele bir dosya ağacı oluşturuyor
- Bu program bir LLM ile yazıldı
- Üretilen çıktı yaklaşık 20GiB ve 1,8 milyon dosya boyutunda
- Oluşturulan ağacın kökünde, var olmayan rastgele bir dize tekrar tekrar aranıyor
while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
- Yeterince büyük bir arama ağacının yeniden üretim için gerekli olduğu gözlemlendi
- 24 çekirdekli bir sistemde, tüm ağaç çekirdeğin blok önbelleğine sığacak kadar boş RAM varsa çökme genellikle yaklaşık 1 dakika sonra meydana geliyor
Çökme noktası
- Gerçek sonuç, core dump bırakan bir
SIGSEGV - Yığın izinin en üstünde musl mallocng'nin
get_metafonksiyonu yer alıyor ve çökme, heap meta veri bütünlüğü denetim noktasında oluşuyor - Çağrı akışı,
opendir'incallocçağırmasıyla başlıyor ve Rust standart kütüphanesinin dizin dolaşımı ile ripgrepignore::walkworker'ına uzanıyorget_meta→__malloc_allzerop→calloc→opendirstd::fs::read_dir→ignore::walk::Work::read_dir→ignore::walk::Worker::run
- Analiz materyali olarak core dump ve ilgili rg ikilisi eklendi
Beklenen davranış ve mevcut durum
- Beklenen davranış, büyük ölçekli ve yüksek eşzamanlılıklı aramalarda da segmentation fault olmadan çalışması
- Sağlanan içerikte nedenin kesinleştiği, bir düzeltme önerisi, inceleme sonucu veya nihai olarak çözülüp çözülmediği bilgisi yer almıyor
1 yorum
Hacker News yorumları
Çekirdek yamasında ilginç bir bölüm var: https://lore.kernel.org/all/CALCETrXbj__SFQMzPZhES5y6-sh4np-...
2 yıl önce böyle bir analiz, topluluğa cömertçe zaman bağışlamanın sonucu sayılırdı; ama artık kaynağını ve token maliyetinin sadece 0,06 dolar olduğunu bildiğim için okumak istemiyorum. Bunun ileride olacaklara işaret etmesi de bunda etkili
Birkaç yıl sonra insanların hataları elle kurcalaması son çare olacak ve başka AI ajanları raporları okuyup düzeltmeleri doğrulayacak. Derleyicinin ürettiği assembly'yi ve kamusal alanda cep telefonu kullananları görmezden geldiğimiz gibi, alay etme aşamasından umursamama aşamasına geçeceğiz
musl'un varsayılan ayırıcısını sırf kullanışlı diye olduğu gibi kullanmayı anlıyorum ama amacı doğrudan hız olan bir uygulamada daha hızlı bir ayırıcıya geçilmemiş olması garip
mallocng çoklu iş parçacığı çekişmesinde zayıf. Normalde G/Ç darboğazlı olan bir uygulama bile musl ile derlenince sadece 8 iş parçacığında
mallocdarboğazına girdi; mimalloc'a geçince performans 20 kat arttı, glibc varsayılan yapılandırmasına çok yaklaştı ve glibc+mimalloc'dan biraz daha yavaştıGerçekten ilginç bir sorun var ama bunun en başta bu şekilde ortaya çıkmaması gerekirdi
opendiriçinde gerçekleşiyor. Rust'ın kullandığı ayırıcı değiştirme yöntemi, tüm sürecin ayırıcısını değiştirmiyor; yalnızca Rust kodunun çağırdığı ayırıcıyı değiştiriyorYine de küresel kilit kullanan bir ayırıcıdan geçmek zorundaysa, ripgrep'in libc'nin
opendirkullanımından kaçınması daha iyi olabilirBir HPC kümesinde büyük ölçekli küme dosya sistemi üzerinde ripgrep çalıştırıyorsanız, hemen durup iş akışını yeniden tasarlamalısınız. Bu tür işler çok sayıda küçük G/Ç üretir; bu da büyük küme dosya sistemlerinin Aşil topuğudur
Kümenin yüksek bant genişlikli bellek katmanında yapılması gereken işi dosya sisteminin metadata katmanına itersiniz ve aynı anda sadece birkaç kullanıcı bunu çalıştırsa bile tüm yüksek bant genişlikli dosya sistemi felç olabilir
https://isolveproblems.substack.com/p/how-microsoft-vaporize... içeriği az da olsa doğruysa, Azure'un dosya sistemi soyutlamasında optimize edilmemiş tek bir yol bile kullanım artışıyla muazzam bir etki alanına dönüşebilir
Çekirdek hatası analizine doğrudan bağlantı vermek daha iyi olabilir: https://github.com/dfoxfranke/ripgrep-3494-analysis
Headlineparagrafında bıraktım“Yeni page fault almış anonim bir sayfaya thread'in yazdığı değer yaklaşık 10 komut sonra aynı thread tarafından tekrar okunurken kayboluyor ve fonksiyon çalışması sırasında sayfanın backing'i değiştiriliyor”, “fault anında pagemap okunursa backing çekirdeğin zero page'i oluyor”, “mekanizma, VMA başına kilitli anonim fault hızlı yolu ile eşzamanlı
munmap'in TLB shootdown'ı arasındaki etkileşime localize ediliyor” gibi cümleler sürüp gidiyorBir sayfanın
backing'inin ne olduğu,freshly-faultedya da “yaklaşık 10 komut sonra”nın ne anlama geldiği, mekanizmanın nasıl “localize” edildiği anlaşılmıyor. Teknik açıklamadan çok yan yana dizilmiş kelimeler gibi duruyorDüzgün bir teknik belgenin örneği şu: https://yifan.lu/2019/01/11/the-first-f00d-exploit/
Sonuç kabaca Linux 7.0 ile musl 1.2.5 birleşiminde bir sorun varmış gibi görünüyor ama aynı fiziksel Threadripper CPU üzerinde ağır yük altında bile yeniden üretim hâlâ ancak aralıklı olarak mümkün olduğu için donanım sorununu dışlayamamışlar
Muhtemelen CPU'nun garip bir anda taşınmasıyla oluşan karmaşık bir yarış durumu ya da sayfa tablosu kaldırma yolunun yanlış PTE'yi geçici olarak görünür kıldığı bir hata. Ayrıca zero page'in PFN'inin 0 olduğunu da sanmıyorum
Tahminimce, doğrudan sayfa tablosu kaldırma sürecinde CPU'nun üst düzey sayfalama yapılarındaki önbelleğe alınmış girdiler üzerinden önceden serbest bırakılmış ve yeniden kullanılmış tabloları okumasına izin verilmiş olabilir. Daha önce buna benzer bir şeyi debug etmiştim; gerçekten korkunçtu
Hata neden başka libc'de değil de yalnızca musl libc'de ortaya çıkıyor?
Normalde musl'un iş parçacığı yığını boyutundan şüphelenirdim ama bunun çekirdek hatası olduğu doğrulandı mı merak ediyorum