halfcountplus has asked for the wisdom of the Perl Monks concerning the following question:

I've written an http server, much of it using Inline::C, and I have a memory leak I cannot pin down.

Every few score requests, mem usage grows by exactly 132 kB. By "every few score" I mean irregularly, and on average. Sometimes it happens after 10 requests, sometimes 100. The requests are all identical, generated via apachebench.

I do not believe this is because of anything explicit in the C routines since there is only one malloc call, for a file slurp, which in this test would only be a few hundred bytes, the pointer is free'd a few lines later after its content is assigned to an SV, and such a leak would grow in a regular linear fashion every time a new memory page was needed.

132 kB is a power of two value so I suspect it is some data structure perl is growing. However, I cannot find anything leaky in my perl code either -- I have checked for circularity with Test::Memory::Cycle, and I have a recursive rountine for checking reference counts of objects/structures and their members before they are deleted or undef'd.

I've tried valgrind, but it is almost useless for me with perl since even "helloworld.pl" reports thousands of kB remaining in hundreds of blocks.

Anyone know a way I can trace allocation, hopefully in real time?

later: I did find one stray reference that accounts for the major leak, however I still have the same issue on a much smaller scale -- the increase is now every 1000-2000 requests. See below http://www.perlmonks.org/?node_id=925997 for how I am tracing references.

finally: I found the smaller leak by watching PL_sv_count as per Dave's suggestion, qv. http://www.perlmonks.org/?node_id=926128. No more leaky http server!

Replies are listed 'Best First'.
Re: Tracing memory leak
by BrowserUk (Patriarch) on Sep 14, 2011 at 18:09 UTC
    I do not believe this is because of anything explicit in the C routines since there is only one malloc call, ... and such a leak would grow in a regular linear fashion every time a new memory page was needed.

    That is a bad assumption. Perl's memory allocator often requests a power-of-two sized large chunk of memory from the OS which it then parcels out to the program as a bunch of smaller requests on demand.

    My guess is that you are failing to mortalise the SV which you are populating with the inbound data.

    If you posted the code, it would probably be the work of a few minutes to confirm that or otherwise track the leak down.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      My guess is that you are failing to mortalise the SV which you are populating with the inbound data. If you posted the code, it would probably be the work of a few minutes to confirm that or otherwise track the leak down.

      The code is too substantial to post, but maybe there is something I have wrong WRT to mortality.

      #!/usr/bin/perl use strict; use warnings FATAL => qw(all); use Inline 'C'; my $test = eg(); reportHash($test); reportScalar($test->{x}); $test = undef; __DATA__ __C__ SV *eg () { HV *rv = newHV(); hv_store(rv, "x", 1, newSVpv("hello", 0), 0); return newRV_noinc((SV*)rv); } void reportHash (SV *ref) { HV *hash = (HV*)SvRV(ref); fprintf(stderr,"rc: %d\n", SvREFCNT((SV*)hash)); } void reportScalar (SV *s) { fprintf(stderr,"rc: %d\n", SvREFCNT(s)); }
      My understanding is that that since $test has a reference count of 1, and the SV inside it has a reference count of 1, that $test=undef should free the memory used. Running that in a while(1) loop seems to confirm this -- there is no growth. Where in this code do you see an issue? Everything else follows the same pattern (I am not using the inline stack).
        That example code leaks an SV. You return a newly-minted RV that points to a HV. In the $test assignment, the value of that RV is copied to $test; the original RV leaks.

        In your production code, try examining the value of the perl internals variable PL_sv_count (e.g. write a little C wrapper function that returns its value to perl space). This var shows the total number of SVs allocated. If it keeps growing, it means that you're leaking SVs.

        Dave.

        You might want to look into Devel::Peek:

        #!/usr/bin/perl use strict; use warnings FATAL => qw(all); use Inline 'C'; use Devel::Peek; my $test = eg(); Dump( $test ); $test = undef; __DATA__ __C__ SV *eg () { HV *rv = newHV(); hv_store(rv, "x", 1, newSVpv("hello", 0), 0); return newRV_noinc((SV*)rv); }

        Outputs:

        C:\test>junkic SV = RV(0x267460) at 0x267450 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x2d0200 SV = PVHV(0x357b620) at 0x2d0200 REFCNT = 1 FLAGS = (SHAREKEYS) ARRAY = 0x36132d8 (0:7, 1:1) hash quality = 100.0% KEYS = 1 FILL = 1 MAX = 7 RITER = -1 EITER = 0x0 Elt "x" HASH = 0x9303a5e5 SV = PV(0x37cf50) at 0x37f128 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x36d2f18 "hello"\0 CUR = 5 LEN = 8

        Which looks fine, but only serves to tell us that what you've posted isn't indicative of your actual code.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Tracing memory leak
by Anonymous Monk on Sep 14, 2011 at 18:08 UTC

    Are you storing anything between connections?

    Perhaps you could print the length of the dumper string for any globals to the console, and see if they grow over time.

      Are you storing anything between connections?

      No. Connections and requests are hashes (in hashes), but these are deleted after the connection is closed or the request is completed. So, eg, if I send 100000 requests with a concurrency of 50, the number of buckets in the connections hash won't exceed 64, and the number of used buckets afterward is what I'd expect.

Re: Tracing memory leak
by halfcountplus (Hermit) on Sep 14, 2011 at 20:31 UTC
    Here's what I'm using to recursively check and correct reference counts before deletion:
    void Reference_recursivePurge (SV *ref, char *txt) { char label[1024]; int rc; if (!SvROK(ref)) { rc = SvREFCNT(ref); if (rc > 1) { fprintf(stderr,"REFCOUNT for %s: %d\n", txt, rc); while (rc > 1) { SvREFCNT_dec(ref); rc--; } } return; } if (SvTYPE(SvRV(ref)) == SVt_PVAV) { AV *ray = (AV*)SvRV(ref); SV **cur; int i, len = av_len(ray); for (i = 0; i <= len; i++) { cur = av_fetch(ray, i, 1); sprintf(label, "%s[%d]", txt, i); Reference_recursivePurge(*cur, label); i++; } rc = SvREFCNT(ray); if (rc > 1) { fprintf(stderr,"REFCOUNT for %s: %d\n", txt, rc); while (rc > 1) { SvREFCNT_dec((SV*)ray); rc--; } } return; } if (SvTYPE(SvRV(ref)) == SVt_PVHV) { HV *hash = (HV*)SvRV(ref); SV *cur; char *key; int len; hv_iterinit(hash); while ((cur = hv_iternextsv(hash, &key, &len))) { sprintf(label, "%s->%s", txt, key); Reference_recursivePurge(cur, label); } rc = SvREFCNT(hash); if (rc > 1) { fprintf(stderr,"REFCOUNT for %s: %d\n", txt, rc); while (rc > 1) { SvREFCNT_dec((SV*)hash); rc--; } } } }
    Manually decrementing the count is a temporary hack solution; the reporting provides a tidy clue, then the idea here is to isolate the problem and see if getting the structure in question free'd via decrement solves the memory use issue.
A reply falls below the community's threshold of quality. You may see it by logging in.