Indagine sugli arresti anomali a lungo termine di un'app per fotocamere industriali: la perdita dell'handle (parte 1)
· Aggiornato il: · Go Komura · Sviluppo Windows, Indagine bug, Telecamera industriale, Handle leak, Progettazione log
Quando un’app Windows si blocca improvvisamente dopo essere stata utilizzata per molto tempo, il primo istinto è molto spesso quello di sospettare una perdita di memoria. In realtà, tuttavia, non è raro che una perdita di handle sia il principale colpevole, per poi emergere settimane dopo come un fallimento secondario.
Questo articolo presenta un caso in cui abbiamo esaminato un’app Windows che controllava una telecamera industriale che si è bloccata improvvisamente dopo circa un mese di funzionamento continuo. Man mano che abbiamo ristretto il campo, la causa si è rivelata essere una perdita di handle che si verificava nel percorso di errore attorno alla riconnessione della fotocamera.
In questa prima parte, spiegheremo cos’è una perdita di handle, come abbiamo isolato questo incidente e quali registri dovresti conservare per evitare che si ripeta. Nella seconda parte, Quando un’app di controllo della telecamera industriale si blocca improvvisamente dopo un mese (Parte 2) - Cos’è Application Verifier e come costruire una base di test del percorso di fallimento, discutiamo della creazione di una base per il test del percorso di fallimento.
I nomi propri e alcuni campi di registro sono stati oscurati, ma il modo di pensare è ampiamente condiviso tra le app di controllo delle apparecchiature Windows in generale.
Sommario
- Prima la conclusione (in una riga)
- Cos’è una perdita di handle?
- 2.1. Cosa significa “handle” qui
- 2.2. Perché tende ad affiorare solo dopo un funzionamento prolungato
- 2.3. Differenza da una perdita di memoria
- Caso di studio: un’app di controllo della telecamera industriale che si blocca improvvisamente dopo un mese
- 3.1. I sintomi
- 3.2. I parametri che abbiamo esaminato per primi
- 3.3. La perdita che era la causa principale
- Come lo abbiamo isolato
- 4.1. Comprimere il tempo invece di attendere una riproduzione su scala mensile
- 4.2. Leggi la pendenza di
Handle Count - 4.3. Controlla l’abbinamento di
create / openeclose / dispose - 4.4. Per le perdite di handle, trova dove si è verificata la perdita, non dove si è bloccato
- I registri necessari per prevenire il ripetersi
- 5.1. Il set minimo da mantenere per primo
- 5.2. I tronchi che abbiamo effettivamente rafforzato
- 5.3. Con quale granularità raccogliere
- Una guida decisionale approssimativa
- Riepilogo
- Riferimenti
1. Prima la conclusione (in una riga)
- In un’app di controllo che si arresta in modo anomalo solo dopo un funzionamento prolungato, guardare sempre
Handle Count, non soloPrivate Bytes - Le perdite di handle tendono a nascondersi non nel percorso normale ma nei percorsi
timeout/reconnect/ guasto parziale / ritorno anticipato - La linea che effettivamente si blocca è spesso il punto in cui non è più stato possibile creare un nuovo handle in seguito, non il punto in cui è trapelato
- I log di cui hai bisogno innanzitutto sono: il contesto
operation / session, ilhandle countdel processo, l’abbinamento delle risorseopen / closee gli errori Win32 / HRESULT / SDK - Piuttosto che attendere una riproduzione su scala mensile, è più veloce eseguire i percorsi di connessione-disconnessione-riconnessione-errore migliaia di volte in un breve ciclo
- Application Verifier, trattato nella Parte 2, è piuttosto efficace, ma la base è essere in grado di tracciare prima i guasti complessivi con i propri registri
Insomma, la prima cosa da fare in un caso come questo è non per fissare il fatto che “è crollato dopo un lungo periodo”, ma per dare una forma osservabile alla crescita delle risorse e ai percorsi di fallimento.
Quando viene rilevata una perdita di handle, di solito questa presenta già l’aspetto di un guasto secondario. Quindi, se guardi solo all’eccezione al momento dell’incidente, tendi ad andare nella direzione sbagliata.
2. Cos’è una perdita di handle?
2.1. Cosa significa “handle” qui
Un handle qui è l’identificatore attraverso il quale un processo Windows fa riferimento alle risorse OS. Esempi di ciò che rientra in questo includono:
| Categoria | Esempi |
|---|---|
| Oggetti del kernel | evento, mutex, semaforo, thread, processo, timer di attesa |
| I / O | aperture di file, tubi, prese, dispositivi |
| Comune nel controllo delle apparecchiature | eventi interni della telecamera SDK, oggetti di attesa legati alle registrazioni di callback, handle relative al thread di acquisizione |
Ciò che tende a diventare un problema in particolare nelle app di controllo è il modello di “dimenticarsi di chiudere una risorsa che è stata aperta temporaneamente per qualche operazione, su un percorso di errore parziale”.
Il flusso tipico è simile a questo.
- Crea un evento ad ogni riconnessione
- La registrazione della richiamata o l’avvio dell’acquisizione non riesce a metà
- Il percorso del successo lo chiude, ma il percorso del fallimento no
- I brevi test di routine esercitano solo il percorso del successo, quindi passano inosservati
Questo tipo si presenta abbastanza regolarmente, sia nella revisione del codice che nella produzione.
2.2. Perché tende ad affiorare solo dopo un funzionamento prolungato
Una perdita di handle non necessariamente rompe le cose in modo spettacolare in un colpo solo. Ciò che in realtà è più grave è una perdita su una piccola pendenza, in cui un guasto fa perdere solo un handle.
flowchart LR
A[Normal operation] --> B[Occasional timeout / reconnect]
B --> C[Failure path creates an event handle]
C --> D[CloseHandle is never called]
D --> E[Handle Count creeps up slightly]
E --> F[Repeats hundreds of times]
F --> G[CreateEvent / SDK open fails]
G --> H[Crash / stall somewhere else]
Se durante la riconnessione si perde anche solo un handle, nel giro di pochi minuti non succede nulla. Ma in un’app di controllo delle apparecchiature in esecuzione 24 ore su 24, 7 giorni su 7, condizioni limite come timeout, reinizializzazioni e ripristino della disconnessione si verificano ripetutamente. Il risultato è la strana presentazione di un problema che emerge solo settimane dopo.
Ciò che conta qui è che la perdita di handle in sé non è necessariamente la linea che si schianta. Le modalità comuni di rottura sono queste.
- Un’API che crea un nuovo evento / file / thread non funziona
- SDK non può creare una risorsa di cui ha bisogno internamente e restituisce solo un codice di errore generico
- La gestione degli errori dopo l’errore è ridotta e l’app dereferenzia un
null/ handle non valido e si blocca - I timeout aumentano e di conseguenza un watchdog o un controller upstream interrompe il processo
In altre parole, il luogo dell’incidente è “l’ultima vittima”, non necessariamente il “colpevole originale”.
2.3. Differenza da una perdita di memoria
Per i difetti dopo un funzionamento prolungato, il primo sospetto è una perdita di memoria. Questo istinto è naturale, ovviamente, ma le perdite di handle a volte sono più veloci da trovare se osservate lungo un asse diverso.
| Aspetto | Perdita di memoria | Perdita di handle |
|---|---|---|
| Metriche da controllare prima | Private Bytes, Commit, Working Set |
Handle Count |
| Sintomi tipici | Pressione della memoria, paginazione, rallentamenti, OOM | Create* / Open* / SDK errori inizializzazione interna, errori secondari |
| Dove tende a nascondersi | Cache, riferimenti conservati, contenuti gratuiti dimenticati | Asimmetria tra create / open e close / dispose |
| Come si presenta | La memoria si insinua | Il conteggio degli handle aumenta e non torna mai indietro |
Pertanto, quando si isolano i problemi a lungo termine, guardare solo alla memoria è come guidare con un occhio chiuso.
Come minimo, guardare Handle Count e Thread Count insieme rende le cose molto più facili da risolvere.
3. Case Study: un’app di controllo della telecamera industriale che si blocca improvvisamente dopo un mese
3.1. I sintomi
L’incidente è stato semplice.
- Un’app Windows che controlla una telecamera industriale funziona 24 ore su 24, 7 giorni su 7
- Funziona bene normalmente
- Dopo circa un mese, un giorno l’app si blocca improvvisamente
- Dopo un riavvio, funziona di nuovo bene per un po’
La prima difficoltà è che ci vuole molto tempo per bloccarsi. Aspettare un mese per ogni tentativo di riproduzione è brutale come indagine.
Ciò che rendeva il tutto ancora più sgradevole era il fatto che il luogo dell’incidente non era esattamente lo stesso ogni volta. A volte è successo subito dopo l’inizio di una riconnessione, a volte all’inizio dell’acquisizione, a volte dopo una chiamata SDK fallita.
Con quella presentazione, inizialmente puoi sospettare quanto segue.
- Instabilità sul lato della fotocamera SDK
- Guasti temporanei causati dalla comunicazione o dalla disconnessione del dispositivo
- Una perdita di memoria
- Una corsa attorno al threading
- Un errore di inizializzazione non visualizzato nei registri
In altre parole, eravamo in una situazione con troppi candidati “vagamente sospetti”.
3.2. I parametri che abbiamo esaminato per primi
Quindi la prima cosa che abbiamo fatto è stata osservare come stavano crescendo le risorse del processo nel suo insieme. In questo caso gli andamenti osservati sono stati più o meno i seguenti.
| Metrico | Tendenza osservata | Lettura |
|---|---|---|
Handle Count |
Si insinua dopo riconnessioni e timeout, non torna più giù | Sospetta una perdita di handle |
Private Bytes |
Fluttua, ma la pendenza di aumento monotona è debole | Il principale colpevole non è necessariamente heap |
Thread Count |
Essenzialmente piatto | È improbabile una perdita di thread |
| Luogo dell’incidente | Ogni volta leggermente diverso | È probabile un fallimento secondario |
A questo punto, la nostra attenzione si era notevolmente ristretta. È stato più naturale leggere la situazione non come “si blocca dopo un mese”, ma “qualcosa perde un po’ alla volta lungo il percorso, e di conseguenza si blocca dopo un mese.”
3.3. La perdita che era la causa principale
La causa ultima era una mancata chiusura di un handle di evento creato sul percorso di errore di inizializzazione durante la riconnessione della telecamera.
Semplificato, il flusso appare così.
sequenceDiagram
participant App as Control app
participant OS as Windows
participant SDK as Camera SDK
App->>OS: CreateEvent
App->>SDK: Register callback
SDK-->>App: Partial failure / timeout
Note over App: Returns on the failure path
Note over App: CloseHandle is never called
loop Repeated reconnects
App->>OS: Handle Count creeps up
end
App->>OS: Next CreateEvent / Open
OS-->>App: Failure
App-->>App: Crashes as a secondary failure
Come schizzo del codice, la perdita appare così.
handle = CreateEvent(...)
if (!RegisterCallback(handle))
{
return Error; // CloseHandle(handle) is missing
}
if (!StartAcquisition())
{
return Error; // close is missing here too
}
...
CloseHandle(handle)
Anche il motivo per cui questo supera i test brevi è abbastanza facile da capire.
- Un avvio normale -> un arresto normale lo chiude
- Gli errori si verificano solo parzialmente durante la riconnessione
- Non esiste alcun test che permetta di superare questo percorso di fallimento
- In produzione si accumula poco alla volta nel corso delle settimane
In altre parole, la struttura era: “invisibile se si osserva solo il percorso normale, ma perde regolarmente sui percorsi guasti.”
La correzione non è appariscente.
- Avvicinare le responsabilità di
create / openeclose / dispose - Spostare il rilascio in
finally/ distruttori / un oggetto di sessione in modo che avvenga sempre anche in caso di fallimento parziale - Rendere esplicita la proprietà riguardo alla registrazione della richiamata e all’inizio dell’acquisizione
- Esprimere “chi chiude” attraverso le responsabilità del codice, non i commenti
Solo con parole è difficile da capire, quindi ecco anche una riscrittura concreta dello stesso processing.
In C++, prepara un piccolo tipo RAII che detiene l’handle e non lasci HANDLE grezzi in giro per le funzioni.
// C++17 / Windows
#include <windows.h>
#include <utility>
class UniqueHandle
{
public:
UniqueHandle() noexcept = default;
explicit UniqueHandle(HANDLE h) noexcept : h_(h) {}
UniqueHandle(const UniqueHandle&) = delete;
UniqueHandle& operator=(const UniqueHandle&) = delete;
UniqueHandle(UniqueHandle&& other) noexcept
: h_(std::exchange(other.h_, nullptr)) {}
UniqueHandle& operator=(UniqueHandle&& other) noexcept
{
if (this != &other)
{
reset(std::exchange(other.h_, nullptr));
}
return *this;
}
~UniqueHandle() { reset(); }
HANDLE get() const noexcept { return h_; }
explicit operator bool() const noexcept { return h_ != nullptr; }
void reset(HANDLE h = nullptr) noexcept
{
if (h_ != nullptr)
{
::CloseHandle(h_);
}
h_ = h;
}
private:
HANDLE h_ = nullptr;
};
Usandolo, non serve più aggiungere CloseHandle manualmente sul percorso di errore.
// Membro di CameraSession: UniqueHandle frameReady_;
bool CameraSession::Reconnect()
{
UniqueHandle frameReady{ ::CreateEventW(nullptr, TRUE, FALSE, nullptr) };
if (!frameReady)
{
return false; // La creazione stessa è fallita. Non c'è nulla da chiudere
}
if (!RegisterCallback(frameReady.get()))
{
return false; // Anche qui il return esce, ma il distruttore chiude
}
if (!StartAcquisition())
{
// Se fallisce dopo la registrazione, togli la registrazione prima di chiudere.
// Senza questo, il distruttore chiude l'handle ma l'SDK lo conserva ancora.
// Alla prossima frame segnala un numero di handle già liberato;
// se quello stesso numero è stato riutilizzato per un'altra risorsa, si alza un evento che non c'entra.
UnregisterCallback();
return false;
}
// Solo in caso di successo, trasferisci la proprietà al session
frameReady_ = std::move(frameReady);
return true;
}
In C#, molto spesso non si può risolvere con un semplice using, quindi usiamo una proprietà flag: se non è stata passata, la buttiamo via in finally. Scrivere semplicemente using var distruggerebbe anche il caso di successo.
// C# / .NET 8
// Campo di CameraSession: private ManualResetEvent? _frameReady;
public bool Reconnect()
{
var frameReady = new ManualResetEvent(false);
var handedOver = false;
var registered = false;
try
{
if (!RegisterCallback(frameReady))
{
return false;
}
registered = true;
if (!StartAcquisition())
{
return false;
}
_frameReady?.Dispose();
_frameReady = frameReady;
handedOver = true;
return true;
}
finally
{
if (!handedOver)
{
// Prima di buttare, togli il riferimento che l'esterno conserva.
// L'SDK tiene l'handle passato alla registrazione, quindi l'ordine inverte
// causerebbe l'accesso a un handle già liberato.
if (registered)
{
UnregisterCallback();
}
frameReady.Dispose();
}
}
}
Entrambi fanno la stessa cosa: qualunque sia il punto in cui si esce, risorse senza proprietario dichiarata vengono sempre rilasciate. Invece di far dipendere il “chiudere in caso di errore” da un commento umano ogni volta, lo si delega al tipo e a finally.
Questa non è tanto una tecnica speciale quanto una gestione che incorpora la durata delle risorse nel codice.
4. Come lo abbiamo isolato
4.1. Comprimere il tempo invece di attendere una riproduzione su scala mensile
In questo tipo di indagine, aspettare un mese per ogni tentativo è un cattivo approccio. Quello che dovresti fare è perseguire i percorsi sospetti più e più volte in breve tempo.
In questo caso, abbiamo compresso la riproduzione eseguendo un ciclo come questo.
flowchart LR
A[Start] --> B[Open camera]
B --> C[Start acquisition]
C --> D[Simulated timeout / disconnect]
D --> E[Reconnect]
E --> F[Resume acquisition]
F --> G{Repeat N times}
G -- Yes --> D
G -- No --> H[Check the deltas at the end]
Il punto è dedicare il proprio tempo alle operazioni a vita ai confini, non ai periodi di routine “i frame stanno arrivando”.
Gli scenari concretamente efficaci assomigliano a questi.
- Eseguire
open -> start -> stop -> closein grandi volumi - Attivare deliberatamente i timeout e scorrere le riconnessioni
- Forza un errore subito dopo la registrazione della richiamata
- Iniettare interruzioni di disconnessione, riconnessione e arresto delle gare
Non è necessario riprodurre perfettamente un mese di funzionamento reale. Al contrario, calpestare il presunto limite della vita migliaia di volte ti avvicina molto più alla causa.
4.2. Leggi la pendenza di Handle Count
In un’indagine sulle perdite di handle, guardare solo i valori assoluti può creare confusione. Ciò che conta è se il conteggio torna indietro dopo le operazioni che dovrebbero restituirlo e quanti handle si ottengono per quante operazioni.
Approssimativamente il seguente ordine funziona bene.
- Stabilire una linea di base dopo il riscaldamento
- Registra
Handle Countdopo ogni riconnessione / avvio-arresto / chiusura - Guarda il delta per ciclo
- Osserva anche la pendenza aggregata su diversi cicli
Ad esempio, una vista come questa.
leakSlope =
(currentHandleCount - baselineHandleCount)
/ reconnectCount
Il valore assoluto di 2000 sia alto o basso varia in base all’app. Ma se è +1 per riconnessione e non ritorna mai, è abbastanza sospetto.
Il trucco qui è non guardare Handle Count da solo, ma registrare almeno quanto segue insieme ad esso.
Handle CountPrivate BytesThread CountReconnectCount- In quale fase ti trovi attualmente
Con questo, puoi dire abbastanza rapidamente se “la memoria sta crescendo”, “i thread stanno crescendo” o “le risorse non ritornano ad ogni riconnessione”.
4.3. Controlla l’abbinamento di create / open e close / dispose
Anche una volta che sai che il Handle Count a livello di processo è sospetto, questo da solo non ti porta al luogo della perdita.
Ciò di cui hai bisogno dopo sono registri che mostrino i cicli di vita delle risorse come coppie.
Come immagine, log strutturati come questi.
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
Ciò che conta qui è non fare affidamento solo su osHandle.
I valori dell’handle Windows possono essere riutilizzati in seguito, quindi nei log è più semplice rintracciarli se si riporta almeno quanto segue.
sessionIdresourceIdkindaction(Create / Open / Register / Close / Dispose / Unregister)osHandlephase
Con questo in atto, diventa molto più facile individuare il flusso unilungato in cui esiste una Creazione ma non una Chiusura.
4.4. Per le perdite di handle, trova dove si è verificata la perdita, non dove si è bloccato
Questo punto è piuttosto importante.
Una perdita di handle spesso si presenta in questo modo.
- La linea che si blocca:
CreateEventfallisce - La vera perdita di handle:
CloseHandleera scomparso da un percorso di fallimento da giorni prima
In altre parole, l’API che alla fine è crollata rappresenta l’uscita del danno, non necessariamente l’ingresso della causa.
Quindi l’ordine di indagine dovrebbe essere:
- Guarda quale risorsa continua a crescere
- Guarda a quale confine dell’operazione non riesce a tornare
- Trova dove l’accoppiamento di
create / openeclose / disposeè interrotto - Leggi per ultimo il luogo dell’incidente
In questo ordine, hai molte meno probabilità di perderti.
5. I registri necessari per prevenire il ripetersi
5.1. Il set minimo da mantenere per primo
Ciò che ha funzionato in questa indagine non è stato semplicemente aumentare il volume dei registri. Si trattava di aggiungere metodicamente “informazioni che ti consentono di raggiungere la causa in un secondo momento.”
Come minimo, vuoi mantenere quanto segue.
| Categoria | Campi minimi richiesti | Motivo |
|---|---|---|
| Contesto operativo | cameraId, sessionId, operationId, reconnectCount, phase |
Per legare l’evento a quale operazione, su quale iterazione |
| Risorse di processo | handleCount, privateBytes, workingSet, threadCount |
Per prima cosa isolare ciò che sta crescendo |
| Ciclo di vita delle risorse | action, resourceId, kind, osHandle, owner |
Per tracciare le coppie di create / open e close / dispose |
| Risultati delle chiamate esterne | win32Error, HRESULT, sdkError, timeoutMs |
Per confrontare i tipi di errore in seguito |
| Transizioni di stato | OpenStart, OpenDone, ReconnectStart, ReconnectDone, ShutdownStart, ecc. |
Per sapere in quale fase le cose si sono guastate |
| Ambiente di esecuzione | pid, tid, buildVersion, machineName |
Correlare con dump / simboli / artefatti distribuiti |
Non stiamo sostenendo che ciò sia sufficiente. Ma senza almeno questo, ti ritroveresti facilmente con registri che non registrano altro che il fatto che “si è bloccato”.
5.2. I tronchi che abbiamo effettivamente rafforzato
In questo caso, abbiamo rafforzato i log nelle seguenti direzioni.
- Battito cardiaco periodico
- Emette
Handle Count/Private Bytes/Thread Count/ReconnectCountogni 1-5 minuti
- Emette
- Registri di confine per sessione della telecamera
OpenStartCallbackRegisteredAcquisitionStartTimeoutDetectedReconnectStartReconnectDoneCloseStartCloseDone
- Registri del ciclo di vita delle risorse
Create / Open / RegistereClose / Dispose / Unregisterper eventi / thread / file / timer / token di registrazione SDK
- Normalizzazione degli errori
- Non fermarsi al messaggio di eccezione; emettono
win32Error,HRESULT,sdkErrorephaseinsieme
- Non fermarsi al messaggio di eccezione; emettono
Ciò che è importante è non cambiare la forma dei registri tra successo e fallimento. Se i fallimenti assumono un formato diverso, l’aggregazione diventa successivamente dolorosa.
5.3. Con quale granularità raccogliere
Una trappola comune qui è “scarica tutto a INFO”. Ma se lo fai, ti ritroverai di fronte a un muro di registri quando li leggerai in seguito. Questo è abbastanza doloroso.
In termini di granularità, grosso modo la seguente suddivisione è realistica.
- Monitoraggio periodico
Handle Count,Private Bytes,Thread Count,ReconnectCount
- Confini dell’operazione
- Inizio / fine sessione / fallimento della sessione
- Confini delle risorse
create / open / registereclose / dispose / unregister
- Dettagli errore
- Codici di errore, stack, trigger di acquisizione di dump
La registrazione dettagliata per frame in genere non è necessaria. Per i difetti a lungo termine, i log che consentono di leggere “quale responsabilità lo ha aperto e quale responsabilità lo ha chiuso” sono molto più efficaci.
6. Una guida decisionale approssimativa
- Si blocca solo dopo giorni o settimane
- Per prima cosa aggiungi un battito cardiaco per
Handle Count/Private Bytes/Thread Count
- Per prima cosa aggiungi un battito cardiaco per
- Sono presenti nuovi tentativi / riconnessioni / chiusure
- Costruisci prima un’imbracatura che superi solo questi limiti di volume
- Utilizzo intenso di SDKs / P / Invoke / Win32 nativi
- Vale la pena applicare Application Verifier (Parte 2).
- Una GUI vive nello stesso processo
- Oltre a
Handle Count, guarda ancheGDI Objects/USER Objects
- Oltre a
- L’eccezione al momento dell’incidente non dice nulla
- È più veloce mettere prima in ordine i log strutturati di operazione / sessione / ciclo di vita della risorsa
Quest’ultimo elemento è piuttosto importante. Nelle indagini sui bug, ciò che decide il risultato spesso non è la tecnica di analisi in sé, ma se le cose sono in una forma osservabile.
7. Riepilogo
Per un’app che si blocca solo dopo un funzionamento prolungato, guarda Handle Count, non solo la memoria. Le perdite di handle tendono a nascondersi nei percorsi di guasto di flussi anomali piuttosto che nel percorso normale, e il luogo dell’incidente è solitamente l’uscita di un guasto secondario, non il luogo in cui si è verificata la perdita. Quando si tratta di leggere i sintomi, alla fine si riduce a questi tre punti.
A scopo preventivo, avvicinare le responsabilità di create / open e close / dispose, conservare i registri che contengono il contesto per sessione / operazione e registrare sia le risorse del processo che i cicli di vita delle risorse. Durante i test, invece di attendere una riproduzione su scala mensile, esegui timeout / riconnessione / spegnimento in cicli brevi e rendi “tracciabile quando si rompe” - non solo “non si rompe” - il criterio di accettazione. Ciò che ha funzionato in questo caso è stata questa combinazione. Nella Parte 2, utilizziamo Application Verifier per evidenziare modalità di errore difficili da attivare, come l’esaurimento della memoria, e gestire le anomalie in anticipo.
Nelle app di controllo, il normale percorso di lavoro è importante, ma essere in grado di dire “cosa è successo” quando le cose si rompono conta molto nelle operazioni a lungo termine.
Le perdite di handle sono esattamente il tipo di difetto in cui tale differenza ripaga. Se li guardi attraverso i tassi di crescita, i confini e le coppie di responsabilità – piuttosto che solo nel momento in cui si verificano – diventano notevolmente più facili da perseguire.
8. Riferimenti
- Funzione GetProcessHandleCount (processthreadsapi.h)
- Process.HandleCount Immobile (System.Diagnostics)
- Parte 2: Quando un’app di controllo della telecamera industriale si blocca improvvisamente dopo un mese (Parte 2) - Cos’è Application Verifier e come costruire una base di test del percorso di fallimento
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
Costruire una base di test del percorso di fallimento Windows con Application Verifier
Cos'è Application Verifier, organizzato insieme a come costruire una base di test del percorso di fallimento Windows utilizzando Handles,...
Perché le ritrasmissioni TCP bloccano la comunicazione con telecamere industriali, e come isolarle
Come isolare la causa quando la comunicazione con telecamere industriali si blocca per diversi secondi a causa di ritrasmissioni TCP, cop...
Icone nella system tray e notifiche toast nelle app Windows — le insidie di NotifyIcon e come scegliere l'AppNotification giusta
Una guida pratica per mantenere un'applicazione Windows aziendale residente nella system tray (area di notifica) e avvisare l'utente tram...
Fino a quando funzioneranno le applicazioni VB6? — Lo stato del supporto al runtime e un percorso pratico verso la migrazione a .NET
Fino a quando continueranno a funzionare le applicazioni VB6? Questo articolo chiarisce l'asimmetria tra la politica di supporto del runt...
Era giapponese, festività e date di chiusura nelle applicazioni aziendali — progettazione resiliente ai cambi di era, JapaneseCalendar e calcolo dei giorni lavorativi nella pratica
Mostrare «Reiwa 8» su un documento, calcolare i giorni lavorativi escludendo le festività, pagare entro la fine del mese successivo dopo ...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Casi di studio correlati
Questi casi mostrano un approccio simile all’analisi, alla definizione delle priorità o alla riprogettazione.
Come abbiamo ricondotto un arresto dopo una lunga esecuzione a una perdita di handle
Caso di studio su come punti di osservazione e log migliori abbiano trasformato un arresto mensile in un’indagine mirata su una perdita di handle.
Servizi collegati all’argomento
L’articolo è direttamente collegato ai servizi seguenti.
Analisi dei bug e delle cause
Isolare i guasti che si verificano solo dopo operazioni di lunga durata è un tema che si adatta molto bene al nostro servizio di indagine dei bug e di analisi delle cause principali.
Sviluppo di applicazioni Windows
Se desideri esaminare come viene creata la tua app Windows, inclusa la progettazione della registrazione e l'osservabilità operativa, puoi collegarti anche alla nostra consulenza sullo sviluppo di applicazioni Windows.
Domande frequenti
Domande che ricorrono nelle consulenze sull’argomento dell’articolo.
- Cos'è una perdita di handle in un'applicazione Windows?
- Un handle è l'identificatore attraverso il quale un processo Windows fa riferimento alle risorse OS: oggetti del kernel come eventi, mutex e thread, oltre alle aperture di file, pipe, socket e dispositivi. Una perdita di handle si verifica quando una risorsa viene aperta ma mai chiusa, quindi il conteggio degli handle del processo aumenta e non torna mai indietro. Il tipico colpevole è un percorso di fallimento: un evento creato a ogni riconnessione viene chiuso sul percorso di successo, ma un ritorno anticipato su un fallimento parziale salta il CloseHandle e i test brevi che esercitano solo il percorso di successo non se ne accorgono mai.
- In che modo una perdita di handle è diversa da una perdita di memoria?
- Per una perdita di memoria, le metriche da controllare per prime sono Byte privati, Commit e Working set; per una perdita di handle, è Handle Count. Le perdite di handle si presentano come errori delle API Create/Open o dell'inizializzazione interna dell'SDK, non come uso eccessivo della memoria, e si nascondono nell'asimmetria tra le chiamate create / open e close / dispose. Quando si isolano i problemi a lungo termine, guardare solo la memoria è come guidare con un occhio chiuso: come minimo, monitorare il conteggio degli handle e del conteggio dei thread insieme ad esso.
- Perché una perdita di handle provoca l'arresto anomalo dell'app solo dopo settimane di funzionamento?
- Perché una perdita di piccola pendenza (una perdita per ogni guasto occasionale come un timeout o una riconnessione) si accumula troppo lentamente per avere importanza in pochi minuti, ma in un'app di controllo delle apparecchiature 24 ore su 24, 7 giorni su 7, tali condizioni al contorno si ripetono più e più volte. Alla fine un'API che crea un nuovo evento, file o thread fallisce e l'app si arresta in modo anomalo come errore secondario altrove. È importante sottolineare che il sito dell'incidente è l'ultima vittima, non il colpevole originale: indaga su quale risorsa continua a crescere e dove l'accoppiamento creazione / chiusura viene interrotto e leggi il sito dell'incidente per ultimo.
- Come posso riprodurre una perdita di handle senza aspettare settimane?
- Comprimere il tempo percorrendo ripetutamente i percorsi sospetti in un breve ciclo invece di attendere una riproduzione su scala mensile. Esegui apri -> avvia -> arresta -> chiudi in grandi volumi, attiva deliberatamente i timeout e scorre le riconnessioni e forza gli errori subito dopo la registrazione della richiamata. Stabilire una linea di base del conteggio degli handle dopo il riscaldamento, registrare il conteggio dopo ogni ciclo e leggere la pendenza: una perdita di +1 handle per riconnessione che non ritorna mai è altamente sospetta anche se il numero assoluto sembra innocuo.
Profilo dell’autore
Pagina di presentazione dell’autore dell’articolo.
Go Komura
Rappresentante di KomuraSoft LLC
Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.