in reply to Re^2: semi-panic: attempt to dup freed string?
in thread semi-panic: attempt to dup freed string?

Hypothesis: SVs are owned by the tread in which they were created. When a thread exits, any SVs it owns are freed.

By the way, using a string buffer to store a pointer is a waste. An IV is guaranteed to be big enough. Or you can use the PV itself (using SvPV_set) if you set LEN to zero.

  • Comment on Re^3: semi-panic: attempt to dup freed string?

Replies are listed 'Best First'.
Re^4: semi-panic: attempt to dup freed string?
by BrowserUk (Patriarch) on Mar 24, 2011 at 21:56 UTC
    Hypothesis: SVs are owned by the tread in which they were created. When a thread exits, any SVs it owns are freed.

    Nice hypothesis!++

    By the way, using a string buffer to store a pointer is a waste. An IV is guaranteed to be big enough. Or you can use the PV itself (using SvPV_set) if you set LEN to zero.

    Indeed. Or, as RVs and IVs are now aliases for the same thing, skip the intermediary completely, which appears to fix the problems Update: No it doesn't. I was so happy to see the back of the panic, that I missed that the contained value is updated by the thread :(

    #! perl -slw use strict; package O; use Inline C => Config => BUILD_NOISY => 1; use Inline C => <<'END_C', NAME => 'xso', CLEAN_AFTER_BUILD => 0; typedef struct { SV**sv; } O; void *mem( size_t size ) { void *p; Newx( p, size, char ); return p; } SV *new( char *package ) { O *o = (O*)mem( sizeof( O ) ); SV *rv = newRV( o ); o->sv = mem( sizeof( SV* ) ); sv_bless( rv, gv_stashpv( package, 0 ) ); SvREADONLY_on( rv ); printf( "N:rv:%p o:%p\n", rv, o ); return rv; } int set( SV *rv, SV *in ) { O *o = *(O**)SvRV( rv ); printf( "S:rv;%p o:%p\n", rv, o ); o->sv = newSVsv( in ); return 1; } SV *get( SV *rv ) { O *o = *(O**)SvRV( rv ); SV *old = newSVsv( o->sv ); SvREFCNT_inc( old ); printf( "G:rv;%p o:%p old:%p\n", rv, o, old ); return old; } void DESTROY( SV *rv ) { printf( "DESTROY:%s\n", SvPV_nolen( rv ) ); } void CLONE( SV *rv ) { printf( "CLONE:%s\n", SvPV_nolen( rv ) ); } END_C package main; use threads; use Devel::Peek; my $o = O->new(); print $o; $o->set( "abcde" ); print $o->get(); print "\nthreaded\n"; async { $o->set( "12345" ); }->join; print $o->get(); __END__ C:\test>xso N:rv:000000000025E128 o:00000000040D6738 O=SCALAR(0x40d6738) S:rv;000000000025E248 o:0000000003CB6088 G:rv;000000000025E248 o:0000000003CB6088 old:00000000002B7980 abcde threaded CLONE:O S:rv;0000000004361010 o:000000000432F5F8 DESTROY:O=SCALAR(0x4361028) G:rv;000000000025E248 o:0000000003CB6088 old:00000000002B79E0 abcde DESTROY:O=SCALAR(0x40d6738)

    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.

      I'm guessing you're trying to avoid the overhead of :shared magic? Are you trying to share scalar between two threads or just transfer a scalar from one thread to another? Does it have to be a scalar, or can it just be a string?

        I'm looking to share a blessed reference to any arbitrary lump of memory--the struct in this example.

        Skipping the intermediate SV, ie. RV->struct rather than RV->SvUV->struct seemed like a good idea as it avoided the subject error, but it doesn't work because when the RV is cloned, Perl assumes it's value is a SV* and clones that "SV" also, which screws things completely.

        Using RV->SvUV->struct recreates the original problem.

        I think that I should be able to prevent the SvUV being destroyed prematurely by having it's refcount incremented by the cloning process and decremented by the DESTROY method. The problem is that the CLONE method is called as a class method, not an instance method making it impossible to take full control of the cloning method.

        I don;t currently see a way around this?


        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^4: semi-panic: attempt to dup freed string?
by BrowserUk (Patriarch) on Mar 25, 2011 at 08:31 UTC
    By the way, using a string buffer to store a pointer is a waste. An IV is guaranteed to be big enough.

    I think the reason people place pointers into the string buffer of the PV is to circumvent perl's overzealous CLONEing code from messing with it.

    If you try RV->pointer_to_non_SV, you cannot bless the RV because perl check what is pointed at.

    If you try RV->SvUV->pointer_to_nonSV, you can bless the Rv, but when CLONE gets it hands on it, it not only clones the RV and the SvUV, but because the SvUVs are aliased for SvRVs, it also assumes that the value in the SvUV is an SV* and clones that also. At least that is what seems to happen?

    By going the RV->SvPV->string(containing pointer_to_non_SV), CLONE does expect the string to contain a pointer and so copies the original string value verbatim.

    It is a messy roundabout route, but I don't see a viable alternative? And even that doesn't seem to work properly.

    Grr. Why is none of this shit documented? Where are the people that know how this stuff works?


    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.

      but because the SvUVs are aliased for SvRVs, it also assumes that the value in the SvUV is an SV* and clones that also.

      If Perl assume all UVs were really pointers to SVs, it would crash left and right. Either the scalar is a reference (ROK) or a UV (IOK + IsUV). What you describe will only happen for the former. I didn't suggest using a reference.

        If Perl assume all UVs were really pointers to SVs, it would crash left and right.

        Remember, I'm only suggesting this can happen during cloning, not normal operations. Remember that SvRVs & SvIVs used to be different types. Maybe the cloning code, written before this amalgamation, only checked for SvTYPE and has never been updated to further distinguish RVs from IV/UVs?

        Maybe you have a better explanation for this observed behaviour:

        ... package main; use threads; use Devel::Peek; my $o = O->new(); print $o; $o->set( "abcde" ); print $o->get(); print "\nthreaded\n"; async { print $o->get( ); $o->set( 12345 ); print $o->get( ); }->join; print $o->get(); __END__ C:\test>xso N:rv:000000000035E128 o:0000000003FFA818 O=SCALAR(0x3ffa818) S:rv;000000000035E248 o:0000000003C318F0 G:rv;000000000035E248 o:0000000003C318F0 old:000000000035E128 abcde threaded G:rv;0000000004258CA8 o:0000000004229090 old:0000000000000000 S:rv;0000000004258CA8 o:0000000004229090 G:rv;0000000004258CA8 o:0000000004229090 old:0000000004259920 12345 G:rv;000000000035E248 o:0000000003C318F0 old:000000000035E128 abcde

        Before the thread, I set a value into the struct, and get it back.

        Then I move into the thread, the RV gets cloned, the SvUV it points to gets cloned; but the value within the SvUV (labelled 'o:' in the printfs), also changes. And as the numeric value inside the UV (which we know is a pointer but Perl shouldn't), has also changed. It no longer points to the original struct. Hence, 'old:' no longer has a value.

        I then insert a different value into the struct, and successfully retrieve that value. I then exit the thread and, and get the value again, and lo, I get the original value set, not the one I modified inside the thread.

        No, the *only* explanation that I can come up with for that behaviour is that when the RV and UV get cloned, it also clones the thing that the UV points to; even though it shouldn't know that the numberic value in the UV is a pointer. Do you have a better explanation?

        The full code:

        #! perl -slw use strict; package O; use Inline C => Config => BUILD_NOISY => 1; use Inline C => <<'END_C', NAME => 'xso', CLEAN_AFTER_BUILD => 0; typedef struct { SV**sv; } O; void *mem( size_t size ) { void *p; Newx( p, size, char ); return p; } SV *new( char *package ) { O *o = (O*)mem( sizeof( O ) ); SV *rv = newRV( (SV*)o ); o->sv = mem( sizeof( SV* ) ); sv_bless( rv, gv_stashpv( package, 0 ) ); SvREADONLY_on( rv ); printf( "N:rv:%p o:%p\n", rv, o ); return rv; } int set( SV *rv, SV *in ) { O *o = ( *(O**)SvUV( rv ) ) - 1; printf( "S:rv;%p o:%p\n", rv, o ); o->sv = newSVsv( in ); return 1; } SV *get( SV *rv ) { O *o = ( *(O**)SvUV( rv ) ) - 1; SV *old = newSVsv( o->sv ); SvREFCNT_inc( old ); printf( "G:rv;%p o:%p old:%p\n", rv, o, o->sv ); return old; } END_C package main; use threads; use Devel::Peek; my $o = O->new(); print $o; $o->set( "abcde" ); print $o->get(); print "\nthreaded\n"; async { print $o->get( ); $o->set( 12345 ); print $o->get( ); }->join; print $o->get();

        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.