`setenv` não é thread-safe, e C não quer consertar isso
(evanjones.ca)- As funções
setenv()eunsetenv()da linguagem C não podem ser usadas com segurança em programas que utilizam threads - Essas funções modificam estado global e podem causar conflitos quando outra thread chama
getenv() - Colisões também ocorrem em outras linguagens que usam funções da biblioteca padrão de C, como
os.Setenvdo Go estd::env::set_var()do Rust - Foram necessários 2 dias para rastrear o problema relacionado e reportar o bug em um programa Go
- Isso acontece porque o resolver de DNS interno do Go usa
getaddrinfo(), que por sua vez chamagetenv()
- Isso acontece porque o resolver de DNS interno do Go usa
- Mas esse problema é muito antigo. Já havia um texto sobre isso em 2017, e no final estava escrito “nos vemos em 5 anos, em 2022!”, mas ele reapareceu novamente em 2023
- Isso é uma falha do padrão POSIX, que estendeu o padrão C para permitir a modificação de variáveis de ambiente
- A parte mais irritante é que muitas pessoas que podem influenciar padrões ou manter bibliotecas C não consideram isso um problema
- O motivo é que a especificação deixa claro que
setenv()não pode ser usado junto com threads - Portanto, se alguém fizer isso e ocorrer um crash, a culpa seria dessa pessoa
- Então, deveríamos “ler cuidadosamente a especificação de todas as funções, não usar software escrito por outras pessoas e não usar threads”
- Mas isso é uma suposição irrealista no software moderno
- Em vez disso, deveríamos tentar criar APIs que sejam difíceis de estragar e que evoluam junto com as mudanças do ecossistema
- Como a linguagem C e sua biblioteca padrão continuam desempenhando um papel importante na base da maior parte do software, precisamos encontrar uma forma de melhorá-las — ou uma forma de abandoná-las
Por que setenv() não é thread-safe
getenv()retornachar*, e a aplicação não precisa liberar isso depois- Enquanto uma thread usa esse ponteiro, outra thread pode alterar a mesma variável de ambiente com
setenv()ouunsetenv() - O padrão C inclui apenas
getenv(), mas a maioria das implementações segue o padrão POSIX e inclui funções para modificar o ambiente putenv()adiciona umchar*ao conjunto de variáveis de ambiente, e se a aplicação modificar essa memória após o retorno deputenv(), a variável de ambiente também será modificadaenvironé um array de ponteiros terminado em NULL (char**) que a aplicação pode ler e atribuir, e o acesso a esse array não é thread-safe
Como as variáveis de ambiente são implementadas
- Quando a aplicação sobrescreve uma variável existente, a implementação precisa decidir como lidar com isso
- glibc e Solaris/Illumos nunca liberam variáveis de ambiente, e os valores retornados por
getenv()são imutáveis e podem ser usados com segurança entre threads - musl e FreeBSD/Apple liberam variáveis de ambiente, então pode ocorrer um crash se outra thread usar o ponteiro retornado por
getenv()depois quesetenv()for chamado - Garantir que o conjunto de variáveis de ambiente seja atualizado de forma thread-safe é um segundo problema, e é isso que causa crashes no glibc
Por que programas usam variáveis de ambiente
- Variáveis de ambiente são úteis para configurar bibliotecas compartilhadas ou runtimes de linguagem incluídos em outros programas
- Usuários podem alterar a configuração sem que o autor do programa precise passar explicitamente esses parâmetros
- Muitas bibliotecas chamam
getenv(), e os programas precisam alterar essas variáveis para configurar as bibliotecas que utilizam
Esse problema precisa ser resolvido, e há formas de fazer isso
- Na minha opinião, é absurdo que isso seja um problema conhecido há tanto tempo
- Milhares de horas foram desperdiçadas depurando o problema ou discutindo formas de contorná-lo
- Formas de resolver o problema
- Criar uma implementação thread-safe como a de Illumos/Solaris
- Isso tem algumas limitações. Há vazamento de memória em
setenv(), e ainda não é seguro se o programa usarputenv()ouenviron - Mesmo assim, isso é uma melhoria em relação às implementações atuais de Linux e Apple
- Isso tem algumas limitações. Há vazamento de memória em
- A segunda opção é adicionar uma nova API para obter todas as variáveis de ambiente que seja thread-safe por projeto, como
getenv_s()da Microsoft
- Criar uma implementação thread-safe como a de Illumos/Solaris
- A solução que eu prefiro é usar as duas abordagens
- Isso reduz a chance de problemas em programas e bibliotecas existentes, além de oferecer um caminho para evitar completamente o problema em código novo ou em linguagens como Go e Rust
- Adicionar uma função semelhante a
getenv_s()que copie uma variável de ambiente para um buffer fornecido pelo usuário - Adicionar uma API thread-safe para iterar por todas as variáveis de ambiente ou copiar todas elas
- Marcar
getenv()como obsoleto e recomendar, em vez dele, uma nova funçãogetenv()thread-safe - Marcar
putenv()como obsoleto e recomendarsetenv()no lugar - Marcar
environcomo obsoleto e recomendar o uso das funções de variáveis de ambiente - Atualizar a implementação de variáveis de ambiente para que seja thread-safe
3 comentários
"Porque a especificação deixa claro que não se pode usar
setenv()junto com threads" ==> ao usar uma API ou SDK, o básico do básico é sempre verificar a specification com muito cuidado. Sinceramente, só parece uso forçado.O problema é usar uma funcionalidade que já foi mal projetada desde o início.
setenvnão é thread-safe, e C não quer corrigir isso