Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Re^4: when is destroy function called

by roboticus (Canon)
on Mar 02, 2013 at 21:35 UTC ( #1021461=note: print w/ replies, xml ) Need Help??


in reply to Re^3: when is destroy function called
in thread when is destroy function called

Athanasius:

Yeah, I could've been more clear...

I meant that when setting up the print statement:

print 'My enemy must ', $enemy->obey(), undef($enemy), '. His phaser is named ', $phaser->{name}, eval('$enemy->exterminate()'), ". Bye!\n";

that I suspect that perl may be putting a reference to the thing $enemy refers to on the stack for the purpose of calling the obey() method (and possibly for the undef thing, too). So that we'd have something like this:

*** Before print statement *** $enemy------------------------->Dalek:{phaser=> + } (ref=1) | +---------------+ v $phaser------------------------>Phaser:{name=> + } (ref=2) | +--------------+ v 'Exterminator' (ref=1) argument stack: -empty- *** After print setup, before print executes *** $enemy------------------------->Dalek:{phaser=> + } (ref=2)<-+ | | +---------------+ | v | $phaser------------------------>Phaser:{name=> + (ref=2) | | | +--------------+ | v | 'Exterminator' (ref=2) | ^ | +--------------+ | argument stack: '. Bye!\n' | | eval('$enemy->exterminate()' | | ref---------------------------+ | '. His phaser is named ' | undef($enemy) | obey()<-ref---------------------------------+ 'My enemy must '

At this point, I'm guessing the stack contains a reference to the Dalek that $enemy also points to, so it can call the obey method.

*** Executing through print, just after undef step *** $enemy--->undef Dalek:{phaser=> + } (ref=1)<-+ | | +---------------+ | v | $phaser------------------------>Phaser:{name=> + (ref=2) | | | +--------------+ | v | 'Exterminator' (ref=2) | ^ | +--------------+ | argument stack: '. Bye!\n' | | eval('$enemy->exterminate()' | | ref---------------------------+ | '. His phaser is named ' | undef($enemy) | obey()<-ref---------------------------------+ 'My enemy must '

Here, we've already executed the obey() method so the string gets printed, and we've just executed undef, so the link between $enemy and the Dalek is now broken. But the argument stack for print still holds the reference to the Dalek.

*** Print just finished, now removing items from stack ... *** $enemy--->undef Dalek:{phaser=> + } (ref=1)<-+ | | +---------------+ | v | $phaser------------------------>Phaser:{name=> + (ref=2) | | | +--------------+ | v | 'Exterminator' (ref=1) | | argument stack: obey()<-ref---------------------------------+ 'My enemy must '

Most of the argument stack for the print statement is gone, the next item to go is the reference of interest. (Note: the reference count to the 'Exterminaor' string has already dropped back to 1.) So perl dutifully reduces the references count for the item on the stack, and notices the count went to 0:

$enemy--->undef Dalek:{phaser=> + } (ref=0)<-+ | | +---------------+ | v | $phaser------------------------>Phaser:{name=> + (ref=2) | | | +--------------+ | v | 'Exterminator' (ref=1) | | argument stack: obey()<-ref---------------------------------+ 'My enemy must '

...at which point it tells the Dalek to go DESTROY itself. While it destroys itself, it discards the reference to the Phaser (reducing its reference count):

$enemy--->undef Dalek:{phaser=> } (ref=0)<-+ | $phaser------------------------>Phaser:{name=> + (ref=1) | | | +--------------+ | v | 'Exterminator' (ref=1) | | argument stack: obey()<-ref---------------------------------+ 'My enemy must '

The Phaser still has a reference, so it's going to stick around. When the DESTROY method returns, perl then frees the Dalek memory, and continues to remove items from the argument stack:

$enemy--->undef {FREE MEMORY } $phaser------------------------>Phaser:{name=>'Exterminator'} (ref=1) argument stack: 'My enemy must '

I guess I should've put a reference count on the 'Exterminator' string, as well, but I didn't think of it until after I drew all of that, and I'm not quite ambitious enough to fix that oversight.

Here's a bit of code I came up with in an attempt to lend some weight to my theory. It's not proof, as I can't think of a way to examine the variables in the middle of stack cleanup (short of running perl under gdb):

#!/usr/bin/perl use Data::Dumper; use Devel::Peek; { package Foo; sub new { my $class = shift; my $val = shift; return bless { thing=>$val }; } sub val { my $self = shift; return "val: " . (++$self->{theVal}); } sub DESTROY { print "destroyed at ", ${shift}->{theVal}, "\n"; } } my $b = [ ]; my $t = Foo->new($b); print "--- before print ---\n"; Dump($b); Dump($t); print "--- Do the print ---\n"; print $t->val(), Dump($b), Dump($t), undef($t), Dump($b), Dump($t), ".\n"; print "--- after print ---\n"; Dump($b); Dump($t);

The results are a bit tedious to read through, though:

$ perl ex_destructo.pl --- before print --- SV = RV(0x8c650b4) at 0x8c650a8 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8c47818 SV = PVAV(0x8c4886c) at 0x8c47818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) SV = RV(0x8c650a4) at 0x8c65098 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8c476f8 SV = PVHV(0x8c4fa64) at 0x8c476f8 REFCNT = 1 FLAGS = (OBJECT,SHAREKEYS) STASH = 0x8c650c8 "Foo" ARRAY = 0x8c719d0 (0:7, 1:1) hash quality = 100.0% KEYS = 1 FILL = 1 MAX = 7 RITER = -1 EITER = 0x0 Elt "thing" HASH = 0x20b61f7d SV = RV(0x8c65054) at 0x8c65048 REFCNT = 1 FLAGS = (ROK) RV = 0x8c47818 SV = PVAV(0x8c4886c) at 0x8c47818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) --- Do the print --- SV = RV(0x8c650b4) at 0x8c650a8 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8c47818 SV = PVAV(0x8c4886c) at 0x8c47818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) SV = RV(0x8c650a4) at 0x8c65098 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8c476f8 SV = PVHV(0x8c4fa64) at 0x8c476f8 REFCNT = 1 FLAGS = (OBJECT,OOK,SHAREKEYS) STASH = 0x8c650c8 "Foo" ARRAY = 0x8c6b8d0 (0:6, 1:2) hash quality = 125.0% KEYS = 2 FILL = 2 MAX = 7 RITER = -1 EITER = 0x0 Elt "theVal" HASH = 0x90484e39 SV = PVIV(0x8c5b04c) at 0x8cd6348 REFCNT = 1 FLAGS = (IOK,POK,pIOK,pPOK) IV = 1 PV = 0x8cd17e0 "1"\0 CUR = 1 LEN = 4 Elt "thing" HASH = 0x20b61f7d SV = RV(0x8c65054) at 0x8c65048 REFCNT = 1 FLAGS = (ROK) RV = 0x8c47818 SV = PVAV(0x8c4886c) at 0x8c47818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) SV = RV(0x8c650b4) at 0x8c650a8 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8c47818 SV = PVAV(0x8c4886c) at 0x8c47818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) SV = RV(0x8c650a4) at 0x8c65098 REFCNT = 1 FLAGS = (PADMY) val: 1. destroyed at --- after print --- SV = RV(0x8c650b4) at 0x8c650a8 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8c47818 SV = PVAV(0x8c4886c) at 0x8c47818 REFCNT = 1 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) SV = RV(0x8c650a4) at 0x8c65098 REFCNT = 1 FLAGS = (PADMY)

Update: I went ahead and updated the diagram to show the reference counts for the 'Exterminated' string as well. It make the node a bit longer, but I think it helps illustrate things. I also added a couple readmore tags to make a long post somewhat shorter.

...roboticus

When your only tool is a hammer, all problems look like your thumb.


Comment on Re^4: when is destroy function called
Select or Download Code
Re^5: when is destroy function called
by Athanasius (Prior) on Mar 04, 2013 at 04:10 UTC

    Hello roboticus,

    Thanks for taking so much trouble in your explanation! I’ve been trying — with limited success :-( — to wrap my head around it for about a day now, which is why I haven’t replied sooner.

    I’m confused by this:

    Here, we’ve already executed the obey() method so the string gets printed, and we’ve just executed undef, so the link between $enemy and the Dalek is now broken. But the argument stack for print still holds the reference to the Dalek.

    I would have expected that each argument is popped from the stack as it is processed, so that its reference count is decremented as it leaves the stack. But I admit I don’t really know how all this works.

    Your use of Devel::Peek is interesting. (Incidentally, I think the expression ${shift}->{theVal} should be shift->{theVal}?) Looking at the reference counts, I can’t see the increment you predict between “before print” and “Do the print” — what should I be looking at?

    I did find this in the section Reference Counts and Mortality in perlguts:

    There are some convenience functions available that can help with the destruction of xVs. These functions introduce the concept of "mortality". An xV that is mortal has had its reference count marked to be decremented, but not actually decremented, until "a short time later". Generally the term "short time later" means a single Perl statement, such as a call to an XSUB function.

    If that passage is relevant to the situation we’ve been discussing, it supports your idea that it is the decrementing of the reference count to zero which is delayed until the print statement completes, and that, when the reference count reaches zero, the destructor is called immediately.

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Athanasius:

      Sorry for the delay in responding, I had to read a lot more than I expected!

      In general, a subroutine won't remove arguments from its argument list unless explicitly told to (shift,pop) in the subroutine. Especially as the subroutine might either use the argument again later, or might pass its complete argument list to another subroutine for handling. In C, the caller builds the stack with arguments, calls the function, and when the function returns the caller removes the items from the stack. That's the mental model I have of perl (at this time), for right or wrong.

      Regarding the reference counts: the output is pretty tedious to read. Here's a stripped version of the program in which we'll look just at $b. It makes tracing the refcount to the anonymous array easier to track:

      #!/usr/bin/perl use Devel::Peek; { package Foo; sub new { my $class = shift; my $val = shift; return bless { thing=>$val }; } sub val { my $self = shift; return "val: " . (++$self->{theVal}); } sub DESTROY { print "destroyed at ", ${shift}->{theVal}, "\n"; } } my $b = [ ]; print "--- before building t ---\n"; Dump($b); my $t = Foo->new($b); print "--- after building t ---\n"; Dump($b); print "--- printing 1 ---\n"; print $t->val(), Dump($b), undef($t), "--- printing 2---\n", Dump($b), ".\n"; print "--- after print ---\n"; Dump($b);

      Here you can see that the reference count start at 1 (line x), and increase to 2 when we build t. It's 2 when we start the print, and still 2 after the undef. But immediately after the print, it's back to 1 (and we see the destroy text).

      --- before building t --- SV = RV(0x81df0cc) at 0x81df0c0 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x81c1818 SV = PVAV(0x81c286c) at 0x81c1818 REFCNT = 1 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) --- after building t --- SV = RV(0x81df0cc) at 0x81df0c0 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x81c1818 SV = PVAV(0x81c286c) at 0x81c1818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) --- printing 1 --- SV = RV(0x81df0cc) at 0x81df0c0 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x81c1818 SV = PVAV(0x81c286c) at 0x81c1818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) SV = RV(0x81df0cc) at 0x81df0c0 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x81c1818 SV = PVAV(0x81c286c) at 0x81c1818 REFCNT = 2 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL) val: 1--- printing 2--- . destroyed at --- after print --- SV = RV(0x81df0cc) at 0x81df0c0 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x81c1818 SV = PVAV(0x81c286c) at 0x81c1818 REFCNT = 1 FLAGS = () ARRAY = 0x0 FILL = -1 MAX = -1 ARYLEN = 0x0 FLAGS = (REAL)

      After checking your pointer to Reference Counts and Mortality in perlguts, it led me a lot of reading. I think you're correct: the section http://perldoc.perl.org/perlcall.html#Passing-Parameters in perlcall shows an example call to LeftString from C. It shows the caller building the argument list, and destroying them afterwards with FREETMPS. In the notes below the example, note 5 clinches it (for me, anyhow).

      Now that I've done all that reading, maybe I should try to dig into the perl source again and try to wrap my brain around some of it....

      ...roboticus

      When your only tool is a hammer, all problems look like your thumb.

        roboticus:

        Thanks for the explanation. As a test, I augmented the example I gave above with an equivalent function in C. (This was my first experience using Inline::C. To call the Perl sub from within C, I copied the example in the section “Calling Perl from C” of the Inline::C-Cookbook.)

        #! perl use strict; use warnings; $| = 1; use Inline C => <<'END_C'; void baz(SV* foo_ref) { printf("Begin baz()\n"); fflush(stdin); dSP; ENTER; SAVETMPS; XPUSHs(sv_2mortal(newSVpvf("Plus an extra line"))); PUTBACK; call_pv("destruct", G_DISCARD); printf("Before FREETMPS\n"); fflush(stdin); FREETMPS; LEAVE; printf("-End- baz()\n"); fflush(stdin); } END_C { package Foo; sub new { my ($class, $num) = @_; my $self = { id => $num, }; return bless $self, $class; } sub DESTROY { my ($self) = @_; print 'Foo::DESTROY(', $self->{id}, ")\n"; } } my $foo1 = Foo->new('first'); my $foo2 = Foo->new('second'); bar($foo1); print "Back in main (1)\n"; baz($foo2); print "Back in main (2)\n"; sub bar { print "Begin bar()\n"; destruct($_[0]); print "-End- bar()\n"; } sub destruct { undef $_[0]; }

        Output:

        22:55 >perl 552c_SoPW.pl Begin bar() Foo::DESTROY(first) -End- bar() Back in main (1) Begin baz() Foo::DESTROY(second) Before FREETMPS -End- baz() Back in main (2) 22:56 >

        From which it seems that the object is destroyed — meaning its reference count has fallen to zero — before the FREETMPS macro is reached. In which case the addition of the object to the sub’s call stack has not caused its reference count to be incremented? (Or am I simply misunderstanding here?)

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://1021461]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (20)
As of 2014-07-11 18:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    When choosing user names for websites, I prefer to use:








    Results (235 votes), past polls