File Coverage

blib/lib/PerlIO/via/Timeout.pm
Criterion Covered Total %
statement 83 94 88.3
branch 20 42 47.6
condition 18 39 46.1
subroutine 19 21 90.4
pod 6 8 75.0
total 146 204 71.5


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