Enquête sur les plantages après un fonctionnement de longue durée d'une caméra industrielle - La fuite de handles (partie 1)
· Mis à jour le: · Go Komura · Développement Windows, Investigation de bug, Caméra industrielle, Fuite de handles, Conception de journalisation
Lorsqu’une application Windows plante soudainement après un fonctionnement de longue durée, le premier réflexe est très souvent de soupçonner une fuite mémoire. Pourtant, dans les faits, il n’est pas rare que la véritable coupable soit une fuite de handles, qui ne se révèle qu’après plusieurs semaines, sous la forme d’une panne secondaire.
Cet article présente le cas d’une application Windows pilotant une caméra industrielle, dont le plantage soudain après environ un mois de fonctionnement continu a fait l’objet d’une enquête. Une fois l’isolement du problème mené à son terme, la cause s’est révélée être une fuite de handles survenant sur le chemin d’échec lié à la reconnexion de la caméra.
Dans cette première partie, nous exposons ce qu’est une fuite de handles, comment nous avons isolé cet incident, et quels journaux conserver pour éviter qu’il ne se reproduise. Dans la seconde partie, Quand une application de pilotage de caméra industrielle plante soudainement après un mois (partie 2) - Qu’est-ce qu’Application Verifier et comment construire un socle de tests des cas anormaux, nous abordons la construction d’un socle de tests des cas anormaux.
Les noms propres et certains champs de journal ont été masqués, mais la façon de penser elle-même est très largement partagée par l’ensemble des applications Windows de pilotage d’équipements.
Table des matières
- La conclusion d’abord (en une ligne)
- Qu’est-ce qu’une fuite de handles ?
- 2.1. Ce que l’on entend ici par « handle »
- 2.2. Pourquoi cela ne se révèle-t-il souvent qu’après un fonctionnement de longue durée ?
- 2.3. Différence avec une fuite mémoire
- Étude de cas : une application de pilotage de caméra industrielle qui plante soudainement après un mois
- 3.1. Les symptômes observés
- 3.2. Les indicateurs examinés en premier
- 3.3. La fuite qui était la cause profonde
- Comment nous avons isolé le problème
- 4.1. Compresser le temps au lieu d’attendre une reproduction à l’échelle du mois
- 4.2. Lire la pente du
Handle Count - 4.3. Examiner l’appariement entre
create/openetclose/dispose - 4.4. Pour une fuite de handles, chercher « où elle a fui », pas « où elle a planté »
- Les journaux nécessaires pour éviter que cela ne se reproduise
- 5.1. L’ensemble minimal à conserver en premier
- 5.2. Les journaux réellement renforcés
- 5.3. À quelle granularité collecter
- Guide de décision sommaire
- Résumé
- Références
1. La conclusion d’abord (en une ligne)
- Dans une application de pilotage qui ne plante qu’après un fonctionnement de longue durée, il faut impérativement surveiller le
Handle Count, pas seulementPrivate Bytes - Une fuite de handles a tendance à se cacher non pas dans le chemin normal, mais dans les chemins
timeout/reconnect/ échec intermédiaire /early return - La ligne où l’application plante réellement est souvent l’endroit où elle n’a plus pu créer un nouveau handle par la suite, et non l’endroit où la fuite a eu lieu
- Les journaux à mettre en place en premier sont : le contexte
operation/session, lehandle countdu process, l’appariementopen/closedes ressources, et les erreurs Win32 / HRESULT / SDK - Plutôt que d’attendre une reproduction à l’échelle du mois, il est plus rapide de rejouer des milliers de fois, dans une boucle courte, les chemins de connexion, déconnexion, reconnexion et échec
- Application Verifier, abordé dans la seconde partie, est assez efficace, mais la base consiste avant tout à pouvoir suivre, avec ses propres journaux, les ruptures de cycle de vie (lifetime)
En résumé, dans ce genre de dossier, ce qu’il faut faire en premier n’est pas de regarder le fait que « ça a planté après une longue période », mais de rendre observables la façon dont les ressources augmentent et les chemins d’échec empruntés.
Une fuite de handles porte le plus souvent déjà le visage d’une panne secondaire au moment où on la découvre. C’est pourquoi se contenter de regarder l’exception au moment du plantage conduit facilement à partir dans une direction complètement erronée.
2. Qu’est-ce qu’une fuite de handles ?
2.1. Ce que l’on entend ici par « handle »
Le handle dont il est question ici est l’identifiant par lequel un processus Windows référence une ressource du système d’exploitation. En font partie, par exemple, les éléments suivants.
| Catégorie | Exemples |
|---|---|
| Objets noyau | event, mutex, semaphore, thread, process, waitable timer |
| E/S | open sur un file, un pipe, un socket, un device |
| Fréquent en pilotage d’équipements | events internes du SDK de la caméra, objets d’attente liés à l’enregistrement de callbacks, handles liés au thread d’acquisition d’images |
Ce qui pose problème en particulier dans les applications de pilotage, c’est le schéma consistant à « oublier de fermer, sur un chemin d’échec intermédiaire, une ressource ouverte temporairement pour une opération donnée ».
Voici typiquement le déroulement.
- On crée un event à chaque reconnexion
- L’enregistrement du callback ou le démarrage de l’acquisition échoue en cours de route
- Le chemin de succès ferme la ressource, mais pas le chemin d’échec
- Les tests courts habituels n’empruntant que le chemin de succès, cela passe inaperçu
Ce type de fuite se glisse assez couramment, aussi bien en revue de code qu’en exploitation réelle.
2.2. Pourquoi cela ne se révèle-t-il souvent qu’après un fonctionnement de longue durée ?
Une fuite de handles ne casse pas forcément tout de façon spectaculaire en une seule fois. Ce qui est en réalité plus gênant, c’est une fuite à faible pente, où chaque échec ne fait perdre qu’un seul handle.
flowchart LR
A[Fonctionnement normal] --> B[timeout / reconnect occasionnel]
B --> C[Le chemin d'échec crée un Event Handle]
C --> D[CloseHandle n'est jamais appelé]
D --> E[Le Handle Count augmente légèrement]
E --> F[Se répète des centaines de fois]
F --> G[CreateEvent / l'open du SDK échoue]
G --> H[Crash / arrêt ailleurs]
Si une seule reconnexion ne fait perdre qu’un seul handle, rien ne se produit en quelques minutes. Mais dans une application de pilotage d’équipements qui tourne 24 h/24 et 7 j/7, des conditions limites comme le timeout, la réinitialisation ou la reprise après déconnexion se répètent sans cesse. Le résultat est cette présentation étrange d’un problème qui ne se révèle que plusieurs semaines plus tard.
Ce qui compte ici, c’est que la fuite de handles elle-même ne constitue pas forcément la ligne où l’application plante. Voici les modes de défaillance les plus fréquents.
- Une API censée créer un nouvel event, fichier ou thread échoue
- Le SDK ne parvient pas à créer en interne la ressource dont il a besoin, et ne renvoie qu’un code d’échec générique
- La gestion d’erreur après l’échec est trop légère, et l’application plante en manipulant un handle
nullou invalide - Les timeouts se multiplient, et le processus finit par être tué par un watchdog ou un contrôleur de niveau supérieur
Autrement dit, le point de plantage est « la dernière victime », pas forcément « le premier coupable ».
2.3. Différence avec une fuite mémoire
Face à un défaut qui survient après un fonctionnement de longue durée, le premier réflexe est de soupçonner une fuite mémoire. C’est naturel en soi, bien sûr, mais il est parfois plus rapide d’examiner une fuite de handles sous un autre angle.
| Aspect | Fuite mémoire | Fuite de handles |
|---|---|---|
| Indicateur à surveiller en premier | Private Bytes, Commit, Working Set |
Handle Count |
| Symptômes typiques | Pression mémoire, paging, ralentissement, OOM | Échecs de Create* / Open* / d’initialisation interne du SDK, pannes secondaires |
| Endroit où elle se cache facilement | Caches, références conservées, oublis de libération | Asymétrie entre create/open et close/dispose |
| Comment elle se manifeste | La mémoire augmente progressivement | Le handle count augmente progressivement sans redescendre |
C’est pourquoi, lors de l’isolement d’un problème de fonctionnement de longue durée, se contenter de « ne regarder que la mémoire » revient facilement à conduire avec un œil fermé.
Surveiller au minimum Handle Count et Thread Count ensemble permet d’y voir beaucoup plus clair.
3. Étude de cas : une application de pilotage de caméra industrielle qui plante soudainement après un mois
3.1. Les symptômes observés
L’incident était simple.
- Une application Windows pilotant une caméra industrielle tourne 24 h/24 et 7 j/7
- En temps normal, elle fonctionne sans problème
- Au bout d’environ un mois, l’application plante brusquement un jour donné
- Après un redémarrage, elle refonctionne normalement pendant un moment
La première difficulté est que « le temps avant le plantage est long ». Attendre un mois pour chaque tentative de reproduction est extrêmement pénible pour une investigation.
Ce qui rendait les choses encore plus délicates, c’est que l’endroit du plantage n’était pas exactement le même à chaque fois. Tantôt juste après le début d’une reconnexion, tantôt au démarrage de l’acquisition d’images, tantôt après l’échec d’un appel au SDK.
Avec cette présentation, on peut au départ suspecter n’importe lequel des éléments suivants.
- Une instabilité du côté du SDK de la caméra
- Une panne transitoire causée par la communication ou une déconnexion du périphérique
- Une fuite mémoire
- Une race condition autour des threads
- Un échec d’initialisation qui n’apparaît pas dans les journaux
Autrement dit, on se trouvait dans une situation où il y avait beaucoup trop de « suspects vaguement plausibles ».
3.2. Les indicateurs examinés en premier
La première chose qui a été faite fut donc d’examiner la façon dont l’ensemble des ressources du process augmentait. Dans ce cas précis, les tendances observées étaient à peu près les suivantes.
| Indicateur | Tendance observée | Lecture |
|---|---|---|
Handle Count |
Augmente peu à peu après un reconnect ou un timeout, et ne redescend pas | On suspecte une fuite de handles |
Private Bytes |
Il y a des variations, mais la pente d’augmentation monotone est faible | Le coupable n’est pas forcément le tas (heap) |
Thread Count |
Pratiquement stable | Une fuite de threads est peu probable |
| Point de plantage | Légèrement différent à chaque fois | Une panne secondaire est probable |
À ce stade, le champ d’investigation s’était considérablement resserré. Car il était plus naturel de voir la situation non pas comme « ça plante après un mois », mais comme « quelque chose fuit progressivement en cours de route, et cela finit par faire planter l’application après un mois ».
3.3. La fuite qui était la cause profonde
La cause finale était un oubli de fermeture d’un event handle créé sur le chemin d’échec d’initialisation lors de la reconnexion de la caméra.
En simplifiant, le déroulement se présente ainsi.
sequenceDiagram
participant App as Application de pilotage
participant OS as Windows
participant SDK as SDK caméra
App->>OS: CreateEvent
App->>SDK: Enregistrement du callback
SDK-->>App: Échec partiel / timeout
Note over App: Retour sur le chemin d'échec
Note over App: CloseHandle n'est jamais appelé
loop Reconnexions répétées
App->>OS: Le Handle Count augmente peu à peu
end
App->>OS: CreateEvent / Open suivant
OS-->>App: Échec
App-->>App: Plante en tant que panne secondaire
En guise d’illustration du code, voici à quoi ressemble la fuite.
handle = CreateEvent(...)
if (!RegisterCallback(handle))
{
return Error; // CloseHandle(handle) est manquant
}
if (!StartAcquisition())
{
return Error; // close manque ici aussi
}
...
CloseHandle(handle)
La raison pour laquelle cela passe facilement inaperçu avec des tests courts est également assez facile à comprendre.
- Un démarrage normal suivi d’un arrêt normal referme bien la ressource
- L’échec ne se produit que pendant une reconnexion
- Il n’existe aucun test qui emprunte massivement ce chemin d’échec
- En production, la fuite s’accumule peu à peu sur plusieurs semaines
Autrement dit, la structure était la suivante : « invisible si l’on ne regarde que le chemin normal, mais qui fuit couramment sur les chemins anormaux ».
L’orientation retenue pour la correction n’a rien de spectaculaire.
- Rapprocher les responsabilités de
create/openet declose/dispose - Déplacer la libération vers un
finally, un destructeur ou un objet de session, afin qu’elle se produise systématiquement même en cas d’échec intermédiaire - Clarifier la propriété (ownership) avant et après l’enregistrement du callback et le démarrage de l’acquisition
- Exprimer « qui ferme la ressource » à travers la responsabilité du code, et non par des commentaires
Il ne s’agit pas tant d’une technique particulière que d’un travail de rangement consistant à intégrer le cycle de vie des ressources directement dans le code.
4. Comment nous avons isolé le problème
4.1. Compresser le temps au lieu d’attendre une reproduction à l’échelle du mois
Dans ce genre d’investigation, attendre un mois à chaque tentative est une mauvaise approche. Ce qu’il faut faire, c’est emprunter le chemin suspect de nombreuses fois en peu de temps.
Dans ce cas précis, la reproduction a été compressée en faisant tourner une boucle de ce type.
flowchart LR
A[Démarrage] --> B[Ouverture de la caméra]
B --> C[Démarrage de l'acquisition]
C --> D[timeout / déconnexion simulés]
D --> E[Reconnexion]
E --> F[Reprise de l'acquisition]
F --> G{Répéter N fois ?}
G -- Oui --> D
G -- Non --> H[Vérifier les écarts à la fin]
Le point clé est de consacrer le temps aux opérations de cycle de vie aux frontières, plutôt qu’au temps normal où « les images arrivent ».
Concrètement, voici les scénarios qui se révèlent efficaces.
- Faire tourner massivement
open -> start -> stop -> close - Provoquer délibérément des timeouts pour déclencher des reconnexions
- Forcer un échec juste après l’enregistrement du callback
- Introduire des interruptions de déconnexion, des interruptions de reconnexion et des races de shutdown
Il n’est pas nécessaire de reproduire à la perfection un mois d’exploitation réelle. Au contraire, emprunter des milliers de fois la frontière de cycle de vie (lifetime edge) que l’on soupçonne rapproche bien davantage de la cause.
4.2. Lire la pente du Handle Count
Dans une investigation de fuite de handles, se contenter de regarder les valeurs absolues peut prêter à confusion. Ce qui compte, c’est de savoir si le compteur redescend bien après une opération censée le faire redescendre, et de combien il augmente pour combien d’opérations.
Pour l’approche, l’ordre suivant est généralement le plus lisible.
- Établir la baseline après le warm-up
- Enregistrer le
Handle Countaprès chaque reconnect / start-stop / close - Observer l’écart cycle par cycle
- Observer aussi la pente agrégée sur plusieurs cycles
Voici, par exemple, la façon de la lire.
leakSlope =
(currentHandleCount - baselineHandleCount)
/ reconnectCount
Savoir si une valeur absolue de 2000 est élevée ou faible varie selon l’application. En revanche, si l’on constate +1 par reconnexion, sans jamais redescendre, c’est très suspect.
L’astuce ici est de ne pas regarder Handle Count isolément, mais de consigner au moins les éléments suivants en parallèle.
Handle CountPrivate BytesThread CountReconnectCount- La phase actuelle
Cela permet de savoir assez rapidement si « c’est la mémoire qui augmente », si « ce sont les threads qui augmentent », ou si « les ressources ne redescendent pas à chaque reconnexion ».
4.3. Examiner l’appariement entre create/open et close/dispose
Même une fois établi que le Handle Count global du process est suspect, cela seul ne permet pas de remonter jusqu’à l’endroit de la fuite.
Ce dont on a besoin ensuite, ce sont des journaux qui montrent le cycle de vie des ressources par paires.
À titre d’illustration, voici le type de journal structuré concerné.
CameraSession session=421 cameraId=CAM01 phase=ReconnectStart reason=FrameTimeout handleCount=1824 privateBytesMB=418
CameraResource session=421 resourceId=evt-884 kind=Event name=FrameReady action=Create osHandle=0x00000ABC handleCount=1825
CameraResource session=421 resourceId=evt-884 kind=Event name=FrameReady action=Close osHandle=0x00000ABC handleCount=1824
Ce qui compte ici, c’est de ne pas se fier uniquement à osHandle.
Les valeurs de handle Windows pouvant être réutilisées ultérieurement, il est plus facile de suivre la trace en faisant porter au moins les éléments suivants dans les journaux.
sessionIdresourceIdkindaction(Create/Open/Register/Close/Dispose/Unregister)osHandlephase
En procédant ainsi, il devient plus facile de repérer un flux « à un seul poumon » où l’on a un Create sans Close correspondant.
4.4. Pour une fuite de handles, chercher « où elle a fui », pas « où elle a planté »
Ce point est particulièrement important.
Une fuite de handles se présente souvent sous cette forme.
- La ligne du plantage : échec de
CreateEvent - La véritable fuite :
CloseHandlemanquait sur le chemin d’échec depuis plusieurs jours déjà
Autrement dit, l’API qui a fini par échouer en dernier est la sortie du dommage, pas forcément l’entrée de la cause.
L’ordre d’investigation à privilégier est donc le suivant.
- Regarder quelle ressource continue d’augmenter
- Regarder à quelle frontière d’opération elle ne redescend pas
- Rechercher l’endroit où l’appariement entre
create/openetclose/disposeest rompu - Lire le point de plantage en dernier
Suivre cet ordre permet de bien moins se perdre en cours de route.
5. Les journaux nécessaires pour éviter que cela ne se reproduise
5.1. L’ensemble minimal à conserver en premier
Ce qui a été efficace dans cette investigation n’était pas simplement d’augmenter le volume des journaux. C’était d’ajouter méthodiquement des « informations permettant de remonter plus tard jusqu’à la cause ».
Au minimum, voici ce qu’il est souhaitable de conserver.
| Catégorie | Champs minimaux souhaités | Raison |
|---|---|---|
| Contexte de l’opération | cameraId, sessionId, operationId, reconnectCount, phase |
Pour relier l’événement à quelle opération, et à quelle occurrence |
| Ressources du process | handleCount, privateBytes, workingSet, threadCount |
Pour isoler en premier lieu ce qui augmente |
| Cycle de vie des ressources | action, resourceId, kind, osHandle, owner |
Pour suivre les paires create/open et close/dispose |
| Résultat des appels externes | win32Error, HRESULT, sdkError, timeoutMs |
Pour comparer plus tard les types d’échec |
| Transitions d’état | OpenStart, OpenDone, ReconnectStart, ReconnectDone, ShutdownStart, etc. |
Pour savoir au milieu de quelle phase les choses se sont dégradées |
| Environnement d’exécution | pid, tid, buildVersion, machineName |
Pour faire correspondre dumps, symboles et livrables |
Nous ne prétendons pas que cela suffise. Mais sans au moins cela, on se retrouve facilement avec des journaux qui ne conservent rien de plus que le fait que « ça a planté ».
5.2. Les journaux réellement renforcés
Dans ce cas, les journaux ont été renforcés dans les directions suivantes.
- Heartbeat périodique
- Émission de
Handle Count/Private Bytes/Thread Count/ReconnectCounttoutes les 1 à 5 minutes
- Émission de
- Journaux de frontière par session de caméra
OpenStartCallbackRegisteredAcquisitionStartTimeoutDetectedReconnectStartReconnectDoneCloseStartCloseDone
- Journaux de cycle de vie des ressources
Create/Open/RegisteretClose/Dispose/Unregisterpour les events, threads, files, timers et tokens d’enregistrement du SDK
- Normalisation des erreurs
- Ne pas s’arrêter au seul message d’exception ; émettre en même temps
win32Error,HRESULT,sdkErroretphase
- Ne pas s’arrêter au seul message d’exception ; émettre en même temps
Ce qui importe, c’est de ne pas changer la forme des journaux entre le succès et l’échec. Si le cas anormal seul adopte un format différent, l’agrégation devient difficile par la suite.
5.3. À quelle granularité collecter
Une erreur fréquente ici consiste à se dire « pour l’instant, je log tout en INFO ». Mais en procédant ainsi, on se retrouve face à un mur de journaux au moment de les relire. C’est assez pénible.
En termes de granularité, la répartition suivante est globalement réaliste.
- Surveillance périodique
Handle Count,Private Bytes,Thread Count,ReconnectCount
- Frontières d’opération
- start / done / fail de la session
- Frontières de ressources
create/open/registeretclose/dispose/unregister
- Détails en cas d’anomalie
- code d’erreur, stack, déclencheur de capture de dump
Un journal détaillé pour chaque frame n’est généralement pas nécessaire. Ce qui est en réalité efficace face à un défaut de longue durée, ce sont des journaux qui permettent de lire « quelle responsabilité a ouvert la ressource, et quelle responsabilité l’a fermée ».
6. Guide de décision sommaire
- Plante seulement après quelques jours à quelques semaines
- Mettre en place en premier un heartbeat pour
Handle Count/Private Bytes/Thread Count
- Mettre en place en premier un heartbeat pour
- Il y a des retry / reconnect / shutdown
- Construire d’abord un harnais de test qui fait tourner massivement uniquement ces frontières
- Usage important de SDK natifs / P/Invoke / Win32
- Il vaut vraiment la peine d’appliquer Application Verifier, abordé dans la seconde partie
- Une interface graphique cohabite aussi
- En plus de
Handle Count, il est préférable de surveiller aussiGDI Objects/USER Objects
- En plus de
- L’exception au moment du plantage seule ne dit rien
- Il est plus rapide de mettre d’abord en ordre des journaux structurés de cycle de vie pour operation / session / resource
Ce dernier point est particulièrement important. Dans l’investigation de bug, ce n’est souvent pas la technique d’analyse elle-même qui décide de l’issue, mais le fait d’avoir mis les choses sous une forme observable.
7. Résumé
Pour une application qui ne plante qu’après un fonctionnement de longue durée, il faut surveiller non seulement la mémoire, mais aussi le Handle Count. Une fuite de handles a tendance à se cacher non pas dans le chemin normal, mais dans le chemin d’échec des cas anormaux, et le point de plantage est le plus souvent la sortie d’une panne secondaire, et non l’endroit où la fuite a eu lieu. En matière de lecture des symptômes, tout se ramène finalement à ces trois points.
Pour la prévention, il faut rapprocher les responsabilités de create/open et de close/dispose, conserver des journaux porteurs de contexte par session / opération, et enregistrer à la fois les ressources du process et le cycle de vie des ressources. Côté tests, plutôt que d’attendre une reproduction à l’échelle du mois, faire tourner timeout / reconnect / shutdown dans des boucles courtes, et retenir comme critère d’acceptation non seulement « ça ne casse pas », mais aussi « c’est traçable quand ça casse ». Ce qui a fonctionné dans ce cas, c’est cette combinaison. Dans la seconde partie, nous utilisons Application Verifier pour faire remonter par anticipation des modes de défaillance difficiles à déclencher, comme l’épuisement de la mémoire ou les anomalies de handles.
Dans une application de pilotage, il est important que le chemin normal fonctionne, mais pouvoir dire « ce qui s’est passé » quand elle casse compte énormément en exploitation de long terme.
Une fuite de handles est exactement le type de défaut où cette différence se paie. Plutôt que de ne regarder qu’au moment où elle se produit, l’examiner à travers la façon dont elle augmente, les frontières et les paires de responsabilités la rend bien plus facile à suivre.
8. Références
- Fonction GetProcessHandleCount (processthreadsapi.h)
- Propriété Process.HandleCount (System.Diagnostics)
- Seconde partie : Quand une application de pilotage de caméra industrielle plante soudainement après un mois (partie 2) - Qu’est-ce qu’Application Verifier et comment construire un socle de tests des cas anormaux
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Construire un socle de test des cas anormaux Windows avec Application Verifier
Ce qu'est Application Verifier, expliqué avec la construction d'un socle de test des cas anormaux Windows à l'aide de Handles, Heaps, Low...
Pourquoi les retransmissions TCP bloquent la communication d'une caméra industrielle, et comment isoler la cause
Comment isoler la cause quand la communication d'une caméra industrielle se bloque pendant plusieurs secondes à cause de retransmissions ...
La gestion d'incident ne s'arrête pas au rétablissement — Un modèle de postmortem (prévention de la récurrence) pour les petites équipes de développement
Traiter un incident comme terminé une fois corrigé et les excuses présentées garantit que vous le reproduirez. Cet article traduit le pos...
Concevoir la conservation des journaux et des dumps lors du crash d'une application Windows
Comment combiner journalisation habituelle, marqueur de crash final, WER LocalDumps et processus de surveillance pour conserver, même qua...
Introduction à la collecte des dumps de crash Windows - WER/ProcDump/WinDbg
Pour traquer les crashs d'applications Windows difficiles à reproduire, nous passons en revue l'utilisation de WER LocalDumps, ProcDump, ...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Analyse de bugs et incidents de longue durée
Pannes intermittentes, diagnostic des communications, crashs après longue exécution et tests des chemins d'échec.
Études de cas associées
Ces études de cas présentent une démarche proche d’analyse, de priorisation ou de refonte.
Comment nous avons relié un plantage après une longue exécution à une fuite de handles
Étude de cas sur la transformation d’un plantage mensuel en enquête sur une fuite de handles grâce à de meilleurs points d’observation et à une journalisation adaptée.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Analyse des bugs et des causes
Isoler une panne qui ne survient qu'après un fonctionnement de longue durée est un sujet qui correspond particulièrement bien à notre service d'investigation de bug et d'analyse de cause racine.
Développement d'applications Windows
Si vous souhaitez revoir la conception de votre application Windows, y compris la conception de la journalisation et l'observabilité en exploitation, cela rejoint aussi notre accompagnement en développement d'applications Windows.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- Qu'est-ce qu'une fuite de handles ?
- C'est le fait qu'un processus Windows oublie de fermer un handle référençant une ressource du système d'exploitation - event, mutex, file, socket, etc. -, ce qui fait augmenter continuellement le Handle Count. Le schéma le plus fréquent consiste à ouvrir temporairement une ressource pour une opération donnée, puis à oublier de la fermer sur un chemin d'échec intermédiaire tel qu'un timeout, une reconnexion ou un early return ; comme les tests courts habituels n'empruntent presque toujours que le chemin de succès, ce genre de fuite passe facilement inaperçu.
- Comment distinguer une fuite mémoire d'une fuite de handles ?
- Les indicateurs à surveiller ne sont pas les mêmes. Pour une fuite mémoire, ce sont Private Bytes et Commit qui augmentent progressivement, tandis que pour une fuite de handles, c'est le Handle Count qui augmente progressivement sans jamais redescendre. Lors de l'isolement d'un problème de fonctionnement de longue durée, se contenter de surveiller la mémoire revient à conduire avec un œil fermé : il est essentiel de suivre aussi le Handle Count et le Thread Count en parallèle. Si une interface graphique cohabite dans le même processus, il faut également surveiller les GDI Objects et les USER Objects.
- Pourquoi une fuite de handles ne fait-elle planter l'application qu'après un fonctionnement de longue durée ?
- Parce qu'une fuite à faible pente - un seul handle perdu par échec occasionnel - ne provoque rien de visible en quelques minutes ; mais dans une application fonctionnant 24 h/24 et 7 j/7, des conditions limites comme les timeouts ou les reconnexions se répètent sans cesse et la fuite s'accumule sur plusieurs semaines. Elle finit par se révéler sous forme de panne secondaire, au moment où une API censée créer un nouvel event, un fichier ou un thread échoue à son tour. Il est important de retenir que le point de plantage est le plus souvent la dernière victime, et non l'endroit où la fuite a eu lieu.
- Comment mener l'investigation d'une fuite de handles ?
- Sans attendre une reproduction qui prend un mois, compressez le temps en rejouant des milliers de fois, dans une boucle courte, les frontières suspectes du cycle de vie des ressources - open -> start -> stop -> close, timeout, reconnexion, etc. Établissez une baseline après la période de warm-up, observez l'écart et la pente du Handle Count après chaque cycle, recherchez à l'aide de journaux structurés portant sessionId, resourceId et action les endroits où l'appariement entre create/open et close/dispose est rompu, puis lisez le point de plantage en dernier : c'est l'ordre qui permet le moins de se perdre.
Profil de l’auteur
Page de présentation de l’auteur de l’article.
Go Komura
Représentant de KomuraSoft LLC
Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.
Liens publics