27-04-2020, 10:44 AM
Salut,
Ce que tu décris ensuite, oui, c'est le principe d'une transaction: une session connectée à la DB et qui lance une transaction va faire des modifications en DB qui ne seront pas lisibles par les autres sessions, tant que cette 1ere session n'aura pas dit "ok, j'ai finit" (ce qui s'appelle "commit"). Pour cela, la plupart des implémentations de DB utilisent des "locks": la 1ere session, qui a lancé une transaction et qui fait des actions, peut "locker" des tables/lignes/colonnes pour que d'autres ne puissent y toucher tant que le "commit" n'est pas fait. Ainsi, les autres sessions attendent que la 1ere session ait fini son travail (soit commit, soit "rollback", ie: "j'ai fini mais tout ce que jai fait, t'oublie en fait et tu sauve rien en DB!").
- JS envoie 1 query à PHP, qui ouvre la session, et mouline
- JS envoie une 2e query à PHP alors que la 1ere est encore en attente: la session PHP est lockée, et cette query poireaute...
- JS envoie une 3e query à PHP qui indique l'action du joueur: la session PHP est lockée, et cette query poireaute...
Si la 1ere & la 2eme query sont dépilées "trop lentement" par le serveur, alors la 3e sera traitée probablement après la fin du combat et l'action du joueur sera ignoré. Néanmoins, ce scénario n'implique rien sur la connexion des joueurs.
Ce problème se règle, je dirai, par une correction d'archi et l'utilisation des paramètres d'ouverture de session:
- JS ne doit faire qu'1 seule query en parallèle (long polling, ou polling répété) pour connaitre les infos envoyées par les autres joueurs, il faudra donc corriger ton client; de plus, la session devra être ouverte "sans lock", c'est à dire avec session_start(array('read_and_close' => true))
- La query envoyée par le JS pour dire l'action choisie par le joueur sera lancée en parallèle du long polling, éventuellement là aussi avec un read_and_close pour la session (y'a pas de raison de garder ladite session lockée côté serveur
La boucle côté JS, je ne sais pas si ce sera une bonne idée. Si elle a un autre intérêt que "planquer un truc que je comprends pas dans mon code", c'est une idée possible. Si c'est juste pour planquer ton bug, c'est une mauvaise idée
Je t'incite donc très très fortement à passer tout ça en InnoDB, et à utiliser UNE transaction pour CHAQUE page appelée.
Citation :est-ce dû à la différence de connexion ?Non, c'est dû à un mauvais code
Citation :L'écriture de la table serait-elle lock par le joueur à connexion trop rapide qui la lit en boucle ?Non, car une fois côté serveur, la connexion client n'a aucun impact. Elle n'en aurait que sur la concurrence de "quel joueur est le 1er à 'exécuter' le script PHP". C'est pour cela que je peux affirmer "Non" à la première question.
Ce que tu décris ensuite, oui, c'est le principe d'une transaction: une session connectée à la DB et qui lance une transaction va faire des modifications en DB qui ne seront pas lisibles par les autres sessions, tant que cette 1ere session n'aura pas dit "ok, j'ai finit" (ce qui s'appelle "commit"). Pour cela, la plupart des implémentations de DB utilisent des "locks": la 1ere session, qui a lancé une transaction et qui fait des actions, peut "locker" des tables/lignes/colonnes pour que d'autres ne puissent y toucher tant que le "commit" n'est pas fait. Ainsi, les autres sessions attendent que la 1ere session ait fini son travail (soit commit, soit "rollback", ie: "j'ai fini mais tout ce que jai fait, t'oublie en fait et tu sauve rien en DB!").
Citation : Il faudra que je vérifie si c'est le JS qui renvoie le joueur vers le PHP, ou si le PHP attend de lui-même pour finalement renvoyer la donnée au joueur (il me semble que c'est cette deuxieme configuration, mais il faut que je vérifie).Par défaut, les sessions "classiques" de PHP (sur le disque dur) sont bloquantes. Donc, oui, il faut que tu vérifies que ton JS ne fasse pas inutilement plusieurs appels en paralèle, mais logiquement, même si 2 appels du même joueur sont reçus côté serveur, alors le serveur va ouvrir la session PHP pour le 1er appel, et le 2nd sera mis en attente. Toutefois, si jamais tu as un client JS qui fait ce genre d'empilement, cela peut impliquer le scénario suivant:
- JS envoie 1 query à PHP, qui ouvre la session, et mouline
- JS envoie une 2e query à PHP alors que la 1ere est encore en attente: la session PHP est lockée, et cette query poireaute...
- JS envoie une 3e query à PHP qui indique l'action du joueur: la session PHP est lockée, et cette query poireaute...
Si la 1ere & la 2eme query sont dépilées "trop lentement" par le serveur, alors la 3e sera traitée probablement après la fin du combat et l'action du joueur sera ignoré. Néanmoins, ce scénario n'implique rien sur la connexion des joueurs.
Ce problème se règle, je dirai, par une correction d'archi et l'utilisation des paramètres d'ouverture de session:
- JS ne doit faire qu'1 seule query en parallèle (long polling, ou polling répété) pour connaitre les infos envoyées par les autres joueurs, il faudra donc corriger ton client; de plus, la session devra être ouverte "sans lock", c'est à dire avec session_start(array('read_and_close' => true))
- La query envoyée par le JS pour dire l'action choisie par le joueur sera lancée en parallèle du long polling, éventuellement là aussi avec un read_and_close pour la session (y'a pas de raison de garder ladite session lockée côté serveur
La boucle côté JS, je ne sais pas si ce sera une bonne idée. Si elle a un autre intérêt que "planquer un truc que je comprends pas dans mon code", c'est une idée possible. Si c'est juste pour planquer ton bug, c'est une mauvaise idée
Citation :apparemment ça pourrait lock la BdD.Non, car MyISAM ne gère pas les transactions. Donc, il n'y a aucun lock possible côté DB. Ce n'est en revanche pas une bonne chose car transactionner les appels à la DB permet d'éviter que deux scripts PHP ne se "marchent dessus". Par exemple, sans transaction, un joueur pourrait (en gros) voler un objet à un autre alors que cet autre joueur vend l'objet exactement au même moment. Là, je dirai que tu n'as aucune idée de comment ton code va réagir : ) Autre exemple, si le joueur vend son objet, alors tu vas certainement faire 3 queries SQL (une pour savoir s'il a l'objet, une pour ajouter de l'or au joueur et une pour retirer l'objet): quid si le serveur SQL plante à un moment dans ces 3 queries? sans transaction, le joueur pourrait avoir gagné son or [là le serveur plante] et la query retirant l'objet ne sera jamais exécutée, et le joueur aura donc encore son objet.
Je t'incite donc très très fortement à passer tout ça en InnoDB, et à utiliser UNE transaction pour CHAQUE page appelée.
Citation : - essayer de commit la table d'update du joueur 1Non, car cela n'a pas de sens: tu commites une transaction, pas une "table". Dans le cas spécifique que tu as ici, je pense que tu n'aura pas "une" transaction pour toute la page: les pages en long polling ne suivent pas ce pattern. En effet, sur un long polling avec MySQL dans lequel tu "demandes en boucle à la DB si il y a du nouveau, sinon, tu sleep(1) si oui tu l'envoie au joueur", alors transactionner le tout ne donnera rien, puisque tes queries MySQL ne verront jamais les modifications faites par les autres scripts PHP (principe d'isolation d'une transaction; bon, ok, tu peux changer ce niveau d'isolation, mais c'est hors sujet on va dire). Donc, tu vas ici entrer dans la boucle, ouvrir la transaction, regarder ce qu'il y a dans la DB, fermer la transaction, si il y a du changement, l'envoyer au joueur, sinon, sleeper.
Citation : Je viens de me rendre compte Xenos, que quand tu m'avais dis de commit la table, j'ai commit la table de lecture, et pas celle d'UPDATE ! Peut-être que je dois commit la table d'update ?Cette phrase n'a pas de sens, cf réponse d'au-dessus [je n'ai jamais pu te dire de "commit la table"]