Commit | Line | Data |
b2e54862 |
1 | package # hide from the pauses |
2 | namespace::clean::_PP_OSE; |
3 | |
4 | use warnings; |
5 | use strict; |
6 | |
b2e54862 |
7 | use Tie::Hash; |
8 | use Hash::Util::FieldHash 'fieldhash'; |
9 | |
aabe8f1c |
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. |
b2e54862 |
25 | |
26 | fieldhash my %hh; |
b2e54862 |
27 | { |
28 | package namespace::clean::_TieHintHashFieldHash; |
29 | use base 'Tie::StdHash'; |
30 | sub DELETE { |
aabe8f1c |
31 | $_->() for @{ $_[0]->{$_[1]} }; |
b2e54862 |
32 | shift->SUPER::DELETE(@_); |
aabe8f1c |
33 | 1; # put the preceding statement in void context so the free is immediate |
b2e54862 |
34 | } |
35 | } |
36 | |
b2e54862 |
37 | sub on_scope_end (&) { |
38 | $^H |= 0x020000; |
39 | |
40 | tie(%hh, 'namespace::clean::_TieHintHashFieldHash') |
41 | unless tied %hh; |
42 | |
aabe8f1c |
43 | push @{ $hh{\%^H} ||= [] }, shift; |
b2e54862 |
44 | } |
45 | |
46 | 1; |