unofficial mirror of meta@public-inbox.org
 help / color / mirror / Atom feed
* [PATCH 0/5] use sendmsg|writev to reduce syscalls
@ 2024-06-19 23:40 Eric Wong
  2024-06-19 23:41 ` [PATCH 1/5] ds: remove needless O_APPEND import Eric Wong
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Eric Wong @ 2024-06-19 23:40 UTC (permalink / raw)
  To: meta

First two are overdue cleanups, the rest introduce iovec support
to allow combining more send/write calls together in order to
reduce syscall (and Perl subroutine dispatch) overhead.

It's much more pleasant to read strace output, now.

Eric Wong (5):
  ds: remove needless O_APPEND import
  ds: update indentation to match rest of source
  use sendmsg w/ MSG_MORE to reduce syscalls
  http: set Content-Length for simple array responses
  http: use writev for known Content-Length responses

 MANIFEST                     |   1 +
 devel/sysdefs-list           |   1 +
 lib/PublicInbox/Compat.pm    |   4 +-
 lib/PublicInbox/DS.pm        | 407 +++++++++++++++++++----------------
 lib/PublicInbox/DSdeflate.pm |  11 +-
 lib/PublicInbox/HTTP.pm      |  31 +--
 lib/PublicInbox/IMAP.pm      |  14 +-
 lib/PublicInbox/Syscall.pm   |  66 +++++-
 t/syscall.t                  |  20 ++
 9 files changed, 328 insertions(+), 227 deletions(-)
 create mode 100644 t/syscall.t

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/5] ds: remove needless O_APPEND import
  2024-06-19 23:40 [PATCH 0/5] use sendmsg|writev to reduce syscalls Eric Wong
@ 2024-06-19 23:41 ` Eric Wong
  2024-06-19 23:41 ` [PATCH 2/5] ds: update indentation to match rest of source Eric Wong
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2024-06-19 23:41 UTC (permalink / raw)
  To: meta

tmpfile doesn't require O_APPEND to be specified, just a boolean
for whether or not the file requires O_APPEND.
---
 lib/PublicInbox/DS.pm | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm
index a6fec954..ae0525dc 100644
--- a/lib/PublicInbox/DS.pm
+++ b/lib/PublicInbox/DS.pm
@@ -25,7 +25,7 @@ use v5.10.1;
 use parent qw(Exporter);
 use bytes qw(length substr); # FIXME(?): needed for PublicInbox::NNTP
 use POSIX qw(WNOHANG sigprocmask SIG_SETMASK SIG_UNBLOCK);
-use Fcntl qw(SEEK_SET :DEFAULT O_APPEND);
+use Fcntl qw(SEEK_SET :DEFAULT);
 use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
 use Scalar::Util qw(blessed);
 use PublicInbox::Syscall qw(%SIGNUM
@@ -471,7 +471,7 @@ sub drop {
 
 sub tmpio ($$$) {
 	my ($self, $bref, $off) = @_;
-	my $fh = tmpfile('wbuf', $self->{sock}, O_APPEND) or
+	my $fh = tmpfile 'wbuf', $self->{sock}, 1 or
 		return drop($self, "tmpfile $!");
 	$fh->autoflush(1);
 	my $len = length($$bref) - $off;

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/5] ds: update indentation to match rest of source
  2024-06-19 23:40 [PATCH 0/5] use sendmsg|writev to reduce syscalls Eric Wong
  2024-06-19 23:41 ` [PATCH 1/5] ds: remove needless O_APPEND import Eric Wong
@ 2024-06-19 23:41 ` Eric Wong
  2024-06-19 23:41 ` [PATCH 3/5] use sendmsg w/ MSG_MORE to reduce syscalls Eric Wong
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2024-06-19 23:41 UTC (permalink / raw)
  To: meta

Our changes aren't compatible with Danga::Socket at all at this
point.  While we're at it, depend more on subroutine prototypes
to get some compile-time checking.
---
 lib/PublicInbox/DS.pm | 357 +++++++++++++++++++++---------------------
 1 file changed, 178 insertions(+), 179 deletions(-)

diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm
index ae0525dc..17a8a1df 100644
--- a/lib/PublicInbox/DS.pm
+++ b/lib/PublicInbox/DS.pm
@@ -18,7 +18,7 @@
 # sock: underlying socket
 # rbuf: scalarref, usually undef
 # wbuf: arrayref of coderefs or tmpio (autovivified))
-#        (tmpio = [ GLOB, offset, [ length ] ])
+#	(tmpio = [ GLOB, offset, [ length ] ])
 package PublicInbox::DS;
 use strict;
 use v5.10.1;
@@ -42,16 +42,14 @@ my $reap_armed;
 my @active; # FDs (or objs) returned by epoll/kqueue
 our (%AWAIT_PIDS, # pid => [ $callback, @args ]
 	$cur_runq, # only set inside next_tick
-     @FD_MAP, # fd (num) -> PublicInbox::DS object
-     $Poller, # global Select, Epoll, DSPoll, or DSKQXS ref
-
-     @post_loop_do,              # subref + args to call at the end of each loop
-
-     $loop_timeout,               # timeout of event loop in milliseconds
-     @Timers,                    # timers
-     %UniqTimer,
-     $in_loop,
-     );
+	@FD_MAP, # fd (num) -> PublicInbox::DS object
+	$Poller, # global Select, Epoll, DSPoll, or DSKQXS ref
+	@post_loop_do,	# subref + args to call at the end of each loop
+	$loop_timeout,	# timeout of event loop in milliseconds
+	@Timers,
+	%UniqTimer,
+	$in_loop,
+);
 
 Reset();
 
@@ -318,23 +316,22 @@ This is normally (always?) called from your subclass via:
 
 =cut
 sub new {
-    my ($self, $sock, $ev) = @_;
-    $self->{sock} = $sock;
-    my $fd = fileno($sock);
-
-    $Poller //= _InitPoller();
+	my ($self, $sock, $ev) = @_;
+	$self->{sock} = $sock;
+	my $fd = fileno($sock);
+	$Poller //= _InitPoller();
 retry:
-    if ($Poller->ep_add($sock, $ev)) {
-        if ($! == EINVAL && ($ev & EPOLLEXCLUSIVE)) {
-            $ev &= ~EPOLLEXCLUSIVE;
-            goto retry;
-        }
-        die "EPOLL_CTL_ADD $self/$sock/$fd: $!";
-    }
-    defined($FD_MAP[$fd]) and
+	if ($Poller->ep_add($sock, $ev)) {
+		if ($! == EINVAL && ($ev & EPOLLEXCLUSIVE)) {
+			$ev &= ~EPOLLEXCLUSIVE;
+			goto retry;
+		}
+		die "EPOLL_CTL_ADD $self/$sock/$fd: $!";
+	}
+	defined($FD_MAP[$fd]) and
 		croak("BUG: FD:$fd in use by $FD_MAP[$fd] (for $self/$sock)");
 
-    $FD_MAP[$fd] = $self;
+	$FD_MAP[$fd] = $self;
 }
 
 # for IMAP, NNTP, and POP3 which greet clients upon connect
@@ -374,75 +371,80 @@ sub close {
 
 # portable, non-thread-safe sendfile emulation (no pread, yet)
 sub send_tmpio ($$) {
-    my ($sock, $tmpio) = @_;
-
-    sysseek($tmpio->[0], $tmpio->[1], SEEK_SET) or return;
-    my $n = $tmpio->[2] // 65536;
-    $n = 65536 if $n > 65536;
-    defined(my $to_write = sysread($tmpio->[0], my $buf, $n)) or return;
-    my $written = 0;
-    while ($to_write > 0) {
-        if (defined(my $w = syswrite($sock, $buf, $to_write, $written))) {
-            $written += $w;
-            $to_write -= $w;
-        } else {
-            return if $written == 0;
-            last;
-        }
-    }
-    $tmpio->[1] += $written; # offset
-    $tmpio->[2] -= $written if defined($tmpio->[2]); # length
-    $written;
+	my ($sock, $tmpio) = @_;
+
+	sysseek($tmpio->[0], $tmpio->[1], SEEK_SET) or return;
+	my $n = $tmpio->[2] // 65536;
+	$n = 65536 if $n > 65536;
+	my $to_write = sysread($tmpio->[0], my $buf, $n) // return;
+	my $total = 0;
+	while ($to_write > 0) {
+		if (defined(my $w = syswrite($sock, $buf, $to_write, $total))) {
+			$total += $w;
+			$to_write -= $w;
+		} else {
+			$total ? last : return;
+		}
+	}
+	$tmpio->[1] += $total; # offset
+	$tmpio->[2] -= $total if defined($tmpio->[2]); # length
+	$total;
 }
 
 sub epbit ($$) { # (sock, default)
 	$_[0]->can('stop_SSL') ? PublicInbox::TLS::epollbit() : $_[1];
 }
 
+sub epwait ($$) {
+	my ($io, $ev) = @_;
+	$Poller->ep_mod($io, $ev) and croak("EPOLL_CTL_MOD($io): $!");
+}
+
 # returns 1 if done, 0 if incomplete
-sub flush_write ($) {
-    my ($self) = @_;
-    my $sock = $self->{sock} or return;
-    my $wbuf = $self->{wbuf} or return 1;
+sub flush_write {
+	my ($self) = @_;
+	my $sock = $self->{sock} or return;
+	my $wbuf = $self->{wbuf} or return 1;
 
 next_buf:
-    while (my $bref = $wbuf->[0]) {
-        if (ref($bref) ne 'CODE') {
-            while ($sock) {
-                my $w = send_tmpio($sock, $bref); # bref is tmpio
-                if (defined $w) {
-                    if ($w == 0) {
-                        shift @$wbuf;
-                        goto next_buf;
-                    }
-                } elsif ($! == EAGAIN && (my $ev = epbit($sock, EPOLLOUT))) {
-                    epwait($sock, $ev | EPOLLONESHOT);
-                    return 0;
-                } else {
-                    return $self->close;
-                }
-            }
-        } else { #(ref($bref) eq 'CODE') {
-            shift @$wbuf;
-            my $before = scalar(@$wbuf);
-            $bref->($self);
-
-            # bref may be enqueueing more CODE to call (see accept_tls_step)
-            return 0 if (scalar(@$wbuf) > $before);
-        }
-    } # while @$wbuf
-
-    delete $self->{wbuf};
-    1; # all done
+	while (my $bref = $wbuf->[0]) {
+		if (ref($bref) ne 'CODE') { # bref is tmpio
+			while ($sock) {
+				my $w = send_tmpio $sock, $bref;
+				if (defined $w) {
+					if ($w == 0) {
+						shift @$wbuf;
+						goto next_buf;
+					}
+				} elsif ($! == EAGAIN && (my $ev = epbit
+							$sock, EPOLLOUT)) {
+					epwait $sock, $ev | EPOLLONESHOT;
+					return 0;
+				} else {
+					return $self->close;
+				}
+			}
+		} else { #(ref($bref) eq 'CODE') {
+			shift @$wbuf;
+			my $before = scalar(@$wbuf);
+			$bref->($self);
+			# bref may be enqueueing more CODE to call
+			# (see accept_tls_step)
+			return 0 if (scalar(@$wbuf) > $before);
+		}
+	} # while @$wbuf
+
+	delete $self->{wbuf};
+	1; # all done
 }
 
 sub rbuf_idle ($$) {
-    my ($self, $rbuf) = @_;
-    if ($$rbuf eq '') { # who knows how long till we can read again
-        delete $self->{rbuf};
-    } else {
-        $self->{rbuf} = $rbuf;
-    }
+	my ($self, $rbuf) = @_;
+	if ($$rbuf eq '') { # who knows how long till we can read again
+		delete $self->{rbuf};
+	} else {
+		$self->{rbuf} = $rbuf;
+	}
 }
 
 # returns true if bytes are read, false otherwise
@@ -451,8 +453,8 @@ sub do_read ($$$;$) {
 	my ($ev, $r, $s);
 	$r = sysread($s = $self->{sock}, $$rbuf, $len, $off // 0) and return $r;
 
-	if (!defined($r) && $! == EAGAIN && ($ev = epbit($s, EPOLLIN))) {
-		epwait($s, $ev | EPOLLONESHOT);
+	if (!defined($r) && $! == EAGAIN && ($ev = epbit $s, EPOLLIN)) {
+		epwait $s, $ev | EPOLLONESHOT;
 		rbuf_idle($self, $rbuf);
 	} else {
 		$self->close;
@@ -462,7 +464,7 @@ sub do_read ($$$;$) {
 
 # drop the socket if we hit unrecoverable errors on our system which
 # require BOFH attention: ENOSPC, EFBIG, EIO, EMFILE, ENFILE...
-sub drop {
+sub drop ($@) {
 	my $self = shift;
 	carp(@_);
 	$self->close;
@@ -472,12 +474,12 @@ sub drop {
 sub tmpio ($$$) {
 	my ($self, $bref, $off) = @_;
 	my $fh = tmpfile 'wbuf', $self->{sock}, 1 or
-		return drop($self, "tmpfile $!");
+		return drop $self, "tmpfile $!";
 	$fh->autoflush(1);
 	my $len = length($$bref) - $off;
 	my $n = syswrite($fh, $$bref, $len, $off) //
-		return drop($self, "write ($len): $!");
-	$n == $len or return drop($self, "wrote $n < $len bytes");
+		return drop $self, "write ($len): $!";
+	$n == $len or return drop $self, "wrote $n < $len bytes";
 	[ $fh, 0 ] # [1] = offset, [2] = length, not set by us
 }
 
@@ -490,112 +492,109 @@ it returns 1, caller should stop waiting for 'writable' events)
 
 =cut
 sub write {
-    my ($self, $data) = @_;
-
-    # nobody should be writing to closed sockets, but caller code can
-    # do two writes within an event, have the first fail and
-    # disconnect the other side (whose destructor then closes the
-    # calling object, but it's still in a method), and then the
-    # now-dead object does its second write.  that is this case.  we
-    # just lie and say it worked.  it'll be dead soon and won't be
-    # hurt by this lie.
-    my $sock = $self->{sock} or return 1;
-    my $ref = ref $data;
-    my $bref = $ref ? $data : \$data;
-    my $wbuf = $self->{wbuf};
-    if ($wbuf && scalar(@$wbuf)) { # already buffering, can't write more...
-        if ($ref eq 'CODE') {
-            push @$wbuf, $bref;
-        } else {
-            my $tmpio = $wbuf->[-1];
-            if (ref($tmpio) eq 'ARRAY' && !defined($tmpio->[2])) {
-                # append to tmp file buffer
-                $tmpio->[0]->print($$bref) or return drop($self, "print: $!");
-            } else {
-                my $tmpio = tmpio($self, $bref, 0) or return 0;
-                push @$wbuf, $tmpio;
-            }
-        }
-        return 0;
-    } elsif ($ref eq 'CODE') {
-        $bref->($self);
-        return 1;
-    } else {
-        my $to_write = length($$bref);
-        my $written = syswrite($sock, $$bref, $to_write);
-
-        if (defined $written) {
-            return 1 if $written == $to_write;
-            requeue($self); # runs: event_step -> flush_write
-        } elsif ($! == EAGAIN) {
-            my $ev = epbit($sock, EPOLLOUT) or return $self->close;
-            epwait($sock, $ev | EPOLLONESHOT);
-            $written = 0;
-        } else {
-            return $self->close;
-        }
-
-        # deal with EAGAIN or partial write:
-        my $tmpio = tmpio($self, $bref, $written) or return 0;
-
-        # wbuf may be an empty array if we're being called inside
-        # ->flush_write via CODE bref:
-        push @{$self->{wbuf}}, $tmpio; # autovivifies
-        return 0;
-    }
+	my ($self, $data) = @_;
+
+	# nobody should be writing to closed sockets, but caller code can
+	# do two writes within an event, have the first fail and
+	# disconnect the other side (whose destructor then closes the
+	# calling object, but it's still in a method), and then the
+	# now-dead object does its second write.  that is this case.  we
+	# just lie and say it worked.  it'll be dead soon and won't be
+	# hurt by this lie.
+	my $sock = $self->{sock} or return 1;
+	my $ref = ref $data;
+	my $bref = $ref ? $data : \$data;
+	my $wbuf = $self->{wbuf};
+	if ($wbuf && scalar(@$wbuf)) { # already buffering, can't write more...
+		if ($ref eq 'CODE') {
+			push @$wbuf, $bref;
+		} else {
+			my $tmpio = $wbuf->[-1];
+			if (ref($tmpio) eq 'ARRAY' && !defined($tmpio->[2])) {
+				# append to tmp file buffer
+				$tmpio->[0]->print($$bref) or
+					return drop($self, "print: $!");
+			} else {
+				$tmpio = tmpio $self, $bref, 0 or return 0;
+				push @$wbuf, $tmpio;
+			}
+		}
+		0;
+	} elsif ($ref eq 'CODE') {
+		$bref->($self);
+		1;
+	} else {
+		my $to_write = length $$bref;
+		my $w = syswrite $sock, $$bref, $to_write;
+
+		if (defined $w) {
+			return 1 if $w == $to_write;
+			requeue $self; # runs: event_step -> flush_write
+		} elsif ($! == EAGAIN) {
+			my $ev = epbit $sock, EPOLLOUT or return $self->close;
+			epwait $sock, $ev | EPOLLONESHOT;
+			$w = 0;
+		} else {
+			return $self->close;
+		}
+
+		# deal with EAGAIN or partial write:
+		my $tmpio = tmpio $self, $bref, $w or return 0;
+
+		# wbuf may be an empty array if we're being called inside
+		# ->flush_write via CODE bref:
+		push @{$self->{wbuf}}, $tmpio; # autovivifies
+		0;
+	}
 }
 
 use constant MSG_MORE => ($^O eq 'linux') ? 0x8000 : 0;
 
 sub msg_more ($$) {
-    my $self = $_[0];
-    my $sock = $self->{sock} or return 1;
-    my $wbuf = $self->{wbuf};
+	my $self = $_[0]; # $_[1] = buf
+	my ($sock, $wbuf, $n, $nlen, $tmpio);
+	$sock = $self->{sock} or return 1;
+	$wbuf = $self->{wbuf};
 
-    if (MSG_MORE && (!defined($wbuf) || !scalar(@$wbuf)) &&
+	if (MSG_MORE && (!defined($wbuf) || !scalar(@$wbuf)) &&
 		!$sock->can('stop_SSL')) {
-        my $n = send($sock, $_[1], MSG_MORE);
-        if (defined $n) {
-            my $nlen = length($_[1]) - $n;
-            return 1 if $nlen == 0; # all done!
-            # queue up the unwritten substring:
-            my $tmpio = tmpio($self, \($_[1]), $n) or return 0;
-            push @{$self->{wbuf}}, $tmpio; # autovivifies
-            epwait($sock, EPOLLOUT|EPOLLONESHOT);
-            return 0;
-        }
-    }
-
-    # don't redispatch into NNTPdeflate::write
-    PublicInbox::DS::write($self, \($_[1]));
-}
+		$n = send($sock, $_[1], MSG_MORE);
+		if (defined $n) {
+			$nlen = length($_[1]) - $n;
+			return 1 if $nlen == 0; # all done!
+			# queue up the unwritten substring:
+			$tmpio = tmpio($self, \($_[1]), $n) or return 0;
+			push @{$self->{wbuf}}, $tmpio; # autovivifies
+			epwait $sock, EPOLLOUT|EPOLLONESHOT;
+			return 0;
+		}
+	}
 
-sub epwait ($$) {
-	my ($io, $ev) = @_;
-	$Poller->ep_mod($io, $ev) and croak("EPOLL_CTL_MOD($io): $!");
+	# don't redispatch into NNTPdeflate::write
+	PublicInbox::DS::write($self, \($_[1]));
 }
 
 # return true if complete, false if incomplete (or failure)
 sub accept_tls_step ($) {
-    my ($self) = @_;
-    my $sock = $self->{sock} or return;
-    return 1 if $sock->accept_SSL;
-    return $self->close if $! != EAGAIN;
-    my $ev = PublicInbox::TLS::epollbit() or return $self->close;
-    epwait($sock, $ev | EPOLLONESHOT);
-    unshift(@{$self->{wbuf}}, \&accept_tls_step); # autovivifies
-    0;
+	my ($self) = @_;
+	my $sock = $self->{sock} or return;
+	return 1 if $sock->accept_SSL;
+	return $self->close if $! != EAGAIN;
+	my $ev = PublicInbox::TLS::epollbit() or return $self->close;
+	epwait $sock, $ev | EPOLLONESHOT;
+	unshift @{$self->{wbuf}}, \&accept_tls_step; # autovivifies
+	0;
 }
 
 # return value irrelevant
 sub shutdn_tls_step ($) {
-    my ($self) = @_;
-    my $sock = $self->{sock} or return;
-    return $self->close if $sock->stop_SSL(SSL_fast_shutdown => 1);
-    return $self->close if $! != EAGAIN;
-    my $ev = PublicInbox::TLS::epollbit() or return $self->close;
-    epwait($sock, $ev | EPOLLONESHOT);
-    unshift(@{$self->{wbuf}}, \&shutdn_tls_step); # autovivifies
+	my ($self) = @_;
+	my $sock = $self->{sock} or return;
+	return $self->close if $sock->stop_SSL(SSL_fast_shutdown => 1);
+	return $self->close if $! != EAGAIN;
+	my $ev = PublicInbox::TLS::epollbit() or return $self->close;
+	epwait $sock, $ev | EPOLLONESHOT;
+	unshift @{$self->{wbuf}}, \&shutdn_tls_step; # autovivifies
 }
 
 # don't bother with shutdown($sock, 2), we don't fork+exec w/o CLOEXEC
@@ -603,7 +602,7 @@ sub shutdn_tls_step ($) {
 sub shutdn ($) {
 	my ($self) = @_;
 	my $sock = $self->{sock} or return;
-	$sock->can('stop_SSL') ? shutdn_tls_step($self) : $self->close;
+	$sock->can('stop_SSL') ? shutdn_tls_step $self : $self->close;
 }
 
 sub dflush {} # overridden by DSdeflate

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/5] use sendmsg w/ MSG_MORE to reduce syscalls
  2024-06-19 23:40 [PATCH 0/5] use sendmsg|writev to reduce syscalls Eric Wong
  2024-06-19 23:41 ` [PATCH 1/5] ds: remove needless O_APPEND import Eric Wong
  2024-06-19 23:41 ` [PATCH 2/5] ds: update indentation to match rest of source Eric Wong
@ 2024-06-19 23:41 ` Eric Wong
  2024-06-19 23:41 ` [PATCH 4/5] http: set Content-Length for simple array responses Eric Wong
  2024-06-19 23:41 ` [PATCH 5/5] http: use writev for known Content-Length responses Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2024-06-19 23:41 UTC (permalink / raw)
  To: meta

In places where we made multiple send(..., MSG_MORE) calls in
quick succession, we now use sendmsg(2) to provide the same
semantics with fewer syscalls.  While this may be less efficient
inside the kernel for small messages, syscalls are expensive
nowadays and we can avoid userspace copies and large allocations
when streaming large HTTP chunks in /T/, /t/, and t.mbox.gz
endpoints.

This allows *BSD systems lacking MSG_MORE to save some syscalls
when writing HTTP chunked encoding, among other things.
---
 MANIFEST                     |  1 +
 lib/PublicInbox/DS.pm        | 56 +++++++++++++++++++++++-------------
 lib/PublicInbox/DSdeflate.pm | 11 ++++---
 lib/PublicInbox/HTTP.pm      | 11 ++-----
 lib/PublicInbox/IMAP.pm      | 14 ++++-----
 lib/PublicInbox/Syscall.pm   | 37 ++++++++++++++++++------
 t/syscall.t                  | 14 +++++++++
 7 files changed, 94 insertions(+), 50 deletions(-)
 create mode 100644 t/syscall.t

diff --git a/MANIFEST b/MANIFEST
index 5796e05b..3fc01a43 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -610,6 +610,7 @@ t/solve/bare.patch
 t/solver_git.t
 t/spamcheck_spamc.t
 t/spawn.t
+t/syscall.t
 t/tail_notify.t
 t/thread-cycle.t
 t/thread-index-gap.t
diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm
index 17a8a1df..c5f44860 100644
--- a/lib/PublicInbox/DS.pm
+++ b/lib/PublicInbox/DS.pm
@@ -35,7 +35,9 @@ use PublicInbox::Select;
 use PublicInbox::OnDestroy;
 use Errno qw(EAGAIN EINVAL ECHILD);
 use Carp qw(carp croak);
+use List::Util qw(sum);
 our @EXPORT_OK = qw(now msg_more awaitpid add_timer add_uniq_timer);
+my $sendmsg_more = PublicInbox::Syscall->can('sendmsg_more');
 
 my $nextq; # queue for next_tick
 my $reap_armed;
@@ -471,8 +473,8 @@ sub drop ($@) {
 	undef;
 }
 
-sub tmpio ($$$) {
-	my ($self, $bref, $off) = @_;
+sub tmpio ($$$;@) {
+	my ($self, $bref, $off, @rest) = @_;
 	my $fh = tmpfile 'wbuf', $self->{sock}, 1 or
 		return drop $self, "tmpfile $!";
 	$fh->autoflush(1);
@@ -480,6 +482,7 @@ sub tmpio ($$$) {
 	my $n = syswrite($fh, $$bref, $len, $off) //
 		return drop $self, "write ($len): $!";
 	$n == $len or return drop $self, "wrote $n < $len bytes";
+	@rest and (print $fh @rest or return drop $self, "print rest: $!");
 	[ $fh, 0 ] # [1] = offset, [2] = length, not set by us
 }
 
@@ -548,30 +551,43 @@ sub write {
 	}
 }
 
-use constant MSG_MORE => ($^O eq 'linux') ? 0x8000 : 0;
-
-sub msg_more ($$) {
-	my $self = $_[0]; # $_[1] = buf
-	my ($sock, $wbuf, $n, $nlen, $tmpio);
+sub msg_more ($@) {
+	my $self = shift;
+	my ($sock, $wbuf);
 	$sock = $self->{sock} or return 1;
 	$wbuf = $self->{wbuf};
 
-	if (MSG_MORE && (!defined($wbuf) || !scalar(@$wbuf)) &&
+	if ($sendmsg_more && (!defined($wbuf) || !scalar(@$wbuf)) &&
 		!$sock->can('stop_SSL')) {
-		$n = send($sock, $_[1], MSG_MORE);
-		if (defined $n) {
-			$nlen = length($_[1]) - $n;
-			return 1 if $nlen == 0; # all done!
-			# queue up the unwritten substring:
-			$tmpio = tmpio($self, \($_[1]), $n) or return 0;
-			push @{$self->{wbuf}}, $tmpio; # autovivifies
-			epwait $sock, EPOLLOUT|EPOLLONESHOT;
-			return 0;
+		my ($s, $tip, $tmpio);
+		$s = $sendmsg_more->($sock, @_);
+		if (defined $s) {
+			my $exp = sum(map length, @_);
+			return 1 if $s == $exp;
+			while (@_) {
+				$tip = shift;
+				if ($s >= length($tip)) { # fully written
+					$s -= length($tip);
+				} else { # first partial write
+					$tmpio = tmpio $self, \$tip, $s, @_
+						or return 0;
+					last;
+				}
+			}
+			$tmpio // return drop $self, "BUG: tmpio on $s != $exp";
+		} elsif ($! == EAGAIN) {
+			$tip = shift;
+			$tmpio = tmpio $self, \$tip, 0, @_ or return 0;
+		} else { # client disconnected
+			return $self->close;
 		}
+		push @{$self->{wbuf}}, $tmpio; # autovivifies
+		epwait $sock, EPOLLOUT|EPOLLONESHOT;
+		0;
+	} else {
+		# don't redispatch into NNTPdeflate::write
+		PublicInbox::DS::write($self, join('', @_));
 	}
-
-	# don't redispatch into NNTPdeflate::write
-	PublicInbox::DS::write($self, \($_[1]));
 }
 
 # return true if complete, false if incomplete (or failure)
diff --git a/lib/PublicInbox/DSdeflate.pm b/lib/PublicInbox/DSdeflate.pm
index 539adf0f..a9f9693b 100644
--- a/lib/PublicInbox/DSdeflate.pm
+++ b/lib/PublicInbox/DSdeflate.pm
@@ -91,12 +91,15 @@ sub do_read ($$$$) {
 }
 
 # override PublicInbox::DS::msg_more
-sub msg_more ($$) {
-	my $self = $_[0];
+sub msg_more ($@) {
+	my $self = shift;
 
 	# $_[1] may be a reference or not for ->deflate
-	my $err = $zout->deflate($_[1], $zbuf);
-	$err == Z_OK or die "->deflate failed $err";
+	my $err;
+	for (@_) {
+		$err = $zout->deflate($_, $zbuf);
+		$err == Z_OK or die "->deflate failed $err";
+	}
 	1;
 }
 
diff --git a/lib/PublicInbox/HTTP.pm b/lib/PublicInbox/HTTP.pm
index b7728bd2..3ca0d18c 100644
--- a/lib/PublicInbox/HTTP.pm
+++ b/lib/PublicInbox/HTTP.pm
@@ -213,14 +213,9 @@ sub response_header_write {
 
 # middlewares such as Deflater may write empty strings
 sub chunked_write ($$) {
-	my $self = $_[0];
-	return if $_[1] eq '';
-	msg_more($self, sprintf("%x\r\n", length($_[1])));
-	msg_more($self, $_[1]);
-
-	# use $self->write(\"\n\n") if you care about real-time
-	# streaming responses, public-inbox WWW does not.
-	msg_more($self, "\r\n");
+	my ($self, $buf) = @_;
+	$buf eq '' or
+		msg_more $self, sprintf("%x\r\n", length($buf)), $buf, "\r\n";
 }
 
 sub identity_write ($$) {
diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm
index b12533cb..0eb21b6a 100644
--- a/lib/PublicInbox/IMAP.pm
+++ b/lib/PublicInbox/IMAP.pm
@@ -605,8 +605,7 @@ sub fetch_blob_cb { # called by git->cat_async via ibx_async_cat
 
 sub emit_rfc822 {
 	my ($self, $k, undef, $bref) = @_;
-	$self->msg_more(" $k {" . length($$bref)."}\r\n");
-	$self->msg_more($$bref);
+	$self->msg_more(" $k {" . length($$bref)."}\r\n", $$bref);
 }
 
 # Mail::IMAPClient::message_string cares about this by default,
@@ -626,20 +625,18 @@ sub emit_flags { $_[0]->msg_more(' FLAGS ()') }
 
 sub emit_envelope {
 	my ($self, undef, undef, undef, $eml) = @_;
-	$self->msg_more(' ENVELOPE '.eml_envelope($eml));
+	$self->msg_more(' ENVELOPE ', eml_envelope($eml));
 }
 
 sub emit_rfc822_header {
 	my ($self, $k, undef, undef, $eml) = @_;
-	$self->msg_more(" $k {".length(${$eml->{hdr}})."}\r\n");
-	$self->msg_more(${$eml->{hdr}});
+	$self->msg_more(" $k {".length(${$eml->{hdr}})."}\r\n", ${$eml->{hdr}});
 }
 
 # n.b. this is sorted to be after any emit_eml_new ops
 sub emit_rfc822_text {
 	my ($self, $k, undef, $bref) = @_;
-	$self->msg_more(" $k {".length($$bref)."}\r\n");
-	$self->msg_more($$bref);
+	$self->msg_more(" $k {".length($$bref)."}\r\n", $$bref);
 }
 
 sub emit_bodystructure {
@@ -970,8 +967,7 @@ sub partial_emit ($$$) {
 		} else {
 			$len = length($str);
 		}
-		$self->msg_more(" $k {$len}\r\n");
-		$self->msg_more($str);
+		$self->msg_more(" $k {$len}\r\n", $str);
 	}
 }
 
diff --git a/lib/PublicInbox/Syscall.pm b/lib/PublicInbox/Syscall.pm
index 4cbe9623..80b46c19 100644
--- a/lib/PublicInbox/Syscall.pm
+++ b/lib/PublicInbox/Syscall.pm
@@ -22,7 +22,8 @@ use POSIX qw(ENOENT ENOSYS EINVAL O_NONBLOCK);
 use Socket qw(SOL_SOCKET SCM_RIGHTS);
 use Config;
 our %SIGNUM = (WINCH => 28); # most Linux, {Free,Net,Open}BSD, *Darwin
-our ($INOTIFY, %PACK);
+our ($INOTIFY, %CONST);
+use List::Util qw(sum);
 
 # $VERSION = '0.25'; # Sys::Syscall version
 our @EXPORT_OK = qw(epoll_ctl epoll_create epoll_wait
@@ -290,7 +291,8 @@ EOM
 
 BEGIN {
 	if ($^O eq 'linux') {
-		%PACK = (
+		%CONST = (
+			MSG_MORE => 0x8000,
 			TMPL_cmsg_len => TMPL_size_t,
 			# cmsg_len, cmsg_level, cmsg_type
 			SIZEOF_cmsghdr => SIZEOF_int * 2 + SIZEOF_size_t,
@@ -303,7 +305,7 @@ BEGIN {
 				'i', # msg_flags
 		);
 	} elsif ($^O =~ /\A(?:freebsd|openbsd|netbsd|dragonfly)\z/) {
-		%PACK = (
+		%CONST = (
 			TMPL_cmsg_len => 'L', # socklen_t
 			SIZEOF_cmsghdr => SIZEOF_int * 3,
 			CMSG_DATA_off => SIZEOF_ptr == 8 ? '@16' : '',
@@ -316,11 +318,12 @@ BEGIN {
 
 		)
 	}
-	$PACK{CMSG_ALIGN_size} = SIZEOF_size_t;
-	$PACK{SIZEOF_cmsghdr} //= 0;
-	$PACK{TMPL_cmsg_len} //= undef;
-	$PACK{CMSG_DATA_off} //= undef;
-	$PACK{TMPL_msghdr} //= undef;
+	$CONST{CMSG_ALIGN_size} = SIZEOF_size_t;
+	$CONST{SIZEOF_cmsghdr} //= 0;
+	$CONST{TMPL_cmsg_len} //= undef;
+	$CONST{CMSG_DATA_off} //= undef;
+	$CONST{TMPL_msghdr} //= undef;
+	$CONST{MSG_MORE} //= 0;
 }
 
 # SFD_CLOEXEC is arch-dependent, so IN_CLOEXEC may be, too
@@ -455,7 +458,7 @@ sub nodatacow_dir {
 	if (open my $fh, '<', $_[0]) { nodatacow_fh($fh) }
 }
 
-use constant \%PACK;
+use constant \%CONST;
 sub CMSG_ALIGN ($) { ($_[0] + CMSG_ALIGN_size - 1) & ~(CMSG_ALIGN_size - 1) }
 use constant CMSG_ALIGN_SIZEOF_cmsghdr => CMSG_ALIGN(SIZEOF_cmsghdr);
 sub CMSG_SPACE ($) { CMSG_ALIGN($_[0]) + CMSG_ALIGN_SIZEOF_cmsghdr }
@@ -527,6 +530,22 @@ require PublicInbox::CmdIPC4;
 	}
 	@ret;
 };
+
+*sendmsg_more = sub ($@) {
+	use bytes qw(length substr);
+	my $sock = shift;
+	my $iov = join('', map { pack 'P'.TMPL_size_t, $_, length } @_);
+	my $mh = pack(TMPL_msghdr,
+			undef, 0, # msg_name, msg_namelen (unused)
+			$iov, scalar(@_), # msg_iov, msg_iovlen
+			undef, 0, # msg_control, msg_controllen (unused),
+			0); # msg_flags (unused)
+	my $s;
+	do {
+		$s = syscall($SYS_sendmsg, fileno($sock), $mh, MSG_MORE);
+	} while ($s < 0 && $!{EINTR});
+	$s < 0 ? undef : $s;
+};
 }
 
 1;
diff --git a/t/syscall.t b/t/syscall.t
new file mode 100644
index 00000000..19390d09
--- /dev/null
+++ b/t/syscall.t
@@ -0,0 +1,14 @@
+use v5.12;
+use autodie;
+use Test::More;
+use PublicInbox::Syscall;
+use Socket qw(AF_UNIX SOCK_STREAM);
+my $sendmsg_more = PublicInbox::Syscall->can('sendmsg_more') or
+	plan skip_all => "sendmsg syscalls not defined on $^O";
+
+socketpair(my $s1, my $s2, AF_UNIX, SOCK_STREAM, 0);
+is $sendmsg_more->($s1, 'hello', 'world'), 10, 'sendmsg_more expected size';
+is sysread($s2, my $buf, 11), 10, 'reader got expected size from sendmsg_more';
+is $buf, 'helloworld', 'sendmsg_more sent expected message';
+
+done_testing;

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/5] http: set Content-Length for simple array responses
  2024-06-19 23:40 [PATCH 0/5] use sendmsg|writev to reduce syscalls Eric Wong
                   ` (2 preceding siblings ...)
  2024-06-19 23:41 ` [PATCH 3/5] use sendmsg w/ MSG_MORE to reduce syscalls Eric Wong
@ 2024-06-19 23:41 ` Eric Wong
  2024-06-19 23:41 ` [PATCH 5/5] http: use writev for known Content-Length responses Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2024-06-19 23:41 UTC (permalink / raw)
  To: meta

We'll be able to combine the header and body in a single
writev(2) call in the next commit.
---
 lib/PublicInbox/Compat.pm |  4 +++-
 lib/PublicInbox/HTTP.pm   | 13 ++++++++++---
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/lib/PublicInbox/Compat.pm b/lib/PublicInbox/Compat.pm
index 78cba90e..8ed2d7a1 100644
--- a/lib/PublicInbox/Compat.pm
+++ b/lib/PublicInbox/Compat.pm
@@ -8,7 +8,7 @@ use v5.12;
 use parent qw(Exporter);
 require List::Util;
 
-our @EXPORT_OK = qw(uniqstr);
+our @EXPORT_OK = qw(uniqstr sum0);
 
 # uniqstr is in List::Util 1.45+, which means Perl 5.26+;
 # so maybe 2030 for us since we need to support enterprise distros.
@@ -21,4 +21,6 @@ no warnings 'once';
 	grep { !$seen{$_}++ } @_;
 };
 
+*sum0 = List::Util->can('sum0') // sub (@) { List::Util::sum(@_) // 0 };
+
 1;
diff --git a/lib/PublicInbox/HTTP.pm b/lib/PublicInbox/HTTP.pm
index 3ca0d18c..d0e5aa29 100644
--- a/lib/PublicInbox/HTTP.pm
+++ b/lib/PublicInbox/HTTP.pm
@@ -21,6 +21,7 @@
 package PublicInbox::HTTP;
 use strict;
 use parent qw(PublicInbox::DS);
+use bytes qw(length);
 use Fcntl qw(:seek);
 use Plack::HTTPParser qw(parse_http_request); # XS or pure Perl
 use Plack::Util;
@@ -36,6 +37,7 @@ use constant {
 	CHUNK_MAX_HDR => 256,
 };
 use Errno qw(EAGAIN);
+use PublicInbox::Compat qw(sum0);
 
 # Use the same configuration parameter as git since this is primarily
 # a slow-client sponge for git-http-backend
@@ -165,7 +167,7 @@ sub app_dispatch {
 	}
 }
 
-sub response_header_write {
+sub response_header_write ($$$) {
 	my ($self, $env, $res) = @_;
 	my $proto = $env->{SERVER_PROTOCOL} or return; # HTTP/0.9 :P
 	my $status = $res->[0];
@@ -189,6 +191,11 @@ sub response_header_write {
 	my $term = defined($len) || $chunked;
 	my $prot_persist = ($proto eq 'HTTP/1.1') && ($conn !~ /\bclose\b/i);
 	my $alive;
+	if (!$term && ref($res->[2]) eq 'ARRAY') {
+		$len = sum0(map length, @{$res->[2]});
+		$h .= "Content-Length: $len\r\n";
+		$term = 1;
+	}
 	if (!$term && $prot_persist) { # auto-chunk
 		$chunked = $alive = 2;
 		$alive = 3 if $env->{REQUEST_METHOD} eq 'HEAD';
@@ -454,7 +461,7 @@ use v5.12;
 sub write {
 	# ([$http], $buf) = @_;
 	PublicInbox::HTTP::chunked_write($_[0]->[0], $_[1]);
-	$_[0]->[0]->{sock} ? length($_[1]) : undef;
+	$_[0]->[0]->{sock} ? bytes::length($_[1]) : undef;
 }
 
 sub close {
@@ -469,7 +476,7 @@ our @ISA = qw(PublicInbox::HTTP::Chunked);
 sub write {
 	# ([$http], $buf) = @_;
 	PublicInbox::HTTP::identity_write($_[0]->[0], $_[1]);
-	$_[0]->[0]->{sock} ? length($_[1]) : undef;
+	$_[0]->[0]->{sock} ? bytes::length($_[1]) : undef;
 }
 
 1;

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 5/5] http: use writev for known Content-Length responses
  2024-06-19 23:40 [PATCH 0/5] use sendmsg|writev to reduce syscalls Eric Wong
                   ` (3 preceding siblings ...)
  2024-06-19 23:41 ` [PATCH 4/5] http: set Content-Length for simple array responses Eric Wong
@ 2024-06-19 23:41 ` Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2024-06-19 23:41 UTC (permalink / raw)
  To: meta

We could use sendmsg(2) without MSG_MORE here, too, but
writev(2) is simpler to setup and call and we may want to use it
with pipes or regular files in the future, too, not just sockets.
---
 devel/sysdefs-list         |  1 +
 lib/PublicInbox/DS.pm      | 78 ++++++++++++++++++++++----------------
 lib/PublicInbox/HTTP.pm    |  9 +++--
 lib/PublicInbox/Syscall.pm | 29 +++++++++++++-
 t/syscall.t                |  8 +++-
 5 files changed, 87 insertions(+), 38 deletions(-)

diff --git a/devel/sysdefs-list b/devel/sysdefs-list
index ba51de6c..1345e0ba 100755
--- a/devel/sysdefs-list
+++ b/devel/sysdefs-list
@@ -152,6 +152,7 @@ int main(void)
 
 	D(SYS_sendmsg);
 	D(SYS_recvmsg);
+	D(SYS_writev);
 
 	STRUCT_BEGIN(struct flock);
 		PR_NUM(l_start);
diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm
index c5f44860..f807c626 100644
--- a/lib/PublicInbox/DS.pm
+++ b/lib/PublicInbox/DS.pm
@@ -38,6 +38,7 @@ use Carp qw(carp croak);
 use List::Util qw(sum);
 our @EXPORT_OK = qw(now msg_more awaitpid add_timer add_uniq_timer);
 my $sendmsg_more = PublicInbox::Syscall->can('sendmsg_more');
+my $writev = PublicInbox::Syscall->can('writev');
 
 my $nextq; # queue for next_tick
 my $reap_armed;
@@ -551,41 +552,54 @@ sub write {
 	}
 }
 
+sub _iov_write ($$@) {
+	my ($self, $cb) = (shift, shift);
+	my ($tip, $tmpio, $s, $exp);
+	$s = $cb->($self->{sock}, @_);
+	if (defined $s) {
+		$exp = sum(map length, @_);
+		return 1 if $s == $exp;
+		while (@_) {
+			$tip = shift;
+			if ($s >= length($tip)) { # fully written
+				$s -= length($tip);
+			} else { # first partial write
+				$tmpio = tmpio $self, \$tip, $s, @_ or return 0;
+				last;
+			}
+		}
+		$tmpio // return drop $self, "BUG: tmpio on $s != $exp";
+	} elsif ($! == EAGAIN) {
+		$tip = shift;
+		$tmpio = tmpio $self, \$tip, 0, @_ or return 0;
+	} else { # client disconnected
+		return $self->close;
+	}
+	push @{$self->{wbuf}}, $tmpio; # autovivifies
+	epwait $self->{sock}, EPOLLOUT|EPOLLONESHOT;
+	0;
+}
+
 sub msg_more ($@) {
 	my $self = shift;
-	my ($sock, $wbuf);
-	$sock = $self->{sock} or return 1;
-	$wbuf = $self->{wbuf};
-
+	my $sock = $self->{sock} or return 1;
+	my $wbuf = $self->{wbuf};
 	if ($sendmsg_more && (!defined($wbuf) || !scalar(@$wbuf)) &&
-		!$sock->can('stop_SSL')) {
-		my ($s, $tip, $tmpio);
-		$s = $sendmsg_more->($sock, @_);
-		if (defined $s) {
-			my $exp = sum(map length, @_);
-			return 1 if $s == $exp;
-			while (@_) {
-				$tip = shift;
-				if ($s >= length($tip)) { # fully written
-					$s -= length($tip);
-				} else { # first partial write
-					$tmpio = tmpio $self, \$tip, $s, @_
-						or return 0;
-					last;
-				}
-			}
-			$tmpio // return drop $self, "BUG: tmpio on $s != $exp";
-		} elsif ($! == EAGAIN) {
-			$tip = shift;
-			$tmpio = tmpio $self, \$tip, 0, @_ or return 0;
-		} else { # client disconnected
-			return $self->close;
-		}
-		push @{$self->{wbuf}}, $tmpio; # autovivifies
-		epwait $sock, EPOLLOUT|EPOLLONESHOT;
-		0;
-	} else {
-		# don't redispatch into NNTPdeflate::write
+			!$sock->can('stop_SSL')) {
+		_iov_write $self, $sendmsg_more, @_;
+	} else { # don't redispatch into NNTPdeflate::write
+		PublicInbox::DS::write($self, join('', @_));
+	}
+}
+
+sub writev ($@) {
+	my $self = shift;
+	my $sock = $self->{sock} or return 1;
+	my $wbuf = $self->{wbuf};
+	if ($writev && (!defined($wbuf) || !scalar(@$wbuf)) &&
+			!$sock->can('stop_SSL')) {
+		_iov_write $self, $writev, @_;
+	} else { # don't redispatch into NNTPdeflate::write
 		PublicInbox::DS::write($self, join('', @_));
 	}
 }
diff --git a/lib/PublicInbox/HTTP.pm b/lib/PublicInbox/HTTP.pm
index d0e5aa29..73632aee 100644
--- a/lib/PublicInbox/HTTP.pm
+++ b/lib/PublicInbox/HTTP.pm
@@ -190,9 +190,10 @@ sub response_header_write ($$$) {
 	my $conn = $env->{HTTP_CONNECTION} || '';
 	my $term = defined($len) || $chunked;
 	my $prot_persist = ($proto eq 'HTTP/1.1') && ($conn !~ /\bclose\b/i);
-	my $alive;
+	my ($alive, $res_body);
 	if (!$term && ref($res->[2]) eq 'ARRAY') {
-		$len = sum0(map length, @{$res->[2]});
+		($res_body, $res->[2]) = ($res->[2], []);
+		$len = sum0(map length, @$res_body);
 		$h .= "Content-Length: $len\r\n";
 		$term = 1;
 	}
@@ -210,7 +211,9 @@ sub response_header_write ($$$) {
 	}
 	$h .= 'Date: ' . http_date() . "\r\n\r\n";
 
-	if (($len || $chunked) && $env->{REQUEST_METHOD} ne 'HEAD') {
+	if ($res_body) {
+		$self->writev($h, @$res_body);
+	} elsif (($len || $chunked) && $env->{REQUEST_METHOD} ne 'HEAD') {
 		msg_more($self, $h);
 	} else {
 		$self->write(\$h);
diff --git a/lib/PublicInbox/Syscall.pm b/lib/PublicInbox/Syscall.pm
index 80b46c19..ebcedb89 100644
--- a/lib/PublicInbox/Syscall.pm
+++ b/lib/PublicInbox/Syscall.pm
@@ -61,7 +61,7 @@ our ($SYS_epoll_create,
 	$SYS_recvmsg);
 
 my $SYS_fstatfs; # don't need fstatfs64, just statfs.f_type
-my ($FS_IOC_GETFLAGS, $FS_IOC_SETFLAGS);
+my ($FS_IOC_GETFLAGS, $FS_IOC_SETFLAGS, $SYS_writev);
 my $SFD_CLOEXEC = 02000000; # Perl does not expose O_CLOEXEC
 our $no_deprecated = 0;
 
@@ -95,6 +95,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 100;
 		$SYS_sendmsg = 370;
 		$SYS_recvmsg = 372;
+		$SYS_writev = 146;
 		$INOTIFY = { # usage: `use constant $PublicInbox::Syscall::INOTIFY'
 			SYS_inotify_init1 => 332,
 			SYS_inotify_add_watch => 292,
@@ -111,6 +112,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 138;
 		$SYS_sendmsg = 46;
 		$SYS_recvmsg = 47;
+		$SYS_writev = 20;
 		$INOTIFY = {
 			SYS_inotify_init1 => 294,
 			SYS_inotify_add_watch => 254,
@@ -127,6 +129,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 138;
 		$SYS_sendmsg = 0x40000206;
 		$SYS_recvmsg = 0x40000207;
+		$SYS_writev = 0x40000204;
 		$FS_IOC_GETFLAGS = 0x80046601;
 		$FS_IOC_SETFLAGS = 0x40046602;
 		$INOTIFY = {
@@ -164,6 +167,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 100;
 		$SYS_sendmsg = 341;
 		$SYS_recvmsg = 342;
+		$SYS_writev = 146;
 		$FS_IOC_GETFLAGS = 0x40086601;
 		$FS_IOC_SETFLAGS = 0x80086602;
 		$INOTIFY = {
@@ -179,6 +183,7 @@ if ($^O eq "linux") {
 		$SYS_signalfd4 = 313;
 		$SYS_renameat2 //= 357;
 		$SYS_fstatfs = 100;
+		$SYS_writev = 146;
 		$FS_IOC_GETFLAGS = 0x40086601;
 		$FS_IOC_SETFLAGS = 0x80086602;
 	} elsif ($machine =~ m/^s390/) { # untested, no machine on cfarm
@@ -191,6 +196,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 100;
 		$SYS_sendmsg = 370;
 		$SYS_recvmsg = 372;
+		$SYS_writev = 146;
 	} elsif ($machine eq 'ia64') { # untested, no machine on cfarm
 		$SYS_epoll_create = 1243;
 		$SYS_epoll_ctl = 1244;
@@ -216,6 +222,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 44;
 		$SYS_sendmsg = 211;
 		$SYS_recvmsg = 212;
+		$SYS_writev = 66;
 		$INOTIFY = {
 			SYS_inotify_init1 => 26,
 			SYS_inotify_add_watch => 27,
@@ -233,6 +240,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 100;
 		$SYS_sendmsg = 296;
 		$SYS_recvmsg = 297;
+		$SYS_writev = 146;
 	} elsif ($machine =~ m/^mips64/) { # cfarm only has 32-bit userspace
 		$SYS_epoll_create = 5207;
 		$SYS_epoll_ctl = 5208;
@@ -243,6 +251,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 5135;
 		$SYS_sendmsg = 5045;
 		$SYS_recvmsg = 5046;
+		$SYS_writev = 5019;
 		$FS_IOC_GETFLAGS = 0x40046601;
 		$FS_IOC_SETFLAGS = 0x80046602;
 	} elsif ($machine =~ m/^mips/) { # 32-bit, tested on mips64 cfarm host
@@ -255,6 +264,7 @@ if ($^O eq "linux") {
 		$SYS_fstatfs = 4100;
 		$SYS_sendmsg = 4179;
 		$SYS_recvmsg = 4177;
+		$SYS_writev = 4146;
 		$FS_IOC_GETFLAGS = 0x40046601;
 		$FS_IOC_SETFLAGS = 0x80046602;
 		$SIGNUM{WINCH} = 20;
@@ -287,6 +297,7 @@ EOM
 # (I'm assuming Dragonfly copies FreeBSD, here, too)
 	$SYS_recvmsg = 27;
 	$SYS_sendmsg = 28;
+	$SYS_writev = 121;
 }
 
 BEGIN {
@@ -466,8 +477,9 @@ sub CMSG_LEN ($) { CMSG_ALIGN_SIZEOF_cmsghdr + $_[0] }
 use constant msg_controllen_max =>
 	CMSG_SPACE(10 * SIZEOF_int) + SIZEOF_cmsghdr; # space for 10 FDs
 
-if (defined($SYS_sendmsg) && defined($SYS_recvmsg)) {
 no warnings 'once';
+
+if (defined($SYS_sendmsg) && defined($SYS_recvmsg)) {
 require PublicInbox::CmdIPC4;
 
 *send_cmd4 = sub ($$$$;$) {
@@ -548,6 +560,19 @@ require PublicInbox::CmdIPC4;
 };
 }
 
+if (defined($SYS_writev)) {
+*writev = sub {
+	my $fh = shift;
+	use bytes qw(length substr);
+	my $iov = join('', map { pack 'P'.TMPL_size_t, $_, length } @_);
+	my $w;
+	do {
+		$w = syscall($SYS_writev, fileno($fh), $iov, scalar(@_));
+	} while ($w < 0 && $!{EINTR});
+	$w < 0 ? undef : $w;
+};
+}
+
 1;
 
 =head1 WARRANTY
diff --git a/t/syscall.t b/t/syscall.t
index 19390d09..8f0e9efa 100644
--- a/t/syscall.t
+++ b/t/syscall.t
@@ -4,11 +4,17 @@ use Test::More;
 use PublicInbox::Syscall;
 use Socket qw(AF_UNIX SOCK_STREAM);
 my $sendmsg_more = PublicInbox::Syscall->can('sendmsg_more') or
-	plan skip_all => "sendmsg syscalls not defined on $^O";
+	plan skip_all => "sendmsg syscall not defined on $^O";
+my $writev = PublicInbox::Syscall->can('writev') or
+	plan skip_all => "writev syscall not defined on $^O";
 
 socketpair(my $s1, my $s2, AF_UNIX, SOCK_STREAM, 0);
 is $sendmsg_more->($s1, 'hello', 'world'), 10, 'sendmsg_more expected size';
 is sysread($s2, my $buf, 11), 10, 'reader got expected size from sendmsg_more';
 is $buf, 'helloworld', 'sendmsg_more sent expected message';
 
+is $writev->($s1, 'hello', 'world'), 10, 'writev expected size';
+is sysread($s2, $buf, 11), 10, 'reader got expected size from writev';
+is $buf, 'helloworld', 'writev sent expected message';
+
 done_testing;

^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2024-06-19 23:41 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-19 23:40 [PATCH 0/5] use sendmsg|writev to reduce syscalls Eric Wong
2024-06-19 23:41 ` [PATCH 1/5] ds: remove needless O_APPEND import Eric Wong
2024-06-19 23:41 ` [PATCH 2/5] ds: update indentation to match rest of source Eric Wong
2024-06-19 23:41 ` [PATCH 3/5] use sendmsg w/ MSG_MORE to reduce syscalls Eric Wong
2024-06-19 23:41 ` [PATCH 4/5] http: set Content-Length for simple array responses Eric Wong
2024-06-19 23:41 ` [PATCH 5/5] http: use writev for known Content-Length responses Eric Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).