Partage
  • Partager sur Facebook
  • Partager sur Twitter

Bloquer un script à une seule exécution simultanée

Sujet résolu
    18 juillet 2019 à 15:27:13

    Bonjour à tous,

    J'utilise un script qui permet de mettre à jour plusieurs donnée de mon site. Cette mise à jour s'effectue toutes les 30 minutes et est déclenché par n'importe quel utilisateur de manière automatique. Il y a un compte à rebourd et dès qu'il atteint 0 le script se lance.

    Le problème est que s'il y a 2 utilisateurs qui refresh en même temps le script s'exécute 2 fois.

    J'ai alors tenté de protéger avec les Transactions sql et j'ai rajouté des "FOR UPDATE" dans tous les select du script mais rien ne va.

    J'ai du mal à vraiment cerner le problème donc en trouver une solution complique tout.

    Voici mon script (simplifie)

    <?php
    // require_once toutes les fonctions nécessaire
    
    try
    {
    	$pdo = PDO2::getInstance();
    	//We start our transaction.
    	$pdo->beginTransaction();
    	
    	// Récupération tournoi actifs
    	$tournoi_actif_array = trans_info_bffa_tournoi_actif($pdo);
    	
    	
    		// Cloture les tournois fini
    		trans_bffa_tournoi_end($pdo, $tournoi_actif_array);
    		
    		// Répartition des points pour chaque tournoi cloturé
    		
    		foreach($tournoi_actif_array as $tournoi_id)
    		{
    			// Sélectionne que les tournois terminé parmi tous les tournois mis à jour
    			if ($tournoi_id['date_end'] <= date('Y-m-d H:i:s'))
    			{
    				// Info des players
    				$data_player = trans_info_bffa_player($pdo, $tournoi_id['id']);
    				
    				// Calcul du score
    				$score_player = array();
    				$i = 0;
    				foreach($data_player as $key => $value)
    				{
    					$score_check = bffa_score($value, $tournoi_id['battlemin'], $tournoi_id['battlemax']);
    					// Si null on n'ajoute rien
    					if ($score_check !== null)
    					{
    						$score_player[$i]['user_id'] = $value['user_id'];
    						$score_player[$i]['score'] = $score_check;
    						$score_player[$i]['battle'] = $value['battles'];
    						$i++;
    					}
    				}
    				
    				// Tri par score
    				$score_player = array_tri($score_player, 'score', SORT_DESC);
    				
    				// Récupérations des points
    				$score = trans_bffa_points($score_player, $tournoi_id['potsize'], $tournoi_id['playermax']);
    				
    				// Enregistrement des points
    				foreach($score as $value)
    				{
    					// Transaction points
    					trans_points($pdo, $value['user_id'], $value['prime'], $tournoi_id['id'], TYPE_BFFA, $value['score'], $value['battle'], $value['points']);
    					
    					// Désactive le tournoi
    					bffa_stop_stat($tournoi_id['id'], $value['user_id']);
    				}
    				
    				
    			}
    		}
    	
    	
    	//We've got this far without an exception, so commit the changes.
    	$pdo->commit();
    }
    //Our catch block will handle any exceptions that are thrown.
    catch(Exception $e)
    {
    	//An exception has occured, which means that one of our database queries
    	//failed.
    	
    	save_error_msg($e->getMessage());
    	
    	//Rollback the transaction.
    	$pdo->rollBack();
    }
    ?>

    La fonction save_error_msg() enregistre la valeur de la variable dans un fichier txt (rien n'est enregistré pour info).

    EDIT : le résultat est que trans_points(); est exécuté 2 fois et donc les joueurs sont récompensé 2 fois.

    Je vais développé ici les fonctions

    $pdo = PDO2::getInstance();

    Vient de cette class :

    <?php
    /**
     * Classe implémentant le singleton pour PDO
     * @author Savageman
     */
     
    class PDO2 extends PDO {
     
        private static $_instance;
     
        /* Constructeur : héritage public obligatoire par héritage de PDO */
        public function __construct( ) {
         
        }
        // End of PDO2::__construct() */
     
        /* Singleton */
        public static function getInstance() {
         
            if (!isset(self::$_instance)) {
                 
                try {
                 
                    self::$_instance = new PDO(SQL_DSN, SQL_USERNAME, SQL_PASSWORD);
    				self::$_instance->exec("set names utf8");
                 
                } catch (PDOException $e) {
                 
                    echo $e;
                }
            } 
            return self::$_instance; 
        }
        // End of PDO2::getInstance() */
    }
    // end of file */

    toutes les autres fonctions à part trans_bffa_points(), bffa_score() et array_tri sont des fonctions interagissant uniquement avec la DB SQL


    Merci d'avance !

    -
    Edité par okuni 18 juillet 2019 à 15:38:44

    • Partager sur Facebook
    • Partager sur Twitter
      18 juillet 2019 à 15:38:25

      okuni a écrit:

      Bonjour à tous,

      J'utilise un script qui permet de mettre à jour plusieurs donnée de mon site. Cette mise à jour s'effectue toutes les 30 minutes et est déclenché par n'importe quel utilisateur de manière automatique. Il y a un compte a rebourd et dès qu'il atteint 0 le script se lance.

      Tu pars donc du principe que tu as au moins un utilisateur sur ton site toutes les 30 minutes, c'est une mauvaise idée.

      Si tu veux qu'un script se déclenche de cette manière, utilises plutôt CRON, qui ne nécessite pas l’interaction d'un utilisateur et qui par conséquent en plus d'être plus sûr, te permettra une intervalle respectée.

      -
      Edité par Lartak 18 juillet 2019 à 15:38:47

      • Partager sur Facebook
      • Partager sur Twitter

      Face a quelqu'un pour qui l'on n'éprouve que de l'aversion et du mépris, les yeux d'un homme deviennent extrêmement froids et cruels.

        18 juillet 2019 à 15:39:54

        Je n'ai pas Cron pour l'instant.

        J'utilise en réalité mon NAS qui envoi une commande toute les 30 minutes.

        Et si le site n'est pas mis à jour ce n'est pas grave.

        • Partager sur Facebook
        • Partager sur Twitter
          18 juillet 2019 à 15:48:16

          Et que vient faire l'utilisateur dans ce cas là ?

          Je ne vois donc pas le rapport entre l'interaction de l'utilisateur et une tâche déjà planifiée.

          L'utilisateur ne devrait pas avoir accès au script.

          • Partager sur Facebook
          • Partager sur Twitter

          Face a quelqu'un pour qui l'on n'éprouve que de l'aversion et du mépris, les yeux d'un homme deviennent extrêmement froids et cruels.

            18 juillet 2019 à 16:40:37

            Il n'y a pas accès, c'est invisible pour lui. Et mon NAS plante parfois. L'utilisateur est donc la en backup. Mais comme je l'ai dis, si l'update ne s'effectue pas, ce n'est pas grave. Le script sert entre autre a actualiser le score de chaque joueur. Si le calcul est décalé, cela ne pose aucun problème car les tournois durent une journée entière (24h).

            Bref, c'est la seule option que j'ai trouvé actuellement n'ayant pas de CRON.

            Merci pour ton aide

            • Partager sur Facebook
            • Partager sur Twitter
              18 juillet 2019 à 16:55:28

              okuni a écrit:

              Il n'y a pas accès, c'est invisible pour lui.

              Ce n'est pas de ce genre d'accès que je parlais, mais il ne faut pas que l'utilisateur en accédant à une page du site, que ça puisse lancer l'exécution du script.

              Donc pour faire simple, il faut que le script ne puisse être exécuté que par un accès directement interne au serveur et non qu'il puisse être exécuté via une interaction de l'utilisateur, même si ce n'est qu'en se rendant à une url spécifique.

              Inutile de reparler de CRON, étant donné que le NAS se charge lui même de la tâche planifiée, ce que je ne pouvais pas deviner, c'est pour ça que je t'avais parlé de CRON.

              Tout portait à croire dans ce que tu as dit dans le contenu du sujet que le script n'était exécuté que par l'interaction d'un utilisateur et non via une tâche déjà planifiée.

              • Partager sur Facebook
              • Partager sur Twitter

              Face a quelqu'un pour qui l'on n'éprouve que de l'aversion et du mépris, les yeux d'un homme deviennent extrêmement froids et cruels.

                18 juillet 2019 à 20:11:53

                Bonjour,

                Il y a un compte à rebours (...) donc il doit y avoir sous une forme quelconque (un timer, une date, ..), une valeur qui indique quand le script a été lancé : il suffit donc de tester : valeur inférieure à 30 minutes je ne lance pas l'action, valeur supérieure à 30 minutes, je lance l'action. Que ce soit en automatique ou via un "truc" caché à l'utilisateur.

                A+

                -
                Edité par monkey3d 18 juillet 2019 à 20:13:21

                • Partager sur Facebook
                • Partager sur Twitter
                  18 juillet 2019 à 22:03:56

                  Merci pour ta réponse monkey3d.

                  malheureusement ça ne suffit pas.

                  J'ai fais ceci :

                  <?php
                  $filename = CHEMIN_CACHE.'bc_update.txt';
                  	$update_actif = 1;
                  	
                  	// Si le fichier existe on l'ouvre sinon on le créée
                  	if (file_exists($filename))
                  	{
                  		$monfichier = fopen($filename, 'r+');
                  		$date_maj = fgets($monfichier);
                  
                  		if ((time() - $date_maj) <= 60)
                  		{
                  			// Derniere maj il y a moins de 60 secondes, update inutile
                  			$update_actif = 0;
                  		}
                  	}
                  	else
                  	{
                  		$monfichier = fopen($filename, 'a+');
                  		// Lit la date. format : timestamp
                  		$date_maj = fgets($monfichier);
                  	}

                  Je fait l'update uniquement si $update_actif = 1;

                  Le problème est qu'apparemment (c'est ce que je pense mais sans comprendre) ce script se lance parfois 2 fois exactement en même temps.

                  Donc comment protéger un script et éviter qu'ils ne s'exécute plusieurs fois en même temps ? (plusieurs personnes en même temps)

                  C'est également une question purement théorique sans forcément être associé à ce problème. Je pensais que les Transactions SQL résolverait mon problème mais ça n'a pas l'air d'être le cas.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    19 juillet 2019 à 6:23:38

                    Il y a pas mal de possibilités pour empêcher qu'un process ne s'exécute plusieurs fois sous php.

                    Avec le code que tu montres peut-être utiliser flock : https://www.php.net/manual/fr/function.flock.php

                    Un tuto qui peut t'intéresser : http://m.timarhome.fr/news/2010-10-14-verrouiller-une-action-dans-un-script-php (je n'ai pas testé).

                    Tu as aussi les sémaphores plus complexes : https://www.php.net/manual/fr/book.sem.php

                    Je ne vois pas avec le code que tu montres ce que vienent faire les transactions SQL ?????

                    Enfin ta variable $update_actif semble être un booléen alors pourquoi ne pas utiliser pour seules valeurs true et false qui me semblent plus parlantes que 0 et 1 .... même si tu penses que cela revient au même ou presque.

                    A+

                    • Partager sur Facebook
                    • Partager sur Twitter
                      19 juillet 2019 à 9:41:05

                      Je ne connaissais pas la fonction flock().

                      je vais essayer ça merci :)

                      Merci également pour l'autre tuto, je vais lire ça tranquille ce week-end.

                      Pour les transactions sql en fait quasi toutes les fonctions renvoi vers des requetes.

                      Par exemple la première fonction :

                      $tournoi_actif_array= trans_info_bffa_tournoi_actif($pdo);

                      correspond à :

                      <?php
                      function trans_info_bffa_tournoi_actif($pdo)
                      {
                      	$req = $pdo->prepare('SELECT id, tournoi_name, date_start, date_end, potsize, playermax, rating_min, rating_max, battlemin, battlemax
                      	FROM '.PREFIXE.'bffa
                      	WHERE tournoi_actif=1 AND date_start <= now() FOR UPDATE');
                      	$req->execute();
                      	
                      	return $req->fetchAll();
                      }

                      Pour $update_actif c'est vrai que je pourrais écrire true/false. J'ai pris l'habitude de garder la meme nomenclature que dans ma DB SQL qui correspond a 1/0.

                      Je suis d'accord que ce n'est pas exactement la même chose mais ça suffit dans ce cas.

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Bloquer un script à une seule exécution simultanée

                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                      • Editeur
                      • Markdown