1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
# RFC 8054 NNTP COMPRESS DEFLATE implementation
# RFC 4978 IMAP COMPRESS=DEFLATE extension
#
# RSS usage for 10K idle-but-did-something NNTP clients on 64-bit:
# 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::DSdeflate;
use strict;
use v5.10.1;
use Compress::Raw::Zlib;
my %IN_OPT = (
-Bufsize => 1024,
-WindowBits => -15, # RFC 1951
-AppendOutput => 1,
);
# global deflate context and buffer
my ($zout, $zbuf);
{
my $err;
$zbuf = \(my $initial = ''); # replaced by $next in dflush/write
($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";
}
sub enable {
my ($class, $self) = @_;
my ($in, $err) = Compress::Raw::Zlib::Inflate->new(%IN_OPT);
if ($err != Z_OK) {
warn("Inflate->new failed: $err\n");
return;
}
bless $self, $class;
$self->{zin} = $in;
}
# overrides PublicInbox::DS::compressed
sub compressed { 1 }
sub do_read ($$$$) {
my ($self, $rbuf, $len, $off) = @_;
my $zin = $self->{zin} or return; # closed
my $doff;
my $dbuf = delete($self->{dbuf}) // '';
$doff = length($dbuf);
my $r = PublicInbox::DS::do_read($self, \$dbuf, $len, $doff) or return;
# Workaround inflate bug appending to OOK scalars:
# <https://rt.cpan.org/Ticket/Display.html?id=132734>
# We only have $off if the client is pipelining, and pipelining
# is where our substr() OOK optimization in event_step makes sense.
if ($off) {
my $copy = $$rbuf;
undef $$rbuf;
$$rbuf = $copy;
}
# assert(length($$rbuf) == $off) as far as NNTP.pm is concerned
# -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;
} else {
delete $self->{zin};
$self->close;
}
0;
}
# override PublicInbox::DS::msg_more
sub msg_more ($$) {
my $self = $_[0];
# $_[1] may be a reference or not for ->deflate
my $err = $zout->deflate($_[1], $zbuf);
$err == Z_OK or die "->deflate failed $err";
1;
}
sub dflush ($) {
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 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->deflate($_[1], $deflated);
$err == Z_OK or die "->deflate failed $err";
$err = $zout->flush($deflated, Z_FULL_FLUSH);
$err == Z_OK or die "->flush failed $err";
# We can still let the socket layer do buffering:
PublicInbox::DS::write($self, $deflated);
}
1;
|