File Coverage

blib/lib/Future/IO/Impl/Ppoll.pm
Criterion Covered Total %
statement 132 137 96.3
branch 22 34 64.7
condition 14 24 58.3
subroutine 23 23 100.0
pod 2 5 40.0
total 193 223 86.5


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::Impl::Ppoll 0.05;
7              
8 12     12   2959772 use v5.20;
  12         46  
9 12     12   73 use warnings;
  12         28  
  12         626  
10 12     12   68 use base qw( Future::IO::ImplBase );
  12         20  
  12         2328  
11              
12 12     12   57540 use feature qw( postderef signatures );
  12         28  
  12         1720  
13 12     12   87 no warnings qw( experimental::postderef experimental::signatures );
  12         19  
  12         622  
14              
15 12     12   81 use Carp;
  12         28  
  12         1131  
16             our @CARP_NOT = qw( Future::IO::Impl::Ppoll::_Future );
17              
18 12     12   6337 use IO::Ppoll 0.14 qw( POLLIN POLLOUT POLLPRI POLLHUP POLLERR POLLNVAL );
  12         117762  
  12         1232  
19 12     12   135 use POSIX qw( SIG_BLOCK sigprocmask );
  12         39  
  12         78  
20 12     12   20941 use Struct::Dumb qw( readonly_struct );
  12         23  
  12         100  
21 12     12   823 use Time::HiRes qw( time );
  12         35  
  12         106  
22              
23             BEGIN {
24 12 50   12   2816 if( $^V ge v5.36 ) {
25 12         183 builtin->import(qw( refaddr ));
26 12 50       1396 warnings->unimport(qw( experimental::builtin )) if $^V lt v5.40;
27             }
28             else {
29 0         0 require Scalar::Util;
30 0         0 Scalar::Util->import(qw( refaddr ));
31             }
32             }
33              
34             BEGIN {
35 12     12   189 Future::IO->VERSION( '0.20' );
36             # Just check for sanity
37             IO::Ppoll->$_ == Future::IO->$_ or die "This implementation relies on the Future::IO $_ constant being the same as system\n"
38 12   50     18923 for qw( POLLIN POLLOUT POLLPRI POLLHUP POLLERR POLLNVAL );
39             }
40              
41             __PACKAGE__->APPLY;
42              
43             =head1 NAME
44              
45             C - implement C using C
46              
47             =head1 DESCRIPTION
48              
49             =for highlighter language=perl
50              
51             This module provides an implementation for L which uses the
52             C system call, via L.
53              
54             There are no additional methods to use in this module; it simply has to be
55             loaded, and it will provide the C implementation methods:
56              
57             use Future::IO;
58             use Future::IO::Impl::Ppoll;
59              
60             my $f = Future::IO->sleep(5);
61             ...
62              
63             Note that the C method will take over the operation of C<$SIG{CHLD}>,
64             and will print a warning if it discovers some other code has already set it.
65             Make sure not to otherwise override this signal handler in your program, or
66             else the C futures will stop working.
67              
68             =cut
69              
70             readonly_struct Poller => [qw( events f )];
71             my %fh_by_refaddr;
72             my %pollers_by_refaddr;
73              
74             readonly_struct Alarm => [qw( time f )];
75             my @alarms;
76              
77             my $ppoll;
78 164   66 164 0 267 sub ppoll () { $ppoll //= IO::Ppoll->new }
  164         231  
  164         1009  
79              
80             sub _update_poll ( $fh )
81 106     106   175 {
  106         171  
  106         197  
82 106         188 my $refaddr = refaddr $fh;
83 106 50       530 defined $fh->fileno or
84             carp "Filehandle $fh lost its fileno (was closed?) during poll";
85              
86 106         763 my $mask = 0;
87 106   50     2189 $mask |= $_->events for ( $pollers_by_refaddr{$refaddr} // [] )->@*;
88              
89 106         940 ppoll()->mask( $fh => $mask );
90 106 100       2109 if( $mask ) {
91 70         210 $fh_by_refaddr{$refaddr} = $fh;
92             }
93             else {
94 36         125 delete $fh_by_refaddr{$refaddr};
95 36         143 delete $pollers_by_refaddr{$refaddr};
96             }
97             }
98              
99             our $RECURSION;
100              
101             sub _tick ( $ )
102 58     58   693 {
  58         97  
103 58 50       191 carp "Future::IO::Impl::Ppoll is not reentrant; strange things may happen" if $RECURSION;
104 58         151 local $RECURSION = 1;
105              
106 58         149 my $ppoll = ppoll();
107              
108 58         400 my $timeout = undef;
109 58 100       175 if( @alarms ) {
110             # These are sorted by time order, so head is soonest
111 5         179 $timeout = $alarms[0]->time - time();
112 5 50       54 $timeout = 0 if $timeout < 0;
113             }
114              
115 58         1410 $ppoll->poll( $timeout );
116              
117 58         2077094 foreach my $refaddr ( keys %fh_by_refaddr ) {
118 61         228 my $fh = $fh_by_refaddr{$refaddr};
119             # If the filehandle has no fileno, that means it was closed. It was
120             # definitely invalid
121 61 50       221 my $revents = ( defined $fh->fileno ) ? $ppoll->events( $fh ) : POLLNVAL
    100          
122             or next;
123              
124 52 50       1023 my $pollers = $pollers_by_refaddr{$refaddr} or next;
125             # TODO: if nobody cared, maybe we should remove it?
126              
127             # Find the next poller which cares about at least one of these events
128 52         186 foreach my $idx ( 0 .. $#$pollers ) {
129 55 100       1764 my $want_revents = $revents & ( $pollers->[$idx]->events | POLLHUP|POLLERR|POLLNVAL )
130             or next;
131              
132 52         529 my ( $poller ) = splice @$pollers, $idx, 1, ();
133              
134 52 50 33     511 $poller and $poller->f and $poller->f->done( $want_revents );
135 52         8533 last;
136             }
137              
138 52         140 _update_poll( $fh );
139             }
140              
141 58         593 my $now = time();
142 58   100     716 while( @alarms and $alarms[0]->time <= $now ) {
143 5         208 ( shift @alarms )->f->done;
144             }
145             }
146              
147 6         14 sub alarm ( $class, $time )
148 6     6 1 899 {
  6         11  
  6         35  
149 6         46 my $f = Future::IO::Impl::Ppoll::_Future->new;
150              
151             # TODO: Binary search
152 6         67 my $idx = 0;
153 6   66     93 $idx++ while $idx < @alarms and $alarms[$idx]->time < $time;
154              
155 6         49 splice @alarms, $idx, 0, Alarm( $time, $f );
156              
157 1     1   553 $f->on_cancel( sub ( $self ) {
  1         16  
  1         3  
158 1         2 my $idx = 0;
159 1   33     46 $idx++ while $idx < @alarms and $alarms[$idx]->f != $self;
160              
161 1         18 splice @alarms, $idx, 1, ();
162 6         447 } );
163              
164 6         197 return $f;
165             }
166              
167 5         13 sub sleep ( $class, $secs )
168 5     5 1 195155 {
  5         10  
  5         9  
169 5         48 $class->alarm( time() + $secs );
170             }
171              
172 54         143 sub poll ( $, $fh, $events )
173 54     54 0 1946923 {
  54         142  
  54         91  
174 54 50       180 defined $fh or
175             croak "Expected a defined filehandle for ->poll";
176              
177 54         117 my $refaddr = refaddr $fh;
178              
179 54   100     357 my $pollers = $pollers_by_refaddr{$refaddr} //= [];
180              
181 54         434 my $f = Future::IO::Impl::Ppoll::_Future->new;
182 54         604 my $poller = Poller( $events, $f );
183              
184 54         3508 push @$pollers, $poller;
185              
186 54         196 _update_poll( $fh );
187 54         518 return $f;
188             }
189              
190             my $captured_sigchld;
191             my %waitpid_futures;
192              
193             sub waitpid ( $, $pid )
194 3     3 0 568714 {
  3         41  
  3         14  
195 3 100       90 unless( $captured_sigchld ) {
196 2 50 33     154 $SIG{CHLD} and $SIG{CHLD} ne "IGNORE" and
197             warn "Future::IO::Impl::Ppoll is replacing \$SIG{CHLD}";
198              
199 3     3   18 $SIG{CHLD} = sub ( $ ) {
  3         6  
200 3         27 foreach my $pid ( keys %waitpid_futures ) {
201 3 50       127 next unless waitpid( $pid, POSIX::WNOHANG ) > 0;
202 3         30 my $wstatus = $?;
203              
204 3         13 my $fs = delete $waitpid_futures{$pid};
205 3         95 $_->done( $wstatus ) for @$fs;
206             }
207 2         156 };
208              
209 2         14 $captured_sigchld = 1;
210              
211 2         160 sigprocmask( SIG_BLOCK, POSIX::SigSet->new( POSIX::SIGCHLD ) );
212             # SIGCHLD is now blocked but will be atomically unblocked by the ppoll()
213             # operation
214             }
215              
216 3         359 my $f = Future::IO::Impl::Ppoll::_Future->new;
217              
218 3 50       279 if( waitpid( $pid, POSIX::WNOHANG ) > 0 ) {
219 0         0 my $wstatus = $?;
220 0         0 $f->done( $wstatus );
221 0         0 return $f;
222             }
223              
224 3         124 push $waitpid_futures{$pid}->@*, $f;
225              
226 3         29 return $f;
227             }
228              
229             package Future::IO::Impl::Ppoll::_Future {
230 12     12   94 use base qw( Future );
  12         20  
  12         2989  
231              
232             sub await ( $self )
233 50     50   10861 {
  50         132  
  50         102  
234 50         331 Future::IO::Impl::Ppoll->_tick until $self->is_ready;
235 50         703 return $self;
236             }
237             }
238              
239             =head1 AUTHOR
240              
241             Paul Evans
242              
243             =cut
244              
245             0x55AA;