File Coverage

blib/lib/IO/AsyncX/Sendfile.pm
Criterion Covered Total %
statement 46 50 92.0
branch 12 22 54.5
condition 1 2 50.0
subroutine 10 10 100.0
pod n/a
total 69 84 82.1


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