Fiabilité système

cs_Gerard Messages postés 121 Date d'inscription jeudi 10 janvier 2002 Statut Membre Dernière intervention 7 août 2018 - 14 nov. 2011 à 22:37
cs_Gerard Messages postés 121 Date d'inscription jeudi 10 janvier 2002 Statut Membre Dernière intervention 7 août 2018 - 12 déc. 2011 à 18:38
Bonjour,
j'ai posé il y a quelque temps une question et je n'ai pas eu de réponse ce qui me surprend pas mal, puisqu'à chaque fois que j'en ai posée une, j'ai eu des réponses vraiment très intéressantes.
Peut-être n'ai-je pas été assez clair? Personne n'aurait-il de réponse à cette question?
Voilà: je fais tourner un serveur qui traite plusieurs milliers de requêtes par jour, et ceci 24H sur 24.
Normalement, toutes les données que ce serveur gère sont en mémoire, même s'il en fait une copie sur disque pour les récupérer en cas de problème.
Parfois, il apparait des erreurs, une tous les 2 ou trois jours, rarement la même, où il semble que des données soient corrompues en mémoire, alors que leur copie sur le disque est intègre.

Ma question est simple: se peut-il que des données en mémoire se perdent spontanément ou est-ce une mauvaise gestion de ma part de certains aspects de mon serveur qui viendrait corrompre ses données?

La réponse à cette question est importante pour moi, car s'il est envisageable que des données se perdent, alors la solution est de faire de la redondance et de la gestion d'exceptions, ce qui est lourd de conséquences, sinon c'est dans mon soft qu'il faut que je continue à chercher ce qui cloche. (Entre parenthèses cela fait environ 6 mois que je cherche en vain...)

Comment font les institutions comme les banques qui ne peuvent se permettre la moindre erreur?

Merci à celui ou ceux qui pourraient un peu éclairer ma lanterne...

6 réponses

cs_rt15 Messages postés 3874 Date d'inscription mardi 8 mars 2005 Statut Modérateur Dernière intervention 7 novembre 2014 13
19 nov. 2011 à 19:44
Salut,

Dire que les données d'un processus ne peuvent pas être corrompues par un "élément extérieur" à l’application serait mentir.
En théorie, il peut arriver que des données soient corrompues suite à un problème de hardware, à cause d'un problème avec le système d'exploitation, ou encore à cause d'un autre processus un peu envahissant. Et il existe probablement d'autres raisons.

Mais d'une manière générale :
-> Le hardware fonctionne bien et fait exactement ce qu'on lui demande. L'exception du premier pentium confirme cette règle. Et quand il fonctionne mal, on a plus souvent droit à un écran bleu qu'à une corruption des données d'un processus.
-> Les systèmes d'exploitations modernes (Peu importe lesquels) fonctionne relativement bien, et assure notamment un rôle d'isolation des processus. Les fonctions qu'ils fournissent sont pratiquement exemptes de bug, car elles sont souvent utilisées par un très grand nombre d'applications de part le monde. Bref, il tu as plus de chance de gagner au loto que de trouver un bug de l'OS. Quant au plantage de l'OS à proprement parlé, ils sont plus souvent dus à l'hardware ou à un driver qu'au système d'exploitation lui même. Attention cependant, un OS trop chargé peu parfois avoir du mal à servir les processus (Par exemple si trop de handles ouverts. Aisément vérifiable avec process explorer).
-> Le système d'exploitation assure plus ou moins l'isolation des processus les uns par rapport aux autres. Chaque processus à l'impression d'être le seul en train de fonctionner sur le PC. L'OS assure un virtualisation de la mémoire, et un processus n'a donc pas accès facilement aux données gérées par les autres processus. Mais bien sûr un processus peut quand même modifier les données d'un autres, soit en modifiant directement les données, soit en injectant des bouts de code dans l'autre processus. Mais toutes ses opérations nécessite l'usage de fonctions généralement pas utilisés par les applications normales (WriteProcessMemory, CreateRemoteThread, SetWindowsHookEx...). Et le système d'exploitation peut refuser à un processus l'usage des ces fonctions s'il n'a pas les droits suffisants. Bref, il faudrait qu'une application bien particulière soit présente sur le PC pour interférer avec un autre processus, genre un virus/malware, un anti-virus pourri, ou un programme réalisé spécifiquement pour récupérer des données dans ton application.

Bref, dans 99,99% des cas, un problème de corruption de données dans une application est due à elle même.
Cela peut provenir des dépendances (Utilisation d'une dll dans une mauvaise version par exemple), ou encore du compilo dans de très rare cas (Certains compilos génère du mauvais code lorsque l'on utilise des flags d'optimisation trops avancés), ou encore d'autre chose.
Mais très souvent, cela vient du code de l'application !

En effet, il est tellement simple de corrompre des données :
type TPointWithName = record
  lpName: Array[0..10] of Char;
  nX: Integer;
  nY: Integer;
end;

...

procedure TForm1.Button1Click(Sender: TObject);
var
  point: TPointWithName;
begin
  with point do
  begin
    nX:= 10;
    nY:=  10;
    StrCopy(lpName, 'Hello world!');
    // Devrait afficher "10 10". Bin non.
    ShowMessage(IntToStr(nX) + ' ' + IntToStr(nY));
  end;
end;


Pour ce qui est des applications dans le domaine bancaire, bin y a pas de miracles... Du moins pas sur celles sur lesquelles j'ai travaillées (Code C, C++, java et autre, au kilomètre, traitant notamment des échanges inter bancaires).
Bien sûr, il y a des outils qui sont utilisés de manière plus ou moins régulière, permettant de détecter des problèmes automatiquement, tels que valgrind et purify (google pour plus d'info).
Mais il y a surtout des batteries de tests en cascade (Réalisés par les développeurs, par des outils d'intégration continue, par des équipes qualités, par les services clients, par le client lui même...).
Au final, la plupart des problèmes sont trouvés bien avant que l'application soit en production. Il y a toujours des bugs dans l'application (Zéro bug ça n'existe pas) bien sûr, mais dans des fonctionnalités ou des cas que le client n'utilise pas. En prod, il y a surtout des problèmes de perfs (Temps passé sur un traitement excessif) face à la charge réelle, quoiqu'il y ait bien sûr aussi des tests de charge.
On peut aussi introduire une résistance aux erreurs dans l'architecture de l'application, avec notamment les transactions (A base d'écriture disque, ou avec un base de données) et la reprise sur panne permettant de rejouer un traitement qui a échoué. Tout un tas de contrôles peuvent aussi être effectués sur les données à différents stades, et on laisse souvent la possibilité au client d'ajouter ses propres contrôles sous forme de plugin ou autre.

Mais en fin de compte, quand un souci est détecté, il est affecté à un développeur qui est chargé de le fixer (Souvent le développeur qui connait le mieux la partie du logiciel qui a un problème). Il peut bien sûr demander de l'aide à n'importe qui dans l'équipe, ou refiler le problème a quelqu'un d'autre. A plusieurs, donc avec le cumule des compétences (S'il y en a...) et en y passant le temps nécessaire, on peut résoudre les problèmes les plus tordus. Il y a toujours une explication.


Après ce pavé insipide et ne t'aidant pas le moins du monde, je t'invite effectivement à chercher ce qui cloche dans le source, car la réponse y est probablement.

Je vois que dans un de tes précédents message tu as parfois des problèmes de violation d'accès.
C'est un problème sympathique car :
1) Le message d'erreur contient généralement une adresse, qui peut permettre de retrouver la ligne de code correspondante.
2) Windows peut être configuré pour faire un dump (Globalement copie de la mémoire du processus dans un fichier) d'un processus qui crash sur violation d'accès. Tu peux ensuite exploiter ce dump avec un débogueur pour récupérer toutes les infos nécessaire, quoique ce n'est pas forcément évident. Il est aussi possible de déclencher directement un débogueur pour récupérer l'erreur. En gros tout se fait via la clé de registre aedebug (Google de aedebug pour plus d'info).

Bref, si tu ne connais pas déjà, renseigne toi peut être sur ces méthodes.

Bon allé je continue mon pavé un peu. Prenons le code suivant :
procedure TForm1.Button1Click(Sender: TObject);
var
  lpChars: PChar;
begin
  lpChars: = Pointer(3);
  StrCopy(lpChars, 'Hello world');
end;


A l'exécution, hors débogueur, il me fait le plantage suivant :
"Violation d'accès à l'adresse 00408180 dans le module 'Project1.exe'. Ecriture de l'adresse 00000003."

Ces messages d'erreurs sont en fait très précis.

Sans rentrer dans le détail (64 bits, PAE...), l'espace d'adressage d'un processus est une suite d'octets numérotés de 0 à FFFFFFFF. Cet espace est découpé en pages de 4ko. Le système d'exploitation, en coopération avec le processeur, est capable de fournir de nouvelles pages à un processus qui demande de la mémoire.
"J'ai besoin de 5000 octets !"
"Tiens, voilà deux pages démarrant à l'adresse 0033C000."
Et l'OS et le processeurs se charge de traduire les adresses virtuelles des deux pages situés à 0033C000 vers des adresses physique de la RAM.
Le processus peut accéder à n'importe quelle page allouée. Mais s'il utilise une adresse qui ne tombe pas dans une page allouée, il y a alors violation d'accès, comme ci-dessus.
Toute cette mécanique de page est assez traitre. Supposons qu'un tableau de 50 caractères se trouve au début d'une page... On peut alors faire tab[1000] sans avoir de violation d'accès, pourtant on est très largement hors du tableau. On peut d'ailleurs écraser des données d'autres tableaux ou variable qui se trouvait dans la même page. A l'inverse, si le tableau est positionné sur les 50 derniers octets de la page, tab[1000] fera planter le programme (Sauf si la page suivante est allouée aussi).

Revenons au message d'erreur.
"Ecriture de l'adresse 00000003" -> Le processus cherche à écrire à l'adresse 3 (Autrement dit au 4ème octet de la première page). Ce n'est pas une page allouée. Le processus plante. A noter qu'il est précisé que c'est une écriture, pas une lecture.

"dans le module 'Project1.exe'" -> Bin zut, c'est dans mon module. Ça pourrait être dans une dll, par exemple dans user32.dll que j'utilise (Ce qui peut arriver si je lui donne une adresse pourrie). Mais là nan, c'est carrément dans mon .exe. Donc soit dans mon code, soit du code machine ajouté à l'exe, donc toute les fonctions de base de Delphi par exemple.

"à l'adresse 00408180" -> C'est l'adresse de l'instruction processeur qui a cherché à écrire à l'adresse 00000003. Si je dispose des sources exactes qui ont servis à compilé l'exécutable qui a planté, je peux tenter ma chance sous Delphi via "Chercher"->"Erreur d'exécution..." et entrer cette adresse. Mais il n'y a pas de garantit que la recompilation du projet fasse que les mêmes instructions se trouvent à 00408180 comme dans l'exe qui a planté...

Une deuxième approche est d'avoir pris soin de cocher "Informations de débogage TD32" sous "Projets"->"Options..."->"Lieur". Cela augmente la taille de l'exe mais n'est pas censé poser de problèmes de performance ou de consommation mémoire.

Donc mon exe à planté et j'avais coché la case. Je prend mon .exe et je l'ouvre avec TD32 (Le fait d'utiliser un débogueur compatible borland permet d'avoir les symboles). Je ne sais pas s'il y a mieux que le vieux TD32 pour ce boulot.

Sous TD32 donc, faire "espace" puis utiliser la souris. "File", "Open", et ouverture du .exe.
Puis par exemple "View"->"CPU" pour avoir ce dont on attend d'un débogueur ->
A gauche en haut les instructions assembleur.
En bas, de quoi afficher de la mémoire.
A droite, les registres, à l’extrême droite les flags.
En bas, la pile.

On cherche une instruction, donc clique droit sur la fenêtre de gauche, puis "Goto...", et on entre l'adresse. Attention à l'unité de l'adresse...
Donc il est préférable d'aller dans "Options", "Langage", par exemple "Pascal".
Donc dans le goto, comme on est en Pascal et qu'on veut entrer de l'hexa, il faut le dollar ->
$408180

Ça donne ça :


L'instruction est rep movsd. Un coup de google peut nous expliquer le fonctionnement de cette instruction (Move Data from String to String).

Mais dans quelle fonction sommes nous ? Il faut remonter un peu plus haut dans le code pour le voir.


Cela donne déjà de bonnes idées !
StrCopy qui plante en cherchant à écrire à l'adresse 3, cela veut dire que quelque part dans notre code, on passe un pointeur vérolé à StrCopy.

Mais on ne sais pas quel appel à StrCopy l'a fait planté. Cela pourrait être dans notre code ou depuis la runtime de Delphi par exemple. Il nous faudrait la pile d'appel, autrement dit, la succession de fonctions appelées qui ont amenés à l'appel à StrCopy. Ce n'est pas explicitement dit ni dans le message d'erreur, et TD32 ne peut pas le deviner non plus.
En fait, ce qu'il nous faut, c'est un dump du processus au moment de la violation d'accès.

Pour vista, il faut passer par le gestionnaire de processus alors que la fenêtre du plantage est affichée. Sous XP, utiliser dr watson.

Une fois qu'on a un dump, on peut l'ouvrir avec windbg, téléchargeable avec les debugging tools for windows.
Utiliser la commande "k" pour voir la pile d'appel.
Windbg ne comprend pas les symboles générés Delphi, mais on obtiens les adresses que l'on peut exploiter via TD32.
Dans mon cas, ça aide pas car le gestionnaire d'exception de TApplication capture l'exception (L'application ne crash donc pas)...

Il y a cependant des outils (Pas forcément gratuits) permettant de simplifier un peu tout ça dans le cas du Delphi genre madExcept. Regarder aussi du côté de JclDebug qui devrait permettre de retrouver la stack trace d'une exception.


Bref, je te souhaite bon courage pour la suite.
0
cs_rt15 Messages postés 3874 Date d'inscription mardi 8 mars 2005 Statut Modérateur Dernière intervention 7 novembre 2014 13
19 nov. 2011 à 20:06
Un lien et un autre lien de plus pour jcldebug.
0
cs_Gerard Messages postés 121 Date d'inscription jeudi 10 janvier 2002 Statut Membre Dernière intervention 7 août 2018
19 nov. 2011 à 20:34
Merci de ta rponse très longue et très détaillée? Je te remercie bien car je pense au temps que cela a du te prendre.
Je vois quelques pistes pour m'aider dans les recherches. Je vais les regarder de plus près.

J'ai récemment trouvé une erreur assez subtile mais que j'ai pu régler.
Bien sûr le serveur est multithread - un thread par joueur qui se connecte, et ils ont accés aux mêmes données, qui sont en général des Listes. Certains en lecture d'autres en écriture. J'ai mis en place l'équivalent de mutex afin de faire patentier le client suivant tant que le premier n'a pas fini.
Cela m'a supprimé un type d'erreur, mais j'en ai encore d'autres...
0
cs_Gerard Messages postés 121 Date d'inscription jeudi 10 janvier 2002 Statut Membre Dernière intervention 7 août 2018
12 déc. 2011 à 07:36
Bonjour,

Je reviens te donner des nouvelles et te remercier encore.
les nouvelles sont bonnes: j'ai enfin - grâce à toi - mis le doigt sur le problème et trouvé une (la?) solution.
Quel était la nature du problème? Le serveur gère des joueurs au travers de threads qui sont bien sûrs indépendants, mais comme ces joueurs sont classés, le classement est fait dans un document partagé par tous les threads.
Ce classement est une TStringlist, c'est-à-dire un TAD (type abstrait de données) qui en fait ne gère que des pointeurs vers des chaînes de caractères à 0 terminal.
Or lorsqu'il y a beaucoup de joueurs, ces listes prennent du temps à établir, jusqu'à une milliseconde en fin de journée. Pendant ce temps il arrive que d'autres joueurs soit fournissent aussi des résultats soit consultent les leurs.
Or si la liste est modifiée pendant qu'un joueur la consulte, en particulier si elle est remise à 0 pour la réécrire, le pointeur sensé consulter se retrouve à pointer dans une zone erronée.
Il semblerait que ceci ne déclenche pas nécessairement une erreur tout de suite, mais que quelque part, il y a une certaine propagation de l'erreur (je dis peut-être une bétise!) et que cela finit par se traduire par des erreurs diverses qui n'ont pas toujours à voir avec la cause.
Quelle solution ai-je adoptée?
J'ai créé un nouveau type de TStringList qui possède un drapeau qu'on lève lorsqu'un joueur consulte la liste ou la modifie. Avec une gestion d'exception pour être sûr de rabaisser le drapeau en fin de compte. Lorsqu'un joueur veut accéder à cette liste, il le fait au travers de méthodes qui attendent que le drapeau soit abaissé pour exécuter la tâche demandée.
Voilà cela fait maintenant plus d'une semaine que je n'ai plus eu d'erreurs, donc je pense que j'ai bien trouvé l'origine du problème.
je viens de créer une nouvelle solution que je viens de mettre en place.
Lorsqu'un joueur fournit des résultats, son classement est fait (très rapide, durée indépendante du nombre de joueurs car il suffit de l'affecter à une classe), mais la liste nes joueurs classés n'est plus dressée dans le flux. Cette liste est flaguée comme n'étant plus à jour, et une procédure de fond, donc dans la racine du programme, lance périodiquement un thread à haute priorité afin de remettre à jour cette liste (et celle du classement général!). Il suffit d'adjoindre à ces listes les joueurs qui demandent les résultats et une fois qu'elles sont mises à jour de leur envoyer.
Les documents communs sont donc traités dans le tronc commun et non plus dans chaque thread de client.

Voilà, je ne sais pas si cela t'a intéressé, mais c'était pour te tenir au courant et peut-être que cette explication servira à d'autres!

merci encore...
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
12 déc. 2011 à 18:02
« Quelle solution ai-je adoptée?
J'ai créé un nouveau type de TStringList qui possède un drapeau qu'on lève lorsqu'un joueur consulte la liste ou la modifie. Avec une gestion d'exception pour être sûr de rabaisser le drapeau en fin de compte. »


- C'est comme un Mutex, non ?
0
cs_Gerard Messages postés 121 Date d'inscription jeudi 10 janvier 2002 Statut Membre Dernière intervention 7 août 2018
12 déc. 2011 à 18:38
Oui, d'ailleurs je l'ai dénommé MonMutex...
0
Rejoignez-nous