X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FNNTPdeflate.pm;h=8efb662f5ab5ec3d168e2742b8a0e72da4080ba3;hb=95bdac7f09c69036efed537a4d03d5bdd2ae4eb6;hp=66210bfa84c040c1a2caa90f8d4c01e59d2e5c67;hpb=77c66b4cdb1d52321ed3cb6352fe0b72312cbb71;p=public-inbox.git diff --git a/lib/PublicInbox/NNTPdeflate.pm b/lib/PublicInbox/NNTPdeflate.pm index 66210bfa..8efb662f 100644 --- a/lib/PublicInbox/NNTPdeflate.pm +++ b/lib/PublicInbox/NNTPdeflate.pm @@ -1,14 +1,19 @@ -# Copyright (C) 2019 all contributors +# Copyright (C) 2019-2020 all contributors # License: AGPL-3.0+ # RFC 8054 NNTP COMPRESS DEFLATE implementation -# Warning, enabling compression for C10K NNTP clients is rather -# expensive in terms of memory use. # # RSS usage for 10K idle-but-did-something NNTP clients on 64-bit: -# TLS + DEFLATE : 1.8 GB (MemLevel=9, 1.2 GB with MemLevel=8) -# TLS only : <200MB -# plain : <50MB +# TLS + DEFLATE[a] : 1.8 GB (MemLevel=9, 1.2 GB with MemLevel=8) +# TLS + DEFLATE[b] : ~300MB +# TLS only : <200MB +# plain : <50MB +# +# [a] - initial implementation using per-client Deflate contexts and buffer +# +# [b] - memory-optimized implementation using a global deflate context. +# It's less efficient in terms of compression, but way more +# efficient in terms of server memory usage. package PublicInbox::NNTPdeflate; use strict; use warnings; @@ -23,44 +28,55 @@ my %IN_OPT = ( -AppendOutput => 1, ); -my %OUT_OPT = ( - # nnrpd (INN) and Compress::Raw::Zlib favor MemLevel=9, - # but the zlib C library and git use MemLevel=8 - # as the default. Using 8 drops our memory use with 10K - # TLS clients from 1.8 GB to 1.2 GB, but... - # FIXME: sometimes clients fail with 8, so we use 9 - # -MemLevel => 9, - - # needs more testing, nothing obviously different in terms of memory - -Bufsize => 65536, +# global deflate context and buffer +my $zbuf = \(my $buf = ''); +my $zout; +{ + my $err; + ($zout, $err) = Compress::Raw::Zlib::Deflate->new( + # nnrpd (INN) and Compress::Raw::Zlib favor MemLevel=9, + # the zlib C library and git use MemLevel=8 as the default + # -MemLevel => 9, + -Bufsize => 65536, # same as nnrpd + -WindowBits => -15, # RFC 1951 + -AppendOutput => 1, + ); + $err == Z_OK or die "Failed to initialize zlib deflate stream: $err"; +} - -WindowBits => -15, # RFC 1951 - -AppendOutput => 1, -); sub enable { my ($class, $self) = @_; + my ($in, $err) = Compress::Raw::Zlib::Inflate->new(%IN_OPT); + if ($err != Z_OK) { + $self->err("Inflate->new failed: $err"); + $self->res('403 Unable to activate compression'); + return; + } unlock_hash(%$self); + $self->res('206 Compression active'); bless $self, $class; - $self->{zin} = [ Compress::Raw::Zlib::Inflate->new(%IN_OPT), '' ]; - $self->{zout} = [ Compress::Raw::Zlib::Deflate->new(%OUT_OPT), '' ]; + $self->{zin} = $in; } # overrides PublicInbox::NNTP::compressed sub compressed { 1 } -# SUPER is PublicInbox::DS::do_read, so $_[1] may be a reference or not +# $_[1] may be a reference or not sub do_read ($$$$) { my ($self, $rbuf, $len, $off) = @_; my $zin = $self->{zin} or return; # closed - my $deflated = \($zin->[1]); - my $r = $self->SUPER::do_read($deflated, $len) or return; + my $doff; + my $dbuf = delete($self->{dbuf}) // ''; + $doff = length($dbuf); + my $r = PublicInbox::DS::do_read($self, \$dbuf, $len, $doff) or return; # assert(length($$rbuf) == $off) as far as NNTP.pm is concerned - # -ConsumeInput is true, so $deflated is automatically emptied - my $err = $zin->[0]->inflate($deflated, $rbuf); + # -ConsumeInput is true, so $dbuf is automatically emptied + my $err = $zin->inflate($dbuf, $rbuf); if ($err == Z_OK) { + $self->{dbuf} = $dbuf if $dbuf ne ''; $r = length($$rbuf) and return $r; # nothing ready, yet, get more, later $self->requeue; @@ -74,31 +90,42 @@ sub do_read ($$$$) { # override PublicInbox::DS::msg_more sub msg_more ($$) { my $self = $_[0]; - my $zout = $self->{zout}; # $_[1] may be a reference or not for ->deflate - my $err = $zout->[0]->deflate($_[1], $zout->[1]); + my $err = $zout->deflate($_[1], $zbuf); $err == Z_OK or die "->deflate failed $err"; 1; } -# SUPER is PublicInbox::DS::write, so $_[1] may be a reference or not +sub zflush ($) { + my ($self) = @_; + + my $deflated = $zbuf; + $zbuf = \(my $next = ''); + + my $err = $zout->flush($deflated, Z_FULL_FLUSH); + $err == Z_OK or die "->flush failed $err"; + + # We can still let the lower socket layer do buffering: + PublicInbox::DS::msg_more($self, $$deflated); +} + +# compatible with PublicInbox::DS::write, so $_[1] may be a reference or not sub write ($$) { my $self = $_[0]; - return $self->SUPER::write($_[1]) if ref($_[1]) eq 'CODE'; - my $zout = $self->{zout}; - my $deflated = pop @$zout; + return PublicInbox::DS::write($self, $_[1]) if ref($_[1]) eq 'CODE'; + + my $deflated = $zbuf; + $zbuf = \(my $next = ''); # $_[1] may be a reference or not for ->deflate - my $err = $zout->[0]->deflate($_[1], $deflated); + my $err = $zout->deflate($_[1], $deflated); $err == Z_OK or die "->deflate failed $err"; - $err = $zout->[0]->flush($deflated, Z_PARTIAL_FLUSH); + $err = $zout->flush($deflated, Z_FULL_FLUSH); $err == Z_OK or die "->flush failed $err"; - # PublicInbox::DS::write puts partial writes into another buffer, - # so we can prepare the next deflate buffer: - $zout->[1] = ''; - $self->SUPER::write(\$deflated); + # We can still let the socket layer do buffering: + PublicInbox::DS::write($self, $deflated); } 1;