about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2019-06-24 02:52:56 +0000
committerEric Wong <e@80x24.org>2019-06-24 05:26:39 +0000
commit4f868db3675eeee5994edc4fe79a9a2583623747 (patch)
tree18bd7ddac41675c3331d7156c3782f1c59a44667
parentdf5755b40b4ba1d6048042e18d8ea501755b9a02 (diff)
downloadpublic-inbox-4f868db3675eeee5994edc4fe79a9a2583623747.tar.gz
At least the subset of epoll we use.  EPOLLET might be
difficult to emulate if we end up using it.
-rw-r--r--MANIFEST2
-rw-r--r--lib/PublicInbox/DS.pm16
-rw-r--r--lib/PublicInbox/DSPoll.pm58
-rw-r--r--t/ds-poll.t58
4 files changed, 127 insertions, 7 deletions
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 <meta@public-inbox.org>
+# Licensed the same as Danga::Socket (and Perl5)
+# License: GPL-1.0+ or Artistic-1.0-Perl
+#  <https://www.gnu.org/licenses/gpl-1.0.txt>
+#  <https://dev.perl.org/licenses/artistic.html>
+#
+# 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 <meta@public-inbox.org>
+# Licensed the same as Danga::Socket (and Perl5)
+# License: GPL-1.0+ or Artistic-1.0-Perl
+#  <https://www.gnu.org/licenses/gpl-1.0.txt>
+#  <https://dev.perl.org/licenses/artistic.html>
+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;