Fix pure-perl implementation incorrectly reporting GD during END phase (liz++)
[p5sagit/Devel-GlobalDestruction.git] / lib / Devel / GlobalDestruction.pm
1 package Devel::GlobalDestruction;
2
3 use strict;
4 use warnings;
5
6 our $VERSION = '0.08';
7
8 use Sub::Exporter::Progressive -setup => {
9   exports => [ qw(in_global_destruction) ],
10   groups  => { default => [ -all ] },
11 };
12
13 # we run 5.14+ - everything is in core
14 #
15 if (defined ${^GLOBAL_PHASE}) {
16   eval 'sub in_global_destruction () { ${^GLOBAL_PHASE} eq q[DESTRUCT] }';
17 }
18 # try to load the xs version if it was compiled
19 #
20 elsif (eval {
21   require XSLoader;
22   XSLoader::load(__PACKAGE__, $VERSION);
23   1;
24 }) {
25   # the eval already installed everything, nothing to do
26 }
27 # Not core nor XS
28 # The whole thing is in an eval to prevent perl from parsing it in the
29 # first place under perls where none of this is needed
30 #
31 else {
32   eval <<'PP_IGD' or die $@;
33
34 # SpeedyCGI runs END blocks every cycle but somehow keeps object instances
35 # hence DIAF
36 die("The pure-perl version of @{[__PACKAGE__]} can not function correctly under CGI::SpeedyCGI. "
37   . "Please ensure you have a working compiler, and reinstall @{[__PACKAGE__]} to enable the XS "
38   . "codepath.\n"
39 ) if $CGI::SpeedyCGI::i_am_speedy;
40
41 my ($in_global_destruction, $before_is_installed);
42
43 sub in_global_destruction () { $in_global_destruction }
44
45 # end_av trick suggested by liz++
46 require B;
47 my $add_endblock = sub {
48   push @{ B::end_av()->object_2svref }, sub { $in_global_destruction = 1 };
49 };
50
51 # This block will fire towards the end of the program execution
52 # Use it to inject an END block which is guaranteed to run last
53 # (as long as something else doesn't inject yet another block in
54 # the same manner afterwards, at which point it hardly matters
55 # anyway)
56 #
57 END { $add_endblock->() }
58
59 # threads do not execute the global ENDs (it would be stupid). However
60 # one can register a new END via simple string eval within a thread, and
61 # achieve the same result. A logical place to do this would be CLONE, which
62 # is claimed to run in the context of the new thread. However this does
63 # not really seem to be the case - any END evaled in a CLONE is ignored :(
64 # Hence blatantly hooking threads::create
65 #
66 if ($INC{'threads.pm'}) {
67   my $orig_create = threads->can('create');
68   no warnings 'redefine';
69   *threads::create = sub {
70     { local $@; eval 'END { $in_global_destruction = 1 }' };
71     goto $orig_create;
72   };
73   $before_is_installed = 1;
74 }
75
76 # just in case threads got loaded after us (silly)
77 sub CLONE {
78   unless ($before_is_installed) {
79     require Carp;
80     Carp::croak("You must load the 'threads' module before @{[ __PACKAGE__ ]}");
81   }
82 }
83
84 1;  # keep eval happy
85
86 PP_IGD
87
88 }
89
90 1;  # keep require happy
91
92
93 __END__
94
95 =head1 NAME
96
97 Devel::GlobalDestruction - Expose the flag which marks global
98 destruction.
99
100 =head1 SYNOPSIS
101
102     package Foo;
103     use Devel::GlobalDestruction;
104
105     use namespace::clean; # to avoid having an "in_global_destruction" method
106
107     sub DESTROY {
108         return if in_global_destruction;
109
110         do_something_a_little_tricky();
111     }
112
113 =head1 DESCRIPTION
114
115 Perl's global destruction is a little tricky to deal with WRT finalizers
116 because it's not ordered and objects can sometimes disappear.
117
118 Writing defensive destructors is hard and annoying, and usually if global
119 destruction is happenning you only need the destructors that free up non
120 process local resources to actually execute.
121
122 For these constructors you can avoid the mess by simply bailing out if global
123 destruction is in effect.
124
125 =head1 EXPORTS
126
127 This module uses L<Sub::Exporter::Progressive> so the exports may be renamed,
128 aliased, etc. if L<Sub::Exporter> is present.
129
130 =over 4
131
132 =item in_global_destruction
133
134 Returns true if the interpreter is in global destruction. In perl 5.14+, this
135 returns C<${^GLOBAL_PHASE} eq 'DESTRUCT'>, and on earlier perls, it returns the
136 current value of C<PL_dirty>.
137
138 =back
139
140 =head1 AUTHORS
141
142 Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
143
144 Florian Ragwitz E<lt>rafl@debian.orgE<gt>
145
146 Jesse Luehrs E<lt>doy@tozt.netE<gt>
147
148 Peter Rabbitson E<lt>ribasushi@cpan.orgE<gt>
149
150 Arthur Axel 'fREW' Schmidt E<lt>frioux@gmail.comE<gt>
151
152 Elizabeth Mattijsen E<lt>liz@dijkmat.nlE<gt>
153
154 =head1 COPYRIGHT
155
156     Copyright (c) 2008 Yuval Kogman. All rights reserved
157     This program is free software; you can redistribute
158     it and/or modify it under the same terms as Perl itself.
159
160 =cut