Bonjour,
J'observe une curiosité que je n'arrive pas à expliquer quant à la gestion de la mémoire utilisée. Mon sujet est assez complexe aussi permettez-moi d'en donner le contexte :
Je fais tourner sur mon ordinateur personnel un serveur pour jouer au bridge gratuitement. Il y a environ 900 joueurs qui viennent par jour et couramment plusieurs dizaines de joueurs jouent simultanément à partir de leur navigateur, soit sur ordinateur, soit sur tablette. En pointe le nombre de requêtes qui arrivent sur le serveur est environ de 20 requêtes par seconde.
Or tous les navigateurs - sauf Firefox sur ordinateur - ne respectent pas le point 8.1 de la norme HTTP/1.1 qui stipule qu'ils doivent rester connectés pendant toute la session, mais ils se déconnectent après chaque réponse du serveur pour se reconnecter aussitôt pour la requête suivante qui arrive en général dans la foulée.
Or à chaque nouvelle connexion, un thread est créé et ce thread doit gérer tout ce qui se passe dans la connexion. Ceci est fait au moyen d'un objet TContact qui est assez conséquent puisqu'il doit assurer beaucoup de fonctions entre l'identification du joueur, créer son objet ou le retrouver en mémoire, gérer les erreurs de connexion, interpréter la requête et invoquer la méthode appropriée du joueur et enfin répondre.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
TContact = class(TObject)
private
protected
public
  UnJoueur: TJoueurDonne;   //peut être complet ou simple
  FairePhoto: Boolean;      //Faire une copie de l'état de la reconnexion
{$IFDEF WebJournal}
  WebWrite: Boolean;        //pour limiter l'éventuelle écriture dans le journal web
{$ENDIF}
  Function DecodeAdresseEmail(address: string):String;  //
  procedure DemandePwd(Str1: String);
  Procedure DisplayLock(Str1: String); //affichage dans la fenêtre principale avec verrouillage de l'accès
  Procedure EnvoiFichierType(Fichier,Contenu: String;NoCache: boolean=false);
  procedure ForBrowserHTMLComment;
  procedure ForBrowserHTMLResult;
  procedure GetExecute;     //Traitement des requêtes GET générales
  procedure LireRequete;
  procedure LireSimpleRequete; //pour les joueurs en train de jouer
  function LongStringWrite(var F: File;S: String): boolean; //Procédure d'écriture dans le fichier
  procedure PostExecute;    //traitement des requêtes POST générales
  procedure OnTheCloudConnect(Jouer: Boolean=True; POC: Boolean=false);  //déclenché par la page d'accueil
  Procedure RépondErreur;              //Réponse en cas d'erreur prioritaire sur la réponse normale
  Procedure Répondre;                  //Envoie la Réponse à la requête
  Function SearchOrCreateJoueur: TJoueurDonne;
  Function SearchJoueur: TJoueurDonne;
  Procedure SelectMajorMethod;
  Procedure SelectOtherMethod;
  procedure TraiterDivers;
 
end;
Chaque nouvelle connexion implique le chargement en mémoire d'un nouvel objet Contact, ce qui consomme des ressources aussi bien de CPU de temps et de mémoire.
Il existe une relation grossière entre le nombre de joueurs qui se sont connectés et la mémoire occupée par le serveur qui est environ d'1Mo par joueur. Comme il y a 900 joueurs qui viennent chaque jour et qu'ils sont conservés en mémoire, l'occupation mémoire peut monter jusqu'à 900 Mo, ce qui ne semble pas pour l'instant poser de problème. je constate par ailleurs des bizarreries dans l'occupation mémoire car ce n'est pas une fonction monotone du nombre de joueurs mais cela pourrait faire partie d'une autre discussion.
maintenant que j'ai exposé le contexte, j'en arrive à ma question...
Au lieu de charger les objets contacts à chaque nouvelle connexion, j'ai eu l'idée de faire une "écurie" de contacts. Quand une nouvelle connexion s'établit, il suffit de regarder dans l'écurie s'il y a un contact disponible et de l'enfourcher. Sinon on en crée un. Lors de la déconnexion, au lieu de libérer l'objet contact, on le remet à "l'écurie".
Le code en compilation conditionnelle afin de pouvoir faire une rapide marche arrière :
création de l'écurie :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
{$IFDEF ListeContactsDispo}
  ListeContactsDispo:= TList.Create;
  csListeContacts:= TCriticalSection.Create;
{$ELSE}
{$ENDIF}
Gestion d'une nouvelle connexion :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{$IFDEF ListeContactsDispo}
  if ListeContactsDispo.Count>0 then
  Begin              //Il y a des Objets TContat disponib
    csListecontacts.Acquire;
    try
      UnContact:=Tcontact(ListeContactsDispo[0]);   //prendre le pus ancien
      ListeContactsDispo.Delete(0);      //le retirer de la liste
    finally
      csListecontacts.Release;
    end;
  End else
  Begin
    UnContact:= TContact.Create;
  End;
{$ELSE}
  UnContact:= TContact.Create;
{$ENDIF}
Gestion des déconnexions :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
{$IFDEF ListeContactsDispo}
    UnContact.UnJoueur:= nil; //effacer l'affectation
    UnContact.Tampon.clear;
    csListecontacts.Acquire;
    try
      ListeContactsDispo.Add(UnContact);    //ceci ajoute le pointeur sur le contact dans la liste
    finally
      csListecontacts.Release;
    end;
{$ELSE}
    UnContact.Free;
{$ENDIF}
Or au lieu de voir la mémoire occupée diminuer, elle explose d'un facteur 10 !
maintenant elle est indépendante du nombre de joueurs et ne fait que croitre au fur et à mesure que les joueurs jouent ! Sur l'outil de développement, elle prend environ 5Mo par nouvelle donne jouée par un joueur jusque bien sûr cela plante vers environ 1600Mo occupés...
j'ai vérifié que le nombre de contacts à l"écurie restait stable : ce n'est pas lui qui fait gonfler la mémoire...
Quelqu'un a-t-il un début d'explication à ce phénomène ?