[Rt-commit] rt branch 5.0/leaking-recursive-anon-subs created. rt-5.0.4-7-g215439e78d
BPS Git Server
git at git.bestpractical.com
Thu May 18 10:32:25 UTC 2023
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".
The branch, 5.0/leaking-recursive-anon-subs has been created
at 215439e78d464a33d8f1260dc322cc7e82d963f1 (commit)
- Log -----------------------------------------------------------------
commit 215439e78d464a33d8f1260dc322cc7e82d963f1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Thu May 18 12:09:56 2023 +0300
Fix memory leaks in recursive anon subroutines
Introduce RT::Util::RecursiveSub that helps avoid memory leaks
in recursive anon subroutines.
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 88ac450c0f..15f02bf440 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -52,6 +52,8 @@ use warnings;
package RT::Crypt;
use 5.010;
+use RT::Util ();
+
=head1 NAME
RT::Crypt - encrypt/decrypt and sign/verify subsystem for RT
@@ -393,14 +395,15 @@ sub FindProtectedParts {
if ( $args{'Scattered'} ) {
my %parent;
- my $filter; $filter = sub {
+ my $filter = RT::Util::RecursiveSub(sub {
+ my $self_cb = shift;
$parent{$_[0]} = $_[1];
unless ( $_[0]->is_multipart ) {
return () if $args{'Skip'}{$_[0]};
return $_[0];
}
- return map $filter->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts;
- };
+ return map $self_cb->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts;
+ });
my @parts = $filter->($entity);
return @res unless @parts;
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 444eb1c24e..0995a9e12a 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -72,6 +72,7 @@ use RT::Interface::Web::Menu;
use RT::Interface::Web::Session;
use RT::Interface::Web::Scrubber;
use RT::Interface::Web::Scrubber::Permissive;
+use RT::Util ();
use Digest::MD5 ();
use List::MoreUtils qw();
use JSON qw();
diff --git a/lib/RT/Util.pm b/lib/RT/Util.pm
index 16753a5b33..93e0b924b3 100644
--- a/lib/RT/Util.pm
+++ b/lib/RT/Util.pm
@@ -54,6 +54,7 @@ use warnings;
use base 'Exporter';
our @EXPORT = qw/safe_run_child mime_recommended_filename EntityLooksLikeEmailMessage EmailContentTypes/;
+use Scalar::Util qw/weaken/;
use Encode qw/encode/;
sub safe_run_child (&) {
@@ -250,6 +251,39 @@ sub EmailContentTypes {
return ( 'message/rfc822', 'message/partial', 'message/external-body' );
}
+
+=head2 RecursiveSub
+
+When you need an anonymous sub that call itself, you can't just use:
+
+ my $sub;
+ $sub = sub {
+ my @args = @_;
+ ...
+ $sub->(@args);
+ ...
+ };
+
+as that will create a circular reference and leak memory. Instead, you should:
+
+ my $sub = RecursiveSub(sub {
+ my $self_cb = shift;
+ my @args = @_;
+ ...
+ $self_cb->(@args);
+ ,,,
+ });
+
+=cut
+
+sub RecursiveSub {
+ my $code = shift;
+ my ($sub, $wsub);
+ $sub = $wsub = sub { $code->($wsub, @_) };
+ weaken($wsub);
+ return $sub;
+}
+
RT::Base->_ImportOverlays();
1;
diff --git a/share/html/Articles/Topics.html b/share/html/Articles/Topics.html
index 3f3def46cf..5fe697bb96 100644
--- a/share/html/Articles/Topics.html
+++ b/share/html/Articles/Topics.html
@@ -142,8 +142,8 @@ if ($id) {
$currtopic->Load($id);
}
-my $ProduceTree;
-$ProduceTree = sub {
+my $ProduceTree = RT::Util::RecursiveSub(sub {
+ my $self_cb = shift;
my ( $parentid ) = @_;
my $topics = RT::Topics->new( $session{'CurrentUser'} );
$topics->LimitToObject($currclass);
@@ -157,11 +157,11 @@ $ProduceTree = sub {
Topic => $t,
Class => $currclass_id,
);
- $ProduceTree->( $t->Id ) if $t->Children->Count;
+ $self_cb->( $t->Id ) if $t->Children->Count;
$m->out("</li>");
}
$m->out("</ul>");
-};
+});
my @articles;
if ($id) {
diff --git a/share/html/Elements/MyRT b/share/html/Elements/MyRT
index cdefb4d565..4cc8eb92ba 100644
--- a/share/html/Elements/MyRT
+++ b/share/html/Elements/MyRT
@@ -108,8 +108,8 @@ $sidebar = undef unless $sidebar && @$sidebar;
my $Rows = $user->Preferences( 'SummaryRows', ( RT->Config->Get('DefaultSummaryRows') || 10 ) );
-my $show_cb;
-$show_cb = sub {
+my $show_cb = RT::Util::RecursiveSub(sub {
+ my $self_cb = shift;
my $entry = shift;
my $type;
my $name;
@@ -169,13 +169,13 @@ $show_cb = sub {
}
my @panes = @{ $current_dashboard->Panes->{$entry->{pane}} || [] };
for my $portlet (@panes) {
- $show_cb->($portlet, $depth + 1);
+ $self_cb->($portlet, $depth + 1);
}
} else {
$RT::Logger->error("unknown portlet type '$type'");
}
}
-};
+});
</%INIT>
<%ARGS>
diff --git a/share/html/Elements/RT__Asset/ColumnMap b/share/html/Elements/RT__Asset/ColumnMap
index b6a50b7281..578ef038ec 100644
--- a/share/html/Elements/RT__Asset/ColumnMap
+++ b/share/html/Elements/RT__Asset/ColumnMap
@@ -50,12 +50,12 @@ $Name => undef
$Attr => undef
</%ARGS>
<%ONCE>
-my $linkUsers;
-$linkUsers = sub {
+my $linkUsers = RT::Util::RecursiveSub(sub {
+ my $self_cb = shift;
my ($what, $more) = @_;
if ($what->isa("RT::Group")) {
# Link the users (non-recursively)
- my @ret = map {$linkUsers->($_->[1], $more), ", "}
+ my @ret = map {$self_cb->($_->[1], $more), ", "}
sort {$a->[0] cmp $b->[0]}
map {+[($_->EmailAddress||''), $_]}
@{ $what->UserMembersObj( Recursively => 0 )->ItemsArrayRef };
@@ -71,7 +71,7 @@ $linkUsers = sub {
push @ret, $more->($what) if $more;
return @ret;
}
-};
+});
my $COLUMN_MAP = {
Name => {
attribute => 'Name',
diff --git a/share/html/Search/Elements/ConditionRow b/share/html/Search/Elements/ConditionRow
index 255bb5d03b..b53828ca96 100644
--- a/share/html/Search/Elements/ConditionRow
+++ b/share/html/Search/Elements/ConditionRow
@@ -62,15 +62,15 @@ return unless $Condition && $Condition->{'Name'};
$m->callback( Condition => \$Condition );
return unless $Condition;
-my $handle_block;
-$handle_block = sub {
+my $handle_block = RT::Util::RecursiveSub(sub {
+ my $self_cb = shift;
my $box = shift;
return $box unless ref $box;
my $name = shift;
if ( UNIVERSAL::isa($box, 'ARRAY') ) {
my $res = '';
- $res .= $handle_block->( $_, $name ) foreach @$box;
+ $res .= $self_cb->( $_, $name ) foreach @$box;
return $res;
}
@@ -100,7 +100,7 @@ $handle_block = sub {
$res .= qq{</select>};
return $res;
}
-};
+});
</%INIT>
<%ARGS>
diff --git a/share/html/Ticket/ShowEmailRecord.html b/share/html/Ticket/ShowEmailRecord.html
index 9dfbc1bb33..ab29446aea 100644
--- a/share/html/Ticket/ShowEmailRecord.html
+++ b/share/html/Ticket/ShowEmailRecord.html
@@ -69,8 +69,8 @@ my $show_content = sub {
$m->out( '<a href="'. $href .'">'. loc('download') .'</a>' );
};
-my $show;
-$show = sub {
+my $show = RT::Util::RecursiveSub(sub {
+ my $self_cb = shift;
my $attach = shift;
$m->out('</div>'); # Close rt-header-container
$m->out('<div id="body">');
@@ -85,14 +85,14 @@ $show = sub {
if ( $attach->ContentType =~ m{^multipart/}i ) {
my $children = $attach->Children;
while ( my $child = $children->Next ) {
- $show->( $child );
+ $self_cb->( $child );
}
} else {
$show_content->( $attach );
}
$m->out('</div>') if $plain_text_mono;
$m->out('</div>');
-};
+});
# Set error for error message below. Abort doesn't display well
# because ShowEmailRecord doesn't use the standard RT menus
-----------------------------------------------------------------------
hooks/post-receive
--
rt
More information about the rt-commit
mailing list