Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/Overload/FileCheck.pm
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,10 @@ sub _check {

$file = $_last_call_for if !defined $file && defined $_last_call_for && !defined $_current_mocks->{ $MAP_FC_OP{'stat'} };
my ( $out, @extra ) = $_current_mocks->{$optype}->($file);
$_last_call_for = $file;
# Only cache string filenames, not filehandle references.
# Storing a ref here prevents the filehandle from being garbage collected,
# causing resource leaks (e.g. sockets staying open). See GH #179.
$_last_call_for = ref($file) ? undef : $file;

# FIXME return undef when not defined out

Expand Down
66 changes: 66 additions & 0 deletions t/fh-ref-leak.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/perl

# Test that file check operators do not retain references to filehandles
# passed as arguments. This prevents garbage collection of the filehandle,
# which can cause resource leaks (e.g. sockets staying open).
#
# See: https://github.com/cpanel/Test-MockFile/issues/179

use strict;
use warnings;

use Test2::Bundle::Extended;
use Test2::Tools::Explain;

use Scalar::Util qw(weaken);
use Overload::FileCheck -from_stat => \&my_stat, qw{:check};

sub my_stat {
my ( $stat_or_lstat, $f ) = @_;
return FALLBACK_TO_REAL_OP();
}

# Test that filehandle references are not retained by $_last_call_for
{
my $weak_ref;

{
open my $fh, '<', '/dev/null' or die "Cannot open /dev/null: $!";
$weak_ref = $fh;
weaken($weak_ref);

ok( defined $weak_ref, "weak ref is defined before scope exit" );

# Trigger a file check on the filehandle — this used to store $fh
# in $_last_call_for, preventing garbage collection.
no warnings;
-f $fh;
}

ok( !defined $weak_ref, "filehandle is garbage collected after -f check (no ref leak)" );
}

# Test with -S (the operator from the original bug report)
{
my $weak_ref;

{
open my $fh, '<', '/dev/null' or die "Cannot open /dev/null: $!";
$weak_ref = $fh;
weaken($weak_ref);

no warnings;
-S $fh;
}

ok( !defined $weak_ref, "filehandle is garbage collected after -S check (no ref leak)" );
}

# Test that string filenames still work for _ caching (no regression)
{
no warnings;
ok( -f $0, "-f \$0 works" );
ok( -e _, "-e _ works after -f on string filename" );
}

done_testing;