Как я могу обнаружить рекурсивные вызовы пакетов в Perl?

StackOverflow https://stackoverflow.com/questions/738636

Вопрос

У меня есть проект 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.
$ 
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top