25f44afa7e5c41bf90c02dc768cbd063c14a15c0
[p5sagit/namespace-clean.git] / lib / namespace / clean / _PP_OSE.pm
1 package # hide from the pauses
2   namespace::clean::_PP_OSE;
3
4 use warnings;
5 use strict;
6
7 use Tie::Hash;
8 use Hash::Util::FieldHash 'fieldhash';
9
10 # Here we rely on a combination of several behaviors:
11 #
12 # * %^H is deallocated on scope exit, so any references to it disappear
13 # * A lost weakref in a fieldhash causes the corresponding key to be deleted
14 # * Deletion of a key on a tied hash triggers DELETE
15 #
16 # Therefore the DELETE of a tied fieldhash containing a %^H reference will
17 # be the hook to fire all our callbacks.
18 #
19 # The SUPER:: gimmick is there to ensure the fieldhash is cleaned up in a
20 # timely manner. When you call delete() in non-void context, you get a mortal
21 # scalar whose reference count decreases at the end of the current statement.
22 # During scope exit, â€˜statement’ is not clearly defined, so more scope
23 # unwinding could happen before the mortal gets freed. Forcing the DELETE
24 # in void context localizes the life of the mortal scalar.
25
26 fieldhash my %hh;
27 {
28   package namespace::clean::_TieHintHashFieldHash;
29   use base 'Tie::StdHash';
30   sub DELETE {
31     $_->() for @{ $_[0]->{$_[1]} };
32     shift->SUPER::DELETE(@_);
33     1; # put the preceding statement in void context so the free is immediate
34   }
35 }
36
37 sub on_scope_end (&) {
38   $^H |= 0x020000;
39
40   tie(%hh, 'namespace::clean::_TieHintHashFieldHash')
41     unless tied %hh;
42
43   push @{ $hh{\%^H} ||= [] }, shift;
44 }
45
46 1;