about summary refs log tree commit homepage
path: root/lib/PublicInbox/Listener.pm
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2023-04-12 10:17:42 +0000
committerEric Wong <e@80x24.org>2023-04-14 16:25:34 +0000
commit74bb661ee521d275b636eda4a5caeabb9e387e75 (patch)
tree4da9c5387c86864c294bd771949bfb3f05087766 /lib/PublicInbox/Listener.pm
parent924fcdc5a5095043388bcce85da09af0d3161bd0 (diff)
downloadpublic-inbox-74bb661ee521d275b636eda4a5caeabb9e387e75.tar.gz
While accepting a single connection at-a-time is likely best for
multi-worker and/or load-balanced deployments; accepting
multiple connections at once should be less bad on overloaded
single-worker systems.

We can't automatically pick the best value here since worker
counts are dynamic via SIGTTIN/SIGTTOU.  Process managers
(e.g. systemd) can also spawn multiple instances sharing a
single listener with no knowledge sharing between listeners.
Diffstat (limited to 'lib/PublicInbox/Listener.pm')
-rw-r--r--lib/PublicInbox/Listener.pm45
1 files changed, 22 insertions, 23 deletions
diff --git a/lib/PublicInbox/Listener.pm b/lib/PublicInbox/Listener.pm
index 7cedc349..4669cf04 100644
--- a/lib/PublicInbox/Listener.pm
+++ b/lib/PublicInbox/Listener.pm
@@ -1,14 +1,15 @@
-# Copyright (C) 2015-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 #
 # Used by -nntpd for listen sockets
 package PublicInbox::Listener;
-use strict;
+use v5.12;
 use parent 'PublicInbox::DS';
 use Socket qw(SOL_SOCKET SO_KEEPALIVE IPPROTO_TCP TCP_NODELAY);
 use IO::Handle;
 use PublicInbox::Syscall qw(EPOLLIN EPOLLEXCLUSIVE);
 use Errno qw(EAGAIN ECONNABORTED);
+our $MULTI_ACCEPT = 0;
 
 # Warn on transient errors, mostly resource limitations.
 # EINTR would indicate the failure to set NonBlocking in systemd or similar
@@ -16,37 +17,35 @@ my %ERR_WARN = map {;
         eval("Errno::$_()") => $_
 } qw(EMFILE ENFILE ENOBUFS ENOMEM EINTR);
 
-sub new ($$$) {
-        my ($class, $s, $cb) = @_;
+sub new {
+        my ($class, $s, $cb, $multi_accept) = @_;
         setsockopt($s, SOL_SOCKET, SO_KEEPALIVE, 1);
         setsockopt($s, IPPROTO_TCP, TCP_NODELAY, 1); # ignore errors on non-TCP
         listen($s, 2**31 - 1); # kernel will clamp
         my $self = bless { post_accept => $cb }, $class;
+        $self->{multi_accept} = $multi_accept //= $MULTI_ACCEPT;
         $self->SUPER::new($s, EPOLLIN|EPOLLEXCLUSIVE);
 }
 
 sub event_step {
         my ($self) = @_;
         my $sock = $self->{sock} or return;
-
-        # no loop here, we want to fairly distribute clients
-        # between multiple processes sharing the same socket
-        # XXX our event loop needs better granularity for
-        # a single accept() here to be, umm..., acceptable
-        # on high-traffic sites.
-        if (my $addr = accept(my $c, $sock)) {
-                IO::Handle::blocking($c, 0); # no accept4 :<
-                eval { $self->{post_accept}->($c, $addr, $sock) };
-                warn "E: $@\n" if $@;
-        } elsif ($! == EAGAIN || $! == ECONNABORTED) {
-                # EAGAIN is common and likely
-                # ECONNABORTED is common with bad connections
-                return;
-        } elsif (my $sym = $ERR_WARN{int($!)}) {
-                warn "W: accept(): $! ($sym)\n";
-        } else {
-                warn "BUG?: accept(): $!\n";
-        }
+        my $n = $self->{multi_accept};
+        do {
+                if (my $addr = accept(my $c, $sock)) {
+                        IO::Handle::blocking($c, 0); # no accept4 :<
+                        eval { $self->{post_accept}->($c, $addr, $sock) };
+                        warn "E: $@\n" if $@;
+                } elsif ($! == EAGAIN || $! == ECONNABORTED) {
+                        # EAGAIN is common and likely
+                        # ECONNABORTED is common with bad connections
+                        return;
+                } elsif (my $sym = $ERR_WARN{int($!)}) {
+                        warn "W: accept(): $! ($sym)\n";
+                } else {
+                        warn "BUG?: accept(): $!\n";
+                }
+        } while ($n--);
 }
 
 1;