Modification simultanée d'un fichier

Bonjour à tous,

Je lance de nombreux processus en parallèle, et tous ces processus écrivent un résultat dans un fichier commun. Évidemment, il y a des problèmes d’accès concurrents :

Quelle est la moyen le plus simple de gérer ces problèmes d’accès en sh ?

Merci d’avance pour votre aide.
Duna

EDIT : j’ai vu plusieurs solutions à base de parallel. Cette solution ne me convient pas vraiment, parce-que mes processus sont gérés par SLURM.

J’ai envisagé des solutions à la main, à base de création de fichier .lock dans /tmp.
Puis j’ai découvert flock.

Problème résolu. Ça peut presque servir de T&A.

Tu pourrais donner 1 ou 2 ligne de code pour illustre ta solution ? :023

C’était bien mon objectif, mais je préfère tester avant.

Voici l’idée :

[code]#!/bin.bash
#SBATCH options

[un peu de code]

(
flock -x 200
sed -i “/$jobid:running/c\:w
$jobid:completed” output.file
) 200>/var/lock/mylockfile[/code]

(Voir man flock pour plus de détails.)

Je suis en cours de test de cette machine. Je reviens plus tard pour corriger les erreurs que j’ai certainement faites.

EDIT : Effectivement, il restait des erreurs. Le descripteur de fichier n°200 semble ne pas être disponible. J’ai donc suivi les instructions du manuel de flock, et j’utilise le descripteur n°9. (Pas sûr de cette explication. Si quelqu’un est plus compétent que moi dans ce domaine et lit ça, qu’il me corrige.)
/var/lock est en permissions 775 sur certains systèmes. J’ai donc déplacé le fichier de lock à /tmp

Voici le nouveau script :

[code]#!/bin/sh

(
flock -x 9
sleep 120
) 9>/tmp/mylock.lock[/code]

Une petite explication du fonctionnement (il est encore possible que je fasse des erreurs) :
La partie entre parenthèses s’exécute dans un sub-shell. En créant le sub-shell, le shell parent ouvre un descripteur de fichier (n°9, donc), et le rattache au sub-shell. Ce descripteur 9 redirige vers le fichier /tmp/mylock.lock. Ce fichier est créé s’il n’existe pas, d’où la nécessité d’avoir les droits d’écriture dans le dossier. Il y a moyen aussi de n’utiliser que les droits de lecture via un 9</tmp/mylock.lock, mais le fichier doit déjà existé auparavant.
Dans le sub-shell, flock verrouille l’accès exclusif en écriture au descripteur de fichier 9. Si le verrouillage n’est pas disponible, flock attend. (Possibilité de régler un timeout). Une fois l’accès verrouillé, on passe à la suite. Remplacer sleep 120 par des choses intéressantes.
Une fois la dernière commande du sub-shell terminée, le descripteur 9 est fermé, et son accès exclusif révoqué. Un autre process peut maintenant demander le verrouillage de ce fichier.

Ce système est assez tricky, mais tellement joli !

EDIT 2 : Pour avoir un fonctionnement propre façon lock file, il ne faut pas oublier de supprimer le fichier une fois qu’il n’est plus utilisé. Un simple rm en-dehors du sub-shell suffit. Si le fichier est nécessaire pour un autre processus, il sera re-créer. Si un autre processus a déjà ouvert un accès exclusif, rm échouera.

EDIT 3 : Pour avoir un accès exclusif à un fichier comme dans mon cas présenté tout en haut, inutile de passer par un fichier .lock. Il suffit de verrouiller l’accès à ce fichier de résultats communs :

[code]#!/bin.bash
#SBATCH options

[un peu de code]

(
flock -x 9
sed -i “/$jobid:running/c\$jobid:completed” <&9
) 9<>output.file[/code]

Raah, c’est bô !

EDIT 4 : Bon, en fait, çá marche presque. Avec sed -e, le résultat est parfait. Par contre, si on souhaite de la modification sur place (option -i de sed), on récupère une erreur :

Bizarrement, j’aurais plutôt pensé qu’il manquait un output file, du coup j’ai tenté :

$ cat file foo $ ( sed -i "s/foo/bar/" <>&9) 9<> file bash: syntax error near unexpected token `&' $ ( sed -i "s/foo/bar/" <&9 >&9) 9<> file sed: no input files

Je ne sais pas trop comment faire, là. Mais on sort du sujet, là. C’est du descripteur de fichier, plus des sémaphores.

Pour mon dernier problème : stackoverflow.com/questions/35135764

Pour les non anglophones, Andrea Corbellini m’a répondu que sed prend un nom de fichier lorsqu’on indique l’option -i. Ce nom de fichier est accessible via le lien symbolique /dev/fd/9 :

$ ( file /dev/fd/9 ) 9<> /tmp/lock/file /dev/fd/9: symbolic link to /tmp/lock/file

C’est mignon, n’est-ce pas ?

Merci pour toute ces infos, très intéressant :023

Très intéressant, mais encore faux.
Voir le deuxième commentaire de Charles Duffy sur mon post original : sed -i crée un nouveau fichier, puis copie ce nouveau fichier à la place du fichier d’origine. L’inode associée au nom du fichier d’origine a donc changé, et un descripteur de fichier ouvert avant l’exécution d’un sed -i dans un autre processus ne sera plus valide après.

D’où le commentaire de Charles indiquant qu’il vaut mieux utilisé un fichier .lock séparé, indépendant du fichier à modifier.

J’espère que c’est terminé, cette fois-ci ! (Même si j’aime bien ces magouilles :smiley:)

Et non, ce n’est pas fini. Je n’ai plus d’erreur, mais certaines modifications par sed ne sont pas prises en compte (certainement écrasées par un autre processus).
Plus exactement, chaque processus a un indicatif associé, du type “P77398_1_P-32-2-1”.
Au lancement des processus, un fichier results.txt est créé, contenant uniquement des lignes du type “P77398_1_P-32-2-1:cancelled:0h 00m 00s” (une par processus, avec l’indicatif variant, évidemment).
Lorsqu’un processus se termine, sed change la ligne correspondante en “P77398_1_P-32-2-1:nosolution:0h 6m 38s” (par exemple)

Pourtant, lorsque tous les processus sont terminés, il reste toujours quelques lignes avec “cancelled”. Pour comprendre d’où ça vient, j’ai ajouté des echo et des cat partout dans mon script (DEBUG lvl noob :smiley:). Lorsque je consulte le fichier de log, le fichier results.txt est bien modifié :

$ cat log_file_du_processus_associé *** original content of results.txt *** [...] P77398_1_P-32-2-1:cancelled:0h 00m 00s [...] *** final content of results.txt *** [...] P77398_1_P-32-2-1:nosolution:0h 6m 38s [...]

Et pourtant :

$ grep "P77398_1_P-32-2-1" results.txt P77398_1_P-32-2-1:cancelled:0h 00m 00s

Peut-être que le fait que mes fichiers results.txt et lock file soient sur un NFS pose problème. J’enquête.

Un petit update pour dire que l’utilisation de lockfile au lieu de flock a résolu le problème.
lockfile crée un fichier en lecture seule plutôt que d’ouvrir un descripteur de fichier. J’imagine que NFS est assez bien fait pour synchroniser atomiquement rapidement l’existence des fichiers, mais pas assez bien fait pour synchroniser atomiquement le contenu…

Juste pour l’exemple d’implémentation, lockfile s’utilise comme ça :

[code]#!/bin.bash
#SBATCH options

[un peu de code]

flock -r-1 /var/lock/mylockfile
sed -i “/$jobid:running/c\$jobid:completed” output.file
rm -f /var/lock/mylockfile[/code]

:023 Super merci pour la solution

Je profite de mon passage pour répondre.
J’aime pas les sections critiques, les verrous, etc.
Tu peux déjà essayer de voir si ça fonctionne avec du append :

[code]#!/bin.bash
#SBATCH options

[un peu de code]

echo “$jobid:completed” >> output.file[/code]
(append est bien plus léger et tu aura des chances d’avoir de fait une atomicité des écritures (tu ne fais qu’un seul appel au fs (ouvre/écris/ferme) et le buffer noyau va serialiser ça normalement)
puis tu lira [mono]output.file[/mono] ainsi :

[code]awk -F: ‘{status[$1]=$2} END{for (item in status) print item":"status[item]}’ output.file

ou

tac output.file | uniq -f 1[/code]

Sinon j’aurais tendance à essayer de me tourner vers gnu parallel qui aide à gérer correctement les sorties standard et d’erreur.