Setenv thread-safe değil ve C bunu düzeltmek istemiyor
(evanjones.ca)- C dilindeki
setenv()veunsetenv()fonksiyonları, thread kullanan programlarda güvenle kullanılamıyor - Bu fonksiyonlar global durumu değiştiriyor ve başka bir thread
getenv()çağırdığında çakışmaya yol açabiliyor - Go’daki
os.Setenvve Rust’takistd::env::set_var()gibi C standart kütüphanesi fonksiyonlarını kullanan diğer dillerde de çakışmalar ortaya çıkıyor - Go programında ilgili sorunun izini sürmek ve bug raporu hazırlamak 2 gün sürdü
- Çünkü Go’nun DNS resolver’ı dahili olarak
getaddrinfo()kullanıyor ve bu dagetenv()çağırıyor
- Çünkü Go’nun DNS resolver’ı dahili olarak
- Ama bu sorun çok eski
- 2017’de de bununla ilgili bir yazı var ve yazının sonunda “5 yıl sonra 2022’de görüşürüz!” deniyor; konu 2023’te yeniden karşımıza çıkmış
- Bu, POSIX standardındaki bir kusur; C standardı genişletilerek environment variable’ların değiştirilebilmesi sağlanmış
- En sinir bozucu kısım, standartları etkileyebilen ya da C kütüphanelerini sürdüren birçok kişinin bunu sorun olarak görmemesi
- Bunun nedeni, belirtimde
setenv()’in thread’lerle birlikte kullanılamayacağının açıkça yazılı olması - Yani biri bunu yaparsa çakışma onun suçu sayılıyor
- O halde “tüm fonksiyonların belirtimlerini dikkatle okuyalım, başkalarının yazdığı yazılımları kullanmayalım ve thread kullanmayalım” deniyor
- Ama modern yazılımda bu gerçekçi bir varsayım değil
- Bunun yerine, bozması daha zor olan ve ekosistemdeki değişimlere göre evrilen API’ler geliştirmeye çalışmak gerekiyor
- C dili ve standart kütüphanesi, yazılımların büyük bölümünün temelinde hâlâ önemli bir rol oynuyor; bu yüzden ya iyileştirmenin bir yolunu bulmalı ya da terk etmenin bir yolunu bulmalıyız
setenv() neden thread-safe değil?
getenv()birchar*döndürüyor ve uygulamanın bunu daha sonra serbest bırakması gerekmiyor- Bir thread bu pointer’ı kullanırken başka bir thread
setenv()veyaunsetenv()ile aynı environment variable’ı değiştirebiliyor - C standardında yalnızca
getenv()bulunuyor; ancak çoğu implementasyon POSIX standardını izlediği için environment’ı değiştiren fonksiyonları da içeriyor putenv(), environment variable kümesine birchar*ekliyor; uygulamaputenv()döndükten sonra bu belleği değiştirirse environment variable da değişiyorenviron, uygulamanın okuyabildiği ve atama yapabildiği, NULL ile sonlanan bir pointer dizisi (char**); bu diziye erişim thread-safe değil
Environment variable’lar nasıl implemente ediliyor?
- Uygulama mevcut bir değişkenin üzerine yazdığında implementasyonun bunu nasıl ele alacağına karar vermesi gerekiyor
- glibc ile Solaris/Illumos, environment variable’ları asla serbest bırakmıyor; bu yüzden
getenv()tarafından döndürülen değer sabit kalıyor ve thread’ler arasında güvenle kullanılabiliyor - musl ile FreeBSD/Apple ise environment variable’ları serbest bırakıyor; başka bir thread
setenv()çağırdıktan sonragetenv()’in döndürdüğü pointer’ı kullanmak çakışmaya yol açabiliyor - Environment variable kümesinin thread-safe biçimde güncellendiğinin garanti edilmesi ikinci sorun ve glibc’deki çakışmanın nedeni de bu
Programlar environment variable’ları neden kullanır?
- Environment variable’lar, başka programlara gömülü paylaşımlı kütüphaneleri veya dil runtime’larını yapılandırmak için kullanışlıdır
- Kullanıcılar, program yazarının yapılandırmayı açıkça iletmesine gerek kalmadan ayarları değiştirebilir
- Pek çok kütüphane
getenv()çağırır ve programlar kullandıkları kütüphaneleri yapılandırmak için bu değişkenleri değiştirmek zorunda kalır
Bu sorun çözülmeli ve şu yollar izlenebilir
- Bana göre bunun uzun zamandır bilinen bir sorun olması akıl almaz
- Sorunu debug etmek veya geçici çözümleri tartışmak için binlerce saat boşa harcandı
- Sorunu çözme yolları
- Illumos/Solaris’teki gibi thread-safe bir implementasyon yapmak
- Bunun bazı sınırları var.
setenv()içinde memory leak oluşuyor ve programputenv()ya da environment variable’ları kullanıyorsa yine de güvenli olmuyor - Yine de bu, mevcut Linux ve Apple implementasyonlarına göre bir iyileştirme
- Bunun bazı sınırları var.
- İkinci yol ise Microsoft’un
getenv_s()fonksiyonuna benzer şekilde, tasarım gereği thread-safe olan ve tüm environment variable’ları okumayı sağlayan yeni bir API eklemek
- Illumos/Solaris’teki gibi thread-safe bir implementasyon yapmak
- Benim tercih ettiğim çözüm ikisini birden yapmak
- Böylece mevcut programlar ve kütüphaneler için sorun çıkma olasılığı azaltılırken, Go ve Rust gibi yeni kodlar veya diller için sorunun tamamen önüne geçilecek bir yol da sunulmuş olur
getenv_s()benzeri şekilde, tek bir environment variable’ı kullanıcının sağladığı buffer’a kopyalayan bir fonksiyon eklemek- Tüm environment variable’lar üzerinde dolaşmayı veya hepsini kopyalamayı sağlayan thread-safe API’ler eklemek
getenv()’i artık önerilmeyen olarak işaretleyip yerine thread-safe yeni birgetenv()fonksiyonunu tavsiye etmekputenv()’i artık önerilmeyen olarak işaretleyip yerinesetenv()’i tavsiye etmekenvironkullanımını artık önerilmeyen olarak işaretleyip yerine environment variable fonksiyonlarını tavsiye etmek- Environment variable implementasyonunu thread-safe olacak şekilde güncellemek
3 yorum
"Spesifikasyonda,
setenv()iş parçacıklarıyla birlikte kullanılamayacağının açıkça belirtilmiş olması" ==> API ya da SDK kullanırken specification'ı dikkatle kontrol etmenin temel kuralı tam da budur. Bana göre bu, zorla kullanmaya çalışmaktan başka bir şey değil.Sorun, en baştan kötü tasarlanmış bir özelliği kullanmakta.
....