Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Re: Trying to DESTROY() an object

by kennethk (Abbot)
on Dec 08, 2015 at 16:26 UTC ( [id://1149684]=note: print w/replies, xml ) Need Help??


in reply to SOLVED: Trying to DESTROY() a (closure-wrapped) object

Update: The problem description below is incorrect, but the design question is still appropriate. See 1149699 for a solution. The problem you are hitting is that there is no guaranteed time for garbage collection. Therefore, garbage collection hasn't happen on the very short time scale for your test.

For the demo case, the simplest way of getting your desired output would be localizing, e.g.,

print One::foo(); { my $count = Count->new; no warnings 'redefine'; local *One::foo = sub { $count->{x} = 'x'; return "baz\n"; }; print One::foo(); } print One::foo();
You could also use mock to bless into a subclass One::Debug that looks like:
package One::Debug; our @ISA = 'One'; sub foo { return "baz\n"; } 1;
Really, what's going on here is a bit of an XY Problem. You need to think on how to best encapsulate your patching to cover only your particular problem.

Incidentally, what you are trying use is traditionally called a guard. At least, I think that's what you are trying to do...


#11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Replies are listed 'Best First'.
Re^2: Trying to DESTROY() an object
by Eily (Monsignor) on Dec 08, 2015 at 16:40 UTC

    While I agree that there might be a bit of XY under the question, the problem is not because garbage collection does not happen soon enough. Since the closure (the sub reference in *One::foo) holds the $self scalar, the reference count never falls down to 0, so even if stevieb's program ran for a longer period of time, the object would not be garbage collected. Your solution though, by deleting the sub at the end of the scope also happens to delete its hold over the reference, which makes the deletion of the object possible.

      Somehow I hadn't grokked that in the scenario that $thing is a Count it gets cached in the sub. That is a problem solvable via weaken by saying
      sub mock { my $thing = shift; my $self; if (ref($thing) eq __PACKAGE__){ $self = $thing; } else { $self = bless {}, __PACKAGE__; } my $closure_self = $self; use Scalar::Util 'weaken'; weaken $closure_self; $self->{sub} = \&One::foo; *One::foo = sub { $closure_self->{x} = 'x'; return "baz\n"; }; return $self; }
      which outputs
      foo baz destroying... foo
      as per the spec. Note that the $closure_self misdirect is necessary because of the Class method invocation:
      print One::foo(); { #my $count = Count->new; my $bar = Count->mock; print One::foo(); } print One::foo();
      which would yield
      foo Use of uninitialized value in subroutine dereference at script.pl line + 31. destroying... baz baz (in cleanup) Unable to create sub named "" at script.pl line 31.
      Because $self immediately goes out of scope. It's still a weird misdirection since you are localizing a subroutine clobber and not changing an object behavior.

      #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

        Hello kennethk, You'll have to post a full code for that last output, because I have read your post more than five times and still don't understand the difference between weakening $self or weakening a copy of it.

        Because $self immediately goes out of scope.
        Well, $self and $closure_self are both lexicals of the same scope, so they both go out of scope at the same time, and the closure can close over any of the two and it shouldn't make any difference. By the way, here under perl v5.20, with my proposed code, I could never reproduce your last output:
        main.pl
        use warnings; no warnings 'redefine'; use strict; use lib '.'; use Count; print One::foo(); { my $bar = Count->mock; print One::foo(); } print One::foo();
        Count.pm
        package One; sub foo { return "foo\n"; } 1; package Count; use Scalar::Util qw/weaken/; sub new { return bless {}, shift; } sub unmock { my $self = shift; *One::foo = \&{ $self->{sub} }; } sub mock { my $thing = shift; my $self; if (ref($thing) eq __PACKAGE__){ $self = $thing; } else { $self = bless {}, __PACKAGE__; } weaken $self; $self->{sub} = \&One::foo; *One::foo = sub { $self->{x} = 'x'; return "baz\n"; }; return $self; } sub DESTROY { my $self = shift; print "destroying...\n"; $self->unmock; } 1;

        Thanks kennethk.

        I also found that instead of doing the object redirect, I can weaken() inside of the sub re-definition on $self directly:

        *$sub = sub { weaken $self if ! isweak $self; ... }

        I've been doing some testing with both scenarios to see if without the $closure_self, weird issues crop up.

Re^2: Trying to DESTROY() an object (!gc)
by tye (Sage) on Dec 09, 2015 at 05:24 UTC
    The problem you are hitting is that there is no guaranteed time for garbage collection. Therefore, garbage collection hasn't happen on the very short time scale for your test.

    You must be thinking of some language other than Perl v5. You describe a weakness of many languages. But Perl v5 doesn't have that problem.

    - tye        

      Sorry, I neglected to correct that chunk with the rest of the conversation. Updated appropriately.

      #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (7)
As of 2024-04-24 20:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found