Here goes nothing
[p5sagit/Exception-Guaranteed.git] / lib / Exception / Guaranteed.pm
CommitLineData
3233f09d 1package Exception::Guaranteed;
2
3use warnings;
4use strict;
5
6our $VERSION = '0.00_06';
7$VERSION = eval $VERSION if $VERSION =~ /_/;
8
9use Config;
10use Carp qw/croak cluck/;
11
12use base 'Exporter';
13our @EXPORT = ('guarantee_exception');
14our @EXPORT_OK = ('guarantee_exception');
15
16# this is the minimum acceptable threads.pm version, before it
17# inter-thread signalling may not work right (or is totally missing)
18use constant THREADS_MIN_VERSION => '1.39';
19
20# older perls segfault if the cref behind the goto throws
21# Perl RT#35878
22use constant BROKEN_GOTO => ($] < 5.008_008_1);
23
24# kill (and signaling) plain doesn't work on win32 (works on cygwin though)
25use constant RUNNING_IN_HELL => ($^O eq 'MSWin32');
26
27# perls up until 5.12 (inclusive) seem to be happy with self-signaling
28# newer ones however segfault, so we resort to a killer sentinel fork
29use constant BROKEN_SELF_SIGNAL => (!RUNNING_IN_HELL and $] > 5.012_9);
30
31# win32 can only simulate signals with threads - off we go
32# loading them as early as we can
33if (RUNNING_IN_HELL) {
34 require threads;
35 threads->import;
36}
37elsif (BROKEN_SELF_SIGNAL) {
38 require POSIX; # for POSIX::_exit below
39}
40
41# fail early
42if ($INC{'threads.pm'} and ! eval { threads->VERSION(THREADS_MIN_VERSION) }) {
43 die "At least threads @{[THREADS_MIN_VERSION]} is required in a threaded environment\n";
44}
45
46=head1 NAME
47
48Exception::Guaranteed - Throw exceptions from anywhere - including DESTROY callbacks
49
50=head1 DESCRIPTION
51
52TODO
53
54=cut
55
56my $in_global_destroy;
57END { $in_global_destroy = 1 }
58
59# sig-to-number
60my $sigs = do {
61 my $s;
62 for (split /\s/, $Config{sig_name}) {
63 $s->{$_} = scalar keys %$s;
64 }
65
66 # we do not allow use of these signals
67 delete @{$s}{qw/ZERO ALRM KILL SEGV ILL BUS CHLD/};
68 $s;
69};
70
71# not a plain sub declaration - we want to inline as much
72# as possible into the signal handler when we create it
73# without having to do any extra ENTERSUBs
74my $in_destroy_eval_src = <<'EOS';
75do {
76 if (defined $^S and !$^S) {
77 0;
78 }
79 else {
80 # we can always skip the first 2 frames because we are called either
81 # from the __in_destroy_eval sub generated below whic is called by guarantee_exception
82 # OR
83 # we are called from a signal handler where the first 2 frames are the SIG and an eval
84 my ($f, $r) = 2;
85 while (my $called_sub = (caller($f++))[3] ) {
86 if ($called_sub eq '(eval)') {
87 last
88 }
89 elsif ($called_sub =~ /::DESTROY$/) {
90 $r = 1;
91 }
92 }
93
94 $r;
95 }
96}
97EOS
98
99# we also call it externally, so declare a plain sub as well
100eval "sub __in_destroy_eval { $in_destroy_eval_src }";
101
102
103my $guarantee_state = {};
104sub guarantee_exception (&;@) {
105 my ($cref, $signame) = @_;
106
107 # use SIGABRT unless asked otherwise (available on all OSes afaict)
108 $signame ||= 'ABRT';
109
110 # because throwing any exceptions here is a delicate thing, we make the
111 # exception text and then try real hard to throw when it's safest to do so
112 my $sigwrong = do {sprintf
113 "The requested signal '%s' is not valid on this system, use one of %s",
114 $_[0],
115 join ', ', map { "'$_'" } sort { $sigs->{$a} <=> $sigs->{$b} } keys %$sigs
116 } if (! defined $sigs->{$signame} );
117
118 croak $sigwrong if ( defined $^S and !$^S and $sigwrong );
119
120 if (
121 $in_global_destroy
122 or
123 $guarantee_state->{nested}
124 ) {
125 croak $sigwrong if $sigwrong;
126
127 return $cref->() if BROKEN_GOTO;
128
129 @_ = (); goto $cref;
130 }
131
132 local $guarantee_state->{nested} = 1;
133
134 my (@result, $err);
135 {
136 local $@; # not sure this localization is necessary
137 eval {
138 croak $sigwrong if $sigwrong;
139
140 {
141 my $orig_sigwarn = $SIG{__WARN__} || sub { CORE::warn $_[0] };
142 local $SIG{__WARN__} = sub { $orig_sigwarn->(@_) unless $_[0] =~ /^\t\Q(in cleanup)/ };
143
144 my $orig_sigdie = $SIG{__DIE__} || sub {};
145 local $SIG{__DIE__} = sub { ($err) = @_; $orig_sigdie->(@_) };
146
147 if (!defined wantarray) {
148 $cref->();
149 }
150 elsif (wantarray) {
151 @result = $cref->();
152 }
153 else {
154 $result[0] = $cref->();
155 }
156 }
157
158 # a DESTROY-originating exception will not stop execution, but will still
159 # land the error into $SIG{__DIE__} which places it in $err
160 die $err if defined $err;
161
162 1;
163 } and return ( wantarray ? @result : $result[0] ); # return on successfull eval{}
164 }
165
166### if we got this far - the eval above failed
167### just plain die if we can
168 die $err unless __in_destroy_eval();
169
170### we are in a destroy eval, can't just throw
171### prepare the ninja-wizard exception guarantor
172 if ($sigwrong) {
173 cluck "Unable to set exception guarantor - invalid signal '$signame' requested. Proceeding in undefined state...";
174 die $err;
175 }
176
177 my $use_threads = (
178 RUNNING_IN_HELL
179 or
180 ($INC{'threads.pm'} and threads->tid != 0)
181 );
182 if ($use_threads and ! eval { threads->VERSION(THREADS_MIN_VERSION) } ) {
183 cluck "Unable to set exception guarantor thread - minimum of threads @{[THREADS_MIN_VERSION()]} required. Proceeding in undefined state...";
184 die $err;
185 }
186
187 # non-localized, restorable from within the callback
188 my $orig_handlers = {
189 $signame => $SIG{$signame},
190 BROKEN_SELF_SIGNAL ? ( CHLD => $SIG{CHLD} ) : (),
191 };
192
193 # use a string eval, minimize time spent in the handler
194 # the longer we are here, the further the main thread/fork will
195 # drift down its op-tree
196 my $sig_handler = $SIG{$signame} = eval( sprintf
197 q|sub {
198 if (%s) {
199 %s
200 }
201 else {
202 for (keys %%$orig_handlers) { # sprintf hence the %%
203 if (defined $orig_handlers->{$_}) {
204 $SIG{$_} = $orig_handlers->{$_};
205 }
206 else {
207 delete $SIG{$_};
208 }
209 }
210 die $err;
211 }
212 }|,
213
214 $in_destroy_eval_src,
215
216 $use_threads ? __gen_killer_src_threads ($sigs->{$signame}, $$) :
217 BROKEN_SELF_SIGNAL ? __gen_killer_src_sentinel ($sigs->{$signame}, $$) :
218 __gen_killer_src_selfsig ($sigs->{$signame}, $$)
219 ) or warn "Coderef fail!\n$@";
220
221 # start the kill-loop
222 $sig_handler->();
223}
224
225
226sub __gen_killer_src_threads {
227 return sprintf <<'EOH', $_[0];
228
229 threads->create(
230 sub { $_[0]->kill(%d) },
231 threads->self
232 )->detach;
233EOH
234}
235
236sub __gen_killer_src_sentinel {
237 sprintf <<'EOH', $_[0], $_[1];
238
239 # the SIGCHLD handling is taken care of at the callsite
240 my $killer_pid = fork();
241 if (! defined $killer_pid) {
242 die "Unable to fork ($!) while trying to guarantee the following exception:\n$err";
243 }
244 elsif (!$killer_pid) {
245 kill (%d, %d);
246 POSIX::_exit(0);
247 }
248
249EOH
250}
251
252sub __gen_killer_src_selfsig {
253 "kill( $_[0], $_[1] );"
254}
255
256=head1 AUTHOR
257
258ribasushi: Peter Rabbitson <ribasushi@cpan.org>
259
260=head1 CONTRIBUTORS
261
262None as of yet
263
264=head1 COPYRIGHT
265
266Copyright (c) 2011 the Exception::Guaranteed L</AUTHOR> and L</CONTRIBUTORS>
267as listed above.
268
269=head1 LICENSE
270
271This library is free software and may be distributed under the same terms
272as perl itself.
273
274=cut
275
2761;
277
2781;