2011-01-05 17 views
7

Necesito la habilidad de agregar acciones al final de un bloque léxico donde la acción puede morir. Y necesito la excepción para ser arrojado normalmente y poder ser atrapado normalmente.Necesito un final de acción de alcance léxico que pueda morir normalmente

Lamentablemente, Perl excepciones de casos especiales durante DESTROY tanto agregando "(en la limpieza)" al mensaje y haciéndolo imposible de trazar. Por ejemplo:

{ 
    package Guard; 

    use strict; 
    use warnings; 

    sub new { 
     my $class = shift; 
     my $code = shift; 
     return bless $code, $class; 
    } 

    sub DESTROY { 
     my $self = shift; 
     $self->(); 
    } 
} 

use Test::More tests => 2; 

my $guard_triggered = 0; 

ok !eval { 
    my $guard = Guard->new(
#line 24 
     sub { 
      $guard_triggered++; 
      die "En guarde!" 
     } 
    ); 
    1; 
}, "the guard died"; 

is [email protected], "En guarde! at [email protected] line 24\n", "with the right error message"; 
is $guard_triggered, 1,     "the guard worked"; 

Quiero que pase. En la actualidad, la excepción es totalmente tragada por la evaluación.

Esto es para Test :: Builder2, por lo que no puedo usar nada más que Perl puro.

El problema subyacente es que tengo un código como éste:

{ 
    $self->setup; 

    $user_code->(); 

    $self->cleanup; 
} 

que la limpieza debe ocurrir incluso si el $ user_code muere, otro $ auto entra en un estado extraño. Así que hice esto:

{ 
    $self->setup; 

    my $guard = Guard->new(sub { $self->cleanup }); 

    $user_code->(); 
} 

La complejidad viene porque la limpieza se ejecuta código de usuario arbitrario y se trata de un caso de uso en donde el Código morirá. Espero que esa excepción sea atrapable e inalterada por el guardia.

Estoy evitando envolver todo en bloques eval debido a la forma en que altera la pila.

+1

http://search.cpan.org/~rgarcia/perl-5.10.0-RC2/pod/perlcall.pod#G_KEEPERR explica el mecanismo ''(en limpieza)' ', http: //cpansearch.perl .org/src/JESSE/perl-5.12.2/ext/XS-APItest/t/call.t muestra su uso. todo esto está relacionado con XS, pero tal vez te ayudará a apuntar a ti o a alguien más a una respuesta perl pura. –

+0

@Eric Gracias por la información extra, no sabía de eso. No creo que tenga ninguna forma de llegar a esa bandera sin XS. ¡Seguro sería útil! – Schwern

+0

+1 por hacerme aprender y recordar un par de cosas geniales de Perl como parte de mi intento de respuesta (y me gustaría poder dar +100 por 'Test :: Builder2' :). Creo que puedo haber encontrado una solución posible para usted - vea la respuesta a continuación - por favor critique si no recibí sus requisitos correctamente – DVK

Respuesta

0

ACTUALIZACIÓN: ¡El enfoque a continuación no parece funcionar como está escrito como señaló Eric !.

Dejo esta respuesta en caso de que alguien pueda ponerla en funcionamiento.

El problema es:

que esperaba que al quitar los viejos valor mundial de nuevo en la variable atada mundial una vez que el local se sale del ámbito implicará una llamada a FETCH/almacén, pero de alguna manera esto sólo sucede en silencio sin tocar el mecanismo vinculado (el problema es irrelevante para el manejo de excepciones).


Schwern - No estoy 100% seguro de que puede utilizar la técnica del lazo (robado de Perlmonks post by Abigail) para su caso de uso - aquí está mi intento de hacer lo que creo que estaba tratando de hacer

use Test::More tests => 6; 

my $guard_triggered = 0; 
sub user_cleanup { $guard_triggered++; die "En guarde!" }; # Line 4; 
sub TIESCALAR {bless \(my $dummy) => shift} 
sub FETCH  { user_cleanup(); } 
sub STORE  {1;} 
our $guard; 
tie $guard => __PACKAGE__; # I don't think the actual value matters 

sub x { 
    my $x = 1; # Setup 
    local $guard = "in x"; 
    my $y = 2; #user_code; 
} 

sub x2 { 
    my $x = 1; # Setup 
    local $guard = "in x2"; 
    die "you bastard"; #user_code; 
} 

ok !eval { 
    x(); 
}, "the guard died"; 
is [email protected], "En guarde! at $0 line 4.\n", "with the right error message"; 
is $guard_triggered, 1,     "the guard worked"; 

ok !eval { 
    x2(); 
}, "the guard died"; 
is [email protected], "En guarde! at $0 line 4.\n", "with the right error message"; 
is $guard_triggered, 2,     "the guard worked"; 

SALIDA:

1..6 
ok 1 - the guard died 
ok 2 - with the right error message 
ok 3 - the guard worked 
ok 4 - the guard died 
ok 5 - with the right error message 
ok 6 - the guard worked 
+0

el código de protección no parece estar ejecutándose al final del alcance con este método. en el caso de que el guardia muera, lo hace antes de que se hayan ejecutado todas las declaraciones en el bloque. coloque un 'print ...' después de las líneas 'locales' –

+0

@Eric - hmm .... tiene razón. Esperaba que se llamara al FETCH cuando se necesita restaurar el valor del alcance global, pero me equivoqué y, en cambio, lo llama cuando asigna locales. Déjame jugar con esto un poco más – DVK

+0

¡Un enfoque interesante! Lamentablemente, prefiero evitar el uso de Test :: Builder2. Demasiado fino. – Schwern

2

¿Es esta semánticamente sonido? Por lo que entiendo, usted tiene esta (en pseudocódigo):

try { 
    user_code(); # might throw 
} 
finally { 
    clean_up(); # might throw 
} 

hay dos posibilidades:

  • user_code() y clean_up() nunca va a lanzar en el mismo plazo, en cuyo caso sólo se puede escribirlo como código secuencial sin ningún tipo de negocio divertido de guardia y funcionará.
  • user_code() y clean_up() pueden, en algún momento, lanzar ambos en la misma ejecución.

Si ambas funciones pueden arrojarse, entonces tiene dos excepciones activas. No sé cualquier lenguaje que pueda manejar múltiples excepciones activas actualmente lanzadas, y estoy seguro de que hay una buena razón para esto. Perl agrega (in cleanup) y hace la excepción imposible de trazar; C++ calls terminate(), Java drops the original exception silently, etc, etc

Si acaba de salir de una eval en el que tanto user_code() y cleanup() arrojó excepciones, ¿qué esperas de encontrar en [email protected]?

Por lo general, esto indica que necesita para manejar la excepción de limpieza a nivel local, tal vez haciendo caso omiso de la excepción de limpieza:

try { 
    user_code(); 
} 
finally { 
    try { 
     clean_up(); 
    } 
    catch { 
     # handle exception locally, cannot propagate further 
    } 
} 

o tiene que elegir una excepción a ignorar cuando ambos tiro (que es lo que hace la solución de DVK; ignora la excepción user_code()):

try { 
    user_code(); 
} 
catch { 
    $user_except = [email protected]; 
} 
try { 
    cleanup(); 
} 
catch { 
    $cleanup_except = [email protected]; 
} 
die $cleanup_except if $cleanup_except; # if both threw, this takes precedence 
die $user_except if $user_except; 

o de alguna manera combinar las dos excepciones en una excepción objeto:

try { 
    user_code(); 
} 
catch { 
    try { 
     clean_up(); 
    } 
    catch { 
     throw CompositeException; # combines user_code() and clean_up() exceptions 
    } 
    throw; # rethrow user_code() exception 
} 
clean_up(); 

Creo que debería haber una manera de evitar repetir la línea clean_up() en el ejemplo anterior, pero no puedo pensar en ello.

En resumen, sin saber lo que usted piensa que debería suceder cuando se lanzan ambas partes, su problema no puede ser respondido.

+0

En mi humilde opinión, la incertidumbre semántica no es realmente muy relevante: el problema es la imposibilidad de capturar una excepción envuelta por DESTROY. No creo que '(en la limpieza)' "aproximación de la superación compuesta" que emplea Perl sea un problema tan grande, si la excepción resultante podría haber sido atrapada por un 'eval 'externo que no puede ser debido a' G_KEEPERR'. – DVK

+0

@DVK y ¿por qué crees que la excepción envuelta en DESTROY es imposible de trazar? Es porque estás abandonando silenciosamente cualquier excepción existente que se esté lanzando actualmente y que aún no se haya detectado. DESTROY hace que la excepción sea imposible de atrapar porque estás en una situación de la cual * no puedes * recuperar sin perder información sobre lo que sucedió. No debe encontrarse en una situación en la que las excepciones puedan arrojarse y caer silenciosamente sin ser atrapadas o haciendo que el programa muera. Esa pérdida de información mata la seguridad de las excepciones al evitar que los niveles superiores conozcan las condiciones excepcionales en niveles más bajos. –

Cuestiones relacionadas