Comments 31
Перевод ужасный, но всё равно спасибо.
Функцию setenv небезопасно вызывать в многопоточной среде. Зачастую это представляет проблему, и данный феномен то и дело переоткрывается
гмм... гуглим setenv и видим:
MT-Unsafe
POSIX.1 does not require setenv() or unsetenv() to be reentrant.
функцию setenv небезопасно вызывать в многопоточной среде? да ладно!
Тоже натыкался на такое, пришлось в расте на функцию поставить мьютекс:
Preliminary: | MT-Unsafe race:mntentbuf locale | AS-Unsafe corrupt heap init | AC-Unsafe init corrupt lock mem
https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html
del
Rust не серебряная пуля? Удивительно.
Более интересно, есть для Rust среда исполнения, в которой он был бы полностью изолирован от наследия libc / posix?
И как же обойтись без libс, учитывая что это практически основной интерфейс для вызова ОС?
Так же, как в Go сделали.
В дотнетах вон SetEnvironmentVariable вообще не влияет никак на environ, setenv выставляется только при порождении дочерних процессов и после fork().
Проблема не в setenv при вызове из раста. Проблема в том, что setenv может вызвать какой-то сишный код.
Т.е. внезапно, setenv вызывать безопастно, а вот getenv требует гарантии, что сишный код из другого потока не вызовет setenv. В отсутствии ffi вызовы getenv и setenv безопасны.
Плюс, хочется, всё же иметь интероп с сишным кодом - видеть изменения из сишного кода и прореагировать изменения в сишный код.
А как в дотнете решают эту проблему? Копируют весь environ при запуске?
И как же обойтись без libс
А написать librust что мешает?
Для Java, например, придумали виртуальную машину. Да и для большинства современных языков есть что-то подобное. Ну или делать libRust, наконец, если упор на системное программирование.
С помощью `asm!("syscall", ...);` :)
Но вообще есть https://github.com/bytecodealliance/rustix, правда не довелось воспользоваться. И вот тут https://github.com/bytecodealliance/rustix?tab=readme-ov-file#similar-crates еще перечислены схожие.
Есть-то она есть, конечно, - bare metal, no_std, да только от этого не легче.
Вероятно, WASM. Тут рядом ещё no_std предлагают.
Вот кто бы мог подумать, что возвращать значение из функции через глобальную переменную - плохая идея?
В заголовке погнали на всю libc, а рассказали про cтарую известную проблему с setenv/getenv. Пишут же
The safest solution is to ensure your application only ever calls
setenv()
at the very top ofmain()
, before your first secondary thread is initialized.
Мы разрабатывали для EdgeDB новую возможность выборки HTTP.
...тот относительно редкий случай, когда уже второй абзац показывает, что читать надо всё-таки в оригинале.
Обычный программист старой закалки прочтет доку, пожмет плечами, засунет свои вызовы getenv/setenv под мьютекс, и пойдет дальше. Делов на пять минут. Вместо того, чтобы ныть на весь интернет о плохой libc и выдумывания новых языков, которые сами за него будут это делать. Он воспринимает мир таким, каков он есть. Написано же в доке "не потоко безопасна", значит, безопасным вызовом в многопотоке он должен позаботиться сам. Вопросы тут больше не к libc, а почему они используют в многопотоке библиотеку, которая дёргает не потоко-безопасные функции.
засунет свои вызовы getenv/setenv под мьютекс
...и получит точно такой же гонкой в лицо, когда какая-то из зависимостей дёрнет getenv без всякой блокировки.
Фокус в том, что ни один из вызовов getenv/setenv не был своим. Тут setenv делала одна сторонняя библиотека, в то время как getenv делала другая.
Конечно же, тут можно просто выкинуть эти библиотеки и найти более вменяемые. Однако, если, э-э-э, ныть в интернете недостаточно - то вменяемых библиотек просто не будет.
На это опытный программист просто перехватит вызовы get/setenv - (это элементарно делается) и завернёт таки в мьютекс. Судя по минусам, опытных программистов на хабре осталось не много.
Вам мьютекс не особо поможет - getenv возвращает char*
который вам не принадлежит и который может быть в любой момент освобождён вызовом setenv из другого потока после того как ваш враппер над getenv уже отпустил мьютекс.
Ну почему же не поможет? Если уж такая сильная нужда будет в библиотеке, которая невозбранно в getenv лазит, что никак не отказаться от неё, то для неё можно уж и расстараться и результат в tls скопировать :) Вот если кто-то напрямую будет environ шерстить, тогда уже да...
Поверьте мне, есть масса способов заставить что-то работать так, как тебе нужно. Это я как автор механизма плагинов для Конфигуратора 1С (которых штатно нет) говорю :)
И самый простой способ - перестать вызывать setenv после инициализации приложения.
Намного проще, чем подмена библиотеки, копирование результата в tls и прочее шаманство.
Ну так всё правильно, лучше быть богатым и здоровым, чем бедным и больным.
Как видим, у авторов статьи не получилось перестать вызывать setenv после инициализации приложения. В этом они обвинили libc, и видимо пошли искать другой дивный язык и библиотеки, которые работают идеально и без ошибок :) Эх, подвёл таки "безопасный Rust", а ведь так надеялись, что нашли серебряную пулю.
А что, какая то религия запрещает перехватить вызовы этих функций из сторонних библиотек? Или это уже утерянные знания? Под linux это делается элементарным LD_PRELOAD (https://habr.com/ru/articles/479858/). Кривую библиотеку использовать захочешь - не так раскорячишься.
Мне просто странно, что эти разработчики свой гнев не по адресу направили - разработчики libc честно написали, что get/setenv не потокобезопасна, а они теперь плачут, почему в многопотоке у них проблемы.
PS. Ошибся, кому ответ отправлять. Это ответ на коммент выше.
Стандартная библиотека С не потокобезопасна: проблему не решает даже Rust