Domanda

Ho un progetto Perl erano Ho appena avuto un problema effettuando una chiamata pacchetto di circolare. Il seguente codice illustra il problema.

Quando questo viene eseguito, ogni pacchetto chiamerà l'altro fino a quando tutta la memoria del computer si consuma e si blocca. Sono d'accordo che questo è un disegno male e che le chiamate circolari come questo non deve essere fatta nel design, ma il mio progetto è sufficientemente grande che vorrei rilevare questo in fase di esecuzione.

Ho letto sulla funzione indebolire e dati :: Struttura :: Util, ma non ho trovato un modo per rilevare se c'è un carico pacchetto di circolare (Sono assumere, perché una nuova copia viene effettuata ad ogni iterazione e memorizzati in ogni copia del $ questo hash). Tutte le idee?

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; 
} 
È stato utile?

Soluzione

Qui, avere un po 'di codice troppo. :)

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

Questi sono chiamati come:

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.

Potrebbe CPAN questi, credo. Se qualcuno ha qualche idea in proposito, si prega di condividere.

Altri suggerimenti

Il fatto che questi siano in pacchetti separati non ha nulla a che fare con il fatto che questo viene eseguito all'infinito, consumando tutte le risorse disponibili. Si sta chiamando due metodi da uno dentro l'altro. Questo non è il riferimento circolare, è ricorsione , che non è la stessa cosa. In particolare, weaken non vi aiuterà a tutti. Si otterrebbe esattamente lo stesso effetto da:

sub a {
    b();
}

sub b {
    a();
}

a();

Il modo migliore per evitare questo è non farlo . Molto più utile, se si deve scrivere funzioni ricorsive cercare di non utilizzare più funzioni nella catena ricorsione, ma semplicemente l'uno, in modo da avere un tempo più facile mentalmente tenere traccia di dove le chiamate dovrebbero terminare.

Su come rilevare se qualcosa di simile sta accadendo, si dovrebbe fare qualcosa di semplice come incrementare una variabile con la profondità di ricorsione e terminare (o il ritorno) se la profondità supera un certo valore. Ma davvero non dovrebbe fare affidamento su questo, è simile a scrivere un ciclo while e l'utilizzo di un incremento lì per assicurarsi che la funzione non viene eseguito fuori controllo. Basta non ricorsivamente su un set a meno che non si sa come e quando termina.

Un'altra questione rilevante sarebbe quello che stai cercando di realizzare in primo luogo?

Suggerisco di fare una routine chiamata qualcosa come break_constructor_recursion () che utilizza chiamante () per esaminare lo stack di chiamate in questo modo:

Scopri cosa metodo nella quale pacchetto appena mi ha chiamato.

Cercare il resto dello stack di chiamate vedere se lo stesso metodo in quello stesso pacchetto è ovunque più in alto.

Se è così, die () con qualcosa di appropriato.

Poi si aggiunge una chiamata a break_constructor_recursion () nelle costruttori. Se il costruttore viene chiamato dal suo interno, sarà bombardare fuori.

Ora, questo può gettare falsi positivi; non è impossibile per un costruttore per essere legittimamente chiamato al suo interno. Se avete problemi con questo, direi che basta farlo cercare qualche N ulteriori occorrenze del costruttore prima che identifica un errore. Se ci sono 20 chiamate a sistema :: :: due new () sullo stack, le probabilità che non si recursing sono piuttosto bassi.

La rottura classico sulla doppia ricorsione è quello di utilizzare una variabile di stato per determinare se si è già all'interno di una funzione:

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

Si può fare quello che vuoi, se $in_a è vero, ma dieing o di ritorno è comune. Se si utilizza Perl 5.10 o versione successiva è possibile utilizzare la funzione di state invece di nidificazione della funzione nel suo campo di applicazione:

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;

senza avvisi:

#!/usr/bin/perl 

use strict;

sub foo {
    foo(); 
}

foo();

-

$ perl script.pl 
^C # after death 

con avvisi:

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

Sempre utilizzare sempre le avvertenze.

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.
$ 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top