]> git.ipfire.org Git - thirdparty/public-inbox.git/commitdiff
poll+select: check EBADF + POLLNVAL errors
authorEric Wong <e@80x24.org>
Mon, 30 Oct 2023 18:29:40 +0000 (18:29 +0000)
committerEric Wong <e@80x24.org>
Tue, 31 Oct 2023 03:46:30 +0000 (03:46 +0000)
I hit this in via select running -cindex with some other
experimental patches.  I can't reproduce the problem, though,
but this ensure we have a chance to diagnose it if it happens
again instead of looping on select(2) => EBADF.

lib/PublicInbox/DSPoll.pm
lib/PublicInbox/Select.pm
t/ds-poll.t

index fc282de0049ba32d5db003517400dd6f46741fb6..0446df4c2788044d2055be3b95d110cfee4df10b 100644 (file)
@@ -12,30 +12,39 @@ package PublicInbox::DSPoll;
 use v5.12;
 use IO::Poll;
 use PublicInbox::Syscall qw(EPOLLONESHOT EPOLLIN EPOLLOUT);
+use Carp qw(carp);
+use Errno ();
 
 sub new { bless {}, __PACKAGE__ } # fd => events
 
 sub ep_wait {
        my ($self, $maxevents, $timeout_msec, $events) = @_;
-       my @pset;
+       my (@pset, $n, $fd, $revents, $nval);
        while (my ($fd, $events) = each %$self) {
                my $pevents = $events & EPOLLIN ? POLLIN : 0;
                $pevents |= $events & EPOLLOUT ? POLLOUT : 0;
                push(@pset, $fd, $pevents);
        }
        @$events = ();
-       my $n = IO::Poll::_poll($timeout_msec, @pset);
-       if ($n >= 0) {
-               for (my $i = 0; $i < @pset; ) {
-                       my $fd = $pset[$i++];
-                       my $revents = $pset[$i++] or next;
-                       delete($self->{$fd}) if $self->{$fd} & EPOLLONESHOT;
+       do {
+               $n = IO::Poll::_poll($timeout_msec, @pset);
+       } while ($n < 0 && $! == Errno::EINTR);
+       die "poll: $!" if $n < 0;
+       return if $n == 0;
+       while (defined($fd = shift @pset)) {
+               $revents = shift @pset or next; # no event
+               if ($revents & POLLNVAL) {
+                       carp "E: FD=$fd invalid in poll";
+                       delete $self->{$fd};
+                       $nval = 1;
+               } else {
+                       delete $self->{$fd} if $self->{$fd} & EPOLLONESHOT;
                        push @$events, $fd;
                }
-               my $nevents = scalar @$events;
-               if ($n != $nevents) {
-                       warn "BUG? poll() returned $n, but got $nevents";
-               }
+       }
+       if ($nval && !@$events) {
+               $! = Errno::EBADF;
+               die "poll: $!";
        }
 }
 
index 9df3a6bdbf4f7862ef04a771222d3a971bf169c0..5817c9ef0c7b5e36b5e8b6704def37239f852ebd 100644 (file)
@@ -20,7 +20,8 @@ sub ep_wait {
        }
        @$events = ();
        my $n = select($rvec, $wvec, undef, $msec < 0 ? undef : ($msec/1000));
-       return if $n <= 0;
+       return if $n == 0;
+       die "select: $!" if $n < 0;
        while (my ($fd, $ev) = each %$self) {
                if (vec($rvec, $fd, 1) || vec($wvec, $fd, 1)) {
                        delete($self->{$fd}) if $ev & EPOLLONESHOT;
index 57fac3efa617c7b2075062fb01d1f25fbe8a7280..7a7e2bcfa5652715094cca42621e5cd7ed530368 100644 (file)
@@ -6,13 +6,14 @@
 use v5.12;
 use Test::More;
 use PublicInbox::Syscall qw(EPOLLIN EPOLLOUT EPOLLONESHOT);
+use autodie qw(close pipe syswrite);
 my $cls = $ENV{TEST_IOPOLLER} // 'PublicInbox::DSPoll';
 use_ok $cls;
 my $p = $cls->new;
 
 my ($r, $w, $x, $y);
-pipe($r, $w) or die;
-pipe($x, $y) or die;
+pipe($r, $w);
+pipe($x, $y);
 is($p->ep_add($r, EPOLLIN), 0, 'add EPOLLIN');
 my $events = [];
 $p->ep_wait(9, 0, $events);
@@ -44,4 +45,20 @@ is($p->ep_del($r, 0), 0, 'EPOLL_CTL_DEL OK');
 $p->ep_wait(9, 0, $events);
 is(scalar @$events, 0, 'nothing ready after EPOLL_CTL_DEL');
 
+is($p->ep_add($r, EPOLLIN), 0, 're-add');
+SKIP: {
+       $cls =~ m!::(?:DSPoll|Select)\z! or
+               skip 'EBADF test for select|poll only';
+       my $old_fd = fileno($r);
+       close $r;
+       my @w;
+       eval {
+               local $SIG{__WARN__} = sub { push @w, @_ };
+               $p->ep_wait(9, 0, $events);
+       };
+       ok($@, 'error detected from bad FD');
+       ok($!{EBADF}, 'EBADF errno set');
+       @w and ok(grep(/\bFD=$old_fd invalid/, @w), 'carps invalid FD');
+}
+
 done_testing;