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

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.

Replies are listed 'Best First'.
Re^3: Trying to DESTROY() an object
by kennethk (Abbot) on Dec 08, 2015 at 17:59 UTC
    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;

        use warnings; use strict; use lib '.'; #use Count; print One::foo(); { #my $count = Count->new; my $bar = Count->mock; print One::foo(); } print One::foo(); BEGIN{ package One; sub foo { return "foo\n"; } 1; package Count; 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__; } use Scalar::Util 'weaken'; weaken $self; $self->{sub} = \&One::foo; { no warnings 'redefine'; *One::foo = sub { $self->{x} = 'x'; return "baz\n"; }; } return $self; } sub DESTROY { my $self = shift; print "destroying...\n"; $self->unmock; } 1; }
        outputs
        foo Use of uninitialized value in subroutine dereference at script.pl line + 30. destroying... baz baz (in cleanup) Unable to create sub named "" at script.pl line 30.
        If you weaken $self then you immediately lower the reference count to 0. If you copy $self to $closure_self then you raise the count to 2, then lower it to 1. $self does not go out of scope until after the return, at which point you've passed an external reference to your main block and thus still have a ref count of 1.

        I ran your posted code under ActiveState 5.20, and got the output

        foo destroying... baz baz
        and if I swap the chunk in question to
        my $closure_self = $self; weaken $closure_self; $self->{sub} = \&One::foo; *One::foo = sub { $closure_self->{x} = 'x'; return "baz\n"; }; return $self;
        I get
        foo baz destroying... foo
        Your code cannot restore the old function, because your object was out of scope before your assignment. This could be demonstrated by using Data::Dump in DESTROY:
        sub DESTROY { use Data::Dump; my $self = shift; print "destroying...\n"; print dd($self), "\n"; $self->unmock; }
        which, for your code, outputs
        foo destroying... bless({}, "Count") 1 baz baz
        and for mine
        foo baz destroying... bless({ sub => sub { ... }, x => "x" }, "Count") 1 foo

        You will also note you'll start getting the errors and warnings I posted if you put strict and warnings in your modules instead of just in your script.

        This all raises the point that unmock is actually written terribly, because it creates a whole additional closure about $self. It should really be

        sub unmock { my $self = shift; *One::foo = $self->{sub}; }
        so that you restore the original sub reference.

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

      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.

        I don't think that does what you want. The reference will never be weakened if you never call sub.

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