Événements inotify manquants (dans le répertoire .git)

11

J'observe les fichiers pour les changements en utilisant des événements inotify (en l'occurrence, depuis Python, en appelant dans libc).

Pour certains fichiers pendant un git clone, je vois quelque chose d'étrange: je vois un IN_CREATEévénement, et je vois via lsque le fichier a du contenu, cependant, je ne vois jamais IN_MODIFYou IN_CLOSE_WRITE. Cela me pose des problèmes car j'aimerais répondre IN_CLOSE_WRITEsur les fichiers: en particulier, pour lancer un téléchargement du contenu du fichier.

Les fichiers qui se comportent bizarrement se trouvent dans le .git/objects/packrépertoire et se terminent par .packou .idx. Les autres fichiers créés par git ont une chaîne IN_CREATE-> IN_MODIFY-> plus régulière IN_CLOSE_WRITE(je ne surveille pas les IN_OPENévénements).

C'est à l'intérieur de Docker sur MacOS, mais j'ai vu des preuves de la même chose sur Docker sur Linux dans un système distant, donc je soupçonne que l'aspect MacOS n'est pas pertinent. Je vois cela si je regarde et je suis git clonedans le même conteneur docker.

Mes questions:

  • Pourquoi ces événements manquent-ils dans ces fichiers?

  • Que peut-on y faire? Plus précisément, comment puis-je répondre à la fin des écritures dans ces fichiers? Remarque: idéalement, je voudrais répondre lorsque l'écriture est "terminée" pour éviter de télécharger inutilement / (incorrectement) une écriture "inachevée".


Edit: la lecture de https://developer.ibm.com/tutorials/l-inotify/ il ressemble à ce que je vois est cohérent avec

  • un fichier temporaire distinct, avec un nom similaire tmp_pack_hBV4Alz, en cours de création, de modification et de fermeture;
  • un lien dur est créé vers ce fichier, avec le .packnom final ;
  • le tmp_pack_hBV4Alznom d' origine est supprimé.

Je pense que mon problème, qui essaie d'utiliser inotify comme déclencheur pour télécharger des fichiers, se réduit ensuite à remarquer que le .packfichier est un lien dur vers un autre fichier, et le téléchargement dans ce cas?

Michal Charemza
la source
La réponse pourrait être quelque part ici ...
choroba
@choroba Vous avez peut-être raison ... Je vois beaucoup de références à mmap, et inotify ne signale pas l'accès mmap aux fichiers
Michal Charemza
1
BTW quel est le problème d'origine que vous essayez de résoudre (avec inotify)? Peut-être existe-t-il une solution plus robuste qui tente de deviner ce qu'un processus Git fait / a fait à un référentiel?
kostix
@kostix Cela fait partie de github.com/uktrade/mobius3 , synchronisant les dossiers de départ des utilisateurs à partir de conteneurs exécutant JupyterLab ou RStudio dans AWS Fargate, vers et depuis S3, et dans ces dossiers de départ, il peut y avoir des dossiers .git. Je sais que la solution inotify ne sera jamais «robuste-robuste» ... mais j'espère qu'elle pourra être «suffisamment robuste».
Michal Charemza
1
@tink Il semble que la réponse acceptée soit un patch sur le noyau Linux? Cela fonctionnerait, je le soupçonne en général, mais dans mon cas sur Fargate, je n'ai pas ce contrôle. (Et j'avoue que je crains un peu les conséquences de dépendre à long terme d'un noyau patché même si j'avais ce pouvoir ...)
Michal Charemza

Réponses:

5

Pour répondre à votre question séparément pour git2.24.1 sous Linux 4.19.95:

  • Pourquoi ces événements manquent-ils dans ces fichiers?

Vous ne voyez pas IN_MODIFY/ IN_CLOSE_WRITEevents car git cloneessaiera toujours d'utiliser des liens durs pour les fichiers sous le .git/objectsrépertoire. Lors du clonage sur le réseau ou au-delà des limites du système de fichiers, ces événements réapparaissent.

  • Que peut-on y faire? Plus précisément, comment puis-je répondre à la fin des écritures dans ces fichiers? Remarque: idéalement, je voudrais répondre lorsque l'écriture est "terminée" pour éviter de télécharger inutilement / (incorrectement) une écriture "inachevée".

Afin de détecter la modification des liens matériels, vous devez configurer un gestionnaire pour l' CREATEévénement inotify qui suit et conserve la trace de ces liens. Veuillez noter qu'un simple CREATEpeut également signifier qu'un fichier non vide a été créé. Ensuite, sur IN_MODIFY/ IN_CLOSE_WRITEvers l'un des fichiers, vous devez également déclencher la même action sur tous les fichiers liés. Évidemment, vous devez également supprimer cette relation sur l' DELETEévénement.

Une approche plus simple et plus robuste serait probablement de hacher périodiquement tous les fichiers et de vérifier si le contenu d'un fichier a changé.


Correction

Après avoir vérifié gitattentivement le code source et exécuté gitavec strace, j'ai constaté qu'il gitutilise des fichiers mappés en mémoire, mais principalement pour lire le contenu. Voir l'utilisation xmmapdont est toujours appelé avec PROT_READseulement. . Par conséquent, ma réponse précédente ci-dessous n'est PAS la bonne réponse. Néanmoins, à des fins d'information, je voudrais toujours le garder ici:

  • Vous ne voyez pas les IN_MODIFYévénements car il packfile.cutilise l' mmapaccès aux fichiers et inotifyne signale pas les modifications des mmapfichiers ed.

    À partir de la page de manuel inotify :

    L'API inotify ne signale pas les accès aux fichiers et les modifications qui peuvent se produire en raison de mmap (2), msync (2) et munmap (2).

Ente
la source
Mon mécanisme de détection des modifications dépend de IN_CLOSE_WRITEce qui, selon moi, serait toujours déclenché lors de la fermeture d'un fichier écrit en utilisant mmap, car le fichier aurait dû être ouvert en mode écriture?
Michal Charemza
Je dois enquêter sur cela, mais je soupçonne qu'un fichier mappé en mémoire ne déclenche aucun événement inotify. La plupart des événements intoify sont liés à un état du descripteur de fichier, mais lorsque vous mmapun fichier, les choses peuvent être un peu hors service. Par exemple, vous pouvez toujours écrire dans un descripteur de fichier fermé lorsque le fichier est mappé en mémoire.
Ente
Grattez cela, je viens de tester cet exemple d'implémentation et j'obtiens un CLOSE_WRITE_CLOSEmême si je supprime le closeet munmapà la fin. À creuser plus profondément dans la mise en œuvre git réelle alors ..
Ente
Hmm, j'ai du mal à reproduire votre problème. Dans mes tests avec inotifywaitet git clone(2.24.1), j'obtiens un OPEN-> CLOSE_NOWRITE,CLOSEpour les *.idxfichiers. Vous avez peut-être oublié de configurer un gestionnaire pour CLOSE_NOWRITE,CLOSE? Remarque: vous obtiendrez un *NOWRITE*car toutes les écritures effectuées via la mémoire mappée le sont.
Ente
Oui, il y a CLOSE_NOWRITE: le problème est que je ne vois pas IN_CLOSE_WRITE, et je voudrais répondre aux "modifications" du fichier pour déclencher un téléchargement, mais ignorer le fichier "lit". Remarque, je pense que la limitation mmap + inotify est en quelque sorte un redingue. Je pense que le problème est que les fichiers .pack/ .idxsont initialement créés sous forme de liens durs vers un autre fichier, et donc ne se déclenchent IN_CREATE(et le OPEN-> CLOSE_NOWRITEse produit plus tard lorsque git lit réellement les fichiers).
Michal Charemza
2

Je peux supposer que Git utilise la plupart du temps des mises à jour de fichiers atomiques qui se font comme ceci:

  1. Le contenu d'un fichier est lu en mémoire (et modifié).
  2. Le contenu modifié est écrit dans un fichier séparé (généralement situé dans le même répertoire que celui d'origine, et ayant un nom aléatoire ( mktemp-style).
  3. Le nouveau fichier est alors rename(2)d -d sur le fichier d' origine; cette opération garantit que tout observateur essayant d'ouvrir le fichier en utilisant son nom obtiendra l'ancien contenu ou le nouveau.

Ces mises à jour sont considérées inotify(7)comme des moved_toévénements, puisqu'un fichier "réapparaît" dans un répertoire.

kostix
la source
Ah pour certains fichiers je pense que ça fait ça: je vois les divers IN_MOVED_FROMet les IN_MOVED_TOévénements. Cependant, je ne vois pas cela se produire pour les fichiers .packet.idx
Michal Charemza
Les fichiers de pack peuvent être énormes (plusieurs gigaoctets, jusqu'à 2 Go au moins, je crois); les utiliser à l'aide de mises à jour atomiques peut être prohibitif sur l'espace de stockage, ils peuvent donc être mis à jour en utilisant une autre stratégie.
kostix
2

Sur la base de cette réponse acceptée, je suppose qu'il pourrait y avoir une différence dans les événements en fonction du protocole utilisé (c'est-à-dire ssh ou https).

Observez-vous le même comportement lors de la surveillance du clonage à partir du système de fichiers local avec l' --no-hardlinksoption?

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

Votre comportement observé lors de l'exécution de l'expérience à la fois sur un hôte Linux et Mac élimine probablement ce problème ouvert étant la cause https://github.com/docker/for-mac/issues/896 mais en ajoutant simplement au cas où.

naufragé
la source
2

Il y a une autre possibilité (de l'homme inotify):

Notez que la file d'attente d'événements peut déborder. Dans ce cas, les événements sont perdus. Les applications robustes doivent gérer la possibilité d'événements perdus avec élégance. Par exemple, il peut être nécessaire de reconstruire une partie ou la totalité du cache d'application. (Une approche simple, mais éventuellement coûteuse, consiste à fermer le descripteur de fichier inotify, à vider le cache, à créer un nouveau descripteur de fichier inotify, puis à recréer des surveillances et des entrées de cache pour les objets à surveiller.)

Et bien que cela git clonepuisse générer un flux d'événements important, cela peut se produire.

Comment éviter cela:

  1. Augmentez le tampon de lecture, essayez fcntl (F_SETPIPE_SZ) (cette approche est une supposition, je n'ai jamais essayé).
  2. Lire les événements dans un grand tampon dans un thread dédié, traiter les événements dans un autre thread.
Yury Nevinitsin
la source
2

Peut-être que vous avez fait la même erreur que moi il y a des années. Je n'ai utilisé inotify que deux fois. La première fois, mon code a simplement fonctionné. Plus tard, je n'avais plus cette source et j'ai recommencé, mais cette fois, je manquais des événements et je ne savais pas pourquoi.

Il s'avère que lorsque je lisais un événement, je lisais vraiment un petit lot d'événements. J'ai analysé celui que j'attendais, pensant que c'était tout, c'était tout. Finalement, j'ai découvert qu'il y avait plus que les données reçues, et quand j'ai ajouté un petit code pour analyser tous les événements reçus d'une seule lecture, plus aucun événement n'a été perdu.

donjuedo
la source