| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package IO::BufferedSelect; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 23416 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 35 |  | 
| 4 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 25 |  | 
| 5 | 1 |  |  | 1 |  | 890 | use IO::Select; | 
|  | 1 |  |  |  |  | 1985 |  | 
|  | 1 |  |  |  |  | 618 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 NAME | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | IO::BufferedSelect - Line-buffered select interface | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | use IO::BufferedSelect; | 
| 14 |  |  |  |  |  |  | my $bs = new BufferedSelect($fh1, $fh2); | 
| 15 |  |  |  |  |  |  | while(1) | 
| 16 |  |  |  |  |  |  | { | 
| 17 |  |  |  |  |  |  | my @ready = $bs->read_line(); | 
| 18 |  |  |  |  |  |  | foreach(@ready) | 
| 19 |  |  |  |  |  |  | { | 
| 20 |  |  |  |  |  |  | my ($fh, $line) = @$_; | 
| 21 |  |  |  |  |  |  | my $fh_name = ($fh == $fh1 ? "fh1" : "fh2"); | 
| 22 |  |  |  |  |  |  | print "$fh_name: $line"; | 
| 23 |  |  |  |  |  |  | } | 
| 24 |  |  |  |  |  |  | } | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | The C | 
| 29 |  |  |  |  |  |  | multiple streams simultaneously, blocking until one or more of them is ready for | 
| 30 |  |  |  |  |  |  | reading or writing.  Unfortunately, this requires us to use C and | 
| 31 |  |  |  |  |  |  | C rather than Perl's buffered I/O functions.  In the case of reading, | 
| 32 |  |  |  |  |  |  | there are two issues with combining C | 
| 33 |  |  |  |  |  |  | might block but the data we want is already in Perl's input buffer, ready to | 
| 34 |  |  |  |  |  |  | be slurped in by C; and (2) C might indicate that data is | 
| 35 |  |  |  |  |  |  | available, but C will block because there isn't a full | 
| 36 |  |  |  |  |  |  | C<$/>-terminated line available. | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | The purpose of this module is to implement a buffered version of the C | 
| 39 |  |  |  |  |  |  | interface that operates on I, rather than characters.  Given a set of | 
| 40 |  |  |  |  |  |  | filehandles, it will block until a full line is available on one or more of | 
| 41 |  |  |  |  |  |  | them. | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | Note that this module is currently limited, in that (1) it only does C | 
| 44 |  |  |  |  |  |  | for readability, not writability or exceptions; and (2) it does not support | 
| 45 |  |  |  |  |  |  | arbitrary line separators (C<$/>): lines must be delimited by newlines. | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | =cut | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | our $VERSION = '1.0'; | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | =head1 CONSTRUCTOR | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | =over | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | =item new ( HANDLES ) | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | Create a C object for a set of filehandles.  Note that because | 
| 58 |  |  |  |  |  |  | this class buffers input from these filehandles internally, you should B | 
| 59 |  |  |  |  |  |  | use the C object for reading from them (you shouldn't read from | 
| 60 |  |  |  |  |  |  | them directly or pass them to other BufferedSelect instances). | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =back | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =cut | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | sub new($@) | 
| 67 |  |  |  |  |  |  | { | 
| 68 | 0 |  |  | 0 | 1 |  | my $class   = shift; | 
| 69 | 0 |  |  |  |  |  | my @handles = @_; | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 0 |  |  |  |  |  | my $self = { handles  => \@handles, | 
| 72 | 0 |  |  |  |  |  | buffers  => [ map { '' } @handles ], | 
| 73 | 0 |  |  |  |  |  | eof      => [ map { 0 } @handles ], | 
| 74 |  |  |  |  |  |  | selector => new IO::Select( @handles ) }; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 0 |  |  |  |  |  | return bless $self; | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | =head1 METHODS | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | =over | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | =item read_line | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =item read_line ($timeout) | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | =item read_line ($timeout, @handles) | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | Block until a line is available on one of the filehandles.  If C<$timeout> is | 
| 90 |  |  |  |  |  |  | C, it blocks indefinitely; otherwise, it returns after at most | 
| 91 |  |  |  |  |  |  | C<$timeout> seconds. | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | If C<@handles> is specified, then only these filehandles will be considered; | 
| 94 |  |  |  |  |  |  | otherwise, it will use all filehandles passed to the constructor. | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | Returns a list of pairs S>, where C<$fh> is a filehandle and | 
| 97 |  |  |  |  |  |  | C<$line> is the line that was read (including the newline, ala C).  If | 
| 98 |  |  |  |  |  |  | the filehandle reached EOF, then C<$line> will be undef.  Note that "reached | 
| 99 |  |  |  |  |  |  | EOF" is to be interpreted in the buffered sense: if a filehandle is at EOF but | 
| 100 |  |  |  |  |  |  | there are newline-terminated lines in C's buffer, C | 
| 101 |  |  |  |  |  |  | will continue to return lines until the buffer is empty. | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | =cut | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | sub read_line($;$@) | 
| 106 |  |  |  |  |  |  | { | 
| 107 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 108 | 0 |  |  |  |  |  | my ($timeout, @handles) = @_; | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | # Convert @handles to a "set" of indices | 
| 111 | 0 |  |  |  |  |  | my %use_idx = (); | 
| 112 | 0 | 0 |  |  |  |  | if(@handles) | 
| 113 |  |  |  |  |  |  | { | 
| 114 | 0 |  |  |  |  |  | foreach my $idx( 0..$#{$self->{handles}} ) | 
|  | 0 |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | { | 
| 116 | 0 | 0 |  |  |  |  | $use_idx{$idx} = 1 if grep { $_ == $self->{handles}->[$idx] } @handles; | 
|  | 0 |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  | else | 
| 120 |  |  |  |  |  |  | { | 
| 121 | 0 |  |  |  |  |  | $use_idx{$_} = 1 foreach( 0..$#{$self->{handles}} ); | 
|  | 0 |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 0 |  |  |  |  |  | for( my $is_first = 1 ; 1 ; $is_first = 0 ) | 
| 125 |  |  |  |  |  |  | { | 
| 126 |  |  |  |  |  |  | # If we have any lines in buffers, return those first | 
| 127 | 0 |  |  |  |  |  | my @result = (); | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 0 |  |  |  |  |  | foreach my $idx( 0..$#{$self->{handles}} ) | 
|  | 0 |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | { | 
| 131 | 0 | 0 |  |  |  |  | next unless $use_idx{$idx}; | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 0 | 0 |  |  |  |  | if($self->{buffers}->[$idx] =~ s/(.*\n)//) | 
|  |  | 0 |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | { | 
| 135 | 0 |  |  |  |  |  | push @result, [ $self->{handles}->[$idx], $1 ]; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  | elsif($self->{eof}->[$idx]) | 
| 138 |  |  |  |  |  |  | { | 
| 139 |  |  |  |  |  |  | # NOTE: we discard any unterminated data at EOF | 
| 140 | 0 |  |  |  |  |  | push @result, [ $self->{handles}->[$idx], undef ]; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | # Only give it one shot if $timeout is defined | 
| 145 | 0 | 0 | 0 |  |  |  | return @result if ( @result or (defined($timeout) and !$is_first) ); | 
|  |  |  | 0 |  |  |  |  | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | # Do a select(), optionally with a timeout | 
| 148 | 0 |  |  |  |  |  | my @ready = $self->{selector}->can_read( $timeout ); | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # Read into $self->{buffers} | 
| 151 | 0 |  |  |  |  |  | foreach my $fh( @ready ) | 
| 152 |  |  |  |  |  |  | { | 
| 153 | 0 |  |  |  |  |  | foreach my $idx( 0..$#{$self->{handles}} ) | 
|  | 0 |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | { | 
| 155 | 0 | 0 |  |  |  |  | next unless $fh == $self->{handles}->[$idx]; | 
| 156 | 0 | 0 |  |  |  |  | next unless $use_idx{$idx}; | 
| 157 | 0 |  |  |  |  |  | my $bytes = sysread $fh, $self->{buffers}->[$idx], 1024, length $self->{buffers}->[$idx]; | 
| 158 | 0 | 0 |  |  |  |  | $self->{eof}->[$idx] = 1 if($bytes == 0); | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | 1; | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | __END__ |