From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 7D4F31F4BB for ; Mon, 24 Jun 2019 02:59:25 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 55/57] ds: reimplement IO::Poll support to look like epoll Date: Mon, 24 Jun 2019 02:52:56 +0000 Message-Id: <20190624025258.25592-56-e@80x24.org> In-Reply-To: <20190624025258.25592-1-e@80x24.org> References: <20190624025258.25592-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: At least the subset of epoll we use. EPOLLET might be difficult to emulate if we end up using it. --- MANIFEST | 2 ++ lib/PublicInbox/DS.pm | 16 ++++++----- lib/PublicInbox/DSPoll.pm | 58 +++++++++++++++++++++++++++++++++++++++ t/ds-poll.t | 58 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 lib/PublicInbox/DSPoll.pm create mode 100644 t/ds-poll.t diff --git a/MANIFEST b/MANIFEST index 52c4790e..29920953 100644 --- a/MANIFEST +++ b/MANIFEST @@ -78,6 +78,7 @@ lib/PublicInbox/Config.pm lib/PublicInbox/ContentId.pm lib/PublicInbox/DS.pm lib/PublicInbox/DSKQXS.pm +lib/PublicInbox/DSPoll.pm lib/PublicInbox/Daemon.pm lib/PublicInbox/Emergency.pm lib/PublicInbox/EvCleanup.pm @@ -191,6 +192,7 @@ t/content_id.t t/convert-compact.t t/data/0001.patch t/ds-leak.t +t/ds-poll.t t/edit.t t/emergency.t t/fail-bin/spamc diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm index d6ef0b8d..e3479e66 100644 --- a/lib/PublicInbox/DS.pm +++ b/lib/PublicInbox/DS.pm @@ -142,15 +142,17 @@ sub _InitPoller return if $DoneInit; $DoneInit = 1; - if (!PublicInbox::Syscall::epoll_defined()) { - $Epoll = eval { - require PublicInbox::DSKQXS; - PublicInbox::DSKQXS->import; - PublicInbox::DSKQXS->new; - }; - } else { + if (PublicInbox::Syscall::epoll_defined()) { $Epoll = epoll_create(); set_cloexec($Epoll) if (defined($Epoll) && $Epoll >= 0); + } else { + my $cls; + for (qw(DSKQXS DSPoll)) { + $cls = "PublicInbox::$_"; + last if eval "require $cls"; + } + $cls->import; + $Epoll = $cls->new; } *EventLoop = *EpollEventLoop; } diff --git a/lib/PublicInbox/DSPoll.pm b/lib/PublicInbox/DSPoll.pm new file mode 100644 index 00000000..e65640a8 --- /dev/null +++ b/lib/PublicInbox/DSPoll.pm @@ -0,0 +1,58 @@ +# Copyright (C) 2019 all contributors +# Licensed the same as Danga::Socket (and Perl5) +# License: GPL-1.0+ or Artistic-1.0-Perl +# +# +# +# poll(2) via IO::Poll core module. This makes poll look +# like epoll to simplify the code in DS.pm. This is NOT meant to be +# an all encompassing emulation of epoll via IO::Poll, but just to +# support cases public-inbox-nntpd/httpd care about. +package PublicInbox::DSPoll; +use strict; +use warnings; +use parent qw(Exporter); +use IO::Poll; +use PublicInbox::Syscall qw(EPOLLONESHOT EPOLLIN EPOLLOUT EPOLL_CTL_DEL); +our @EXPORT = qw(epoll_ctl epoll_wait); + +sub new { bless {}, $_[0] } # fd => events + +sub epoll_ctl { + my ($self, $op, $fd, $ev) = @_; + + # not wasting time on error checking + if ($op != EPOLL_CTL_DEL) { + $self->{$fd} = $ev; + } else { + delete $self->{$fd}; + } + 0; +} + +sub epoll_wait { + my ($self, $maxevents, $timeout_msec, $events) = @_; + my @pset; + 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; + push @$events, [ $fd ]; + } + my $nevents = scalar @$events; + if ($n != $nevents) { + warn "BUG? poll() returned $n, but got $nevents"; + } + } + $n; +} + +1; diff --git a/t/ds-poll.t b/t/ds-poll.t new file mode 100644 index 00000000..a397ee06 --- /dev/null +++ b/t/ds-poll.t @@ -0,0 +1,58 @@ +# Copyright (C) 2019 all contributors +# Licensed the same as Danga::Socket (and Perl5) +# License: GPL-1.0+ or Artistic-1.0-Perl +# +# +use strict; +use warnings; +use Test::More; +use PublicInbox::Syscall qw(:epoll); +my $cls = 'PublicInbox::DSPoll'; +use_ok $cls; +my $p = $cls->new; + +my ($r, $w, $x, $y); +pipe($r, $w) or die; +pipe($x, $y) or die; +is(epoll_ctl($p, EPOLL_CTL_ADD, fileno($r), EPOLLIN), 0, 'add EPOLLIN'); +my $events = []; +my $n = epoll_wait($p, 9, 0, $events); +is_deeply($events, [], 'no events set'); +is($n, 0, 'nothing ready, yet'); +is(epoll_ctl($p, EPOLL_CTL_ADD, fileno($w), EPOLLOUT|EPOLLONESHOT), 0, + 'add EPOLLOUT|EPOLLONESHOT'); +$n = epoll_wait($p, 9, -1, $events); +is($n, 1, 'got POLLOUT event'); +is($events->[0]->[0], fileno($w), '$w ready'); + +$n = epoll_wait($p, 9, 0, $events); +is($n, 0, 'nothing ready after oneshot'); +is_deeply($events, [], 'no events set after oneshot'); + +syswrite($w, '1') == 1 or die; +for my $t (0..1) { + $n = epoll_wait($p, 9, $t, $events); + is($events->[0]->[0], fileno($r), "level-trigger POLLIN ready #$t"); + is($n, 1, "only event ready #$t"); +} +syswrite($y, '1') == 1 or die; +is(epoll_ctl($p, EPOLL_CTL_ADD, fileno($x), EPOLLIN|EPOLLONESHOT), 0, + 'EPOLLIN|EPOLLONESHOT add'); +is(epoll_wait($p, 9, -1, $events), 2, 'epoll_wait has 2 ready'); +my @fds = sort(map { $_->[0] } @$events); +my @exp = sort((fileno($r), fileno($x))); +is_deeply(\@fds, \@exp, 'got both ready FDs'); + +# EPOLL_CTL_DEL doesn't matter for kqueue, we do it in native epoll +# to avoid a kernel-wide lock; but its not needed for native kqueue +# paths so DSKQXS makes it a noop (as did Danga::Socket::close). +SKIP: { + if ($cls ne 'PublicInbox::DSPoll') { + skip "$cls doesn't handle EPOLL_CTL_DEL", 2; + } + is(epoll_ctl($p, EPOLL_CTL_DEL, fileno($r), 0), 0, 'EPOLL_CTL_DEL OK'); + $n = epoll_wait($p, 9, 0, $events); + is($n, 0, 'nothing ready after EPOLL_CTL_DEL'); +}; + +done_testing; -- EW