Question

J'ai un projet Perl étaient je viens d'avoir un problème en faisant un appel paquet circulaire. Le code ci-dessous montre le problème.

Lorsque cela est exécuté, chaque paquet appellera l'autre jusqu'à ce que toute la mémoire de l'ordinateur est consommé et il se bloque. Je suis d'accord que cela est une mauvaise conception et que les appels circulaires comme celui-ci ne devrait pas être dans la conception, mais mon projet est suffisamment grand que je voudrais détecter ce au moment de l'exécution.

J'ai lu au sujet de la fonction d'affaiblir et de données :: Structure :: Util, mais je ne l'ai pas trouvé un moyen de détecter s'il y a un paquet circulaire charge (je suis suppose, parce est fait une nouvelle copie à chaque itération et stockée dans chaque copie du $ ce hachage). Toutes les idées?

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; 
} 
Était-ce utile?

La solution

Ici, avoir un code aussi. :)

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;
}

On les appelle comme:

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.

Peut-CPAN ceux-ci, je pense. Si quelqu'un a des pensées à ce sujet, s'il vous plaît partager.

Autres conseils

Le fait que ceux-ci sont dans des emballages séparés n'a rien à voir avec le fait que cela va infiniment, en consommant toutes les ressources disponibles. Vous appelez deux méthodes de uns dans les autres. Ce n'est pas référence circulaire, il est récursivité , qui n'est pas la même chose. En particulier, weaken ne vous aidera pas du tout. Vous obtiendrez exactement le même effet de:

sub a {
    b();
}

sub b {
    a();
}

a();

La meilleure façon d'éviter cela est ne pas le faire . Plus utile, si vous devez écrire des fonctions récursives essayer de ne pas utiliser de multiples fonctions dans la chaîne de récursivité, mais simplement celui, si vous avez un temps plus facile mentalement garder une trace de l'endroit où vos appels doivent mettre fin.

En ce qui concerne la façon de détecter si quelque chose comme cela arrive, vous devez faire quelque chose de simple incrémenter comme une variable avec votre profondeur de récursivité et mettre fin (ou de retour) si la profondeur dépasse une certaine valeur. Mais vous ne devriez pas compter sur cela, il est similaire à l'écriture d'une boucle de while et en utilisant là un incrément pour vous assurer que votre fonction ne fonctionne pas hors de contrôle. Il suffit de ne pas récursif sur un ensemble sauf si vous savez comment et quand il se termine.

Une autre question pertinente serait ce que vous essayez d'accomplir en premier lieu?

Je suggère faire une routine appelée quelque chose comme break_constructor_recursion () qui utilise l'appelant () pour examiner la pile d'appel comme ceci:

En savoir quelle méthode dans ce paquet que m'a appelé.

Recherchez le reste de la pile d'appel voir si la même méthode dans le même paquet est partout plus haut.

Si oui, mourir () avec quelque chose d'approprié.

Ensuite, vous ajoutez un appel à break_constructor_recursion () dans vos constructeurs. Si le constructeur est appelé à l'intérieur lui-même, il va bombarder.

Maintenant, cela peut jeter les faux positifs; il est impossible pour un constructeur à appeler légitimement à l'intérieur lui-même. Si vous avez des problèmes avec cela, je dirais juste l'avoir chercher des N occurrences supplémentaires du constructeur avant qu'il identifie une erreur. S'il y a 20 appels au système :: :: deux nouvelles () sur la pile, les chances que vous n'êtes pas récursion sont assez bas.

La rupture classique sur récursion deux est d'utiliser une variable d'état pour déterminer si vous êtes déjà dans une fonction:

{
    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;
    }
}

Vous pouvez faire ce que vous voulez si $in_a est vrai, mais dieing ou le retour est commun. Si vous utilisez Perl 5.10 ou vous pouvez ensuite utiliser la fonction state au lieu de l'imbrication de la fonction dans sa propre portée:

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;

sans avertissement:

#!/usr/bin/perl 

use strict;

sub foo {
    foo(); 
}

foo();

-

$ perl script.pl 
^C # after death 

avec des avertissements:

#!/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 

Utilisez toujours toujours des avertissements.

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.
$ 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top