Élimination correcte des objets lors de l'arrêt du serveur

9

Je travaille sur un grand projet C ++. Il consiste en un serveur qui expose une API REST, fournissant une interface simple et conviviale pour un système très large comprenant de nombreux autres serveurs. La base de code est assez grande et complexe, et a évolué au fil du temps sans une conception appropriée au départ. Ma tâche est d'implémenter de nouvelles fonctionnalités et de refactoriser / corriger l'ancien code afin de le rendre plus stable et fiable.

À l'heure actuelle, le serveur crée un certain nombre d'objets à longue durée de vie qui ne sont jamais arrêtés ni éliminés à la fin du processus. Cela rend Valgrind presque inutilisable pour la détection des fuites, car il est impossible de distinguer les milliers de fuites légitimes (douteuses) des fuites "dangereuses".

Mon idée est de veiller à ce que tous les objets soient éliminés avant la résiliation, mais lorsque j'ai fait cette proposition, mes collègues et mon patron m'ont opposé en soulignant que le système d'exploitation allait de toute façon libérer cette mémoire (ce qui est évident pour tout le monde) et en éliminant les objets. va ralentir l'arrêt du serveur (qui, pour le moment, est essentiellement un appel à std::exit). J'ai répondu que le fait d'avoir une procédure d'arrêt «propre» n'implique pas nécessairement que l'on doit l'utiliser. Nous pouvons toujours appeler std::quick_exitou simplement kill -9le processus si nous nous sentons impatients.

Ils ont répondu "la plupart des démons et processus Linux ne prennent pas la peine de libérer de la mémoire à l'arrêt". Bien que je puisse voir cela, il est également vrai que notre projet nécessite un débogage précis de la mémoire, car j'ai déjà trouvé une corruption de mémoire, des doubles libérations et des variables non initialisées.

Quelles sont vos pensées? Suis-je en train de poursuivre un effort inutile? Sinon, comment puis-je convaincre mes collègues et mon patron? Si oui, pourquoi et que dois-je faire à la place?

Marty
la source
Outre l'argument des performances (ce qui est raisonnable!), Est-ce beaucoup d'efforts pour isoler les objets à longue durée de vie et ajouter du code de nettoyage pour eux?
Doc Brown

Réponses:

7

Ajoutez un commutateur au processus serveur qui peut être utilisé pendant les mesures de valgrind qui libérera toute la mémoire. Vous pouvez utiliser ce commutateur pour les tests. L'impact sera minime pendant les opérations normales.

Nous avions un long processus qui prendrait plusieurs minutes pour libérer des milliers d'objets. Il était beaucoup plus efficace de simplement sortir et de les laisser mourir. Malheureusement, comme vous l'avez indiqué, cela a rendu difficile la détection de véritables fuites de mémoire avec valgrind ou tout autre outil.

C'était un bon compromis pour nos tests tout en n'affectant pas les performances normales.

Bill Door
la source
1
+1 Pragmatisme FTW. Il y a de la valeur dans la mesure, mais il y a aussi de la valeur dans un arrêt rapide.
Ross Patterson
2
Au lieu d'un commutateur de ligne de commande, vous pouvez également envisager d'implémenter la suppression des objets permanents à l'intérieur d'un bloc #ifdef DEBUG.
Jules
3

La clé ici est la suivante:

Bien que je puisse voir cela, il est également vrai que notre projet nécessite un débogage précis de la mémoire, car j'ai déjà trouvé une corruption de mémoire, des doubles libérations et des variables non initialisées.

Cela implique à peu près directement que votre base de code est composée de rien de plus que de l'espoir et de la chaîne. Les programmeurs C ++ compétents ne disposent pas de double libération.

Vous poursuivez absolument un effort inutile - comme dans, vous abordez un minuscule symptôme du problème réel, à savoir que votre code est à peu près aussi fiable que le module de service Apollo 13.

Si vous programmez correctement votre serveur avec RAII, ces problèmes ne se produiront pas et le problème dans votre question sera éliminé. De plus, votre code peut en fait s'exécuter correctement de temps en temps. C'est donc clairement la meilleure option.

DeadMG
la source
Le problème réside certainement dans une plus grande image. Cependant, il est très rare que l'on puisse trouver des ressources et la permission de refactoriser / réécrire un projet pour mieux le façonner.
Cengiz Can
@CengizCan: Si vous voulez corriger des bugs, vous devez refactoriser. Voilà comment ça marche.
DeadMG
2

Une bonne approche serait de restreindre la discussion avec vos collègues au moyen d'une classification. Compte tenu d'une grande base de code, il n'y a certainement pas une seule raison mais plutôt plusieurs raisons (identifiables) pour les objets à longue durée de vie.

Exemples:

  • Objets de longue durée qui ne sont référencés par personne (fuites réelles). Il s'agit d'une erreur de logique de programmation. Corrigez ceux qui ont une priorité inférieure À MOINS QU'ils soient responsables de l'augmentation de l'empreinte mémoire au fil du temps (et de la détérioration de la qualité de vos applications). S'ils augmentent votre empreinte mémoire au fil du temps, corrigez-les avec une priorité plus élevée.

  • Objets à longue durée de vie, qui sont toujours référencés mais ne sont plus utilisés (en raison de la logique du programme), mais qui ne font pas augmenter votre empreinte mémoire. Examinez le code et essayez de trouver les autres bogues qui y mènent. Ajoutez des commentaires à la base de code s'il s'agit d'une optimisation intentionnelle (performances).

  • Objets de longue durée de vie "par conception". Modèle singleton par exemple. Il est en effet difficile de s'en débarrasser, surtout s'il s'agit d'une application multithread.

  • Objets recyclés. Les objets à longue durée de vie ne doivent pas toujours être mauvais. Ils peuvent également être bénéfiques. Au lieu d'avoir des allocations / désallocations de mémoire à haute fréquence, l'ajout d'objets actuellement inutilisés à un conteneur à partir duquel un tel bloc de mémoire est à nouveau nécessaire peut accélérer une application et éviter la fragmentation de tas. Ils devraient être faciles à libérer au moment de l'arrêt, peut-être dans une version spéciale "instrumentation / vérifié".

  • "objets partagés" - objets qui sont utilisés (référencés) par plusieurs autres objets et personne ne sait exactement quand il est enregistré pour les libérer. Pensez à les transformer en objets comptés par référence.

Une fois que vous avez classé les vraies raisons de ces objets non libérés, il est beaucoup plus facile d'entrer dans une discussion au cas par cas et de trouver une résolution.

BitTickler
la source
0

À mon humble avis, la durée de vie de ces objets ne devrait jamais simplement être faite et laissée à mourir lorsque le système s'arrête. Cela pue les variables globales, que nous connaissons tous comme mauvais mauvais mauvais mauvais mauvais. Surtout à l'ère des pointeurs intelligents, il n'y a pas d'autre raison que la paresse. Mais plus important encore, cela ajoute un niveau de dette technique à votre système que quelqu'un peut avoir à gérer un jour.

L'idée de «dette technique» est que lorsque vous prenez un raccourci comme celui-ci, lorsque quelqu'un veut changer le code à l'avenir (disons, je veux pouvoir faire passer le client en «mode hors ligne» ou «mode veille» ", ou je veux pouvoir changer de serveur sans redémarrer le processus), ils devront faire l'effort de faire ce que vous ne faites pas. Mais ils conserveront votre code, donc ils n'en sauront pas autant que vous, donc cela leur prendra beaucoup plus de temps (je ne parle pas 20% plus longtemps, je parle 20 fois plus longtemps!). Même si c'est vous, alors vous n'aurez pas travaillé sur ce code particulier pendant des semaines ou des mois, et il faudra beaucoup plus de temps pour dépoussiérer les toiles d'araignées afin de l'implémenter correctement.

Dans ce cas, il semble que vous ayez un couplage très étroit entre l'objet serveur et les objets "longue durée de vie" ... il y a des moments où un enregistrement pourrait (et devrait) survivre à la connexion au serveur. La maintenance de chaque modification d'un objet dans un serveur peut être extrêmement coûteuse, il est donc généralement préférable que les objets générés soient réellement synchronisés avec l'objet serveur, avec des appels de sauvegarde et de mise à jour qui changent réellement le serveur. Ceci est communément appelé le modèle d'enregistrement actif. Voir:

http://en.wikipedia.org/wiki/Active_record_pattern

En C ++, chaque enregistrement actif aurait un faible_ptr sur le serveur, ce qui pourrait lancer des choses intelligemment si la connexion au serveur s'éteint. Ces classes peuvent être remplies paresseusement ou par lots, selon vos besoins, mais la durée de vie de ces objets ne doit être que là où ils sont utilisés.

Voir également:

Est-ce une perte de temps de libérer des ressources avant de quitter un processus?

L'autre

IdeaHat
la source
This reeks of global variablesComment passer de "il y a des milliers d'objets à libérer" à "ils doivent être globaux"? C'est tout un saut de logique.
Doval
0

Si vous pouvez facilement identifier où les objets qui devraient survivre indéfiniment sont alloués, une fois la possibilité serait de les allouer en utilisant un mécanisme d'allocation alternatif afin qu'ils n'apparaissent pas dans un rapport de fuite valgrind ou semblent simplement être une allocation unique.

Dans le cas où vous n'êtes pas familier avec l'idée, voici un article sur la façon d'impuissante allocation de mémoire personnalisée en c ++ , bien que notez que votre solution pourrait bien être plus simple que les exemples de cet article car vous n'avez pas du tout besoin de gérer la suppression !

Jules
la source