I'm trying to come up with a fix for the bug related to
storing shared objects inside of shared structures.  The bug
is that when any proxy objects for the shared object are
destroyed, the object's DESTROY routine is called even
thought the object itself should not yet be destroyed.

My problem is that my code (provided below) seg faults, and
I can't figure out why.  Any help, comments, suggestions,
etc. would be appreciated.

The following elicits the bug:
-----
#!/usr/bin/perl

use strict;
use warnings;

use threads;
use threads::shared;

package Jar; {
    my @jar :shared;

    sub new {
        bless(&threads::shared::share({}), shift);
    }

    sub store {
        my ($self, $cookie) = @_;
        push(@jar, $cookie);
        print("JAR   : Cookie stored\n");
        return $jar[-1];        # BUG: The cookie is destroyed here
    }
}

package Cookie; {
    my $destruction_count = 0;

    sub new {
        bless(&threads::shared::share({}), shift);
    }

    sub DESTROY {
        $destruction_count++;
        print("COOKIE: destruction count = $destruction_count\n");
    }
}

package main;

MAIN:
{
    my $jar = Jar->new();
    my $cookie = Cookie->new();

    print("MAIN  : Storing cookie\n");
    $jar->store($cookie);

    print("\nMAIN  : Cookie should not have been destroyed yet\n");

    print("\nMAIN  : Exiting scope\n")
}

print("\nDONE\n");
-----
The above outputs:
    MAIN  : Storing cookie
    JAR   : Cookie stored
    COOKIE: destruction count = 1

    MAIN  : Cookie should not have been destroyed yet

    MAIN  : Exiting scope
    COOKIE: destruction count = 2

    DONE

which shows that DESTROY is called twice - the first time by
the destruction of a proxy object.

I am attempting to fix this bug by first providing a call in
threads::shared (ext/threads/shared/shared.xs) to report on
whether or not a shared object should be destroyed:  If the
ref is shared, and its refcnt is greater than one, then it
should NOT be destroyed.
-----
void
_is_destroyable(SV *shared_var)
    PROTOTYPE: [EMAIL PROTECTED]
    PREINIT:
        SV *ssv;
    CODE:
        shared_var = SvRV(shared_var);
        if (SvROK(shared_var))
            shared_var = SvRV(shared_var);
        ssv = Perl_sharedsv_find(aTHX_ shared_var);
        ST(0) = (ssv && (SvREFCNT(ssv) > 1)) ? &PL_sv_no : &PL_sv_yes;
        /* XSRETURN(1); - implied */
-----
I then use this call inside of Perl_sv_clear (in sv.c) to
control the execution of destructors for shared objects.

My code is in the '#ifdef USE_ITHREADS' block, and follows
the example for "Returning a Scalar" in 'perlcall' (which is
the same as used in do_mark_cloneable_stash in sv.c).

The idea is that a shared object's DESTROY routine should
not be called unless it's the last proxy object being
destroyed (i.e., the refcnt on the "private SV" is 1).
-----
void
Perl_sv_clear(pTHX_ register SV *sv)
{
    dVAR;
    const U32 type = SvTYPE(sv);
    const struct body_details *const sv_type_details
        = bodies_by_type + type;
    HV *stash;

    assert(sv);
    assert(SvREFCNT(sv) == 0);

    if (type <= SVt_IV) {
        /* See the comment in sv.h about the collusion between this early
           return and the overloading of the NULL and IV slots in the size
           table.  */
        return;
    }

    if (SvOBJECT(sv)) {
        if (PL_defstash) {              /* Still have a symbol table? */
#ifdef USE_ITHREADS
            IV destroyable;
            CV *check_destroyable = get_cv("threads::shared::_is_destroyable", 
0);
            if (check_destroyable) {
                dSP;
                int count;
                ENTER;
                SAVETMPS;
                PUSHMARK(SP);
                XPUSHs(sv);
                PUTBACK;
                count = call_sv((SV*)check_destroyable, G_SCALAR);
                SPAGAIN;
                if (count != 1)
                    Perl_croak(aTHX_ "'threads::shared::_is_destroyable' failed 
to
return result");
                destroyable = POPi;
                PUTBACK;
                FREETMPS;
                LEAVE;
            } else {
                destroyable = 1;
            }

            if (destroyable) {
#endif
                HV* stash;
                do {
                    dSP;
                    CV* destructor;
                    stash = SvSTASH(sv);
                    destructor = StashHANDLER(stash,DESTROY);
                    if (destructor) {
                        SV* const tmpref = newRV(sv);
                        SvREADONLY_on(tmpref);   /* DESTROY() could be naughty 
*/
                        ENTER;
                        PUSHSTACKi(PERLSI_DESTROY);
                        EXTEND(SP, 2);
                        PUSHMARK(SP);
                        PUSHs(tmpref);
                        PUTBACK;
                        call_sv((SV*)destructor, 
G_DISCARD|G_EVAL|G_KEEPERR|G_VOID);
                        POPSTACK;
                        SPAGAIN;
                        LEAVE;
                        if(SvREFCNT(tmpref) < 2) {
                            /* tmpref is not kept alive! */
                            SvREFCNT(sv)--;
                            SvRV_set(tmpref, NULL);
                            SvROK_off(tmpref);
                        }
                        SvREFCNT_dec(tmpref);
                    }
                } while (SvOBJECT(sv) && SvSTASH(sv) != stash);

                if (SvREFCNT(sv)) {
                    if (PL_in_clean_objs)
                        Perl_croak(aTHX_ "DESTROY created new reference to dead 
object '%s'",
                              HvNAME_get(stash));
                    /* DESTROY gave object new lease on life */
                    return;
                }
#ifdef USE_ITHREADS
            }
#endif
        }

        if (SvOBJECT(sv)) {
            SvREFCNT_dec(SvSTASH(sv));  /* possibly of changed persuasion */
            SvOBJECT_off(sv);   /* Curse the object. */
            if (type != SVt_PVIO)
                --PL_sv_objcount;       /* XXX Might want something more 
general */
        }
    }

    and so on....
-----
A patch to blead for the above is attached.

Attachment: bug.patch
Description: Binary data

Reply via email to