File Coverage

blib/lib/Whatif.pm
Criterion Covered Total %
statement 37 42 88.1
branch 8 12 66.6
condition n/a
subroutine 7 7 100.0
pod 0 2 0.0
total 52 63 82.5


line stmt bran cond sub pod time code
1             package Whatif;
2              
3 1     1   19299 use strict;
  1         2  
  1         28  
4 1     1   764 use POSIX ();
  1         6521  
  1         24  
5 1     1   8 use Carp qw(croak);
  1         1  
  1         44  
6 1     1   4 use base qw(Exporter DynaLoader);
  1         2  
  1         116  
7 1     1   5 use vars qw(@EXPORT $VERSION $ERR);
  1         1  
  1         312  
8             @EXPORT = qw(whatif ifonly);
9              
10              
11             $VERSION = '1.3';
12              
13              
14             $ERR = undef;
15              
16              
17             bootstrap Whatif $VERSION;
18              
19              
20             # do all the magic
21             sub whatif (&;$) {
22 18     18 0 1131 my ($whatif, $ifonly) = @_;
23              
24 18         41 $ERR = undef;
25 18         171 my $dollardollar = $$;
26            
27             # the way to communicate between the two different versions
28 18         35 my ($in, $out);
29 18         1176 pipe $in, $out;
30              
31             # SPLITTERS!
32 18         59216 my $pid = fork;
33 18 50       658 die "couldn't fork" unless defined $pid;
34              
35              
36             # parent
37 18 100       1525 if ($pid) {
38 8         497 close $out;
39 8         77292 my $got = <$in>;
40              
41             # child succeded, we shut up shop and wait for it to die
42 8 50       282 unless ($got) {
43             # close all open file handles
44 0         0 foreach (0..POSIX::sysconf(&POSIX::_SC_OPEN_MAX)) {
45 0         0 POSIX::close($_);
46             }
47              
48             # wait for the child to die so that we can
49 0         0 waitpid($pid, 0);
50 0         0 POSIX::_exit(0);
51             }
52              
53              
54             # the child failed, set the error ...
55 8         52 $Whatif::ERR = $got;
56              
57             # ... and if we've been given an ifonly block then run it
58 8 100       730 $ifonly->() if (defined $ifonly);
59            
60              
61             # child
62             } else {
63 10         957 close $in;
64             # run the code we been given
65              
66             # some shennanigans, knicked from PPerl
67 10 50       260 if ($] > 5.006001) {
68 10         2946 setreadonly('$', $dollardollar);
69             } else {
70 0         0 $$ = $dollardollar;
71             }
72            
73 10         261 eval { $whatif->() };
  10         583  
74 10         627 print $out $@;
75 10         14630 close $out;
76              
77 10 50       1071 POSIX::_exit(0) if $@;
78             }
79             }
80              
81             # hack
82 5     5 0 3136 sub ifonly (&) { $_[0] }
83              
84              
85             1;
86              
87             =pod
88              
89             =head1 NAME
90              
91             Whatif - provides rollbacks, second chances and ways to overcomes regrets in code
92              
93             =head1 SYNOPSIS
94              
95             my $foo = "foo";
96              
97             whatif {
98             $foo = "bar";
99             }; # foo is now "bar"
100              
101              
102             whatif {
103             $foo = "quux";
104             die;
105             }; # foo is still "bar", the call got rolled backed
106              
107              
108             whatif {
109             $foo = "yoo hoo!";
110             } ifonly {
111             $foo = "erk";
112             }; # foo will be "yoo hoo"
113              
114             whatif {
115             $foo = "here";
116             die "Aaaargh\n";
117             } ifonly {
118             $foo = "there";
119             print Whatif::ERR; # prints Aaaargh
120             }; # foo will be "there"
121              
122             print Whatif::ERR; # also prints Aaaargh
123              
124             whatif {
125             die;
126             };
127             print Whatif::ERR; # prints undef
128              
129             $foo = "outer";
130             whatif {
131             $foo = "middle";
132             whatif { $foo = "inner" };
133             }; # $foo is "inner";
134              
135             $foo = "outer";
136             whatif {
137             $foo = "middle";
138             whatif { $foo = "inner"; die };
139             }; # $foo is "middle";
140              
141              
142            
143             B the semi-colon after the I block - without it you may get odd results;
144              
145              
146             =head1 DESCRIPTION
147              
148             Whatif provides database-like rollbacks but for code instead of
149             database transactions. Think of I blocks as being like
150             I blocks but on steroids.
151              
152             Essentially, if you die within a I block then all code up
153             until that point will be undone. Let's face it we all have regrets and
154             if we can't solve them in software then where can we solve them?
155              
156             But that's not all. Whatif not only provides a way out of that
157             horrible 'OHMYGOD! What have I done?' moments but also gives you a
158             second chance using our special sauce 'Guardian Angel[tm]' technology
159             (patent pending).
160              
161             Simply place an I block after a I block and,
162             should the I block fail, all the code in the I
163             block will be executed. Que convenient!
164              
165             If only life itself could be like that.
166              
167             =head1 BUGS
168              
169             This won't work on systems that don't have fork(). Sorry. I tried to
170             come up with some code that worked by intercepting all writes to %:: but
171             that just became a nightmare and B advised me against it.
172             Then I tried something like
173              
174              
175             void do_magic(SV* coderef)
176             {
177             PerlInterpreter *orig, *copy;
178              
179             orig = Perl_get_context();
180             copy = perl_clone(orig, FALSE);
181              
182             PERL_SET_CONTEXT(copy);
183             perl_call_sv(coderef, G_DISCARD|G_NOARGS|G_EVAL);
184              
185             /* Errk, it failed */
186             if (SvTRUE(ERRSV)) {
187             fprintf(stderr, "Errrrk\n");
188             PERL_SET_CONTEXT(orig);
189             perl_free(copy);
190             /* ooh, it was fine */
191             } else {
192             perl_free(orig);
193             }
194              
195             }
196              
197              
198             but that would have only worked on threaded Perls (i.e 5.8) and, err,
199             didn't work anyway. And after a few hours poking through perlguts and
200             various websites I just went with the current approach.
201              
202             This also won't work where you touch the world outside of Perl's
203             control. Basically if you write something to a socket or a file or a DB
204             then you're going to have to undo your mess yourself. That's what the
205             I block is for. There's nothing I can do about that. Deal.
206              
207              
208             =head1 NOTES AND THANKS
209              
210             B and I came up with the idea not, surprisingly, down the
211             pub but whilst trying to sanitise the house we were moving out of. I imagine
212             that the fumes probably had something to do with it and also suspect
213             he deliberately planted the most crack fuelled idea he could
214             think of into my brain, wound me up and let me go.
215              
216             The current method of I-ing was devised by B who
217             basically gave me pretty much the whole module short of packaging it and
218             providing the I implementation. However he has more sense
219             than I do.
220              
221             B helped with the I testing by
222             patiently typing in semi-lucid commands that I barked at him via IRC
223             whilst I tried random things out without the benefit of my own threaded
224             5.8 box.
225              
226             B's PPerl provided the code
227             for setting readonly variables thanks to patches from the ever helpful
228             B. This means that your PID stays the same even after a
229             successful I block.
230              
231             I'd also like to thank my make up B, my B and you, the B
232             for making all this possible.
233              
234              
235              
236             =head1 COPYING
237              
238             (C)opyright 2002, Simon Wistow
239              
240             Distributed under the same terms as Perl itself.
241              
242             This software is under no warranty and will probably destroy your life,
243             kill your friends, burn your house and bring about the apocalypse
244              
245             =head1 AUTHOR
246              
247             Simon Wistow
248              
249             =cut
250              
251