In my Perl-RPM module, I have a recurring problem. I have tied hashes that
have a struct attached via ~ magic, and the struct includes an HV* into
which I cache the key/value pairs (these generally only need to be computed
the first time through, but the hash has to stay tied). Unfortunately, I'm
pretty much bleeding memory as if from a gaping head wound. The reason?

When I store a value on the (inner) HV*, I originally did not mortalize
it. When I retrieved a value, I made sure that the new copy was
mortalized. In the case of simple function return values, I let the XS glue
handle it. Other places, I would explicitly sv_2mortal(). But what was
happening was this: I would either (a) get a ton of "attempt to free
unreferenced scalar" error messages, or (b) not see my object destructors
until the final clean-up phase of the program.

This is harder to illustrate, since I was never certain where the offense
actually occurs. My typemap entry for the tied datatypes:

RPM::Database           O_RPM_Tied
RPM::Header             O_RPM_Tied

OUTPUT
O_RPM_Tied
        if ($var)
        {
            $arg = sv_bless(sv_2mortal(newRV_noinc((SV*)$var)),
                            gv_stashpv(\"${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\",
                                       TRUE));
        }
        else
        {
            $arg = newSVsv(&PL_sv_undef);
        }

INPUT
O_RPM_Tied
        if (sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVHV))
            $var = (HV*)SvRV($arg);
        else
        {
            rpm_error(aTHX_ RPMERR_BADARG,
                      \"${Package}::$func_name: not a blessed HV reference\");
            XSRETURN_UNDEF;
        }


The return value for the XS typing of RPM::Database::FETCH is
RPM::Header. The return for RPM::Header::FETCH is SV*. I know that I'm
leaking RPM::Header objects that are stored within the inner hash of an
RPM::Database object, but I may well be leaking SV*'s from the inner hash
of each RPM::Header, as well.

(As a side note, the OUTPUT typemap seems inefficient, since XS will
initialized ST(0) with sv_newmortal(), then overwrite this with my
assignment. Is there a more memory-efficient way of doing the OUTPUT
mapping such that it directly assigns the reference into $arg, rather than
creating a new, mortal RV?)

I suspect the problem comes from the fact that I implement
RPM::Database::FETCH essentially by calling the internal rpmhdr_FETCH C
routine (which is what the RPM::Header::FETCH XS stub encapsulates) and
storing the resulting value on the inner hash. I do this something like so:

I start out with:

        /* Step 1: Check to see if this has already been requested and is
           thus cached on the hash itself */
        svp = hv_fetch(dbstruct->storage, (char *)name, namelen, FALSE);
        if (svp && SvROK(*svp))
        {
            FETCH = (RPM__Header)(*svp);
            return (RPM__Header)SvREFCNT_inc((SV *)FETCH);
        }

(dbstruct is my ~-magic struct, and storage is a HV*.) Thus, if I've
previously fetched this and it is on the inner hash, I just return the
value that hv_fetch gave me (the inner hash isn't tied). When I actually
have had to get a new value:

        FETCH = rpmhdr_TIEHASH(aTHX_ "RPM::Header",
                               sv_2mortal(newSViv((unsigned)hdr)),
                               RPM_HEADER_FROM_REF | RPM_HEADER_READONLY);
        /* If name is no longer NULL, it means our vector in was a string
           (key), so put the result back into the hash-cache. */
        if (name != NULL)
        {
            hv_store(dbstruct->storage, (char *)name, namelen,
                     (SV *)FETCH, FALSE);
            SvREFCNT_inc((SV *)FETCH);
        }

The value "hdr" is an internal struct ptr managed by the RPM API
itself. This will return a value of type RPM__Header, which is typedef'd to
HV*. It will have been tied and blessed before rpmhdr_TIEHASH
returns. Here, I've cast it to SV* in order to store it on the inner
hash. Note that the earlier code snippet re-casts it and returns the
RPM__Header type. The OUTPUT typemap picks up from that point. If I removed
*either* of those SvREFCNT_inc() calls, let alone both, I will get
intermittent "attempt to free unreferenced" errors. Not always in my test
suite, but in more involved applications for certain.

Any help appreciated. I imagine the if I can solve this for this
object-case, I'll be able to clean up any cruft in the RPM::Header::FETCH
routine as well.

Randy
--
-------------------------------------------------------------------------------
Randy J. Ray      | Programming is a Dark Art [...] The programmer is fighting
[EMAIL PROTECTED]  | against the two most destructive forces in the universe:
415-777-9810 x246 | entropy and human stupidity. --Dr. Damian Conway

Reply via email to