Remove dependency on Sys::SigAction
authorEric Wilde <ewilde@bsmdevelopment.com>
Mon, 12 Nov 2018 11:22:40 +0000 (00:22 +1300)
committerAndrew Ruthven <andrew@etc.gen.nz>
Mon, 12 Nov 2018 11:22:40 +0000 (00:22 +1300)
bin/mythtv-status

index f5e3991..846bdb9 100755 (executable)
@@ -22,11 +22,10 @@ use Date::Manip;
 use Date::Manip::Date;
 use Getopt::Long;
 use Text::Wrap;
-use POSIX qw/strftime :sys_wait_h/;
+use POSIX qw/strftime :sys_wait_h :signal_h/;
 
 use MIME::Entity;
 use Pod::Usage;
-use Sys::SigAction qw/timeout_call/;
 use Encode;
 
 # Try and load a config file first, and then allow the command line
@@ -1236,20 +1235,83 @@ sub pipe_from_fork ($) {
   $pid;
 }
 
-# Takes a LWP::UserAgent, and a HTTP::Request, returns a HTTP::Request
-# Based on:
-# http://stackoverflow.com/questions/73308/true-timeout-on-lwpuseragent-request-method
+# Takes a LWP::UserAgent and an HTTP::Request.  Returns the result of the
+# HTTP::Request.  Handles hung servers as well as timeouts.  Based on:
+# http://stackoverflow.com/questions/73308/true-timeout-on-lwpuseragent-\
+#   request-method
 sub ua_request_with_timeout {
   my ($ua, $url) = @_;
+  my ($req_timeout);
 
-  # Get whatever timeout is set for LWP and use that to 
-  #  enforce a maximum timeout per request in case of server
-  #  deadlock. (This has happened.)
-  our $res = undef;
-  if( timeout_call( $ua->timeout(), sub {$res = $ua->get($url);}) ) {
+  # Pick up LWP's request timeout setting.
+  $req_timeout = $ua->timeout();
+
+  # If Sys::SigAction is available, we can use it to get whatever timeout
+  # is set for LWP and use that to enforce a maximum timeout per request
+  # in case of server deadlock.
+  if (eval("{ local \$SIG{__DIE__}; require Sys::SigAction; }")) {
+    our $resp = undef;
+
+    if (Sys::SigAction::timeout_call( $req_timeout,
+        sub {$resp = $ua->get($url);})) {
       return undef;
-  } else {
-      return $res;
+    } else {
+      return $resp;
+    }
+  }
+
+  # Otherwise, we roll a hard six with a SIGALRM for the timeout.
+  else {
+    my $resp = undef;
+    our $req_has_timedout = 0;
+    my ($newaction, $oldaction);
+
+    # Create a new SIGALRM handler to set the timed out flag if the
+    # backend request is not answered before the interval has elapsed.
+    # Note that die ends the request within the eval (below).  It is
+    # caught by eval, allowing the code herein to continue and check
+    # for a timeout.
+    $newaction = POSIX::SigAction->new(
+        sub { $req_has_timedout = 1; die "Backend request timeout"; },
+        POSIX::SigSet->new(SIGALRM) );
+
+    # Replace the current SIGALRM handler with our new one, saving the
+    # old one for restoration, later on.  If this fails, we just issue
+    # the request directly and hope for the best.
+    $oldaction = POSIX::SigAction->new();
+
+    if (!sigaction(SIGALRM, $newaction, $oldaction)) {
+      warn "Error setting SIGALRM handler: ".$!."\n" if $verbose;
+      return $ua->get($url);
+    }
+
+    # Within an eval, set the timer and request a response from the
+    # backend.  If the timer pops, the SIGALRM routine will set a flag
+    # and kill the request.  The eval will catch it and we'll get on
+    # with our lives.
+    eval {
+      alarm($req_timeout);
+      $resp = $ua->get($url);
+      alarm(0);
+    };
+
+    # Cancel the SIGALRM, if the eval failed for any reason.  Reset the
+    # SIGALRM handler back to its original state.
+    alarm(0);
+
+    if (!sigaction(SIGALRM, $oldaction )) {
+      warn "Error resetting SIGALRM handler: ".$!."\n" if $verbose;
+    };
+
+    # If the request has timed out, return a HTTP 408 (timeout) response
+    # or maybe just undef.  Otherwise, return the backend's response.
+    if ($req_has_timedout) {
+      warn "Backend request timed out (".$req_timeout." secs)\n" if $verbose;
+#      return HTTP::Response->new(408);
+      return undef;
+    } else {
+      return $resp;
+    }
   }
 }