File Coverage

blib/lib/Future/IO/Resolver/Using/Socket.pm
Criterion Covered Total %
statement 118 150 78.6
branch 25 40 62.5
condition 5 10 50.0
subroutine 20 22 90.9
pod 0 3 0.0
total 168 225 74.6


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2026 -- leonerd@leonerd.org.uk
5              
6             package Future::IO::Resolver::Using::Socket 0.04;
7              
8 6     6   56 use v5.20;
  6         16  
9 6     6   23 use warnings;
  6         7  
  6         250  
10              
11 6     6   22 use feature qw( postderef signatures );
  6         9  
  6         518  
12 6     6   22 no warnings qw( experimental::postderef experimental::signatures );
  6         6  
  6         174  
13              
14 6     6   2186 use Future::IO 0.19 qw( POLLIN );
  6         183800  
  6         286  
15 6     6   2679 use Future::Utils qw( repeat );
  6         10840  
  6         412  
16              
17             # We don't want to import these as they'll get in the way of our named methods
18 6     6   47 use Socket qw(); # getaddrinfo getnameinfo
  6         20  
  6         110  
19              
20 6     6   26 use constant RESOLVER_PRIORITY => 50;
  6         7  
  6         309  
21              
22 6     6   23 use Future::IO::Resolver;
  6         7  
  6         10421  
23             Future::IO::Resolver->ADD_BACKEND( __PACKAGE__ );
24              
25             =head1 NAME
26              
27             C - implement L by wrapping the functions provided by L
28              
29             =head1 DESCRIPTION
30              
31             This module provides a backend implementation for L
32             which uses the regular L functions to perform lookups. In order to
33             operate asynchronously, these calls are made from a forked side-car process.
34              
35             This should not be used directly, but is instead made available via the main
36             dispatch methods in C itself.
37              
38             =cut
39              
40             sub _serialise ( @values )
41 7     7   16 {
  7         59  
  7         21  
42 7 50       44 if( !@values ) { return "\0" }
  0         0  
43 7 50       46 if( @values > 255 ) { die "Cannot serialise list; too big" }
  0         0  
44              
45             return pack( "C", scalar @values ) .
46 7         55 join( "", map { _serialise1( $_ ) } @values );
  39         122  
47             }
48              
49             sub _serialise1 ( $v )
50 39     39   67 {
  39         84  
  39         61  
51 39 100       86 if( !defined $v ) {
52 10         73 return "U";
53             }
54 29   50     90 my $r = ref $v // "";
55 29 50       71 if( $r eq "ARRAY" ) {
56 0         0 return "[" . _serialise( @$v );
57             }
58 29 100       75 if( $r eq "HASH" ) {
59 3         53 return "{" . _serialise( %$v );
60             }
61 26 100       130 if( $v =~ m/^-?\d+$/ ) {
62 6         138 return "i" . pack( "i", $v );
63             }
64             else {
65 20         48 my $l = length $v;
66 20 50       99 return "s" . pack( "C", $l ) . $v if $l < 256;
67 0         0 return "S" . pack( "I", $l ) . $v;
68             }
69             }
70              
71             sub _deserialise ( $sr )
72 11     11   38 {
  11         23  
  11         17  
73 11         52 my $count = unpack( "C", substr $$sr, 0, 1, "" );
74 11         37 my @v;
75 11         35 push @v, _deserialise1( $sr ) for 1 .. $count;
76 11         102 return @v;
77             }
78              
79             sub _deserialise1 ( $sr )
80 93     93   143 {
  93         147  
  93         128  
81 93         199 my $c = substr( $$sr, 0, 1, "" );
82 93 100       199 if( $c eq "U" ) {
83 8         21 return undef;
84             }
85 85 100       196 if( $c eq "{" ) {
86             return +{
87 8         24 _deserialise( $sr )
88             };
89             }
90 77 100       161 if( $c eq "i" ) {
91 28         117 return unpack( "i", substr( $$sr, 0, 4, "" ) );
92             }
93 49 50 33     128 if( $c eq "s" or $c eq "S" ) {
94 49 50       125 my $l = ( $c eq "s" ) ? unpack( "C", substr( $$sr, 0, 1, "" ) ) :
95             unpack( "I", substr( $$sr, 0, 4, "" ) );
96 49         229 return substr( $$sr, 0, $l, "" );
97             }
98              
99 0         0 die "TODO: deserialise type $c";
100             }
101              
102             my %resolvers;
103              
104 0         0 sub run_in_child ( $rd, $wr )
105 0     0 0 0 {
  0         0  
  0         0  
106 0         0 while( !eof $rd ) {
107 0         0 $rd->read( my $buf, 4 );
108 0         0 my $len = unpack "I", $buf;
109              
110 0         0 $buf = "";
111 0         0 $rd->read( $buf, $len - length $buf, length $buf ) while $len > length $buf;
112              
113 0         0 my ( $func, @args ) = _deserialise( \$buf );
114              
115 0         0 my $code = $resolvers{$func};
116 0 0       0 if( $code ) {
117 0         0 my @result = $code->( @args );
118              
119 0         0 $wr->print( pack "I/a*", _serialise( @result ) );
120             }
121             else {
122 0         0 $wr->print( pack "I/a*", _serialise( -1, "Unrecognised resolver func '$func'" ) );
123             }
124             }
125             }
126              
127             my $runf;
128             my $wrpipe;
129             my @result_queue;
130              
131 2     2   5 sub _start ()
  2         3  
132             {
133 2 50       100 pipe( my $child_rd, $wrpipe ) or
134             die "Cannot pipe() - $!";
135 2 50       79 pipe( my $rdpipe, my $child_wr ) or
136             die "Cannot pipe() - $!";
137 2         30 $rdpipe->blocking(0);
138 2         12 $wrpipe->blocking(0);
139 2         17 $wrpipe->autoflush(1);
140              
141 2 50       6831 defined( my $pid = fork() ) or
142             die "Cannot fork() - $!";
143              
144 2 50       154 if( !$pid ) {
145             # child
146 0         0 undef $wrpipe;
147 0         0 undef $rdpipe;
148              
149 0         0 $child_wr->autoflush(1);
150              
151 0         0 run_in_child( $child_rd, $child_wr );
152              
153 0         0 POSIX::_exit(5);
154             }
155              
156             # parent
157 2         309 undef $child_rd;
158 2         425 undef $child_wr;
159              
160 0         0 my $waitf = Future::IO->waitpid( $pid )
161 0     0   0 ->then( sub ( $wstatus ) {
  0         0  
162 0         0 warn my $msg = "Future::IO::Resolver::Using::Socket child process died: $wstatus\n";
163 0         0 $_->fail( $msg ) for @result_queue;
164 0         0 undef $runf;
165 2         256 } );
166              
167             my $recvf = Future::Utils::repeat {
168             Future::IO->read_exactly( $rdpipe, 4 )->then( sub ( $buf ) {
169 4         17 my $len = unpack "I", $buf;
170 4         25 Future::IO->read_exactly( $rdpipe, $len );
171             })->then( sub ( $buf ) {
172 4 50       17 my $f = shift @result_queue or
173             warn "ARGH no result future!";
174 4 50       21 $f->done( $buf ) if $f;
175 4         465 Future->done;
176 6     6   479 });
177 2     4   2660 } while => sub ( $f ) { !$f->failure };
  4         26  
  4         631  
  4         9  
  4         7  
178              
179 2         3092 return Future->wait_any( $waitf, $recvf );
180             }
181              
182 4         7 sub _call ( $func, @args )
183 4     4   9 {
  4         10  
  4         8  
184 4   66     17 $runf //= _start();
185              
186 4         545 $wrpipe->print( pack "I/a*", _serialise( $func, @args ) );
187              
188 4         184 push @result_queue, my $f = $runf->new;
189 3     3   207 return $f->then( sub ( $buf ) {
  3         12  
  3         14  
190 3         15 my ( $err, @result ) = _deserialise( \$buf );
191 3 50       10 if( !$err ) {
192 3         19 return Future->done( @result );
193             }
194             else {
195 0         0 return Future->fail( "$result[0]\n", $func => );
196             }
197 4         124 } );
198             }
199              
200             $resolvers{getaddrinfo} = sub ( @args ) {
201             my ( $err, @res ) = Socket::getaddrinfo( @args );
202             if( $err ) { return ( $err+0, "$err" ); }
203             else { return ( 0, @res ); }
204             };
205              
206             sub getaddrinfo ( $, %args )
207 3     3 0 7 {
  3         9  
  3         7  
208 3         6 my $host = delete $args{host};
209 3         11 my $service = delete $args{service};
210 3         5 my %hints;
211 3         23 $hints{$_} = delete $args{$_} for qw( family socktype protocol flags );
212              
213 3         33 return _call( getaddrinfo => $host, $service, \%hints );
214             }
215              
216             $resolvers{getnameinfo} = sub ( @args ) {
217             my ( $err, @res ) = Socket::getnameinfo( @args );
218             if( $err ) { return ( $err+0, "$err" ); }
219             else { return ( 0, @res ); }
220             };
221              
222             sub getnameinfo ( $, %args )
223 1     1 0 2 {
  1         2  
  1         1  
224 1         3 my $addr = delete $args{addr};
225 1   50     6 my $flags = delete $args{flags} // 0;
226              
227 1         3 return _call( getnameinfo => $addr, $flags );
228             }
229              
230             =head1 TODO
231              
232             =over 4
233              
234             =item *
235              
236             The C + C mechanism used internally should be extracted into a
237             shared helper module, as none of it is specific to name resolvers. It can then
238             be expanded to permit possibly multiple workers, etc. This would be similar to
239             the way that L uses L.
240              
241             =back
242              
243             =cut
244              
245             =head1 AUTHOR
246              
247             Paul Evans
248              
249             =cut
250              
251             0x55AA;