Comment puis-je contourner un appel 'die' dans une bibliothèque Perl que je ne peux pas modifier?

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

  •  19-08-2019
  •  | 
  •  

Question

Oui, le problème vient d’une bibliothèque que j’utilise, et non, je ne peux pas la modifier. J'ai besoin d'une solution de contournement.

En gros, je traite avec une bibliothèque Perl mal écrite, qui se termine par 'die' quand une certaine condition d'erreur est rencontrée lors de la lecture d'un fichier. J'appelle cette routine à partir d'un programme qui parcourt des milliers de fichiers, dont une poignée sont mauvais. Les mauvais fichiers se produisent; Je veux juste que ma routine enregistre une erreur et passe à autre chose.

SI JE POUVAIS modifier la bibliothèque, je changerais simplement la

die "error";

à un

print "error";return;

, mais je ne peux pas. Est-il possible de mettre la routine en place de manière à ce que les mauvais fichiers ne bloquent pas tout le processus?

QUESTIONNAIRE SUIVANT: Utiliser un "eval" bien articuler l'appel sujet au crash fonctionne bien, mais comment puis-je configurer la gestion des erreurs interceptables dans ce cadre? Pour décrire:

J'ai un sous-programme qui appelle la bibliothèque, qui se bloque, parfois plusieurs fois. Plutôt que de diviser chaque appel de ce sous-programme avec un eval {}, je le laisse simplement mourir et utilise un eval {} au niveau qui appelle mon sous-programme:

my $status=eval{function($param);};
unless($status){print $@; next;}; # print error and go to next file if function() fails

Cependant, il existe des conditions d'erreur que je peux et que je détecte dans function (). Quelle est la manière la plus appropriée / élégante de concevoir la capture d’erreurs dans le sous-programme et la routine d’appel afin d’obtenir le comportement correct à la fois pour les erreurs interceptées et les erreurs non interceptées?

Était-ce utile?

La solution

Vous pouvez l'envelopper dans un eval . Voir:

perldoc -f eval

Par exemple, vous pourriez écrire:

# warn if routine calls die
eval { routine_might_die }; warn $@ if $@;

Ceci transformera l'erreur fatale en un avertissement, qui correspond plus ou moins à ce que vous avez suggéré. Si die est appelé, $ @ contient la chaîne qui lui est transmise.

Autres conseils

Est-ce que cela intercepte $ SIG {__ DIE __} ? Si c'est le cas, c'est plus local que vous. Mais il y a quelques stratégies:

  • Vous pouvez évoquer son paquet et remplacer . mourir:

    package Library::Dumb::Dyer;
    use subs 'die';
    sub die {
        my ( $package, $file, $line ) = caller();
        unless ( $decider->decide( $file, $package, $line ) eq 'DUMB' ) {
            say "It's a good death.";
            die @_;
       }
    } 
    
  • Sinon, vous pouvez le intercepter . (recherchez $ SIG sur la page, Markdown ne gère pas le lien complet.)

    my $old_die_handler = $SIG{__DIE__};
    sub _death_handler { 
        my ( $package, $file, $line ) = caller();
        unless ( $decider->decide( $file, $package, $line ) eq 'DUMB DIE' ) {
            say "It's a good death.";
            goto &$old_die_handler;
        }
    }
    $SIG{__DIE__} = \&_death_handler;
    
  • Vous devrez peut-être analyser la bibliothèque, trouver un sous-utilisateur qu'il appelle toujours et l'utiliser pour charger votre gestionnaire $ SIG en remplaçant que .

    my $dumb_package_do_something_dumb = \&Dumb::do_something_dumb;
    *Dumb::do_something_dumb = sub { 
        $SIG{__DIE__} = ...
        goto &$dumb_package_do_something_dumb;
    };
    
  • Ou remplacez un élément intégré qu'il appelle toujours ...

    package Dumb; 
    use subs 'chdir';
    sub chdir { 
        $SIG{__DIE__} = ...
        CORE::chdir @_;
    };
    
  • Si tout échoue, vous pouvez fouetter les yeux du cheval avec ceci:

    package CORE::GLOBAL;
    use subs 'die';
    
    sub die { 
        ... 
        CORE::die @_;
    }
    

Ceci remplacera die globalement, la seule façon de récupérer die consiste à l'adresse en tant que CORE :: die .

Une combinaison de ceci fonctionnera.

Bien que changer un die pour ne pas mourir ait une solution spécifique, comme indiqué dans les autres réponses, en général, vous pouvez toujours remplacer les sous-routines dans d'autres packages. Vous ne modifiez pas du tout la source originale.

Commencez par charger le package d'origine pour obtenir toutes les définitions d'origine. Une fois l’original en place, vous pouvez redéfinir le sous-programme compliqué:

 BEGIN {
      use Original::Lib;

      no warnings 'redefine';

      sub Original::Lib::some_sub { ... }
      }

Vous pouvez même couper et coller la définition d'origine et modifier ce dont vous avez besoin. Ce n'est pas une bonne solution, mais si vous ne pouvez pas changer la source d'origine (ou si vous voulez essayer quelque chose avant de changer l'original), cela peut fonctionner.

De plus, vous pouvez copier le fichier source original dans un répertoire distinct pour votre application. Puisque vous contrôlez ce répertoire, vous pouvez éditer ses fichiers. Vous modifiez cette copie et vous la chargez en ajoutant ce répertoire au chemin de recherche du module de Perl:

use lib qw(/that/new/directory);
use Original::Lib;  # should find the one in /that/new/directory

Votre copie persiste même si quelqu'un met à jour le module d'origine (bien que vous deviez peut-être fusionner les modifications).

J'en parle assez souvent dans Maîtriser Perl , où je montre d'autres techniques à suivre. ce genre de chose. Le truc est de ne pas casser les choses encore plus. Comment ne pas casser les choses dépend de ce que vous faites.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top