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

I am writing a script that randomly chooses an IP from an array, then tries to make an IO::Socket connection to it. I want to wrap the socket connection in alarm() to keep it from hanging forever if something goes wrong. I don't want the whole script to die from the SIGALRM, so I'm writing a signal handler for it.

The problem is, I want the handler to use some variables from outside the handler. I can't pass them as arguments, and I don't want to make them globals. (The reason for no globals is that I want this sub to eventually be a fully-encapsulated object method.)

One solution I had thought of was making the handler local inside the sub (a nested sub). Is this the best way, or is there a better way to accomplish this task?

Here's my code so far (which is currently broken):
#!/usr/bin/perl use strict; use warnings; $|++; use Carp; my $info = $ARGV[0] || 'string_of_data'; print outer($info); sub outer { use IO::Socket; my @ips = ('xxx.xxx.xxx.xx0','xxx.xxx.xxx.xx1','xxx.xxx.xxx.xx2'); my $port = xxxx; ### $SIG{INT} = \&buh_bye; ### per ikegami ### $SIG{ALRM} = \&fallback; ### per ikegami local $SIG{INT} = \&buh_bye; local $SIG{ALRM} = \&fallback; ### srand; ### woops, thanks again ikegami my $index = int(rand(scalar @ips)); alarm(5); my $sock = IO::Socket::INET->new($ips[$index].':'.$port); $index = fallback() unless ($sock); alarm(0); die "$! ($ips[$index])\n" unless ($sock); print $sock "$info$/"; sysread $sock, my ($text), 10000; close($sock); print "ok ($ips[$index])\n"; return $text; } sub fallback { my $signame = shift; print +($signame) ? "Caught SIG$signame! " : "$! "; print "($ips[$index])\nTrying fallback host "; $index -= 1; print "($ips[$index])\n"; $sock = IO::Socket::INET->new($ips[$index].':'.$port); return $index; } sub buh_bye { my $signame = shift; die "\nCaught SIG${signame}!\nIP: $ips[$index]\n"; }

---
It's all fine and dandy until someone has to look at the code.

Replies are listed 'Best First'.
Re: Signal handlers and scoped variables, or Nested subs?
by ikegami (Patriarch) on May 12, 2006 at 22:24 UTC
    I want the handler to use some variables from outside the handler.

    Sounds like you want a closure

    sub outer { my $var; local $SIG{INT} = sub { ... ...[ Read from and/or write to $var ]... ... }; ... }

    Unrelated notes:

    • Your usage of srand makes things less random. Remove it.

    • Replace
      $SIG{...} = ...;
      with
      local $SIG{...} = ...;
      to limit the scope where your handler takes effect.

    • Why is use IO::Socket; inside the sub? That's confusing!

      Thank you for both of your replies.

      The usage of srand was an artifact from before I put the code into sub outer. Thank you for reminding me to remove it!

      I am putting the use statement inside the sub because I want to eventually make the sub into an object method. I don't want to require the main program to know that it should use IO::Socket if it wants to use this method. When I move the sub into the package file, I will remove the use statement from the sub itself and put it into the package file.

      ---
      It's all fine and dandy until someone has to look at the code.
Re: Signal handlers and scoped variables, or Nested subs?
by ikegami (Patriarch) on May 12, 2006 at 22:22 UTC

    You might not have to use alarm. I think you can make the connection non-blocking, then detect when the connection is completed using select. select accepts a timeout, which would replace your use of alarm.

    If that works, the advatage is you can extend that model to initiate connections to more than one address at a time, and work with the connections as they become available. This is particularly useful if you expect a lot of timeouts.

      I am very intrigued by this potential solution. I found out how to make the connection non-blocking (see below), but I the syntax of select is unclear after reading perldoc -f select. Can you please elaborate?
      $sock = IO::Socket::INET->new(PeerAddr => $ips[$index].':'.$port, Bloc +king => 0);

      ---
      It's all fine and dandy until someone has to look at the code.

        Partially tested. Specifically, I've tested the following:

        • If it's the first time you can write to a socket, then it has just been connected.
        • A socket will be flagged as readable when it gets disconnected. eof will return true on this socket.

        You should find the following code very flexible. Think of Client as a state machine.

        use IO::Select (); use IO::Socket::INET (); use Time::HiRes qw( time ); # Optional. { package Client; use constant ST_CONNECTING => 0; use constant ST_SOMETHING1 => 1; use constant ST_SOMETHING2 => 2; use constant ST_SOMETHING3 => 3; # ... sub new { my ($class) = @_; my $self = bless({ sock => $sock, timeout => time() + 30, wannna_d => 0, # True if we want to disconnect the socket. wannna_w => 1, # True if we want to write to the socket. state => ST_CONNECTING, # ... }); } sub connected { my ($self) = @_; $self->{state } = ST_SOMETHING1; $self->{wanna_w} = 0; # ... } sub disconnected { my ($self) = @_; # ... } sub timed_out { my ($self) = @_; # ... } sub data_available { my ($self) = @_; $self->reset_timeout(); # ... } sub ready_to_write { my ($self) = @_; $self->reset_timeout(); my $state = $self->{state}; if ($state == ST_CONNECTING) { $self->connected(); return; } # ... } sub reset_timeout { my ($self) = @_; $self->{timeout} = $time + 30; } } { my %clients; { my $sock = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => '...', Blocking => 0, # How well does this work in Windows? ); $clients{$sock} = Client->new($sock); } for (;;) { my $time = time; my $min_timeout = 3600; # Just so it isn't undef. my $r_sel = IO::Select->new(); my $w_sel = IO::Select->new(); foreach my $client (values %clients) { my $sock = $client->{sock}; my $wanna_d = $client->{wanna_d}; my $wanna_w = $client->{wanna_w}; my $timeout = $client->{timeout} - $time; if ($wanna_d) { delete($clients{$sock}); next; } if ($timeout <= 0) { delete($clients{$sock}); $client->timed_out(); next; } $min_timeout = $timeout if $min_timeout > $timeout; $r_sel->add($sock); $w_sel->add($sock) if $wanna_w; } # In case a "$client->timed_out();" takes a long time. $timeout -= time() - $time; $timeout = 0.001 if $timeout < 0; last if not %clients; my ($r, $w) = IO::Select->select($r_sel, $w_sel, undef, $timeout); foreach my $sock (@$r) { my $client = $clients{$sock}; next if not $client; # Just in case. if (eof($sock)) { delete($clients{$sock}); $client->disconnected(); } else { $client->data_available(); } } foreach my $sock (@$w) { my $client = $clients{$sock}; next if not $client; $client->ready_to_write(); } } }
Re: Signal handlers and scoped variables, or Nested subs?
by kwaping (Priest) on May 16, 2006 at 17:18 UTC
    Thank you to all who replied.

    I ended up solving this issue with an entirely different method. Much thanks goes to Ikegami for putting me on the right path. Here's the final code, though it's not been made into an object method yet.
    #!/usr/bin/perl $|++; use strict; use warnings; our $DEBUG = 1; my $query = $ARGV[0] || 'default data'; print client($query); sub client { use IO::Socket; # <- here for when this sub grows up to become an ob +ject method # do some basic setup stuff my $info = shift; return "No info!" unless ($info); my @ips = ('XXX.XXX.XXX.X0','XXX.XXX.XXX.X1','XXX.XXX.XXX.X2'); my $port = XXXX; # grab an IP randomly my $index = int(rand(scalar @ips)); # try to connect once for each IP my $sock; for (my $counter = 0; $counter < scalar @ips; $counter++) { # debug output print "Trying $ips[$index]... " if ($DEBUG); # try to connect $sock = IO::Socket::INET->new( PeerAddr => $ips[$index].':'.$port +, Blocking => 1, Timeout => 1, ### The key param t +hat I had been missing!!! ); # success! if ($sock) { print "OK!\n" if ($DEBUG); last; } # failure print "Nope\n" if ($DEBUG); # get previous IP (loops around if negative, pretty sweet) $index--; } # return error if all 3 failed return "no sock!" unless ($sock); # write to socket print $sock $info.$/ or die "$!"; # read from socket and close sysread $sock, my ($text), 10000; close($sock); return $text; }

    ---
    It's all fine and dandy until someone has to look at the code.