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