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