2

I'm new to using objects, and don't understand why Try::Tiny won't catch the errors in my try statements. Here is a sample of what I'm trying to do:

use strict;  
use warnings;
use Net::FTP;
use Try::Tiny;

my $TIMEOUT = 5;
my $HOST = 'replace_with_hostname';
my $USER = 'replace_with_username';
my $PASS = 'replace_with_password';
my @ERRORS;
my $ftp = undef;

sub my_sub {
  my $err = shift;
  push(@ERRORS,$err);
  goto END;
}

try {
  local $SIG{ALRM} = sub { die "new\n" };
  alarm $TIMEOUT;
  $ftp = Net::FTP->new($HOST, Passive=>1, DEBUG=>3, Timeout=>2);
  alarm 0;
}
catch {
  die $_ if $_ ne "new\n"; 
  my_sub("FTP: cannot connect to [$HOST]");
};

try {
  local $SIG{ALRM} = sub { die "login\n" };
  alarm $TIMEOUT;
  $ftp->login($USER,$PASS);
  alarm 0;
}
catch {
  die $_ if $_ ne "login\n"; 
  my_sub("FTP: cannot cannot log in");
};

END: {
  if (@ERRORS) {
    print "$ERRORS[0]\n";
    exit -1;
  }
  print "Success\n";
  exit 0;
}

I'm using the goto function because I'll eventually have blocks for WebDAV and CMIS tests, and want to capture all of the errors before I hit END. I can make the preceding do what I want by changing the try-catch blocks like so:

try {
  local $SIG{ALRM} = sub { die "new\n" };
  alarm $TIMEOUT;
  $ftp = Net::FTP->new($HOST, Passive=>1, DEBUG=>3, Timeout=>2) 
    or my_sub("FTP: cannot connect to [$HOST]");
  alarm 0;
}
catch {
  die $_ if $_ ne "new\n"; 
};

However, in both cases, I have the feeling I'm not using Try::Tiny well.

And of course, what I'd really like to do is put the try-catch blocks in a function, just like sid_com did. That doesn't work for me:

sub try_f {
  my $cmd = shift;
  my $msg = shift;
  try {
    local $SIG{ALRM} = sub { die "timeout '$cmd'\n" };
    alarm $TIMEOUT;
    \$cmd or my_sub($msg);
    alarm 0;
  }
  catch {
    die $_ if $_ ne "timeout '$cmd'\n"; 
  };
} # sub

try_f("$ftp = Net::FTP->new(Host=>$HOST, Passive=>1)", "FTP: cannot connect to [$HOST]");

try_f("$ftp->login($USER,$PASS)", "FTP: cannot cannot log in");

The error says $ftp is an uninitialized value, but I don't know how to get around that.

EDIT: Here's the working synthesis of the two answers I received. Thanks, Max and LeoNerd!

use strict;  
use warnings;
use Net::FTP;
use Try::Tiny;

my $HOST = 'replace_with_hostname';
my $USER = 'replace_with_username';
my $PASS = 'replace_with_password';
my @ERRORS;
my $ftp = undef;

sub try_f {
  my $timeout = shift;
  my $command = shift;
  my $message = shift;
  my $label   = shift;
  try {
    local $SIG{ALRM} = sub { die "timeout\n" };
    alarm $timeout;
    $command->() or die "module\n";
    alarm 0;
  }
  catch {
    if ( $_ eq "module\n" || $_ eq "timeout\n" ) {
      push(@ERRORS,"$message: $_");
      goto $label;
    }
    die $_;
  };
}

FTP_BLOCK: {
  try_f(4, sub { $ftp = Net::FTP->new($HOST, Passive=>1, Debug=>3) }, "FTP: cannot connect to [$HOST]", 'END');
  try_f(2, sub { $ftp->login($USER,$PASS) }, "FTP: cannot log in", 'END');
}

END: {
  if (@ERRORS) {
    foreach my $e (@ERRORS) { print "$e"; }
    exit -1;
  }
  print "Success\n";
  exit 0;
}
Community
  • 1
  • 1
Greg
  • 25
  • 4

2 Answers2

4

Net::FTP->new doesn't die on error. Instead, it simply returns undef. Because of this, Try::Tiny isn't able to determine something went wrong, and the catch block isn't executed.

Instead of my_sub, use

$ftp = Net::FTP->new($HOST, Passive=>1, DEBUG=>3, Timeout=>2) or die $@;

The Try::Tiny docs say that $@ won't have the error in the catch block, but Net::FTP->new says it will put an error message there if it returns undef. Which is why you can die with that message in the try block (you could also use your own message in that case, if desired).

Likewise, login returns a false value on error, so you'll need to add an "or die" statement (unfortunately, login does not appear to give an error message on error).

Max Lybbert
  • 19,717
  • 4
  • 46
  • 69
  • Great, the `try-catch` block works now. I'm using `die "bad\n";` and then calling `my_sub()` from the catch when `$_` is either `bad\n` or `new\n`. PS, I'd upvote, but I'm too noob to do so. :-( – Greg Oct 08 '12 at 22:38
  • I made a statement about the exception from the signal handler not being caught by `catch`. It turns out I was misspelling `$SIG{ALRM}`. Once I spelled it correctly, `catch` caught the exception. – Max Lybbert Oct 08 '12 at 22:45
2

You are attempting to pass code around in strings:

try_f("$ftp = Net::FTP->new(Host=>$HOST, Passive=>1)", "FTP: cannot connect to [$HOST]");

\$cmd or my_sub($msg);

That's not going to work.

Might I suggest instead using sub { } to create an anonymous code reference?

try_f( sub { $ftp = ..... } );

then later

$cmd->() or my_sub($msg);
LeoNerd
  • 8,344
  • 1
  • 29
  • 36
  • I'm going to be hanging out here a lot more – Greg Oct 10 '12 at 15:01
  • Thanks, LeoNerd, this works perfectly. Also, now I've got another Perl nugget in my bag...I don't learn these things unless I've got something concrete to do, so I really appreciate the help you and others contribute here. For the next guy: I found [this helpful post](http://stackoverflow.com/questions/834590/why-would-i-use-perl-anonymous-subroutines-instead-of-a-named-one) about anonymous subs (and other goodies). – Greg Oct 10 '12 at 15:08