Setenv thread-safe नहीं है, और C इसे ठीक नहीं करना चाहता
(evanjones.ca)- C भाषा के
setenv()औरunsetenv()फ़ंक्शन थ्रेड का उपयोग करने वाले प्रोग्रामों में सुरक्षित रूप से इस्तेमाल नहीं किए जा सकते - ये फ़ंक्शन global state को बदलते हैं, और जब दूसरे थ्रेड में
getenv()कॉल होता है तो टकराव पैदा कर सकते हैं - Go के
os.Setenvऔर Rust केstd::env::set_var()जैसी दूसरी भाषाओं में भी, जो C standard library फ़ंक्शनों का उपयोग करती हैं, टकराव हो सकता है - Go प्रोग्राम में संबंधित समस्या को ट्रैक करने और bug report करने में 2 दिन लगे
- क्योंकि Go का DNS resolver अंदर से
getaddrinfo()का उपयोग करता है, और वहgetenv()कॉल करता है
- क्योंकि Go का DNS resolver अंदर से
- लेकिन यह समस्या बहुत पुरानी है। 2017 में भी इस पर एक लेख था, और लेख के अंत में लिखा था, 5 साल बाद 2022 में मिलते हैं! लेकिन 2023 में फिर से सामना हुआ
- यह POSIX standard की खामी है, जिसने C standard को बढ़ाकर environment variables को modify करने की अनुमति दी
- सबसे परेशान करने वाली बात यह है कि standard पर असर डालने वाले या C library को maintain करने वाले बहुत से लोग इसे समस्या मानते ही नहीं
- कारण यह है कि specification में साफ़ लिखा है कि
setenv()को थ्रेड के साथ इस्तेमाल नहीं किया जा सकता - इसलिए अगर कोई ऐसा करता है, तो crash उसकी अपनी गलती माना जाता है
- तो हमें "हर फ़ंक्शन की specification बहुत ध्यान से पढ़नी चाहिए, दूसरे लोगों द्वारा लिखा software इस्तेमाल नहीं करना चाहिए, और थ्रेड का उपयोग नहीं करना चाहिए"
- लेकिन आधुनिक software में यह अवास्तविक मान्यता है
- इसके बजाय हमें ऐसे API बनाने की कोशिश करनी चाहिए जिन्हें बिगाड़ना कठिन हो और जो ecosystem के बदलाव के साथ विकसित हों
- C भाषा और standard library अधिकांश software की बुनियाद में अब भी महत्वपूर्ण भूमिका निभाते हैं, इसलिए या तो इन्हें बेहतर बनाने का तरीका ढूँढना होगा या इन्हें छोड़ने का रास्ता
setenv() thread-safe क्यों नहीं है
getenv()char*लौटाता है, और application को बाद में इसे free करने की ज़रूरत नहीं होती- जब एक थ्रेड इस pointer का उपयोग कर रहा हो, तब दूसरा थ्रेड
setenv()याunsetenv()से उसी environment variable को बदल सकता है - C standard में केवल
getenv()शामिल है, लेकिन अधिकांश implementation POSIX standard का पालन करती हैं और environment को modify करने वाले फ़ंक्शन शामिल करती हैं putenv()environment variable set मेंchar*जोड़ता है, और अगर applicationputenv()के return होने के बाद उस memory को modify करे, तो environment variable भी बदल जाता हैenvironNULL-terminated pointers का array (char**) है जिसे application पढ़ और assign कर सकती है, और इस array तक पहुँच thread-safe नहीं है
environment variables कैसे implement किए जाते हैं
- जब application किसी मौजूदा variable को overwrite करती है, तो implementation को तय करना पड़ता है कि इसे कैसे संभालना है
- glibc और Solaris/Illumos environment variables को कभी free नहीं करते, इसलिए
getenv()से लौटाई गई value immutable रहती है और थ्रेड में सुरक्षित रूप से उपयोग की जा सकती है - musl और FreeBSD/Apple environment variables को free करते हैं, इसलिए अगर कोई दूसरा थ्रेड
setenv()कॉल करने के बादgetenv()से लौटे pointer का उपयोग किया जाए तो crash हो सकता है - यह सुनिश्चित करना कि environment variable set thread-safe तरीके से update हो, दूसरी समस्या है, और इसी वजह से glibc में crash होता है
प्रोग्राम environment variables का उपयोग क्यों करते हैं
- environment variables दूसरे प्रोग्रामों में शामिल shared library या language runtime को configure करने के लिए उपयोगी हैं
- इससे user बिना इस बात के कि program author ने explicitly configuration पास की हो, configuration बदल सकता है
- बहुत सी libraries
getenv()कॉल करती हैं, और प्रोग्राम को अपने इस्तेमाल की libraries को configure करने के लिए इन variables को बदलना पड़ता है
इस समस्या को ठीक किया जाना चाहिए, और इसे इस तरह किया जा सकता है
- मेरे विचार में यह बेतुकी बात है कि यह समस्या इतने लंबे समय से ज्ञात है
- इस समस्या को debug करने या workaround पर चर्चा करने में हज़ारों घंटे बर्बाद हो चुके हैं
- समस्या को ठीक करने के तरीके
- Illumos/Solaris की तरह thread-safe implementation बनाना
- इसकी कुछ सीमाएँ हैं।
setenv()में memory leak होता है और अगर प्रोग्रामputenv()याenvironका उपयोग करे तो यह अब भी सुरक्षित नहीं है - लेकिन यह मौजूदा Linux और Apple implementation की तुलना में सुधार है
- इसकी कुछ सीमाएँ हैं।
- दूसरा, Microsoft के
getenv_s()की तरह ऐसा नया API जोड़ना जो design के हिसाब से thread-safe हो और सभी environment variables ला सके
- Illumos/Solaris की तरह thread-safe implementation बनाना
- मेरी पसंदीदा रणनीति दोनों का उपयोग करना है
- इससे मौजूदा प्रोग्रामों और libraries में समस्या आने की संभावना कम होगी, और Go तथा Rust जैसे नए code या भाषाओं के लिए इस समस्या से पूरी तरह बचने का रास्ता मिलेगा
getenv_s()की तरह ऐसा फ़ंक्शन जोड़ना जो एक environment variable को user द्वारा दिए गए buffer में copy करे- सभी environment variables पर iterate करने या सभी variables को copy करने के लिए thread-safe API जोड़ना
getenv()को deprecated के रूप में चिह्नित करना और उसके बजाय नया thread-safegetenv()फ़ंक्शन सुझानाputenv()को deprecated के रूप में चिह्नित करना और उसके बजायsetenv()सुझानाenvironको deprecated के रूप में चिह्नित करना और उसके बजाय environment variable functions सुझाना- environment variable implementation को thread-safe बनाने के लिए update करना
3 टिप्पणियां
"क्योंकि specification में साफ़ तौर पर लिखा है कि
setenv()को threads के साथ इस्तेमाल नहीं किया जा सकता" ==> API या SDK इस्तेमाल करते समय specification docs को बहुत ध्यान से जाँचना बुनियादी बातों में सबसे बुनियादी है। यह तो बस ज़बरदस्ती इस्तेमाल करने जैसा ही लगता है।समस्या यह है कि शुरुआत से ही खराब तरीके से डिज़ाइन किए गए फीचर का इस्तेमाल किया जा रहा है
....