[C] Vinden van (mogelijk) memleak

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
Een van mijn applicaties lijkt wel of geen memory leak te hebben. Ik twijfel omdat er zowel signalen zijn dat die er niet is, maar ook signalen van wel.

Waarom denk ik van wel:
- VMPeak stijgt periodiek, wat ik monitor binnen mijn programma.
- Kernel OOM killer komt langs

Waarom denk ik van niet:
- Valgrind vind geen openstaand geheugen na afsluiten van programma na een tijdje draaien, dat zegt tenminste dat al mijn garbage collectors hun werk doen.
- libleak vind geen lekken terwijl mijn programma draait* Alle langlopende geheugen allocaties horen ook gealloceerd te zijn gedurende de levenscyclus van mijn programma.

Mijn programma draait binnen een LXC container op een Proxmox (Debian X86) host. Het is trouwens multithreaded waardoor mtrace niet werkt.

Er blijven dus een aantal vragen onbeantwoord:
- Lek ik nu wel of geen geheugen?
- Als ik wel een geheugen lek heb, hoe kom ik er dan achter waar als de tools die ik tot nu toe gebruik geen helderheid kunnen geven?
- Als er geen geheugen lek is, waarom stijgt dan mijn VMPeak?

*libleak kijkt naar geheugen allocaties die na langer dan X aantal seconden nog niet opgeruimd zijn.

Sinds de 2 dagen regel reageer ik hier niet meer

Alle reacties


Acties:
  • 0 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
Het hoeft niet perse een memory leak te zijn. Het kan ook gewoon een programmeerfout zijn, zoals bijvoorbeeld wel een lijst vullen maar niet legen, etc.

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
ThomasG schreef op dinsdag 22 september 2020 @ 12:59:
Het hoeft niet perse een memory leak te zijn. Het kan ook gewoon een programmeerfout zijn, zoals bijvoorbeeld wel een lijst vullen maar niet legen, etc.
Dat zie ik niet voor me. Het dynamisch alloceren van en lijst vereist toch dat ik geheugen reserveer. Als ik die lijst nooit leeg maak, dan heeft dat gealloceerde geheugen toch een oneindige lifetime en zou ik verwachten dat dat opgepikt wordt door libleak.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • +1 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
CurlyMo schreef op dinsdag 22 september 2020 @ 13:09:
[...]

Dat zie ik niet voor me. Het dynamisch alloceren van en lijst vereist toch dat ik geheugen reserveer. Als ik die lijst nooit leeg maak, dan heeft dat gealloceerde geheugen toch een oneindige lifetime en zou ik verwachten dat dat opgepikt wordt door libleak.
Stel je alloceerd geheugen, en de verwijzing weggooit maar het niet dealloceerd, dan heb je een memory leak. Op het moment dat de verwijzing gewoon valide blijft, heb je geen memory leak. Als valgrid en libleak geen memory leak vinden, dan is het hoogst waarschijnlijk geen memory leak maar een andere fout. Zoals bijvoorbeeld wel dingen data inlezen, maar niet verwijderen. Of twee objecten die naar elkaar verwijzen, etc. Technisch is er niets mis mee, maar het is in jouw geval niet de bedoeling.

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
ThomasG schreef op dinsdag 22 september 2020 @ 13:26:
[...]
Stel je alloceerd geheugen, en de verwijzing weggooit maar het niet dealloceerd, dan heb je een memory leak. Op het moment dat de verwijzing gewoon valide blijft, heb je geen memory leak.
Dat snap ik. Alleen libleak zegt dat het dat moet kunnen detecteren, want je hebt een stuk geheugen dat na zeg 5 minuten nog steeds gealloceerd is gebleven.

Ik liet libleak nu draaien vanaf 5 seconde init tijd. Ik ga het nu eens direct bij starten mee laten lopen. Misschien zijn het wel lijsten zoals jij die omschrijft, die ik aan het begin van het programma initialiseer.

[ Voor 19% gewijzigd door CurlyMo op 22-09-2020 13:31 ]

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • NESFreak
  • Registratie: December 2009
  • Laatst online: 17:54
Wat ik van de omschrijving van libleak kan opmaken is het volgende?

Een scenario waarbij je telkens dat je een operatie uitvoert een steeds groter wordende kopie maakt van je datastructuur, en vervolgens de oude vrij geeft zou niet door libleak gevonden moeten worden?

Denk aan iets als std::vector waarbij elke keer dat ie 'vol' is een nieuw groter array aanmaakt en daar alles heen verplaatst?

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
NESFreak schreef op dinsdag 22 september 2020 @ 13:38:
Een scenario waarbij je telkens dat je een operatie uitvoert een steeds groter wordende kopie maakt van je datastructuur, en vervolgens de oude vrij geeft zou niet door libleak gevonden moeten worden?
Zover ik het lees en werkend heb gezien geeft libleak een timestamp aan elke geheugen allocatie. Als een bepaalde geheugenallocatie binnen een bepaald tijdsbestek niet wordt vrijgegeven, dan laat libleak dat weten.
Denk aan iets als std::vector waarbij elke keer dat ie 'vol' is een nieuw groter array aanmaakt en daar alles heen verplaatst?
Dan ziet libleak dat het oude opgeruimd wordt en het nieuwe gealloceerd. Als die tweede allocatie blijft bestaan tijdens de runtime van het proces, dan merkt libleak het weer op. Ook als de langlopende allocatie alsnog wordt opgeruimd, dan merkt libleak het op.

Het zijn die lijst allocaties die ik met de hand heb nagelopen en met logging heb gechecked, en daar wordt alles netjes opgeruimd.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
Ik heb een nieuwe tool gevonden Heaptrack van KDE

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
total runtime: 5623.97s.
bytes allocated in total (ignoring deallocations): 500.56MB (89.00KB/s)
calls to allocation functions: 8807881 (1566/s)
temporary memory allocations: 1416402 (251/s)
peak heap memory consumption: 1.76MB
peak RSS (including heaptrack overhead): 69.80MB
total memory leaked: 1.48MB

total runtime: 7257.99s.
bytes allocated in total (ignoring deallocations): 642.40MB (88.51KB/s)
calls to allocation functions: 11354305 (1564/s)
temporary memory allocations: 1828192 (251/s)
peak heap memory consumption: 1.76MB
peak RSS (including heaptrack overhead): 70.26MB
total memory leaked: 1.48MB

total runtime: 7619.21s.
bytes allocated in total (ignoring deallocations): 673.39MB (88.38KB/s)
calls to allocation functions: 11912871 (1563/s)
temporary memory allocations: 1917922 (251/s)
peak heap memory consumption: 1.76MB
peak RSS (including heaptrack overhead): 70.28MB
total memory leaked: 1.48MB

total runtime: 11282.64s.
bytes allocated in total (ignoring deallocations): 989.03MB (87.66KB/s)
calls to allocation functions: 17588883 (1558/s)
temporary memory allocations: 2835329 (251/s)
peak heap memory consumption: 1.76MB
peak RSS (including heaptrack overhead): 70.39MB
total memory leaked: 1.48MB

Hier zie ik dan weer geen schokkende dingen in.

Even zien wat er gebeurt als deze wat langer loopt.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • GlowMouse
  • Registratie: November 2002
  • Niet online
CurlyMo schreef op dinsdag 22 september 2020 @ 19:08:
Ik heb een nieuwe tool gevonden Heaptrack van KDE
De GUI van heaptrack laat zien waar het meeste geheugen is gealloceerd, als je tenminste symbols in je executable hebt. Als het geheugengebruik zodanig oploopt dat de OOM killer langskomt, moet je de oorzaak op die manier kunnen vinden.

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
GlowMouse schreef op dinsdag 22 september 2020 @ 19:26:
[...]

De GUI van heaptrack laat zien waar het meeste geheugen is gealloceerd, als je tenminste symbols in je executable hebt. Als het geheugengebruik zodanig oploopt dat de OOM killer langskomt, moet je de oorzaak op die manier kunnen vinden.
Nadeel is nu dat dat nogal vervuild blijft met de allocatie uit de init fase.

Sinds de 2 dagen regel reageer ik hier niet meer


  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
Volgens mij heb ik hem gevonden :D

Afbeeldingslocatie: https://tweakers.net/i/r8Svz81G8hWGurPq4Pp36MFFmOw=/full-fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():fill(white):strip_exif()/f/image/GuAtVmA9JiWnNOERsbIJ4fb6.jpg?f=user_large

Sinds de 2 dagen regel reageer ik hier niet meer


  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
Nu weten we ook meteen om welke code het ging :+

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
ThomasG schreef op woensdag 23 september 2020 @ 14:52:
[...]
Nu weten we ook meteen om welke code het ging :+
Ik zal een poging wagen.

Ik maak in mijn code gebruik van een threadsafe buffer om datgene wat ik via sockets binnenhaal asynchroon te kunnen verwerken.

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
typedef struct iobuf_t {
    char *buf;
    ssize_t len;
    ssize_t size;
    uv_mutex_t lock;
} iobuf_t;

size_t iobuf_append_remove(struct iobuf_t *a, struct iobuf_t *b) {
    char *p = NULL;
    size_t i = -1;

    uv_mutex_lock(&b->lock);
    uv_mutex_lock(&a->lock);
    i = a->len;

    assert(b != NULL);
    assert(b->len <= b->size);

    if(a->len <= 0) {
    } else if(b->len + a->len <= b->size) {
        memcpy(b->buf + b->len, a->buf, a->len);
        b->len += a->len;
    } else if((p = realloc(b->buf, b->len + a->len + 1)) != NULL) {
        b->buf = p;
        memcpy(b->buf + b->len, a->buf, a->len);
        b->len += a->len;
        b->size = b->len;
    }

    if(i > 0) {
        if(a->buf != NULL) {
            free(a->buf);
        }
        a->len = a->size = 0;
    }

    uv_mutex_unlock(&a->lock);
    uv_mutex_unlock(&b->lock);

    return i;
}

size_t iobuf_append(struct iobuf_t *io, const void *buf, int len) {
    char *p = NULL;

    uv_mutex_lock(&io->lock);
    assert(io != NULL);
    assert(io->len <= io->size);

    if(len <= 0) {
    } else if(io->len + len <= io->size) {
        memcpy(io->buf + io->len, buf, len);
        io->len += len;
    } else if((p = realloc(io->buf, io->len + len + 1)) != NULL) {
        io->buf = p;
        memcpy(io->buf + io->len, buf, len);
        io->len += len;
        io->size = io->len;
    } else {
        len = 0;
    }
    uv_mutex_unlock(&io->lock);

    return len;
}

void iobuf_init(struct iobuf_t *iobuf, size_t initial_size) {
    iobuf->len = iobuf->size = 0;
    iobuf->buf = NULL;
    uv_mutex_init(&iobuf->lock);
}

void iobuf_remove(struct iobuf_t *io, size_t n) {
    uv_mutex_lock(&io->lock);
    if(n > 0 && n <= io->len) {
        memmove(io->buf, io->buf + n, io->len - n);
        io->len -= n;
        if(io->len == 0) {
            free(io->buf);
            io->buf = NULL;
        } else {
            io->buf = realloc(io->buf, io->len + 1);
            io->buf[io->len] = 0;
        }
        io->size = io->len;
    }
    uv_mutex_unlock(&io->lock);
}


De buffer werd o.a. zo gebruikt:
C:
1
poll->read_cb(req, &poll->recv_iobuf.len, poll->recv_iobuf.buf);


Zo'n callback zag er dan weer als volgt uit:
C:
1
static void client_read_cb(uv_poll_t *req, ssize_t *nread, char *buf)


Als ik in de callback de buffer compleet had weten te verwerken, dan deed ik ook weer binnen de callback dit:
C:
1
*nread = 0;


Waarmee ik dus buiten de iobuf_* functies de len van de buffer aanpaste. De len en size zijn echter met elkaar verbonden, waardoor de buffer out-of-sync raakte.

Met de kennis van nu zou ik zo'n read_cb het aantal bytes terug laten geven dat hij heeft kunnen verwerken, maar ja, zo is het nu niet geïmplementeerd. Ik heb het nu zo opgelost:
C:
1
2
3
ssize_t len = poll->recv_iobuf.len;
custom_poll_data->read_cb(req, &len, poll->recv_iobuf.buf);
iobuf_remove(&poll->recv_iobuf, poll->recv_iobuf.len - len);

Sinds de 2 dagen regel reageer ik hier niet meer


  • GlowMouse
  • Registratie: November 2002
  • Niet online
Ik denk dat het grappig bedoeld was omdat je screenshot de naam van de executable laat zien.

Moet het in regel 3 van je oplossing geen "poll->recv_iobuf.len + len" of "len - poll->recv_iobuf.len" zijn? Met de huidige regel 3 kom je negatief uit.

[ Voor 13% gewijzigd door GlowMouse op 23-09-2020 20:27 ]


Acties:
  • +1 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
GlowMouse schreef op woensdag 23 september 2020 @ 19:47:
[...]

Ik denk dat het grappig bedoeld was omdat je screenshot de naam van de executable laat zien.
Aha, dat boeit me dusdanig weinig dat ik het inderdaad niet zo las :)
Moet het in regel 3 van je oplossing geen "poll->recv_iobuf.len + len" of "len - poll->recv_iobuf.len" zijn? Met de huidige regel 3 kom je negatief uit.
De poll->recv_iobuf.len is bij binnenkomst 100. Binnen de read_cb wordt len op 50 gezet. Er zijn van de 100 nu 50 bytes verwerkt. Die eerste 50 bytes mogen dus uit de buffer verwijderd worden en de volgende 50 bytes mogen aan het begin van de buffer gezet worden.

Dus:
code:
1
2
poll->recv_iobuf.len - len
100                  - 50

Er moeten inderdaad 50 bytes in de buffer blijven, want die zijn niet verwerkt.

Sinds de 2 dagen regel reageer ik hier niet meer


  • RudolfR
  • Registratie: Maart 2011
  • Laatst online: 21:15
Valgrind heeft overigens ook een heap profiler 'massif'.
https://valgrind.org/docs/manual/ms-manual.html

met ms_print kun je de output daarvan tekenen in een terminal, mocht je geen gui tot je beschikking hebben.

  • CurlyMo
  • Registratie: Februari 2011
  • Nu online
RudolfR schreef op donderdag 24 september 2020 @ 09:58:
Valgrind heeft overigens ook een heap profiler 'massif'.
https://valgrind.org/docs/manual/ms-manual.html

met ms_print kun je de output daarvan tekenen in een terminal, mocht je geen gui tot je beschikking hebben.
Massif ken ik, maar dat vond ik niet lekker werken. Heaptrack wel.

Sinds de 2 dagen regel reageer ik hier niet meer

Pagina: 1