Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Can I catch a die from someone else's module?

by bartender1382 (Beadle)
on Apr 18, 2022 at 18:44 UTC ( [id://11143055]=perlquestion: print w/replies, xml ) Need Help??

bartender1382 has asked for the wisdom of the Perl Monks concerning the following question:

Can I catch a die from someone else's module?

I am using Spreadsheet::Read, which is great. However, am trying to write code that catches all errors.

The author was kind enough to tell me that : "If a file cannot be found or cannot be opened, ReadData will croak and the ref is false"

However, when I try anything like below (with die, without, etc...) I don't comeback from the module, so I just can't tell what's going on.

p.s. I purposely deformed the filename below so it would fail. Yes, I could check for the file's existence first. But am concerned with other errors that may pop up in a 3rd party module.

try { $book = ReadData ("$upload_dir/yello$filename"); } catch { return SSFILENOTOPENED; }

Replies are listed 'Best First'.
Re: Can I catch a die from someone else's module?
by kcott (Archbishop) on Apr 18, 2022 at 19:59 UTC

    G'day bartender1382,

    I had a look in Spreadsheet::Read but can't find the "If a file cannot be found ..." message you reported. Perhaps it's not an error message but private feedback from the module's author. Regardless, I have no context for the die() or what it's reporting.

    Having said that, I suspect what you want is eval. Compare the following two short pieces of code and their output.

    Here's the type of scenario I think you're dealing with:

    #!/usr/bin/env perl use strict; use warnings; package Module::That::Dies; sub die_here { die "RIP Module::That::Dies" } package main; Module::That::Dies::die_here(); print "main continues ...\n";

    Output:

    RIP Module::That::Dies at ./pm_11143055_trap_mod_errors.pl line 8.

    Here's a modification of that code to do what I think you want:

    #!/usr/bin/env perl use strict; use warnings; package Module::That::Dies; sub die_here { die "RIP Module::That::Dies" } package main; eval { Module::That::Dies::die_here(); 1; } or do { warn "Error from Module::That::Dies:\n$@\n"; }; print "main continues ...\n";

    Output:

    Error from Module::That::Dies: RIP Module::That::Dies at ./pm_11143055_trap_mod_errors.pl line 8. main continues ...

    If that's not what you want, please provide more context for the problem to help us to help you.

    — Ken

      eval { Module::That::Dies::die_here(); 1; } or do { warn "Error from Module::That::Dies:\n$@\n"; };

      bartender1382: I just wanted to highlight that eval { ...; 1 } or do { ... } is indeed the correct pattern to use, instead of the unfortunately still common eval ...; if ($@) - see Bug in eval in pre-5.14 and my reply Re: Bug in eval in pre-5.28. In fact, this use of eval is very similar in function to Try::Tiny. The latter just has a few small advantages as described in its documentation, while the eval solution has the advantage that it is pure Perl built-in (Update: Try::Tiny is pure Perl too, but not a core module).

      Yes, eval definitely works!

      Quick followup:

      Do I need the 1; ? It works with and without it, just wanted to make sure it might not be needed for something internal, or was it there for an example?

      eval { Module::That::Dies::die_here(); 1; } or do { warn "Error from Module::That::Dies:\n$@\n"; };

        Do I need the 1; ?

        Generally, yes, you should include it. The reason is that eval returns undef on failure, which is what the or do { ... } is intended to react to. However, the or would also be triggered if the eval block were to return any other false value, such as 0 or the empty string. For example, the following incorrectly prints "Error from Module::That::Dies:". So unless you can be absolutely sure that the eval block always returns a true value, you should include the 1 (or any other true value) as the last statement of the block.

        sub Module::That::Dies::die_here { return 0 } eval { Module::That::Dies::die_here(); #1; } or do { warn "Error from Module::That::Dies:\n$@\n"; };

        Update: I tend to write it like the following, since the 1 on a line by itsself can look out of place and might be accidentally deleted:

        eval { ... ;1 } or do { # catch ... }
        "Do I need the 1;?"

        ++ Good question.

        I would always include it. If you think other maintainers may not understand its importance, add a comment like:

        eval { ...; 1; # IMPORTANT! Do not remove. See: https://www.perlmonks.org/?nod +e_id=11143055 } or do { ...; };

        Even in situations when the last statement in the eval block evaluates to 1, or some other TRUE value, I would include it:

        eval { ...; $x = 1; 1; } ...;

        Otherwise, if someone removes the '$x = 1;' line, or changes the value, but does not know, or forgets, to add the '1;' statement, the 'eval {...} or do {...};' construct may be broken.

        — Ken

        I'd like to add as a separate comment, while the use if the 1; is "it depends"; I would absolutely always localize $@ as close in scope to the eval {} as make sense; usually right before it (unless it's already been done in the same scope and if there are a series of eval{}s that have already been successfully executed.
        local $@; my $ok = eval { doSomethingButDontWantReturnValButCouldDie() or undef; 1; };
        I think it depends; for this module I would do this since it returns the reference if successful.
        local $@; my $book = eval { Spreadsheet::Read->new($file); };
        or
        local $@; my $book; my $ok = eval { $book = Spreadsheet::Read->new($file); 1; };
        The second example seems like "more work" to handle the error and is a lot messier. So in this case adding the "1" is superfulous since the called sub actually returns something (or doesn't).

        A case where I'd use 1; with an or undef thrown in:

        local $@; my $ok = eval { doSomethingButDontWantReturnValButCouldDie() or undef; 1; };
        I think ultimately, the answer is like I stated above, it depends - on what you're doing and expecting; and how you tend to check for/handle exceptions.

        I've also done this; but usually it's a game time decision for me.

        local $@; my $ok = eval { doSomethingButDontWantReturnValButCouldDie(); 1; } or undef;
Re: Can I catch a die from someone else's module?
by haukex (Archbishop) on Apr 18, 2022 at 19:56 UTC

    Which try/catch module are you using? Because if it's Try::Tiny, note that the try and catch blocks are actually anonymous subs, so when you return from them, you're actually just returning from that block, and not any surrounding function. So the following won't work:

    use Try::Tiny; sub foo { try { die "bang" } catch { return "nope" }; # just returns from this block! return "foo"; } print foo(), "\n"; # prints "foo"

    To work around this, you'll have to either not rely on return from the blocks, instead e.g. using flag variables, or you'll have to switch to a different try/catch module, or perhaps even the new experimental built-in Try Catch Exception Handling in Perl 5.34+.

    However, personally I usually stick with Try::Tiny and work around the limitation described above.

    Update: To answer the question in the title, generally yes, you can catch errors thrown by third party code like this as well. In my experience it's very rare that an error in a module will cause the entire perl interpreter to exit abruptly, and if it does, that's almost certainly a bug.

Re: Can I catch a die from someone else's module?
by LanX (Saint) on Apr 18, 2022 at 20:19 UTC
    for completeness: another way is registering a die handler in %SIG via

    $SIG{"__DIE__"} = sub { do something }

    It depends on the use case and module if that's preferable to block-eval resp. catch...

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

Re: Can I catch a die from someone else's module?
by perlfan (Vicar) on May 01, 2022 at 03:01 UTC
    The answer yes; unless they're catching the exception first then neutralizing it (which would be dumb, but it happens). I think the key is, the ref is false. Maybe try it with eval to double check, I do see croak all over his module,
    local $@; my $book = eval { ReadData("$upload_dir/yello$filename") }; if ($@ or not $book) { # ... handle error }
    You should also head off some issues by checking to see if "$upload_dir/yello$filename" even exists and is readable.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11143055]
Approved by davido
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (6)
As of 2024-04-18 00:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found