La tâche de nettoyage qui aurait supprimé votre avatar

Product

Les espaces de travail ont reçu des avatars dans la v0.4.0. La fonction est modeste : vous importez une image, elle s’affiche dans le sélecteur d’espaces de travail. L’import a marché du premier coup. L’image est apparue partout où elle devait apparaître. Tous les tests passaient.

Elle aurait aussi disparu trois jours plus tard.

Deux façons de perdre un fichier

Dans Notez, un fichier importé arrive dans le stockage objet avant que quoi que ce soit dans la base de données pointe vers lui. C’est voulu : le navigateur envoie les octets directement à S3 avec un POST présigné, si bien qu’un gros import n’occupe jamais l’API. La ligne qui décrit le fichier est écrite d’abord, puis les octets arrivent.

Cet ordre crée un trou. Si quelqu’un choisit un fichier puis ferme l’onglet, les octets peuvent arriver sans que rien n’y fasse jamais référence. Le stockage se remplit d’objets qu’aucune note, aucun avatar et aucun utilisateur ne redemandera jamais.

Il y a donc une tâche de fond. Une fois par jour, elle cherche les fichiers importés mais jamais rattachés à quoi que ce soit, et supprime ceux qui ont plus de trois jours. Le mot qui compte ici, c’est rattaché : un booléen que le code d’import ne positionne pas. Terminer un import marque un fichier comme importé. C’est une autre partie du code qui doit le revendiquer.

Pour les avatars des utilisateurs, cette autre partie, c’est la mise à jour du profil. Quand vous définissez une nouvelle image, le code fait deux choses en plus d’écrire l’URL dans votre compte : il marque le nouveau fichier comme rattaché, et il met en file d’attente la suppression du fichier remplacé. Les deux moitiés comptent. Sans la première, la tâche de nettoyage supprime une image en cours d’utilisation. Sans la seconde, tous les avatars que vous avez définis un jour restent stockés pour toujours et comptent dans votre quota.

Les avatars d’espaces de travail ne faisaient ni l’un ni l’autre. L’URL était écrite dans l’espace de travail, l’image s’affichait, et le fichier restait là, marqué importé, non rattaché, à trois jours de sa suppression.

Pourquoi rien ne l’a détecté

Les tests passaient parce qu’ils testaient ce que j’avais construit. L’import renvoie une URL, l’URL est stockée, le composant l’affiche : tout cela était vrai, et le restait avec le bug. Aucun test ne vérifiait quoi que ce soit sur un booléen d’une autre table, parce qu’il ne m’était pas venu à l’esprit qu’un booléen d’une autre table faisait partie de la fonction.

Sur le moment, la défaillance est en plus invisible. Pas d’erreur, pas d’avertissement dans la console, pas de requête en échec. Entre le bug et son symptôme, il y a trois jours et une exécution de cron, et le symptôme est une image cassée sur l’écran de quelqu’un d’autre.

Ce qui l’a révélé, c’est d’avoir relu le code existant des avatars d’utilisateurs avant de supposer que le nouveau faisait la même chose. Ce code-là avait deux étapes de plus que le mien, et les commentaires expliquaient exactement pourquoi. Voilà pourquoi il faut laisser dans le code la raison, et pas seulement le mécanisme : la prochaine personne qui copie le modèle doit savoir quelles parties sont porteuses.

Supprimer, c’est aussi modifier

Corriger le remplacement a mis au jour un troisième cas. Quand un espace de travail est supprimé, son avatar est rattaché : la tâche de nettoyage, qui ne supprime que les fichiers non rattachés, n’y touchera donc jamais. Plus rien ne pointe vers le fichier, et plus rien n’y pointera. Il est orphelin de la seule façon que la tâche de nettoyage ne sait justement pas réparer.

Désormais, la suppression d’un espace de travail libère son icône au passage.

Le schéma général : si une tâche de fond décide de ce qu’elle garde en vérifiant si quelque chose fait encore référence à un fichier, alors chaque chemin de code qui cesse d’y faire référence doit le signaler. Le remplacement. L’effacement. La suppression du parent. Oubliez-en un, et vous avez une fuite ; inversez-en un, et vous perdez des données. Vus de l’extérieur, ces deux problèmes ne se ressemblent en rien, et ils viennent de la même ligne manquante.

Ce que j’ai changé

  • Définir l’avatar d’un espace de travail marque le fichier comme rattaché, et la tâche de nettoyage n’y touche plus.

  • Le remplacer met l’ancien fichier en file d’attente de suppression, avec le même délai de restauration de 30 jours que tout ce que vous supprimez.

  • Supprimer un espace de travail libère son avatar au lieu de l’abandonner.

Sept tests couvrent désormais ce cycle de vie, y compris les cas qui ne doivent pas se déclencher : renommer un espace de travail ne doit pas mettre son avatar en file de suppression, et réenregistrer la même URL n’est pas un remplacement. J’ai vérifié qu’ils échouent sans le correctif avant de leur faire confiance.

À lire aussi