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 776F61F461 for ; Mon, 13 May 2019 02:56:10 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH] listener: support publicinboxdaemon.multiaccept in config Date: Mon, 13 May 2019 02:56:10 +0000 Message-Id: <20190513025610.30378-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: Similar to the nginx "multi_accept" parameter, this allows single-worker deployments to accept() multiple clients whenever the listen socket reports readiness via epoll_wait/poll/kevent. Unlike nginx, we also parse an integer parameter to determine how many times in a row we call accept(). Single-process deployments with no shared sockets can safely set "true", here; meaning accept() will be retried indefinitely as long as there are clients connecting. Multi-process deployments with many workers should leave this at the default ("false", or "0") or try a small positive integer. Using large values with many workers can lead to imbalanced connections between workers. We also can't reliably detect whether a listen socket is shared between multiple processes. Sockets can be shared via socket activation in systemd (or similar) and TTIN/TTOU signals can adjust worker count of our daemons. --- lib/PublicInbox/Config.pm | 29 +++++++++++++++++++++++++++-- lib/PublicInbox/Listener.pm | 14 +++++++------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm index 09f9179..938685a 100644 --- a/lib/PublicInbox/Config.pm +++ b/lib/PublicInbox/Config.pm @@ -13,6 +13,9 @@ use warnings; require PublicInbox::Inbox; use PublicInbox::Spawn qw(popen_rd); +my $FALSE_RE = qr/\A(?:false|no|off|0)\z/; +my $TRUE_RE = qr/\A(?:true|yes|on|1)\z/; + sub _array ($) { ref($_[0]) eq 'ARRAY' ? $_[0] : [ $_[0] ] } # returns key-value pairs of config directives in a hash @@ -54,6 +57,28 @@ sub new { $self->{css} = _array($css); } + if (defined(my $ma = $self->{'publicinboxdaemon.multiaccept'})) { + # multiaccept=0 accept once (default) + # multiaccept=(1..X) accept an extra X times (or EAGAIN) + # multiaccept=true accept until EAGAIN + if ($ma =~ $TRUE_RE) { + $ma = -1 if $ma ne '1'; + } elsif ($ma =~ $FALSE_RE) { + $ma = 0; + } elsif ($ma =~ /\A(\d+)\z/) { + # positive integer value, leave as-is + } else { + warn +"publicinboxdaemon.multiaccept=$ma not understood\n"; + $ma = undef; + } + + defined($ma) and eval { + no warnings 'once'; + $PublicInbox::Listener::multi_accept = $ma; + }; + } + $self; } @@ -379,9 +404,9 @@ sub _fill { foreach my $k (qw(obfuscate)) { my $v = $self->{"$pfx.$k"}; defined $v or next; - if ($v =~ /\A(?:false|no|off|0)\z/) { + if ($v =~ $FALSE_RE) { $ibx->{$k} = 0; - } elsif ($v =~ /\A(?:true|yes|on|1)\z/) { + } elsif ($v =~ $TRUE_RE) { $ibx->{$k} = 1; } else { warn "Ignoring $pfx.$k=$v in config, not boolean\n"; diff --git a/lib/PublicInbox/Listener.pm b/lib/PublicInbox/Listener.pm index a75a6fd..c4d1a55 100644 --- a/lib/PublicInbox/Listener.pm +++ b/lib/PublicInbox/Listener.pm @@ -10,6 +10,8 @@ use Socket qw(SOL_SOCKET SO_KEEPALIVE IPPROTO_TCP TCP_NODELAY); use fields qw(post_accept); require IO::Handle; +our $multi_accept = 0; + sub new ($$$) { my ($class, $s, $cb) = @_; setsockopt($s, SOL_SOCKET, SO_KEEPALIVE, 1); @@ -26,16 +28,14 @@ sub new ($$$) { sub event_read { my ($self) = @_; my $sock = $self->{sock}; + my $n = $multi_accept; + my ($addr, $c); - # 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)) { + do { + $addr = accept($c, $sock) or return; IO::Handle::blocking($c, 0); # no accept4 :< $self->{post_accept}->($c, $addr, $sock); - } + } while ($n--); } 1; -- EW