1
votes

Vérifiez si le fichier est verrouillé par un processus simultané

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:

  1. Existe-t-il un moyen d'attendre que le fichier ne soit plus verrouillé, tout en conservant la possibilité de lui donner une limite de temps?
  2. Est-ce que fclose () déverrouille automatiquement tous les verrous lorsqu'un autre processus aurait verrouillé le fichier?


2 commentaires

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)


3 Réponses :


0
votes

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.


1 commentaires

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?



1
votes

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


2 commentaires

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 :)



1
votes

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:

  1. lorsque le fichier est verrouillé, une mise en veille qui se rapproche de la durée d'écriture estimée et une vérification de verrouillage ultérieure permettent une attente. Cela pourrait même être mis dans une boucle while avec un intervalle.
  2. 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;
    }
}


0 commentaires