Eventos inotify ausentes (no diretório .git)

11

Estou observando arquivos em busca de alterações usando eventos inotify (por acaso, do Python, chamando a libc).

Para alguns arquivos durante a git clone, vejo algo estranho: vejo um IN_CREATEevento e vejo lsque o arquivo tem conteúdo, no entanto, nunca vejo IN_MODIFYou IN_CLOSE_WRITE. Isso está me causando problemas, pois eu gostaria de responder IN_CLOSE_WRITEnos arquivos: especificamente, para iniciar um upload do conteúdo do arquivo.

Os arquivos que se comportam estranhamente estão no .git/objects/packdiretório e terminam em .packou .idx. Outros arquivos que o git cria têm uma cadeia IN_CREATE-> IN_MODIFY-> mais regular IN_CLOSE_WRITE(não estou observando IN_OPENeventos).

Isso está dentro da janela de encaixe no MacOS, mas vi evidências do mesmo na janela de encaixe no Linux em um sistema remoto, portanto, minha suspeita é que o aspecto do MacOS não é relevante. Estou vendo isso se estiver assistindo e git cloneestiver no mesmo contêiner de docker.

Minhas perguntas:

  • Por que esses eventos estão ausentes nesses arquivos?

  • O que pode ser feito sobre isso? Especificamente, como posso responder à conclusão das gravações nesses arquivos? Nota: idealmente, gostaria de responder quando a escrita estiver "finalizada" para evitar o upload desnecessário / (incorreto) de textos "inacabados".


Edit: Reading https://developer.ibm.com/tutorials/l-inotify/ parece que o que estou vendo é consistente com

  • um arquivo temporário separado, com nome como tmp_pack_hBV4Alz, sendo criado, modificado e fechado;
  • um duro link é criado para este arquivo, com a final .packnome;
  • o tmp_pack_hBV4Alznome original é excluído.

Eu acho que o meu problema, que é tentar usar o inotify como um gatilho para fazer upload de arquivos, reduz a perceber que o .packarquivo é um link físico para outro arquivo e o upload nesse caso?

Michal Charemza
fonte
A resposta pode estar em algum lugar aqui ...
choroba 22/01
@choroba Você pode estar certo ... Vejo muitas referências ao mmap e o inotify não relata o acesso do mmap aos arquivos
Michal Charemza
11
BTW, qual é o problema original que você está tentando resolver (com inotify)? Pode haver alguma solução mais robusta que tentar adivinhar o que um processo Git está fazendo / fez em um repositório?
kostix 22/01
@kostix Isso faz parte do github.com/uktrade/mobius3 , sincronizando as pastas pessoais dos usuários de contêineres executando o JupyterLab ou o RStudio no AWS Fargate, de e para o S3, e nessas pastas pessoais pode haver pastas .git. Sei que a solução inotify nunca será "robusta-robusta" ... mas espero que seja "suficientemente robusta".
Michal Charemza 22/01
11
Parece que a resposta aceita é um patch no kernel do Linux? Suspeito que, em geral, funcionaria, mas no meu caso em Fargate não tenho esse controle. (E admito que tenho um pouco de consequências de depender de um kernel corrigido a longo prazo, mesmo que eu tenha esse poder ...)
Michal Charemza

Respostas:

5

Para responder sua pergunta separadamente para git2.24.1 no Linux 4.19.95:

  • Por que esses eventos estão ausentes nesses arquivos?

Você não vê IN_MODIFY/ IN_CLOSE_WRITEevents porque git clonesempre tentará usar links físicos para arquivos no .git/objectsdiretório. Ao clonar pela rede ou pelos limites do sistema de arquivos, esses eventos aparecerão novamente.

  • O que pode ser feito sobre isso? Especificamente, como posso responder à conclusão das gravações nesses arquivos? Nota: idealmente, gostaria de responder quando a escrita estiver "finalizada" para evitar o upload desnecessário / (incorreto) de textos "inacabados".

Para capturar a modificação de links físicos, você deve configurar um manipulador para o CREATEevento inotify que segue e acompanha esses links. Observe que um simples CREATEtambém pode significar que um arquivo não vazio foi criado. Em seguida, em IN_MODIFY/ IN_CLOSE_WRITEpara qualquer um dos arquivos, você também deve acionar a mesma ação em todos os arquivos vinculados. Obviamente, você também precisa remover esse relacionamento no DELETEevento.

Uma abordagem mais simples e mais robusta provavelmente seria o hash periódico de todos os arquivos e verificar se o conteúdo de um arquivo foi alterado.


Correção

Depois de verificar o gitcódigo-fonte de perto e funcionando gitcom strace, descobri que gitfaz arquivos utilizam memória mapeados, mas principalmente para a leitura de conteúdo. Veja o uso do xmmapqual é sempre chamado PROT_READapenas. . Portanto, minha resposta anterior abaixo NÃO é a resposta correta. No entanto, para fins informativos, eu ainda gostaria de mantê-lo aqui:

  • Você não vê IN_MODIFYeventos porque packfile.cusa mmappara acessar arquivos e inotifynão relata modificações para mmaparquivos ed.

    Na página de manual inotify :

    A API inotify não relata acessos a arquivos e modificações que podem ocorrer devido a mmap (2), msync (2) e munmap (2).

Ente
fonte
Meu mecanismo de detecção de alterações depende do IN_CLOSE_WRITEque eu acho que ainda seria acionado ao fechar um arquivo que foi gravado mmap, porque o arquivo precisaria ter sido aberto no modo de gravação?
Michal Charemza 27/01
Eu tenho que investigar isso, mas eu suspeitaria que um arquivo mapeado na memória não dispara nenhum evento de inotificação. A maioria dos eventos intoify está vinculada a um estado do descritor de arquivo, mas quando mmapum arquivo pode ficar um pouco fora de ordem. Por exemplo, você ainda pode gravar em um descritor de arquivo fechado quando tiver o arquivo mapeado na memória.
Ente
Risque isso, eu só testado este exemplo de implementação e eu faço obter um CLOSE_WRITE_CLOSEmesmo se eu remover o closee munmapno final. Tem que cavar mais fundo na implementação real do git, então ..
terça
Hmm, estou lutando um pouco para reproduzir seu problema. Nos meus testes com inotifywaite git clone(2.24.1), recebo um OPEN-> CLOSE_NOWRITE,CLOSEpara os *.idxarquivos. Talvez você tenha esquecido de configurar um manipulador para CLOSE_NOWRITE,CLOSE? Nota: Você receberá um *NOWRITE*porque todas as gravações ocorridas na memória mapeada são.
Ente
Sim, existem CLOSE_NOWRITE: o problema é que eu não vejo IN_CLOSE_WRITEe gostaria de responder às "alterações" do arquivo para acionar um upload, mas ignore o arquivo "lê". Note que, na verdade, acho que agora a limitação do mmap + inotify é um pouco irritante. Eu acho que o problema é que os arquivos .pack/ .idxsão criados inicialmente como links físicos para outro arquivo e, portanto, apenas acionam IN_CREATE(e o OPEN-> CLOSE_NOWRITEacontece mais tarde quando o git está realmente lendo os arquivos).
Michal Charemza 28/01
2

Posso especular que o Git na maioria das vezes use atualizações atômicas de arquivos, assim:

  1. O conteúdo de um arquivo é lido na memória (e modificado).
  2. O conteúdo modificado é gravado em um arquivo separado (geralmente localizado no mesmo diretório que o original e com um nome aleatório ( mktemp-estilo)).
  3. O novo arquivo é então rename(2)d -d sobre o original; essa operação garante que todo observador que tente abrir o arquivo usando seu nome obtenha o conteúdo antigo ou o novo.

Essas atualizações são vistas inotify(7)como moved_toeventos - uma vez que um arquivo "reaparece" em um diretório.

kostix
fonte
Ah, para alguns arquivos, acho que faz isso: eu vejo os vários IN_MOVED_FROMe IN_MOVED_TOeventos. No entanto, não vejo isso acontecendo nos arquivos .packe.idx
Michal Charemza
Os arquivos do pacote podem ser enormes (vários gigabytes, pelo menos até 2 GiB, eu acredito); manejá-los usando atualizações atômicas pode ser proeminente no espaço de armazenamento; portanto, eles podem ser atualizados usando outra estratégia.
kostix 22/01
2

Com base nessa resposta aceita, eu suponho que possa haver alguma diferença nos eventos com base no protocolo que está sendo usado (por exemplo, ssh ou https).

Você observa o mesmo comportamento ao monitorar a clonagem do sistema de arquivos local com a --no-hardlinksopção?

$ git clone git@github.com:user/repo.git
# set up watcher for new dir
$ git clone --no-hardlinks repo new-repo

Seu comportamento observado ao executar o experimento em um host Linux e Mac provavelmente elimina esse problema em aberto, sendo a causa https://github.com/docker/for-mac/issues/896, mas adicionando apenas o caso.

destruído
fonte
2

Há outra possibilidade (do homem inotify):

Observe que a fila de eventos pode transbordar. Nesse caso, os eventos são perdidos. Aplicativos robustos devem lidar com a possibilidade de eventos perdidos normalmente. Por exemplo, pode ser necessário reconstruir parte ou todo o cache do aplicativo. (Uma abordagem simples, mas possivelmente cara, é fechar o descritor de arquivo inotify, esvaziar o cache, criar um novo descritor de arquivo inotify e recriar entradas de observação e cache para os objetos a serem monitorados.)

E embora git clonepossa gerar um fluxo pesado de eventos, isso pode acontecer.

Como evitar isso:

  1. Aumente o buffer de leitura, tente fcntl (F_SETPIPE_SZ) (essa abordagem é um palpite, nunca tentei).
  2. Leia eventos em um grande buffer em um thread dedicado, processe eventos em outro thread.
Yury Nevinitsin
fonte
2

Talvez você tenha cometido o mesmo erro que cometi anos atrás. Eu só usei inotify duas vezes. Na primeira vez, meu código simplesmente funcionou. Mais tarde, eu não tinha mais essa fonte e comecei novamente, mas desta vez, estava perdendo eventos e não sabia o porquê.

Acontece que quando eu estava lendo um evento, estava realmente lendo um pequeno lote de eventos. Eu analisei o que eu esperava, pensando que era isso, isso era tudo. Eventualmente, descobri que há mais dados recebidos e, quando adicionei um pouco de código para analisar todos os eventos recebidos de uma única leitura, nenhum outro evento foi perdido.

donjuedo
fonte