File Coverage

blib/lib/PerlIO/via/Timeout.pm
Criterion Covered Total %
statement 83 92 90.2
branch 20 40 50.0
condition 18 39 46.1
subroutine 19 20 95.0
pod 5 7 71.4
total 145 198 73.2


line stmt bran cond sub pod time code
1             #
2             # This file is part of PerlIO-via-Timeout
3             #
4             # This software is copyright (c) 2013 by Damien "dams" Krotkine.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9             package PerlIO::via::Timeout;
10             {
11             $PerlIO::via::Timeout::VERSION = '0.29';
12             }
13              
14             # ABSTRACT: a PerlIO layer that adds read & write timeout to a handle
15              
16             require 5.008;
17 1     1   36225 use strict;
  1         3  
  1         47  
18 1     1   6 use warnings;
  1         2  
  1         33  
19 1     1   7 use Carp;
  1         2  
  1         160  
20 1     1   1640 use Errno qw(EBADF EINTR ETIMEDOUT);
  1         2328  
  1         161  
21 1     1   10 use Scalar::Util qw(reftype blessed weaken);
  1         3  
  1         225  
22              
23 1     1   6 use Exporter 'import'; # gives you Exporter's import() method directly
  1         3  
  1         1595  
24              
25             our @EXPORT_OK = qw(read_timeout write_timeout enable_timeout disable_timeout timeout_enabled);
26              
27             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
28              
29              
30             sub _get_fd {
31             # params: FH
32 40 100   40   712 $_[0] or return;
33 34         80 my $fd = fileno $_[0];
34 34 50 33     1373 defined $fd && $fd >= 0
35             or return;
36 34         185 $fd;
37             }
38              
39             my %fd2prop;
40              
41             sub _fh2prop {
42             # params: self, $fh
43 22   33 22   63 my $prop = $fd2prop{ my $fd = _get_fd $_[1]
44             or croak 'failed to get file descriptor for filehandle' };
45 22 100       76 wantarray and return ($prop, $fd);
46 10         38 return $prop;
47             }
48              
49             sub PUSHED {
50             # params CLASS, MODE, FH
51 6     6 0 44846 $fd2prop{_get_fd $_[2]} = { timeout_enabled => 1, read_timeout => 0, write_timeout => 0};
52 6         313 bless {}, $_[0];
53             }
54              
55             sub POPPED {
56             # params: SELF [, FH ]
57 6   50 6 0 22 delete $fd2prop{_get_fd($_[1]) or return};
58             }
59              
60             sub CLOSE {
61             # params: SELF, FH
62 6   50 6   3582 delete $fd2prop{_get_fd($_[1]) or return -1};
63 6 50       86 close $_[1] or -1;
64             }
65              
66             sub READ {
67             # params: SELF, BUF, LEN, FH
68 9     9   252 my ($self, undef, $len, $fh) = @_;
69              
70             # There is a bug in PerlIO::via (possibly in PerlIO ?). We would like
71             # to return -1 to signify error, but doing so doesn't work (it usually
72             # segfault), it looks like the implementation is not complete. So we
73             # return 0.
74 9         36 my ($prop, $fd) = __PACKAGE__->_fh2prop($fh);
75              
76 9         88 my $timeout_enabled = $prop->{timeout_enabled};
77 9         94 my $read_timeout = $prop->{read_timeout};
78              
79 9         13 my $offset = 0;
80 9         21 while ($len) {
81 9 50 100     102 if ( $timeout_enabled && $read_timeout && $len && ! _can_read_write($fh, $fd, $read_timeout, 0)) {
      66        
      66        
82 1   50     20 $! ||= ETIMEDOUT;
83 1         39 return 0;
84             }
85 8         3006184 my $r = sysread($fh, $_[1], $len, $offset);
86 8 50       29 if (defined $r) {
    0          
87 8 50       20 last unless $r;
88 8         12 $len -= $r;
89 8         24 $offset += $r;
90             }
91             elsif ($! != EINTR) {
92             # There is a bug in PerlIO::via (possibly in PerlIO ?). We would like
93             # to return -1 to signify error, but doing so doesn't work (it usually
94             # segfault), it looks like the implementation is not complete. So we
95             # return 0.
96 0         0 return 0;
97             }
98             }
99 8         71 return $offset;
100             }
101              
102             sub WRITE {
103             # params: SELF, BUF, FH
104 3     3   39 my ($self, undef, $fh) = @_;
105              
106 3         22 my ($prop, $fd) = __PACKAGE__->_fh2prop($fh);
107              
108 3         9 my $timeout_enabled = $prop->{timeout_enabled};
109 3         6 my $write_timeout = $prop->{write_timeout};
110              
111 3         7 my $len = length $_[1];
112 3         5 my $offset = 0;
113 3         34 while ($len) {
114 3 50 66     32 if ( $len && $timeout_enabled && $write_timeout && ! _can_read_write($fh, $fd, $write_timeout, 1)) {
      66        
      33        
115 0   0     0 $! ||= ETIMEDOUT;
116 0         0 return -1;
117             }
118 3         792 my $r = syswrite($fh, $_[1], $len, $offset);
119 3 50       13 if (defined $r) {
    0          
120 3         6 $len -= $r;
121 3         16 $offset += $r;
122 3 50       13 last unless $len;
123             }
124             elsif ($! != EINTR) {
125 0         0 return -1;
126             }
127             }
128 3         40 return $offset;
129             }
130              
131             sub _can_read_write {
132 1     1   10 my ($fh, $fd, $timeout, $type) = @_;
133             # $type: 0 = read, 1 = write
134 1         3 my $initial = time;
135 1         6 my $pending = $timeout;
136 1         13 my $nfound;
137              
138 1         5 vec(my $fdset = '', $fd, 1) = 1;
139              
140 1         11 while () {
141 1 50       5 if ($type) {
142             # write
143 0         0 $nfound = select(undef, $fdset, undef, $pending);
144             } else {
145             # read
146 1         500585 $nfound = select($fdset, undef, undef, $pending);
147             }
148 1 50       9 if ($nfound == -1) {
149 0 0       0 $! == EINTR
150             or croak(qq/select(2): '$!'/);
151 0 0 0     0 redo if !$timeout || ($pending = $timeout - (time -
152             $initial)) > 0;
153 0         0 $nfound = 0;
154             }
155 1         21 last;
156             }
157 1         6 $! = 0;
158 1         14 return $nfound;
159             }
160              
161              
162             sub read_timeout {
163 5     5 1 102 my $prop = __PACKAGE__->_fh2prop($_[0]);
164 5 100 50     47 @_ > 1 and $prop->{read_timeout} = $_[1] || 0, _check_attributes($prop);
165 5         63 $prop->{read_timeout};
166             }
167              
168              
169             sub write_timeout {
170 3     3 1 24 my $prop = __PACKAGE__->_fh2prop($_[0]);
171 3 50 0     11 @_ > 1 and $prop->{write_timeout} = $_[1] || 0, _check_attributes($prop);
172 3         34 $prop->{write_timeout};
173             }
174              
175              
176             sub _check_attributes {
177 2 50   2   10 grep { $_[0]->{$_} < 0 } qw(read_timeout write_timeout)
  4         52  
178             and croak "if defined, 'read_timeout' and 'write_timeout' attributes should be >= 0";
179             }
180              
181              
182 0     0 1 0 sub enable_timeout { timeout_enabled($_[0], 1) }
183              
184              
185 1     1 1 36 sub disable_timeout { timeout_enabled($_[0], 0) }
186              
187              
188             sub timeout_enabled {
189 2     2 1 26 my $prop = __PACKAGE__->_fh2prop($_[0]);
190 2 100       24 @_ > 1 and $prop->{timeout_enabled} = !!$_[1];
191 2         28 $prop->{timeout_enabled};
192             }
193              
194             1;
195              
196             __END__