PL_strtab/SHAREKEYS and copy-on-write leak

130 Views Asked by At

Perl internally uses dedicated hash PL_strtab as shared storage for hash's keys, but in fork environment like apache/mod_perl this creates a big issue. Best practice says to preload modules in parent process, but nobody says it's eventually allocates memory for PL_strtab and these pages of memory tend to be implicitly modified in child processes. There are seems to be 2 major reasons of modification:

Reason 1: reallocation (hsplit()) may happen when PL_strtab growths in child process. Reason 2: REFCNT every time new reference created.

Example below shows 16MB copy-on-write leak in attempt to use hash. Attempts to recompile perl with -DNODEFAULT_SHAREKEYS fails (https://rt.perl.org/SelfService/Display.html?id=133384). I was able to get access to PL_strtab via XS module.

Ideally I'm looking for a way to downgrade all hashes created in parent to keep hash keys within a hash (HE object) rather than PL_strtab, i.e. turn off SHAREKEYS flag. This should allow to shrink PL_strtab to minimum possible size. Ideally it should have 0 keys in parent.

Please let me know you think it's theoretically possible via XS.

#!/usr/bin/env perl
use strict;
use warnings;

use Linux::Smaps;

$SIG{CHLD} = sub { waitpid(-1, 1) };

# comment this block
{
    my %h;
    # pre-growth PL_strtab hash, kind of: keys %$PL_strtab = 2_000_000;
    foreach my $x (1 .. 2_000_000) {
        $h{$x} = undef;
    }
}

my $pid = fork // die "Cannot fork: $!";
unless ($pid) {
    # child

    my $s = Linux::Smaps->new($$)->all;
    my $before = $s->shared_clean + $s->shared_dirty;

    {
        my %h;
        foreach my $x (1 .. 2_000_000) {
            $h{$x} = undef;
        }
    }

    my $s2 = Linux::Smaps->new($$)->all;
    my $after = $s2->shared_clean + $s2->shared_dirty;
    warn 'COPY-ON-WRITE: ' . ($before - $after) . ' KB';

    exit 0;
}

sleep 1000;
print "DONE\n";

Note, that sample %h in parent get destroyed and not accessible in child. The only purpose of it is to preallocate more memory for PL_strtab and make copy-on-write issue more noticeable.

The problem that PL_strtab is shared data structure (not %h). It's solely controlled by Perl and there is no way to control it or use IPC::Shareable or any other well-known for me CPAN modules.

Real life example:

  • In apache/mod_perl, Starman or any other prefork environment everybody tries to preload as much as possible modules in parent process. Right?
  • If any of preloaded modules creates hash (even temporary) with big number of keys Perl silently allocates more and more memory for internal PL_strtab hash.
  • PL_strtab silently get touched in children on any attempt to use hashes. Problem even worse, because huge percentage of modules we preload are CPAN modules -> there is no way to know which of them overuse hashes resulting in increased memory footprint of parent process.
0

There are 0 best solutions below