Как я могу обнаружить рекурсивные вызовы пакетов в Perl?
Вопрос
У меня есть проект Perl, в котором у меня только что возникла проблема при циклическом вызове пакета.Код ниже демонстрирует проблему.
Когда это выполняется, каждый пакет будет вызывать другой, пока вся память компьютера не будет использована и он не заблокируется.Я согласен, что это плохой дизайн и что подобные циклические вызовы не следует делать в проекте, но мой проект достаточно велик, и я хотел бы обнаружить это во время выполнения.
Я читал о функции ослабления и Data::Structure::Util, но я не нашел способа определить, существует ли циклическая загрузка пакета (я предполагаю, потому что новая копия создается на каждой итерации и сохраняется в каждой копии хеша $this).Есть идеи?
use system::one;
my $one = new system::one();
package system::one;
use strict;
use system::two;
sub new {
my ($class) = @_;
my $this = {};
bless($this,$class);
# attributes
$this->{two} = new system::two();
return $this;
}
package system::two;
use strict;
use system::one;
sub new {
my ($class) = @_;
my $this = {};
bless($this,$class);
# attributes
$this->{one} = new system::one();
return $this;
}
Решение
Здесь тоже есть код.:)
sub break_recursion(;$) {
my $allowed = @_ ? shift : 1;
my @caller = caller(1);
my $call = $caller[3];
my $count = 1;
for(my $ix = 2; @caller = caller($ix); $ix++) {
croak "found $count levels of recursion into $call"
if $caller[3] eq $call && ++$count > $allowed;
}
}
sub check_recursion(;$) {
my $allowed = @_ ? shift : 1;
my @caller = caller(1);
my $call = $caller[3];
my $count = 1;
for(my $ix = 2; @caller = caller($ix); $ix++) {
return 1
if $caller[3] eq $call && ++$count > $allowed;
}
return 0;
}
Они называются так:
break_recursion(); # to die on any recursion
break_recursion(5); # to allow up to 5 levels of recursion
my $recursing = check_recursion(); # to check for any recursion
my $recursing = check_recursion(10); # to check to see if we have more than 10 levels of recursion.
Думаю, это может быть CPAN.Если у кого-то есть мысли по этому поводу, поделитесь.
Другие советы
Тот факт, что они находятся в отдельных пакетах, не имеет никакого отношения к тому, что это работает бесконечно, потребляя все доступные ресурсы.Вы вызываете два метода друг из друга.Это не циклическая ссылка, это рекурсия, что не одно и то же.В частности, weaken
тебе вообще не поможет.Вы получите точно такой же эффект от:
sub a {
b();
}
sub b {
a();
}
a();
Лучший способ избежать этого — не делай этого.Более полезно то, что если вам нужно писать рекурсивные функции, старайтесь не использовать несколько функций в цепочке рекурсии, а просто использовать одну, чтобы вам было легче мысленно отслеживать, где должны завершиться ваши вызовы.
Что касается того, как определить, происходит ли что-то подобное, вам нужно будет сделать что-то простое, например увеличить переменную на глубину вашей рекурсии и завершить (или вернуть), если ваша глубина превышает определенное значение.Но вам действительно не следует на это полагаться, это похоже на написание while
цикл и используя приращение, чтобы убедиться, что ваша функция не выйдет из-под контроля.Просто не повторяйте набор, если не знаете, как и когда он завершится.
Еще один актуальный вопрос: чего вы пытаетесь достичь в первую очередь?
Я предлагаю создать процедуру, называемую чем-то вродеbreak_constructor_recursion(), которая использует caller() для проверки стека вызовов следующим образом:
Узнать, какой метод в каком пакете мне только что позвонил.
Посмотрите остальную часть стека вызовов и посмотрите, находится ли тот же метод в том же пакете где-нибудь дальше.
Если да, то используйте die() что-нибудь подходящее.
Затем вы добавляете вызов Break_constructor_recursion() в свои конструкторы.Если конструктор вызывается изнутри самого себя, он выйдет из строя.
Теперь это может привести к ложным срабатываниям;вполне возможно, что конструктор может быть законно вызван внутри себя.Если у вас есть проблемы с этим, я бы посоветовал просто поискать N дополнительных вхождений конструктора, прежде чем он обнаружит ошибку.Если в стеке 20 вызовов system::two::new(), вероятность того, что вы не используете рекурсию, довольно мала.
Классический вариант двойной рекурсии — использовать переменную состояния, чтобы определить, находитесь ли вы уже внутри функции:
{
my $in_a;
sub a {
return if $in_a; #do nothing if b(), or someone b() calls, calls a()
$in_a = 1;
b();
$in_a = 0;
}
}
Ты можешь делать все, что захочешь, если $in_a
это правда, но die
возвращение или возвращение — обычное дело.Если вы используете Perl 5.10 или более позднюю версию, вы можете использовать команду state
вместо вложения функции в ее собственную область видимости:
sub a {
state $in_a;
return if $in_a; #do nothing if b(), or someone b() calls, calls a()
$in_a = 1;
b();
$in_a = 0;
}
use warnings;
без предупреждений:
#!/usr/bin/perl
use strict;
sub foo {
foo();
}
foo();
-
$ perl script.pl ^C # after death
с предупреждениями:
#!/usr/bin/perl
use strict;
use warnings;
sub foo {
foo();
}
foo();
-
$ perl script.pl Deep recursion on subroutine "main::foo" at script.pl line 7. ^C # after death
Всегда всегда используйте предупреждения.
use warnings FATAL => qw( recursion );
#!/usr/bin/perl
use strict;
use warnings FATAL => qw( recursion );
sub foo {
foo();
}
foo();
-
$ perl script.pl Deep recursion on subroutine "main::foo" at script.pl line 7. $