creamygoodness has asked for the wisdom of the Perl Monks concerning the following question:
Greets,
I'm having some trouble manipulating refcounts at the C level to prevent an object's DESTROY method from being called. What I'd like to do is increment an object's reference count from C, do a lot of processing which may involve several trips across the Perl/C boundary, then decrement the object's reference count from C, triggering destruction.
Here's a script which illustrates the problem using our old favorite, the CD metaphor:
#!/usr/bin/perl use strict; use warnings; use Inline C => <<'END_C'; typedef struct Artist { char *name; SV *perlobj; } Artist; typedef struct CD { Artist *artist; /* ... */ } CD; /* Constructor for Artist. */ SV* new_artist(char *name) { SV *perlobj, *perlref; Artist *artist; /* allocate and initialize Artist struct */ New(0, artist, 1, Artist); artist->name = savepv(name); /* create perl object and ref; store pointer to object in struct * +/ perlref = newSV(0); artist->perlobj = sv_setref_pv(perlref, "Artist", (void*)artist); return perlref; } /* Constructor for CD. */ SV* new_cd(SV *artist_sv) { CD *cd; Artist *artist = INT2PTR( Artist*, SvIV(SvRV(artist_sv)) ); SV *cd_ref; /* allocate and initialize CD struct */ New(0, cd, 1, CD); cd->artist = artist; /* WHY DOESN'T THIS SAVE THE ARTIST FROM DESTRUCTION? */ SvREFCNT_inc(artist->perlobj); cd_ref = newSV(0); sv_setref_pv(cd_ref, "CD", (void*)cd); return cd_ref; } /* getter */ SV* get_name_from_artist(SV *artist_sv) { Artist *artist = INT2PTR( Artist*, SvIV(SvRV(artist_sv)) ); return newSVpv(artist->name, 0); } /* getter */ SV* get_artist_name_from_cd(SV *cd_sv) { CD *cd = INT2PTR( CD*, SvIV(SvRV(cd_sv)) ); return newSVpv(cd->artist->name, 0); } /* Destructor for Artist objects */ void destroy_artist(SV *artist_sv) { Artist *artist = INT2PTR( Artist*, SvIV(SvRV(artist_sv)) ); Safefree(artist->name); Safefree(artist); } /* Destructor for CD objects */ void destroy_cd(SV *cd_sv) { CD *cd = INT2PTR( CD*, SvIV(SvRV(cd_sv)) ); SvREFCNT_dec(cd->artist->perlobj); Safefree(cd); } END_C package Artist; use strict; use warnings; use Devel::Peek qw( Dump ); sub DESTROY { my $artist = shift; my $artist_name = main::get_name_from_artist($artist); print STDERR "Artist::DESTROY called.\n" . "$artist_name commits suicide -- but why?\n" . "Somebody still loves you! You have a refcount of 2:\n"; Dump($artist); main::destroy_artist($artist); } package CD; use strict; use warnings; sub DESTROY { my $cd = shift; print STDERR "CD::DESTROY called.\n"; main::destroy_cd($cd); } package main; use strict; use warnings; # Create an Artist object. my $artist = new_artist("Johnny Houseburner"); # Save a copy of the Artist object in a CD object. my $johnny_houseburner_cd = new_cd($artist); # Displace the old Artist object in Perl-space. No destruction should + occur, # because the CD object holds a reference. But that doesn't work -- D +ESTROY # gets called. $artist = new_artist("FunThree"); my $funthree_cd = new_cd($artist); # access invalid memory. my $whats_his_name = get_artist_name_from_cd($johnny_houseburner_cd); print STDERR "What was that guy's name again? $whats_his_name?\n";
And here's its output:
$ perl cd_artist.plx Artist::DESTROY called. Johnny Houseburner commits suicide -- but why? Somebody still loves you! You have a refcount of 2: SV = RV(0x182186c) at 0x1856bf8 REFCNT = 1 FLAGS = (PADBUSY,PADMY,ROK) RV = 0x1801380 SV = PVMG(0x1822080) at 0x1801380 REFCNT = 2 FLAGS = (OBJECT,IOK,pIOK) IV = 3880080 NV = 0 PV = 0 STASH = 0x180b56c "Artist" What was that guy's name again? yAGS? CD::DESTROY called. CD::DESTROY called. Bus error $
As soon as the last Perl-space reference to the Artist object goes away, Artist::DESTROY gets called, despite the fact that the reference count of the object is 2. If things were working properly, Artist::DESTROY wouldn't get called until after CD::DESTROY gets called.
We can forestall the destruction by making a copy in Perl:
... and in fact, a directly analogous maneuver of creating a new perl scalar reference from within C succeeds as well...my $copy = $artist; $artist = new_artist("FunThree");
typedef struct CD { Artist *artist; + SV *artist_ref; /* ... */ } CD; @@ -43,7 +44,7 @@ cd->artist = artist; /* WHY DOESN'T THIS SAVE THE ARTIST FROM DESTRUCTION? */ - SvREFCNT_inc(artist->perlobj); + cd->artist_ref = newSVsv(artist_sv); cd_ref = newSV(0); sv_setref_pv(cd_ref, "CD", (void*)cd); @@ -77,7 +78,7 @@ void destroy_cd(SV *cd_sv) { CD *cd = INT2PTR( CD*, SvIV(SvRV(cd_sv)) ); - SvREFCNT_dec(cd->artist->perlobj); + SvREFCNT_dec(cd->artist_ref); Safefree(cd); }
What I don't understand, though, is how creating a new reference via newSVsv differs materially from simply incrementing the reference count of the target object. And while it's possible to add a foo_ref member for each foo member, that's messy and verbose.
/* Yecch. */ typedef struct BloatedObject { Foo *foo; SV *foo_ref; Bar *bar; SV *bar_ref; } BloatedObject; void destroy_bloated_object(BloatedObject *obj) { SvREFCNT_dec(obj->foo_ref); SvREFCNT_dec(obj->bar_ref); Safefree(obj); } /* Better. */ typedef struct LeanObject { Foo *foo; Bar *bar; } LeanObject; void destroy_lean_object(LeanObject *obj) { SvREFCNT_dec(obj->foo->perlobj); SvREFCNT_dec(obj->bar->perlobj); Safefree(obj); }
Suggestions?
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: XS: Manipulating refcounts
by mugwumpjism (Hermit) on Sep 19, 2006 at 06:00 UTC | |
by creamygoodness (Curate) on Sep 19, 2006 at 06:56 UTC | |
by syphilis (Archbishop) on Sep 19, 2006 at 11:34 UTC | |
by creamygoodness (Curate) on Sep 19, 2006 at 11:59 UTC | |
|
Re: XS: Manipulating refcounts
by demerphq (Chancellor) on Sep 19, 2006 at 09:38 UTC | |
by creamygoodness (Curate) on Sep 19, 2006 at 11:16 UTC | |
|
Re: XS: Manipulating refcounts
by Animator (Hermit) on Sep 20, 2006 at 06:34 UTC | |
by Animator (Hermit) on Sep 20, 2006 at 11:19 UTC | |
by creamygoodness (Curate) on Sep 20, 2006 at 18:04 UTC | |
by creamygoodness (Curate) on Sep 20, 2006 at 16:20 UTC | |
by Animator (Hermit) on Sep 20, 2006 at 18:21 UTC | |
by creamygoodness (Curate) on Sep 20, 2006 at 19:11 UTC |