Quand les Threads et les Sockets ne font pas bon ménage


J'ai passé une bonne partie de la nuit sur un bug. La nuit, car c'est une bug qui intervient chez un client qui est ouvert 7j/7 et que le seul moment ou je peux faire des tests c'est quand il ne bosse pas :)

Pour vous situer l'application, elle a été déployée sur une 15ène de postes sur 3 sites différents, et sur un site, l'application ce gèle quand on la lance en fin de journée...voilà des symptômes pour le moins étonnants qui ont résisté longtemps à mes investigations.

Pour simplifier les choses, le site est distant et j'interviens via une prise de contrôle à distance...tout pour être heureux quoi.

L'application en question possède un système de synchronisation en temps réel qui fonctionne avec des sockets. Chaque poste est connecté à tous les autres et il informe en temps réel ses petits copains de toute modification qu'il effectue afin qu'elle se propage sur l'ensemble des postes. Pour que ce soit plus simple et vu que les données échangées sont de petite taille sur un réseau local à 1Gb, l'envoie se fait depuis le Thread principal avec un send(). La réception est elle placée dans un thread secondaire qui peut bloquer autant qu'il veux sur le recv() en attendant des informations.

Tout ce système fonctionne donc parfaitement sur 2 sites, sur mon environnement de développement, et sur le 3ième site...sauf en fin de journée.

J'ai évidemment mis en doute la configuration des postes, l'antivirus, le firewall, voir les câbles réseau (et oui, rien de pire qu'un câble défectueux pour vous faire chercher un bug qui n'existe pas)...mais non rien n'y fait, je ne parviens pas à isoler le problème qui, ça je l'ai déterminé intervient sur le send() qui bloque et fige donc le thread principal.

Dans ce genre de situation, on pourrait être tenté de mettre en place un thread secondaire pour les envoies, mais cela me dérange pour deux raisons: premièrement le bug n'est pas identifié, deuxièmement si le bug se produit dans le thread secondaire, certes le thread principal ne sera plus bloqué mais l'envoie de données ne fonctionnera pas pour autant.

Au final, il m'a fallut pas mal de temps pour identifier ce qu'il se passait. Sur ce site en particulier, et en fin de journée, on se retrouve avec un fichier historique relativement volumineux (quelques Ko), or ce fichier est échangé avec tout poste qui est nouvellement lancé afin qu'il puisse se resynchroniser avec les données du jour. Le problème est là, mais pourquoi la taille des données - qui reste mesurée - vient-elle perturber tout le système ?

En décomposant pas à pas ce qu'il se passait j'ai fini par trouver que le thread secondaire de lecture reçois des données, il déclenche un événement géré dans le Thread principal via la méthode (coe:Synchronize), et c'est dans le traitement de cet événement que se fait l'appel au send(). En simplifiant, un poste dit "coucou je suis nouveau ici", et on lui répond "salut, je t'envoie tout ce qu'il s'est passé aujourd'hui pour que tu sois à jour"...et là bing ça bloque. A noter qu'en fait les deux postes se disent la même chose, car celui qui vient de se connecter possède en réalité déjà une copie de l'historique mais il ne sait si elle est à jour.

J'en suis arrivé à la conclusion qu'il y a saturation des buffers internes du socket, avant de pouvoir terminer son send() il faut des appels à recv() soient effectués pour que le socket puisse emmagasiner d'autres données...ce qui ne se fait plus vu que le thread de lecture est bloqué sur un Synchronize !

Finalement, pour débloquer la situation, ce n'est pas le send() que j'ai placé dans un thread secondaire, mais le Thread secondaire déclenche l’événement via un PostMessage() au lieu du Synchronize afin qu'il puisse lire la prochaine requête sans attendre la fn du traitement de la précédente.

Voilà un bel exemple de comment les choses peuvent foirées alors qu'on pensait avoir tout géré :)
Date de dernière modification : 16/01/2018