Page 1 sur 1

PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 14 nov. 2018, 12:45
par Beethoven
Bonjour, je voudrais mettre un verrou sur un fichier, pour que tant que le verrou n'est pas libéré, être au courant pour mettre en attente le 2ème traitement sur ce même fichier.
Je pensais que les verrous en écriture gèrent le fait de ne pas pouvoir écrire à 2 en même temps et donc, que si on met un verrou en écriture sur un fichier, on ne peut pas en mettre un deuxième tant que le premier n'est pas libéré.
j'ai fait le test ci-dessous, mais il montre que l'on peut successivement acquérir un verrou en écriture sur un même fichier sans qu'il ne soit libéré entre 2.

en effet voici les log retourné par l'exécution de ce script sur apache linux et PHP 5.6 :
  • ------ begin ---------
    lock_for_writing
    file opened
    verrous mis
    verrous mis
    file_put_content: ok
    file_put_contents_locking file opened
    verrous relach\xc3\xa9
    file_put_contents return = fait
et voici le code :
monTest appel une fonction file_put_contents_locking qui en retour de appel une fonction générique lock_for_writing qui lock 2 fois de suite un même fichier en écriture sans problème (voir //1 et //2) :

Code : Tout sélectionner

<?php class maClass { public function lock_for_writing($name, $handler) { error_log("lock_for_writing", 0); $filename = sys_get_temp_dir().'/'.$name.'.lock'; $file = fopen($filename, 'w'); if ($file === false) { error_log("fopen return false", 0); return false; }else{ error_log("file opened", 0); } //1 $lock = flock($file, LOCK_EX);//pour acquérir un verrou exclusif (écriture) if (!$lock) {//il y a une erreur? (ou le fichier est locké?) fclose($file); error_log("flock erreur ou fichier locké", 0); return false; }else{ error_log("verrous mis", 0); } //2 $lock = flock($file, LOCK_EX);//pour acquérir un verrou exclusif (écriture) if (!$lock) {//il y a une erreur? (ou le fichier est locké?) fclose($file); error_log("flock erreur ou fichier locké", 0); return false; }else{ error_log("verrous mis", 0); } // on arrive ici si le vérrou est mis $result = $handler();//on exécute la tâche a faire (passée en paramettre dans $handler) une fois le vérrou mis //une fois la tâche exécutée on libère le verrou $lock2 = flock($file, LOCK_UN);//pour libérer un verrou (partagé ou exclusif). if (!$lock2) {//il y a une erreur? (ou le fichier est locké?) error_log("verrous non relaché", 0); }else{ error_log("verrous relaché", 0); } fclose($file); return $result; } //appel de la fonction générique en passant la fonction à éxécuter en paramettre public function file_put_contents_locking($name, $string) { return $this->lock_for_writing($name, function() use ($name, $string) { $filename = sys_get_temp_dir().'/'.$name.'.lock'; error_log("file_put_content: ".$string, 0); //return file_put_contents($filename, $string); $file = fopen($filename, 'w'); if ($file === false) { error_log("file_put_contents_locking fopen return false", 0); return "pas fait"; }else{ error_log("file_put_contents_locking file opened", 0); } //ftruncate($file, 0); // effacement du contenu fwrite($file, $string); fflush($file); return "fait"; }); } public function monTest() { error_log("------ begin ---------", 0); $ret = $this->file_put_contents_locking("lefichier", "ok"); error_log("file_put_contents return = ".strval($ret), 0); } } $mc = new maClass; $mc->monTest(); ?>
Merci de votre aide ;)

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 14 nov. 2018, 13:11
par Saian
Salut, je pense qu'il n'y a rien d'anormal la dedans, tu fais 2 flock depuis le même script et étant donné que le fichier est justement bloqué par ce script, le script peut ré-exécuter un flock sur le fichier pour éventuellement changer le mode de verrou ou déverrouiller le fichier.

Si tu veux vraiment t'assurer que le verrouillage est fonctionnel, je pense que tu dois avoir 2 scripts qui tournent en parallèle.

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 15 nov. 2018, 10:46
par Beethoven
Merci de ta réponse c'est exactement ce que j'attendais ;)
Je reste tout de même dans une impasse car le deuxième verrou que je voudrait interdire (ou plutot faire passienter) peut venir d'une demande d'un même client web que celui qui ç fait poser le verrou en place.

Mes précédents essais ont démontrés le même comportement que celui présent si je fais lancer ce script 2 fois de suite par un même client web (en appelant une seule fois "$lock = flock($file, LOCK_EX);" dans le script et en retirant "$lock2 = flock($file, LOCK_UN);//pour libérer un verrou (partagé ou exclusif)." pour les besoins de l'essais biensûr.

Comment pourrais donc procéder ?

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 15 nov. 2018, 11:51
par Saian
Alors j'ai jamais utilisé le flock mais de ce que je comprends le verrouillage n'est effectif que durant l'exécution du script.
Dans la documentation il est précisé que le fclose ne déverrouille plus automatiquement le fichier, mais je pense que le fichier est déverrouillé quand le script se termine.

Ça sert à priori à empêcher que 2 scripts php travaillent en même temps sur le même fichier. Comme par exemple si ce même script est lancé par 2 clients différents en parallèle, le deuxième va rester bloquer sur le flock jusqu'à que le fichier soit déverrouillé.

Après je dis peut être des bêtises mais c'est ce que me laisse penser tes retours.

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 15 nov. 2018, 12:55
par Saian
Une idée comme ça. As tu essayé de juste verrouiller le fichier avec un script ?
Tu lances le script une première fois, logiquement il devrait verrouiller, puis tu le relances, logiquement il devrait bloquer sur le verrouillage.

Code : Tout sélectionner

if (flock(fopen($filepath, 'w'), LOCK_EX)) echo 'File locked'; else echo 'Can\'t lock the file';

On y verra peut être un peu plus clair après sur le comportement du flock.

A priori si le deuxième coup ça bloque sur le flock (genre le script tourne et n'affiche rien) alors le verrouillage continue après l'exécution du script, sinon ça suggèrerait que le verrouillage n'est actif que durant l'exécution du script.

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 15 nov. 2018, 17:17
par Beethoven
C'est ce que j'ai refait en modifiant mon script pour qu'il ne fasse qu'un flock LOCK_EX par lancement et aucun flock LOCK_UN.
Cela ne bloque pas :(
A partir du même client web (car c'est mon cas d'utilisation) ,on peut successivement acquérir un verrou en écriture sur un même fichier (dans 2 appel différents) sans qu'il ne soit libéré entre 2.
il semble qu'il est exécuté sur apache soit par le même utilisateur Linux au final soit par le même processus repris ou un truc comme cela ;).

De ce fait, le script peut ré-exécuter un flock sur le même fichier car il croit qu'il faut faire comme cela pour éventuellement changer le mode de verrou ou déverrouiller le fichier ignorant que c'est un autre appel et qu'il devrait être géré comme tel.

Verrais tu une solution pour que cela bloque sur le 2 eme appel j'usqu'à ce que, par exemple, un 3 eme script (différent celui la) demande de libérer ce verrou?

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 15 nov. 2018, 17:34
par Saian
Une idée serait de créer tes propres fonctions lock unlock.
Tu pourrais créer dans la fonction lock un fichier nom-du-fichier.lock que tu supprimerais lors du unlock.
Lors du lock si le fichier existe tu retournes false sinon tu le crées et tu retournes true.

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 15 nov. 2018, 18:09
par Saian
J'ai testé le flock avec ce script :

<?php

function lock($handle) {
  if (flock($handle, LOCK_EX))
    echo "File locked\n";
  else
    echo "Can't lock the file\n";
}



$filename = 'flock-test-file.txt';

$handle = fopen($filename, 'r');
lock($handle);

sleep(1000);

et je confirme que quand on fait tourner le script 2 fois en parallèle, le premier obtient le verrou et le deuxième est bloqué jusqu'à ce qu'on arrête le premier script. Testé en ligne de commande avec php7.1.

Re: PB avec flock, le fichier peut se verrouiller 2 fois de suite sans que le verrou ne soit libéré entre 2

Posté : 19 nov. 2018, 16:07
par Beethoven
Merci bien.
Du fait que le lock ne fonctionne pas en WEB avec PHP, je l'ai viré et juste créé et ouvert un fichier pour indiquer au second appel en simultané que le traitement est à différer.
reste que cela n'est pas infaillible car entre le if et l’écriture du fichier, un autre script peut passer :(
Je ne voir donc pas très bien comment réimplémenter correctement la synchronisation de PHP en WEB.

Voici la MAJ de ma classe qui fonctionne tant que le second appel n'arrive pas au mauvais moment:

Code : Tout sélectionner

<?php class maClass { public function lock_for_writing($fileName, $info, $handler) { error_log("lock_for_writing", 0); $filePath = sys_get_temp_dir().'/'.$fileName.'.lock'; $count = 0; while (file_exists($filePath) && $count < (9 * 60)) {//arrête dans tous les cas avant 9 minutes $count++; sleep(1);//attend 1 seconde } if ($count >= (9 * 60)){ error_log("count return false", 0); return false; } $file = fopen($filePath, 'w');//ouvertue / création fichier if ($file === false) { error_log("fopen return false", 0); return false; }else{ error_log("file opened", 0); } fwrite($file, $info); fflush($file); fclose($file); unlink($filePath); return $result; } //appel de la fonction générique en passant la fonction à éxécuter en paramettre public function file_put_contents_locking($name, $string) { return $this->lock_for_writing($name, "ok", function() use ($name, $string) { $filename = sys_get_temp_dir().'/'.$name.'.lock'; /////////////////////////////////// //mon traitement à faire ici //////////////////////////////// error_log("file_put_content: ".$string, 0); return "fait 1"; }); } public function monTest() { error_log("------ begin ---------", 0); $ret = $this->file_put_contents_locking("jm", "ok"); error_log("file_put_contents return = ".strval($ret), 0); } } $mc = new maClass; $mc->monTest(); ?>