File Coverage

blib/lib/IO/SigGuard.pm
Criterion Covered Total %
statement 30 32 93.7
branch 8 16 50.0
condition 2 5 40.0
subroutine 7 7 100.0
pod 1 3 33.3
total 48 63 76.1


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   263257 use strict;
  3         28  
  3         87  
85 3     3   16 use warnings;
  3         5  
  3         77  
86              
87 3     3   939 use Errno ();
  3         2731  
  3         1549  
88              
89             our $VERSION = '0.03-TRIAL2';
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 690 0   690 0 8924 $read = ( (@_ == 3) ? CORE::sysread( $_[0], $_[1], $_[2] ) : (@_ == 4) ? CORE::sysread( $_[0], $_[1], $_[2], $_[3] ) : die "Wrong args count! (@_)" ) or do {
  690 50       7019544  
    50          
105 0 0       0 redo READ if $! == Errno::EINTR();
106             };
107             }
108              
109 690         11501 return $read;
110             }
111              
112             my $wrote;
113              
114             sub syswrite {
115             WRITE: {
116 651344   33 651344 0 2307227 $wrote = ( (@_ == 2) ? CORE::syswrite( $_[0], $_[1] ) : (@_ == 3) ? CORE::syswrite( $_[0], $_[1], $_[2] ) : (@_ == 4) ? CORE::syswrite( $_[0], $_[1], $_[2], $_[3] ) : die "Wrong args count! (@_)" ) || do {
  651344         3885735  
117              
118             #EINTR means the file pointer is unchanged.
119             redo WRITE if $! == Errno::EINTR();
120             };
121             }
122              
123 651344         1292964 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   106 sub _time { time }
130              
131             sub select {
132 6 50   6 1 20913 die( (caller 0)[3] . ' must have 4 arguments!' ) if @_ != 4;
133              
134 6         94 $os_error = $!;
135              
136 6   50     199 $timer_cr = $TIME_CR || Time::HiRes->can('time') || \&_time;
137              
138 6         36 $start = $timer_cr->();
139 6         18 $last_loop_time = $start;
140              
141             SELECT: {
142 6         11 ($nfound, $timeleft) = CORE::select( $_[0], $_[1], $_[2], $_[3] - $last_loop_time + $start );
  28         10032596  
143 28 100       736 if ($nfound == -1) {
144              
145             #Use of %! will autoload Errno.pm,
146             #which can affect the value of $!.
147 22         13547 my $select_error = $!;
148              
149 22 50       148 if ($! == Errno::EINTR()) {
150 22         104 $last_loop_time = $timer_cr->();
151 22         87 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         21 $! = $os_error;
161             }
162              
163 6 100       28 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;