Indagine sugli arresti anomali a lungo termine di un'app per fotocamere industriali: la perdita dell'handle (parte 1)

· Aggiornato il: · · 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

  1. Prima la conclusione (in una riga)
  2. 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
  3. 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
  4. 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 / open e close / dispose
    • 4.4. Per le perdite di handle, trova dove si è verificata la perdita, non dove si è bloccato
  5. 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
  6. Una guida decisionale approssimativa
  7. Riepilogo
  8. 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 solo Private 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, il handle count del processo, l’abbinamento delle risorse open / close e 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.

Normal operationOccasional timeout / reconnectFailure path creates an event handleCloseHandle is never calledHandle Count creeps up slightlyRepeats hundreds of timesCreateEvent / SDK open failsCrash / 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ì.

Camera SDKWindowsControl appCamera SDKWindowsControl appReturns on the failure pathCloseHandle is never calledloop[Repeated reconnects]CreateEventRegister callbackPartial failure / timeoutHandle Count creeps upNext CreateEvent / OpenFailureCrashes 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 / open e close / 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.

YesNoStartOpen cameraStart acquisitionSimulated timeout / disconnectReconnectResume acquisitionRepeat N timesCheck 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 -> close in 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.

  1. Stabilire una linea di base dopo il riscaldamento
  2. Registra Handle Count dopo ogni riconnessione / avvio-arresto / chiusura
  3. Guarda il delta per ciclo
  4. 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 Count
  • Private Bytes
  • Thread Count
  • ReconnectCount
  • 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.

  • sessionId
  • resourceId
  • kind
  • action(Create / Open / Register / Close / Dispose / Unregister)
  • osHandle
  • phase

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: CreateEvent fallisce
  • La vera perdita di handle: CloseHandle era 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:

  1. Guarda quale risorsa continua a crescere
  2. Guarda a quale confine dell’operazione non riesce a tornare
  3. Trova dove l’accoppiamento di create / open e close / dispose è interrotto
  4. 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.

  1. Battito cardiaco periodico
    • Emette Handle Count / Private Bytes / Thread Count / ReconnectCount ogni 1-5 minuti
  2. Registri di confine per sessione della telecamera
    • OpenStart
    • CallbackRegistered
    • AcquisitionStart
    • TimeoutDetected
    • ReconnectStart
    • ReconnectDone
    • CloseStart
    • CloseDone
  3. Registri del ciclo di vita delle risorse
    • Create / Open / Register e Close / Dispose / Unregister per eventi / thread / file / timer / token di registrazione SDK
  4. Normalizzazione degli errori
    • Non fermarsi al messaggio di eccezione; emettono win32Error, HRESULT, sdkError e phase insieme

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 / register e close / 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
  • 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 anche GDI Objects / USER Objects
  • 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.

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.

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

8. Riferimenti

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

Questi casi mostrano un approccio simile all’analisi, alla definizione delle priorità o alla riprogettazione.

L’articolo è direttamente collegato ai servizi seguenti.

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.

Torna al blog