Как я могу обнаружить повторяющиеся вызовы пакетов в Perl? - PullRequest
6 голосов
/ 10 апреля 2009

У меня есть проект на 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; 
} 

Ответы [ 5 ]

4 голосов
/ 11 апреля 2009

Здесь тоже есть код. :)

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 это, я думаю. Если у кого-то есть какие-либо мысли по этому поводу, пожалуйста, поделитесь.

4 голосов
/ 10 апреля 2009

Тот факт, что они находятся в отдельных пакетах, не имеет ничего общего с тем, что он работает бесконечно, потребляя все доступные ресурсы. Вы вызываете два метода друг из друга. Это не круговая ссылка, это рекурсия , что не одно и то же. В частности, weaken вам совсем не поможет. Вы получите точно такой же эффект от:

sub a {
    b();
}

sub b {
    a();
}

a();

Лучший способ избежать этого - не делайте этого . Более полезно, если вам приходится писать рекурсивные функции, старайтесь не использовать несколько функций в цепочке рекурсий, а просто одну, чтобы вам было легче мысленно отслеживать, где ваши вызовы должны завершаться.

Что касается того, как определить, происходит ли что-то подобное, вам нужно сделать что-то простое, например, увеличить переменную с глубиной рекурсии и завершить (или вернуть), если ваша глубина превышает определенное значение. Но вам действительно не нужно полагаться на это, это похоже на написание цикла while и использование там приращения, чтобы убедиться, что ваша функция не выходит из-под контроля. Только не повторяйте набор, если вы не знаете, как и когда он заканчивается.

Другим важным вопросом будет то, что вы пытаетесь достичь в первую очередь?

3 голосов
/ 10 апреля 2009

Я предлагаю сделать подпрограмму с именем что-то вроде break_constructor_recursion (), которая использует caller () для проверки стека вызовов следующим образом:

Узнайте, какой метод в каком пакете только что вызвал меня.

Просмотрите остальную часть стека вызовов, чтобы увидеть, находится ли этот же метод в том же пакете где-нибудь еще выше.

Если так, умри () с чем-то подходящим.

Затем вы добавляете вызов break_constructor_recursion () в ваши конструкторы. Если конструктор вызывается изнутри, он разбомбится.

Теперь это может привести к ложным срабатываниям; конструктор не может быть законно вызван внутри себя. Если у вас есть проблемы с этим, я бы сказал, просто найдите N дополнительных вхождений конструктора, прежде чем он обнаружит ошибку. Если в стеке 20 вызовов system :: two :: new (), вероятность того, что вы не вернетесь, довольно мала.

2 голосов
/ 11 апреля 2009

Классический разрыв в двойной рекурсии - использование переменной состояния, чтобы определить, находитесь ли вы уже внутри функции:

{
    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;
}
1 голос
/ 12 апреля 2009

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.
$ 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...