Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Tudo sobre pseudo, Parte 3: Lições aprendidas

Eu quis fazer aquilo

Peter Seebach, member of technical staff, Wind River Systems
Author photo
Peter Seebach trabalha com código de origem desde quando isso não era moda. Ele já trabalhou em tudo, de padronização de linguagem a drivers para mouse.

Resumo:  Neste artigo, terceiro da série, Peter Seebach analisa alguns dos vários erros que cometeu durante o desenvolvimento do pseudo. Este artigo não é apenas educacional, mas também é uma ótima oportunidade de tirar sarro dos erros de alguém, sem se sentir culpado.

Visualizar mais conteúdo nesta série

Data:  12/Jul/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  685 visualizações
Comentários:  


Quando eu cuidava de gatos, aprendi que a chave para parecer legal é fingir que cometeu erros de propósito. Ou, como muitos professores dizem, "Bem, quem de vocês percebeu meu erro proposital?"

Durante todo o projeto pseudo, houve muitos inícios falsos interessantes, bugs estranhos e outras experiências instrutivas. Algumas delas são casos muito estranhos e fora de contexto; outras são coisas que eu não acredito que um dia funcionaram. Felizmente, não me lembro das centenas de ocasiões nas quais terminei com um código que nem mesmo compilava. Elevei um simples erro de digitação e uma forma de arte.

Não vamos fazer e dizer que fizemos

Na versão funcional inicial do pseudo, operações de renomeação não funcionavam. Isso não causou nenhuma consequência no mundo real, pois o daemon do pseudo corrigiu as entradas do banco de dados automaticamente em acessos posteriores.

A primeira lição aprendida

A pessoa que diz "acho que seria válido substituir esta parte da infraestrutura por uma nova" realmente o faz. Mesmo se sua agenda já estiver cheia. Isso não é necessariamente uma coisa ruim, mas lembre-se, ao propor uma ação, o ato de propor e voluntariamente fazer estão ligados de forma inextricável para muitas pessoas. Se você estiver defendendo a mudança, esteja preparado para implementá-la. Se você quiser implementar algo, esteja preparado para suportá-la.

Foi uma ótima experiência. Eu não sabia para o que eu estava me candidatando na época. Se soubesse, talvez não tivesse sugerido. Ainda assim, fico feliz que o fiz.

Uma das limitações do mecanismo SQLite SQL é que ele não pode usar índices para comparações LIKE . Ao renomear um diretório, obviamente, você deseja renomear os arquivos dentro do diretório. Portanto, se você estiver renomeando o diretório /foo para /bar, lembre-se de substituir a cadeia de caractere /foo por /bar em cada caminho que comece com /foo/.

Se você fizer isso usando a cláusula SQL (path LIKE ? || '/'), no entanto, não será possível usar o índice, e é terrivelmente lento. Navegando por aí, encontrei uma solução deliciosamente perversa: (path > (? || '/') AND path < (? || '0').

Supondo um sistema ASCII, isso é precisamente equivalente a path/ seguido por qualquer outra coisa, mas por serem apenas operadores relacionais, o índice foi usado. Isso produziu um fator de aceleração de aproximadamente vinte mil mesmo em pequenos sistemas de arquivos.

No entanto, durante a conversão para isso, cometi um pequeno errinho. O saldo é que alterei a ordem das ligações de parâmetro, de tal forma que, se você renomear /foo para /bar, acaba substituindo /bar por /foo em todos os caminhos que começam com /foo/. O que não causou nada, mas pelo menos o fez rapidamente.

Devido à paranoia e verificações de sanidade do pseudo, isso nunca causou resultados estranhos, apenas um monte de avisos em arquivos de log.


Serialização não é suficiente

Uma suposição inicial sobre o pseudo era que não haveria problemas de serialização, pois todas as operações eram serializadas no servidor, e não era possível obter duas operações consecutivas de um determinado cliente fora da ordem. Não era algo tão grave quanto "640K deve ser o suficiente para todos", mas certamente era um erro grave.

No design original, foram tentadas operações subjacentes e reportadas ao servidor no caso de êxito. Para um único programa isso sempre funcionou. No entanto, com vários programas, houve uma possível condição de corrida.

O processo A cria um arquivo temporário, com o número inode 12345. Em seguida, o processo A remove esse arquivo temporário. Depois disso, ele é removido, o Processo B cria um novo arquivo, que reutiliza o número inode 12345. No entanto, quando isso acontece, o daemon do pseudo vê a mensagem de criação do Processo B antes de ver a mensagem de desvinculação do Processo A. O que acontece?

Ao receber a mensagem de criação de B, o daemon do pseudo percebe que há uma entrada antiga no banco de dados (arquivo temporário de A) com o mesmo número inode; ele registra a discrepância e remove a entrada. Em seguida, ele cria uma nova entrada no banco de dados. No entanto, as coisas pioram. Quando a mensagem de exclusão chega, o daemon percebe que há uma entrada antiga no banco de dados (arquivo de B) com o mesmo número inode. Ele remove a entrada falsa e também tenta remover o arquivo de temporário de A do banco de dados. Ao final, o arquivo de B não está mais registrado no banco de dados.

Minha primeira tentativa de corrigir isso foi um fracasso deprimente; modifiquei a operação UNLINK para retornar a entrada anterior do banco de dados para o arquivo e fiz o cliente enviar uma mensagem UNLINK e vinculei novamente um arquivo se a chamada do sistema subjacente tiver falhado. Isso eliminou a condição de corrida. No entanto, isso criou um modo de fracasso ainda pior: rmdir(2) em um diretório com arquivos excluiu as entradas do banco de dados para todos os arquivos (pois remover um diretório significa remover todo seu conteúdo).

A adição do sinalizador "deleting" aos arquivos e a adição das mensagens MAY_UNLINK, DID_UNLINK e CANCEL_UNLINK finalmente corrigiu isso. Essas mensagens permitem que o banco de dados registre se um arquivo está preste a ser excluído, de modo que as mensagens de criação não gerem erros. Em seguida, uma mensagem DID_UNLINK excluirá um arquivo somente se o arquivo tiver o sinalizador deleting definido. Dessa forma, acredito que esse problema foi finalmente derrotado.


Três, três, três bugs em um!

Enfrentamos um problema misterioso ao renomear um diretório fazendo com que os arquivos nesse diretório fossem esquecidos. Esse problema foi o resultado de três bugs distintos; a correção de qualquer um deles corrigia o comportamento problemático.

Ao renomear um diretório, o pseudo verifica se o diretório já é conhecido no banco de dados do pseudo e, se não for, ele cria um diretório com esse nome de modo que a operação de renomeação possa ocorrer normalmente (e, dessa forma, renomeia qualquer arquivo contido nesse diretório e que já era conhecido pelo pseudo). Isso poderia ocorrer se, por exemplo, você criasse um diretório fora do ambiente do pseudo e criasse arquivos dentro desse diretório durante a execução no ambiente do pseudo.

O problema ocorreu pela combinação de três opções. A primeira era que ao vincular um arquivo, o pseudo desvinculava qualquer arquivo existente com o mesmo nome. A segunda era que, ao desvincular um diretório, o pseudo desvinculava conteúdo desse diretório. A combinação dessas opções com o vínculo implícito de uma renomeação significava que, ao renomear um diretório não registrado anteriormente no banco de dados, o pseudo perdia todas as entradas dos arquivos nesse diretório que haviam sido registradas no banco de dados.

Isso, por si só, não teria aparecido em nosso sistema de criação. O que fez isso disparar foi minha decisão completamente inexplicável de tentar aprimorar o controle no caso em que rename(3) renomeia um arquivo nos sistemas de arquivo. Na verdade, isso não pode acontecer, ainda assim por alguma razão, eu não apenas tentei implementar o suporte, mas o fiz de uma maneira muito ruim, de tal forma que o wrapper de renomeação acabava sempre tentando vincular o nome antigo no banco de dados antes de renomear. O resultado foi que ao mover um diretório que continha arquivos, os arquivos eram sempre removidos do banco de dados.

Corrigimos esses erros principais. A ação de desvincular implícita realizada por uma operação LINK agora remove apenas o arquivo nomeado, não quaisquer arquivos que pareçam estar contidos nele. As operações de renomeação não tentam mais criar vínculos. O resultado é que a renomeação de um diretório não acaba mais com as coisas.


um caso de vértice com cinco dimensões

Você já ouviu falar de casos de parâmetros operacionais extremos e de casos de parâmetros fora da operação normal. Este é o único caso de vértice com cinco dimensões que já vi em todos os anos em que trabalho com software.

Quando o sinalizador "deleting" foi adicionado, isso significou uma mudança na estrutura de dados usada pelo pseudo para IPC. Como eu nunca defini a versão, é teoricamente possível que um cliente e um servidor discordem sobre a versão da mensagem IPC que estão usando. No entanto, isso nunca aconteceu; nosso sistema de criação garante a recriação dos componentes ao mesmo tempo.

Ainda assim, tivemos um problema muito estranho no qual um único programa falhava às vezes em um ponto específico da criação. Por "falhar" eu quero dizer "travava indefinidamente esperando uma resposta do daemon". Enquanto isso, o daemon aguardava a entrada do soquete.

Definindo o cenário

Talvez seja necessário detalhar um pouco mais sobre o protocolo do pseudo. Quando um cliente é iniciado, a primeira coisa que ele faz é enviar uma mensagem PSEUDO_MSG_PING para o servidor. As informações nessa mensagem incluem o PID do cliente, o nome do binário do cliente e uma mensagem "tag" opcional para uso no registro de eventos desse cliente. Se não houver uma mensagem tag, ela é simplesmente omitida. (O nome e a tag são enviadas como o "caminho", com seu tamanho indicado no campo pathlen.)

O travamento estava ocorrendo durante o ping. Ele ocorreu apenas no computador de um desenvolvedor, e apenas temporariamente. No entanto, conseguimos rastreá-lo eventualmente.

A mudança que fizemos aumentou o tamanho da estrutura da mensagem do pseudo em quatro bytes. O servidor é inteligente com relação às leituras que ultrapassam o tamanho da estrutura base, mas a parte inicial, ele apenas presume que sempre receberá uma leitura completa. (Ainda não corrigi esse bug.)

Se você conseguisse de alguma forma executar o novo daemon do pseudo com estrutura quatro bytes maior com um cliente pseudo antigo, ele não receberia tantos dados quanto o esperado. O cliente também envia o nome do caminho e a tag. Portanto, a falha em questão aconteceria apenas se o executável em execução tivesse um nome com quatro caracteres (era sed) e não tivesse uma tag definida. Mesmo assim, como obter o cliente pseudo antigo e o daemon pseudo novo?

O mistério revelado

Em nosso sistema de criação, é possível ter ferramentas de hospedagem pré-criadas, espelhadas no diretório de criação como uma árvore de links simbólicos (usando lndir) e, então, qualquer ferramenta que precise ser recriada para receber novas versões é recriada. O desenvolvedor em questão tinha ferramentas de hospedagem antigas, incluindo o daemon do pseudo e biblioteca cliente, que foram espelhadas, e criou novas ferramentas, incluindo um novo daemon e uma nova biblioteca cliente, no diretório do projeto.

Como nós definimos LD_LIBRARY_PATH para apontar para o diretório do projeto, selecionados consistentemente as novas bibliotecas e tudo correu bem. Ainda assim, houve uma pequena falha. É possível definir um caminho de pesquisa de vinculador em um executável, e há duas maneiras de fazer isso. A configuração moderna e simples RUNPATH é usada da maneira esperada. No entanto, a configuração mais antiga e menos simples RPATH tem a característica incomum de ser processada antes de LD_LIBRARY_PATH. O binário em questão tinha sido criado com RPATH definido como $ORIGIN/../lib:$ORIGIN/../lib64. Um excelente cookie $ORIGIN expande para o diretório contendo o binário.

Lembra-se que eu disse que as ferramentas eram espelhadas com links simbólicos? O processamento do cookie $ORIGIN segue links simbólicos. Por isso, ao executar esse executável específico, o vinculador dinâmico acabava procurando, não em LD_LIBRARY_PATH, mas no diretório da biblioteca para as pré-criações, fazendo com que recebesse a biblioteca cliente pseudo antiga. Como o nome do executável continha três caracteres, isso resultou em um travamento em vez de uma falha ou um diagnóstico.

Para reproduzir esse bug, será necessário ter:

  • Uma versão pré-criada do pseudo com no mínimo uma semana de idade
  • Uma árvore de origem que recriaria a versão mais recente
  • Um executável na árvore de pré-criação que não precisasse ser recriado
  • ... com um nome com no máximo três caracteres
  • ... que especificasse um caminho de pesquisa de biblioteca usando $ORIGIN, usando RPATH

O rastreamento disso tudo demorou muito tempo. A correção de longo prazo envolve a adição da versão às mensagens (idealmente usando algum indicador que nunca possa ocorrer nas mensagens atuais) e vários outros aprimoramentos. Também envolve não usar mais RPATH para indicar caminhos de vínculo, e possível copiar os binários em vez de torná-los links simbólicos.


Tendência de API

Tendência de API, restauração

Após o rascunho deste artigo, o pseudo sofreu uma portabilidade parcial para funcionar no Mac OS X, no qual utilitários do sistema principal exigem um getxattr()/setxattr(). Portanto, no OS X, o pseudo simplesmente passa silenciosamente as chamadas. Felizmente, os utilitários em questão não usam setxattr() para implementar os modos de arquivo. Vou lidar com isso quando não puder mais evitar.

Em alguns computadores com Linux recentes, os arquivos copiados com o antigo /bin/cp acabavam com bits de permissão incorretos. No final das contas, a família de funções getxattr()/setxattr() pode ser usada para consultar ou definir modos POSIX, não apenas atributos estendidos. Em um sistema específico, isso é realizado em vez de usar o chmod(). Em seguida, a especificação exige retornar para chmod() se as funções *xattr() falharem e, até o momento, o pseudo as intercepta e falha, definindo errno como ENOTSUP. Talvez seja necessário corrigir isso posteriormente.

Da mesma forma, durante uma fase importante de refatoração, muitos dos wrappers do pseudo foram reimplementados como funções triviais que apenas chamavam outras funções; por exemplo, usando open() com O_CREAT para implementar creat(). Especificamente, muitas funções que tinham variantes de *at() foram implementadas chamando a função *at() com AT_FDCWD como o parâmetro dirfd . Isso funcionou muito bem até que experimentamos em um computador que não fornecia openat().

Provavelmente, à medida que o tempo passa, teremos que desenvolver um controle mais completo para sistemas que oferecem uma gama diferente de suporte de API.


Lições aprendidas e direções futuras

Muitos dos problemas que encontramos durante o desenvolvimento inicial e manutenção contínua do pseudo foram relativamente fáceis de rastrear e diagnosticar. A decisão de se concentrar na robustez e no bom registro em log no início definitivamente compensou. Por outro lado, o conjunto de testes que planejamos escrever "em breve" foi uma falha importante e perceptível; a criação e uso de mais suporte para teste logo no início teria economizado muito tempo.

Embora seja sempre bom usar um código e projetos existentes quando eles correspondem ao que você está fazendo, não tenha medo de concluir que o problema que você está solucionando realmente é um novo problema. Acontece. Não com muita frequência (acho que é a primeira vez que isso acontece comigo), mas acontece, e quando acontecer, esteja preparado.

Para trabalhos futuros, ainda temos muitos aprimoramentos de robustez e de diagnóstico para fazer, mas o próximo campo importante de questionamento poderá ser o desempenho; pois tudo o que o pseudo faz e deveria fazer razoavelmente bem, o faz inegavelmente de forma mais lenta do que o fakeroot, e provavelmente nós podemos melhorar um pouco isso. Ele nunca será tão rápido para armazenar coisas em um formato de banco de dados estável em disco de forma a mantê-las apenas na memória, mas ainda já muito espaço para acelerar as coisas.


Recursos

Aprender

Obter produtos e tecnologias

  • Versão de teste do software IBM: inove o seu próximo projeto de desenvolvimento de software livre usando software para teste, disponível para download ou em DVD.

Discutir

  • comunidade do developerWorks: Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

Sobre o autor

Author photo

Peter Seebach trabalha com código de origem desde quando isso não era moda. Ele já trabalhou em tudo, de padronização de linguagem a drivers para mouse.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=732396
ArticleTitle=Tudo sobre pseudo, Parte 3: Lições aprendidas
publish-date=07122011
author1-email=dw-nospam@seebs.net
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).