Data-Buffer-Shared

 view release on metacpan or  search on metacpan

xt/lock_leak_on_destroy.t  view on Meta::CPAN

#!/usr/bin/perl
# Regression: destroying a handle that still holds a lock must not strand it.
#
# buf_close_map released the process's reader slot only when rdepth == 0, and
# never released a held write lock. So `$b->lock_rd; undef $b;` left the slot
# pinned with a LIVE pid and rdepth > 0 -- and because that pid is alive,
# dead-owner recovery never fires, so every other process's lock_wr starved
# until this process exited.
#
# rdepth is per-process and shared by all handles, so the handle now tracks how
# much of it IT owns (rd_held) and releases exactly that on close.
#
# The waiting writer blocks inside XS (futex), which Perl's alarm cannot
# interrupt, so it runs in a child with a hard timeout from the parent.
use strict;
use warnings;
use Test::More;
use File::Temp qw(tempdir);
use POSIX qw(:sys_wait_h);
use Data::Buffer::Shared;

plan skip_all => 'fork required' unless $Config::Config{d_fork};

my $dir = tempdir(CLEANUP => 1);

for my $mode (qw(rd wr)) {
    my $path = "$dir/leak_$mode.bin";
    unlink $path;

    my $h1 = Data::Buffer::Shared::I64->new($path, 16);
    $mode eq 'rd' ? $h1->lock_rd : $h1->lock_wr;
    undef $h1;                       # destroyed while still holding the lock

    my $pid = fork();
    unless ($pid) {
        my $h2 = Data::Buffer::Shared::I64->new($path, 16);
        $h2->lock_wr;
        $h2->unlock_wr;
        exit 0;
    }

    my ($waited, $done) = (0, 0);
    while ($waited < 10) {
        if (waitpid($pid, WNOHANG) == $pid) { $done = 1; last }
        select undef, undef, undef, 0.2;
        $waited += 0.2;
    }
    unless ($done) { kill 'KILL', $pid; waitpid($pid, 0) }

    ok $done, "a later writer is not starved by a handle destroyed holding a $mode-lock";
    unlink $path;
}

done_testing;



( run in 0.653 second using v1.01-cache-2.11-cpan-14f38c9f855 )