File Coverage

blib/lib/IO/SigGuard.pm
Criterion Covered Total %
statement 31 32 96.8
branch 10 16 62.5
condition 2 5 40.0
subroutine 7 7 100.0
pod 1 3 33.3
total 51 63 80.9


line stmt bran cond sub pod time code
1             package IO::SigGuard;
2              
3             =encoding utf-8
4              
5             =head1 NAME
6              
7             IO::SigGuard - SA_RESTART in pure Perl
8              
9             =head1 SYNOPSIS
10              
11             IO::SigGuard::sysread( $fh, $buf, $size );
12             IO::SigGuard::sysread( $fh, $buf, $size, $offset );
13              
14             IO::SigGuard::syswrite( $fh, $buf );
15             IO::SigGuard::syswrite( $fh, $buf, $len );
16             IO::SigGuard::syswrite( $fh, $buf, $len, $offset );
17              
18             IO::SigGuard::select( $read, $write, $exc, $timeout );
19              
20             =head1 DESCRIPTION
21              
22             C describes how Perl versions from 5.8.0 onward disable
23             the OS’s SA_RESTART flag when installing Perl signal handlers.
24              
25             This module imitates that pattern in pure Perl: it does an automatic
26             restart when a signal interrupts an operation so you can avoid
27             the generally-useless EINTR error when using
28             C, C, and C.
29              
30             For this to work, whatever signal handler you implement will need to break
31             out of this module, probably via either C or C.
32              
33             =head1 ABOUT C and C
34              
35             Other than that you’ll never see EINTR and that
36             there are no function prototypes used (i.e., you need parentheses on
37             all invocations), C and C
38             work exactly the same as Perl’s equivalent built-ins.
39              
40             =head1 ABOUT C
41              
42             To handle EINTR, C has to subtract the elapsed time
43             from the given timeout then repeat the internal C. Because
44             the C built-in’s C<$timeleft> return is not reliable across
45             all platforms, we have to compute the elapsed time ourselves. By default the
46             only means of doing this is the C built-in, which can only measure
47             individual seconds.
48              
49             This works, but there are two ways to make it more accurate:
50              
51             =over
52              
53             =item * Have L loaded, and C will use that
54             module rather than the C built-in.
55              
56             =item * Set C<$IO::SigGuard::TIME_CR> to a compatible code reference. This is
57             useful, e.g., if you have your own logic to do the equivalent of
58             L—for example, in Linux you may prefer to call the C
59             system call directly from Perl to avoid L’s XS overhead.
60              
61             =back
62              
63             In scalar contact, C is a drop-in replacement
64             for Perl’s 4-argument built-in.
65              
66             In list context, there may be discrepancies re the C<$timeleft> value
67             that Perl returns from a call to C
68             this value is generally not reliable anyway, though, so that shouldn’t be a
69             big deal. In fact, on systems like MacOS where the built-in’s C<$timeleft>
70             is completely useless, IO::SigGuard’s return is actually B since it
71             does provide at least a rough estimate of how much of the given timeout value
72             is left.
73              
74             See C for portability notes for C
75              
76             =head1 TODO
77              
78             This pattern could probably be extended to C, C, C, and
79             other system calls that can receive EINTR. If there’s a desire for that I’ll
80             consider adding it.
81              
82             =cut
83              
84 3     3   300031 use strict;
  3         23  
  3         76  
85 3     3   12 use warnings;
  3         6  
  3         67  
86              
87 3     3   775 use Errno ();
  3         2410  
  3         1448  
88              
89             our $VERSION = '0.12';
90              
91             #Set this in lieu of using Time::HiRes or built-in time().
92             our $TIME_CR;
93              
94             #Just in case someone has an actual reason to do this.
95             our $YES_I_REALLY_MEAN_TO_WRITE_ZERO_BYTES;
96             our $TOLERATE_NONERROR_ZERO_WRITE;
97              
98             #As light as possible …
99              
100             my $read;
101              
102             sub sysread {
103             READ: {
104 691 0   691 0 9860 $read = ( (@_ == 3) ? CORE::sysread( $_[0], $_[1], $_[2] ) : (@_ == 4) ? CORE::sysread( $_[0], $_[1], $_[2], $_[3] ) : die "Wrong args count! (@_)" ) or do {
  710 50       7342280  
    100          
105 19 50       476 redo READ if $! == Errno::EINTR();
106             };
107             }
108              
109 691         14335 return $read;
110             }
111              
112             my $wrote;
113              
114             sub syswrite {
115             WRITE: {
116 571082   33 571082 0 2182536 $wrote = ( (@_ == 2) ? CORE::syswrite( $_[0], $_[1] ) : (@_ == 3) ? CORE::syswrite( $_[0], $_[1], $_[2] ) : (@_ == 4) ? CORE::syswrite( $_[0], $_[1], $_[2], $_[3] ) : die "Wrong args count! (@_)" ) || do {
  571082         3944839  
117              
118             #EINTR means the file pointer is unchanged.
119             redo WRITE if $! == Errno::EINTR();
120             };
121             }
122              
123 571082         1262643 return $wrote;
124             }
125              
126             my ($start, $last_loop_time, $os_error, $nfound, $timeleft, $timer_cr);
127              
128             #pre-5.16 didn’t have \&CORE::time.
129 28     28   101 sub _time { time }
130              
131             sub select {
132 6 50   6 1 25858 die( (caller 0)[3] . ' must have 4 arguments!' ) if @_ != 4;
133              
134 6         82 $os_error = $!;
135              
136 6   50     174 $timer_cr = $TIME_CR || Time::HiRes->can('time') || \&_time;
137              
138 6         43 $start = $timer_cr->();
139 6         16 $last_loop_time = $start;
140              
141             SELECT: {
142 6         9 ($nfound, $timeleft) = CORE::select( $_[0], $_[1], $_[2], $_[3] - $last_loop_time + $start );
  28         10022330  
143 28 100       705 if ($nfound == -1) {
144              
145             #Use of %! will autoload Errno.pm,
146             #which can affect the value of $!.
147 22         27140 my $select_error = $!;
148              
149 22 50       114 if ($! == Errno::EINTR()) {
150 22         92 $last_loop_time = $timer_cr->();
151 22         69 redo SELECT;
152             }
153              
154 0         0 $! = $select_error;
155             }
156             else {
157              
158             #select() doesn’t set $! on success, so let’s not clobber what
159             #value was there before.
160 6         16 $! = $os_error;
161             }
162              
163 6 100       35 return wantarray ? ($nfound, $timeleft) : $nfound;
164             }
165             }
166              
167             =head1 REPOSITORY
168              
169             L
170              
171             =head1 AUTHOR
172              
173             Felipe Gasper (FELIPE)
174              
175             … with special thanks to Mario Roy (MARIOROY) for extra testing
176             and a few fixes/improvements.
177              
178             =head1 COPYRIGHT
179              
180             Copyright 2017 by L
181              
182             =head1 LICENSE
183              
184             This distribution is released under the same license as Perl.
185              
186             =cut
187              
188             1;