J'ai un processus qui écrit un fichier en utilisant file_put_contents()
:
if (file_exists($file)) { $fp = fopen($file, 'r+'); if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) { if ($wouldblock) { // how can I wait until the file is unlocked? } else { // what other reasons could there be for not being able to lock? } } // does calling fclose automatically close all locks even is a flock was not obtained above? fclose($file); }
J'ai ajouté le paramètre LOCK_EX
pour empêcher les processus d'écriture dans le même fichier, et empêchent d'essayer de le lire alors qu'il est encore en cours d'écriture.
J'ai des difficultés à le tester correctement en raison de la nature simultanée, et Je ne sais pas comment aborder cela. Je l'ai jusqu'à présent:
file_put_contents ( $file, $data, LOCK_EX );
Les questions étant:
fclose ()
déverrouille automatiquement tous les verrous lorsqu'un autre processus aurait verrouillé le fichier? 3 Réponses :
La première question trouve sa réponse ici Comment pour détecter la fin avec file_put_contents () en php? et parce que PHP est mono-thread, la seule solution est d'utiliser l'extension du noyau PHP en utilisant PTHREADS et un bon article simple à ce sujet est https://www.mullie.eu/parallel-processing-multi-tasking-php/ < / p>
La deuxième question est répondue ici Will flock ' Le fichier est-il déverrouillé lorsque le processus échoue de manière inattendue?
Le fclose () déverrouillera uniquement le handle valide qui est ouvert en utilisant fopen () ou fsockopen () donc si le handle est toujours valide, oui il fermera le fichier et libèrera le verrou.
La réponse à propos de la détection de file_put_contents ()
en cours de connexion concerne une seule requête si je comprends bien. Mais je peux avoir une requête parallèle (même le même fichier PHP) essayant de lire ou d'écrire dans le même fichier. Sauf erreur de ma compréhension, cela n'implique pas des PTHREADS?
J'utilise souvent une petite classe ... qui est sécurisée et rapide, en gros vous ne devez écrire que lorsque vous obtenez un verrou exclusif sur le fichier sinon vous devriez attendre qu'il soit verrouillé ...
lock_file. php
include("lock_file.php"); $file = new Exclusive_Lock("my_file.dat", 2); while (1) { if ($file->acquireLock()) { $data = fopen("my_file.dat", "w+"); $read = "READ: YES"; fwrite($data, $read); fclose($data); $file->releaseLock(); chmod("my_file.dat", 0755); unset($data); unset($read); break; } }
Utilisation simple comme suit:
include("lock_file.php"); $file = new Exclusive_Lock("my_file.dat", 2); if ($file->acquireLock()) { $data = fopen("my_file.dat", "w+"); $read = "READ: YES"; fwrite($data, $read); fclose($data); $file->releaseLock(); chmod("my_file.dat", 0755); unset($data); unset($read); }
Si vous voulez ajouter un niveau plus complexe, vous pouvez utiliser autre astuce ... utilisez while (1)
pour initialiser une boucle infinie qui ne casse que lorsque le verrou exclusif est acquis, non suggéré car peut bloquer votre serveur pendant un temps indéfini ...
<?php /* Reference Material http://en.wikipedia.org/wiki/ACID */ class Exclusive_Lock { /* Private variables */ public $filename; // The file to be locked public $timeout = 30; // The timeout value of the lock public $permission = 0755; // The permission value of the locked file /* Constructor */ public function __construct($filename, $timeout = 1, $permission = null, $override = false) { // Append '.lck' extension to filename for the locking mechanism $this->filename = $filename . '.lck'; // Timeout should be some factor greater than the maximum script execution time $temp = @get_cfg_var('max_execution_time'); if ($temp === false || $override === true) { if ($timeout >= 1) $this->timeout = $timeout; set_time_limit($this->timeout); } else { if ($timeout < 1) $this->timeout = $temp; else $this->timeout = $timeout * $temp; } // Should some other permission value be necessary if (isset($permission)) $this->permission = $permission; } /* Methods */ public function acquireLock() { // Create the locked file, the 'x' parameter is used to detect a preexisting lock $fp = @fopen($this->filename, 'x'); // If an error occurs fail lock if ($fp === false) return false; // If the permission set is unsuccessful fail lock if (!@chmod($this->filename, $this->permission)) return false; // If unable to write the timeout value fail lock if (false === @fwrite($fp, time() + intval($this->timeout))) return false; // If lock is successfully closed validate lock return fclose($fp); } public function releaseLock() { // Delete the file with the extension '.lck' return @unlink($this->filename); } public function timeLock() { // Retrieve the contents of the lock file $timeout = @file_get_contents($this->filename); // If no contents retrieved return error if ($timeout === false) return false; // Return the timeout value return intval($timeout); } } ?>
file_put_contents ()
est très rapide et écrit directement dans le fichier mais comme vous le dites a une limite ... condition de concurrence existe et peut se produire même si vous essayez d'utiliser LOCK_EX
. Je pense qu'une classe php est plus flexible et utilisable ...
Rendez-vous ce fil qui traite une question similaire: php flock behavior quand le fichier est verrouillé par un processus
C'est une solution assez élaborée! Si je comprends bien, cela crée un fichier .lck supplémentaire qui sert de mécanisme de vérification séparé. Ne serait-ce pas plus sensible aux conditions de course (parce que c'est plus d'opérations) que d'utiliser LOCK_EX + flock? Une question similaire est en effet informative, merci!
Heureux de vous aider :)
J'ai écrit un petit test qui utilise sleep ()
afin de pouvoir simuler des processus de lecture / écriture simultanés avec un simple appel AJAX. Il semble que cela réponde aux deux questions:
fclose ()
ne supprime en effet pas le verrou du processus qui est déjà en cours d'exécution, comme confirmé dans certaines des réponses. PHP5.5 et inférieur sur Windows ne prend pas en charge le paramètre $ wouldblock
selon la documentation,
J'ai pu tester cela sur Windows + PHP5.3 et j'ai conclu que le file_is_locked ()
de mon test fonctionnait toujours dans ce scénario:
flock ()
renverrait toujours false mais ne contiendrait pas le paramètre $ wouldblock
mais il serait toujours pris dans ma vérification else
.
if (isset($_POST['action'])) { $file = 'file.txt'; $fp = fopen($file, 'r+'); if ($wouldblock = file_is_locked($fp)) { // wait and then try again; sleep(5); $wouldblock = file_is_locked($fp); } switch ($_POST['action']) { case 'write': if ($wouldblock) { echo 'already writing'; } else { flock($fp, LOCK_EX); fwrite($fp, 'yadayada'); sleep(5); echo 'done writing'; } break; case 'read': if ($wouldblock) { echo 'cant read, already writing'; } else { echo fread($fp, filesize($file)); } break; } fclose($fp); die(); } function file_is_locked( $fp ) { if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) { if ($wouldblock) { return 'locked'; // file is locked } else { return 'no idea'; // can't lock for whatever reason (for example being locked in Windows + PHP5.3) } } else { return false; } }
Pour répondre à votre deuxième question, non, fermer n'efface pas un verrou. Voir la deuxième entrée dans le journal des modifications pour flock .
peut-être avez-vous oublié
flock ($ fp, LOCK_UN)