File Coverage

blib/lib/IO/AsyncX/Sendfile.pm
Criterion Covered Total %
statement 41 45 91.1
branch 12 22 54.5
condition n/a
subroutine 9 9 100.0
pod n/a
total 62 76 81.5


line stmt bran cond sub pod time code
1             package IO::AsyncX::Sendfile;
2             # ABSTRACT: sendfile support for IO::Async
3 1     1   74492 use strict;
  1         1  
  1         37  
4 1     1   5 use warnings;
  1         1  
  1         45  
5              
6             our $VERSION = '0.002';
7              
8             =head1 NAME
9              
10             IO::AsyncX::Sendfile - adds support for L to L
11              
12             =head1 VERSION
13              
14             version 0.002
15              
16             =head1 SYNOPSIS
17              
18             $stream->sendfile(
19             file => 'somefile',
20             )->on_done(sub {
21             $stream->close;
22             });
23              
24             =head1 DESCRIPTION
25              
26             B: This is currently a proof-of-concept, the actual API may vary in later
27             versions. Eventually this functionality will be incorporated into the generic async
28             filehandling API, so this module is provided as a workaround in the interim.
29              
30             Provides a L method on L.
31              
32             =cut
33              
34 1     1   416 use Sys::Sendfile;
  1         524  
  1         54  
35 1     1   5 use Fcntl qw(SEEK_SET SEEK_END);
  1         1  
  1         46  
36 1     1   559 use Future;
  1         6480  
  1         32  
37 1     1   668 use IO::Async::Stream;
  1         19530  
  1         303  
38              
39             =head1 METHODS
40              
41             Note that these methods are injected directly into L.
42              
43             =cut
44              
45             =head2 sendfile
46              
47             Write the contents of the file directly to the socket without reading it
48             into memory first (using the kernel's sendfile call if available).
49              
50             Called with the following named parameters:
51              
52             =over 4
53              
54             =item * file - if defined, this will be used as the filename to open
55              
56             =item * fh - if defined, we'll use this as the filehandle
57              
58             =item * length - if defined, send this much data from the file (default is
59             'everything from current position to end')
60              
61             =back
62              
63             Returns a L which will be resolved with the number of bytes written
64             when successful.
65              
66             Example usage:
67              
68             my $listener = $loop->listen(
69             addr => {
70             family => 'unix',
71             socktype => 'stream',
72             path => 'sendfile.sock',
73             },
74             on_stream => sub {
75             my $stream = shift;
76             $stream->configure(
77             on_read => sub {
78             my ($self, $buffref, $eof) = @_;
79             $$buffref = '';
80             return 0;
81             },
82             );
83             if('send one file') {
84             $stream->sendfile(
85             file => 'test.dat',
86             )->on_done(sub {
87             warn "File send complete: @_\n";
88             $stream->close;
89             });
90             } else {
91             $stream->sendfile(file => 'first.dat');
92             $stream->sendfile(file => 'second.dat');
93             $stream->write('EOF', on_flush => sub { shift->close });
94             }
95             $loop->add($stream);
96             }
97             );
98              
99             If the sendfile call fails, the returned L will fail with the
100             string exception from $! as the failure reason, with sendfile => numeric $!,
101             remaining bytes as the remaining details:
102              
103             ==> ->fail("Some generic I/O error", "sendfile", EIO, 60000)
104              
105             =cut
106              
107             *IO::Async::Stream::sendfile = sub {
108 32     32   155675 my $self = shift;
109 32         94 my %args = @_;
110 32 50       97 die "Stream must be added to loop first" unless $self->loop;
111              
112 32 50       200 if(defined $args{file}) {
113 32 50       1363 open $args{fh}, '<', $args{file} or die "Could not open " . $args{file} . " for input - $!";
114 32         105 binmode $args{fh};
115             }
116 32 50       123 die 'No file?' unless my $fh = delete $args{fh};
117              
118             # Work out how much we need to write
119 32         60 my $total = my $remaining = $args{length};
120 32 50       84 unless(defined $total) {
121 32         57 my $pos = tell $fh;
122 32 50       104 seek $fh, 0, SEEK_END or die "Unable to seek - $!";
123 32         41 $total = $remaining = tell $fh;
124 32 50       81 seek $fh, $pos, SEEK_SET or die "Unable to seek - $!";
125             }
126 32         93 my $f = $self->loop->new_future;
127              
128             $self->write(sub {
129 480     480   1052098 my $stream = shift;
130 480 100       1368 return unless $remaining;
131              
132 448 50       832 unless($remaining > 0) {
133 0         0 $f->fail(EOF => "Attempt to write past EOF, remaining bytes: " . $remaining);
134 0         0 return;
135             }
136              
137 448 50       1007 if(my $written = sendfile $stream->write_handle, $fh, $remaining) {
138 448         44133 $remaining -= $written;
139 448         1339 return ''; # empty string => call us again please
140             }
141              
142 0         0 $f->fail("$!", sendfile => 0 + $!, $remaining);
143 0         0 return;
144             }, on_flush => sub {
145 32 50   32   269 $f->done($total) unless $f->is_ready;
146 32         907 });
147 32         3748 return $f;
148             };
149              
150              
151             1;
152              
153             __END__