Jump to content

How to interrupt function execution after X seconds?


helices

Recommended Posts

I execute a system call via proc_open() that usually completes in less than one minute

Sometimes, it hangs for many hours

I want to exit this parent script after thirty (30) minutes with the error description on STDERR and I also want that system call process killed at this point

Although, a test script using ticks and pcntl_alarm(1800) works using a while() loop, this does NOT work with my production system call

Since ticks is documented as deprecated, I want to find a solution that does not use ticks

What am I missing?

Please, advise. Thank you

~ Mike

Link to comment
Share on other sites

Ticks aren't deprecated...

 

Either ticks won't help you because you're waiting on an external process to complete, or ticks can help because you have code that's actively running but then you don't need ticks in the first place. Apparently it's the latter. Besides, ticks only work in certain PHP setups.

 

What's your code?

Link to comment
Share on other sites

It's your first: "waiting on an external process to complete"

 

This works, but sometimes exceeds (30) minutes: $proc = proc_open($prog, $fd, $pipes, $ldir, null);

 

I have a test script using: pcntl_alarm($time);

before this: while ( $count < $max ) { ... }

which works as desired

 

It took me too long to realize that I needed to proceed pcntl_alarm() with this: declare(ticks=1);

or it does not work    :-(

Does pcntl_alarm() require ticks? Why isn't this documented on the pcntl_alarm() manual page?

 

When I do the $proc = ... thing - even with ticks - the alarm is never caught

 

In the shell or even Perl, alarms work as I expect.

 

What am I missing?

Link to comment
Share on other sites

It's your first: "waiting on an external process to complete"

What's your code?

 

This works, but sometimes exceeds (30) minutes: $proc = proc_open($prog, $fd, $pipes, $ldir, null);

That should not block, or at least not for long. What you do or don't do afterwards may cause problems.

 

It took me too long to realize that I needed to proceed pcntl_alarm() with this: declare(ticks=1);

or it does not work    :-(

Does pcntl_alarm() require ticks? Why isn't this documented on the pcntl_alarm() manual page?

It does not require ticks, but depending on what you do in your code the alarm signal may not be handled without using ticks.

 

When I do the $proc = ... thing - even with ticks - the alarm is never caught

Again, depends on your code.
Link to comment
Share on other sites

As I posted, this IS my code:

    $proc = proc_open($prog, $fd, $pipes, $ldir, null);

 

One specific case, $prog is an lftp session to a remote server, getting or putting files

 

Sometimes, unknown problem on that remote server leave this proc_open() call hanging. In these cases, I want an alarm-like process to do something after (30) minutes, such as write a message to STDERR and exit the script.

 

What else do you need to know?

Link to comment
Share on other sites

$proc = proc_open($prog, $fd, $pipes, $ldir, null);

Well then, if that's your code:

1. You're missing an opening <?php tag

2. $prog is undefined

3. $fd is undefined

4. $pipes is undefined

5. $ldir is undefined

6. You don't do anything with $proc

 

I'm not asking for a single line of code where you think the problem is. I'm asking for ALL of your code.

Edited by requinix
Link to comment
Share on other sites

Suffice it to say that the 10,000 line script works 100% - except that sometimes (1 in 500 runs) that line of code hangs for >30 minutes

 

I've been coding for more than 40 years and PHP for more than 10 years. My code works. The rare hangs are not due to faulty code; rather, they are due to system problems, mostly on remote systems, over which I have 0 control

 

All I am asking for is an example of a multi-tasked timer that can be set immediately prior to the function/subroutine call that includes: proc_open() - so that after X time, I can do something else, other than what that proc_open() is doing.

 

Since "my" code includes PCI related security routines and the bulk of this has zero to do with this particular challenge, I will not be be posting: "ALL of your code"

 

Is this too difficult for you? If so, please, accept my humblest apologies ...

 

~ Mike

Link to comment
Share on other sites

OK, I'm sorry for being abrasive today.

 

If I can find a code example that works without a "loop" to increment ticks, I can take it from there. I don't know why alarm in PHP doesn't work like it does in system and Perl.

 

The following test script does what I want, except I must replace the while() loop with the function call to proc_open():

 

#!/usr/bin/php
<?php
$count = 0;
$interval = 1;
### $interval = 5;
$max = 5;
### $max = 10;
$time = 7;
# $time = 20;
### $time = 900;
$one = alarm_test();
echo "\n\tONE: {$one}\n\n";
$two = post_alarm();
echo "\n\tTWO: {$two}\n\n";
function post_alarm() {
  global $count, $interval, $time;
  $max = $time + 10;
  while ( $count < $max ) {
    $count += $interval;
    print $count."\n";
    sleep($interval);
  }
  return($count);
}
function alarm_test() {
  global $count, $interval, $max, $time;
  declare(ticks=1);
  pcntl_signal(SIGALRM, "signal_handler");
  pcntl_signal(SIGINT,  "signal_handler");
  pcntl_signal(SIGQUIT, "signal_handler");
  pcntl_signal(SIGTERM, "signal_handler");
  pcntl_alarm($time);
  # Execute time sensitive stuff HERE:
  while ( $count < $max ) {
    $count += $interval;
    print $count."\n";
    sleep($interval);
  }
  # Any call to pcntl_alarm() will cancel any previously set alarm
  # If seconds is zero, no new alarm is created
  pcntl_alarm(0);
  echo "\n\tAlarm is RESET - We can do something else NOT time sensitive\n\n";
  return($max);
}
function signal_handler($signal) {
  switch($signal) {
    case SIGTERM:
      echo "Caught SIGTERM\n";
      exit;
    case SIGQUIT:
      echo "Caught SIGQUIT\n";
      exit;
    case SIGINT:
      echo "Caught SIGINT\n";
      exit;
    case SIGALRM:
      echo "\nCaught SIGALRM!\n";
      echo "DO SOMETHING HERE ...\n\n";
      exit;
  }
}
?>

 

 

 

Following is the function that calls proc_open() from one of many scripts that have worked for more than 10,000 times, with the rare (less than 1 in 500 times) case of taking "too long:"

 

function extProg ($array) {
  $ldir = $array['local_directory'];
  $prog = $array['prog'];
  $sdir = $array['remote_dir'];
  $fd = array(
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w"),  // stderr
  );
  $proc = proc_open($prog, $fd, $pipes, $ldir, null);
  $stdout = stream_get_contents($pipes[1]);
  fclose($pipes[1]);
  $stderr = explode("\n",stream_get_contents($pipes[2]));
  fclose($pipes[2]);
  proc_close($proc);
  $err = $stderr[0];
  if ($sdir != '/') {
    $str = "cd ok, cwd={$sdir}";
    if ($stderr[0] == $str) $err = $stderr[1];
  }
  if ($err > '') return $err;
  return 0;
}

 

 

Does this suffice for my code examples?

 

Thank you.

 

~ Mike

Link to comment
Share on other sites

Is this better?

#!/usr/bin/php
<?php
$count = 0;
$interval = 1;
### $interval = 5;
$max = 5;
### $max = 10;
$time = 7;
# $time = 20;
### $time = 900;
$one = alarm_test();
echo "\n\tONE: {$one}\n\n";
$two = post_alarm();
echo "\n\tTWO: {$two}\n\n";
function post_alarm() {
  global $count, $interval, $time;
  $max = $time + 10;
  while ( $count < $max ) {
    $count += $interval;
    print $count."\n";
    sleep($interval);
  }
  return($count);
}
function alarm_test() {
  global $count, $interval, $max, $time;
  declare(ticks=1);
  pcntl_signal(SIGALRM, "signal_handler");
  pcntl_signal(SIGINT,  "signal_handler");
  pcntl_signal(SIGQUIT, "signal_handler");
  pcntl_signal(SIGTERM, "signal_handler");
  pcntl_alarm($time);
  # Execute time sensitive stuff HERE:
  while ( $count < $max ) {
    $count += $interval;
    print $count."\n";
    sleep($interval);
  }
  # Any call to pcntl_alarm() will cancel any previously set alarm
  # If seconds is zero, no new alarm is created
  pcntl_alarm(0);
  echo "\n\tAlarm is RESET - We can do something else NOT time sensitive\n\n";
  return($max);
}
function signal_handler($signal) {
  switch($signal) {
    case SIGTERM:
      echo "Caught SIGTERM\n";
      exit;
    case SIGQUIT:
      echo "Caught SIGQUIT\n";
      exit;
    case SIGINT:
      echo "Caught SIGINT\n";
      exit;
    case SIGALRM:
      echo "\nCaught SIGALRM!\n";
      echo "DO SOMETHING HERE ...\n\n";
      exit;
  }
}
?>
function extProg ($array) {
  $ldir = $array['local_directory'];
  $prog = $array['prog'];
  $sdir = $array['remote_dir'];
  $fd = array(
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w"),  // stderr
  );
  $proc = proc_open($prog, $fd, $pipes, $ldir, null);
  $stdout = stream_get_contents($pipes[1]);
  fclose($pipes[1]);
  $stderr = explode("\n",stream_get_contents($pipes[2]));
  fclose($pipes[2]);
  proc_close($proc);
  $err = $stderr[0];
  if ($sdir != '/') {
    $str = "cd ok, cwd={$sdir}";
    if ($stderr[0] == $str) $err = $stderr[1];
  }
  if ($err > '') return $err;
  return 0;
}
Link to comment
Share on other sites

I have; but, I'm reading them again and not seeing anything jump out at me

 

By "notes," are you referring to this?

http://php.net/manual/en/function.proc-open.php#refsect1-function.proc-open-notes

 

Or, User Contributed Notes:

http://php.net/manual/en/function.proc-open.php#usernotes

 

If the former, are you suggesting that I ought to use popen() instead of proc_open() ?

 

If the latter, please, point me to the specific user note that you mean.

 

Thank you.

Link to comment
Share on other sites

For those who come here later, I have found a solution.

 

I could not find a solution using alarms.

 

Instead, my solution forks a 2nd process to time the 1st. The only problem I've not resolved is eliminating the annoying text "Terminated" from main script output ...

#!/usr/bin/php
<?php
// GLOBALS
$CHILD_PID   = 0;
$PID         = getmypid();
$TIMEOUT     = 5;
// number of seconds to timeout
echo "TIMER: {$TIMEOUT} seconds\n\n";
// Make sure program execution doesn't time out
//   If set to (0), no time limit is imposed
set_time_limit(0);
// Begin timer here
set_timeout();
// Do system stuff here
echo "Doing STUFF here\n\n";
extProg();
// End timer here
clear_timeout($CHILD_PID);
echo "STUFF is _COMPLETE_ here\n\n";
exit(0);

// Stop timer
function clear_timeout($CHILD_PID) {
  posix_kill($CHILD_PID, SIGTERM);
}
// Execute external program
function extProg () {
  $ldir = 'abcdef';
  $prog = "/usr/bin/lftp -e \"cd /_DIR_/;pwd;cls -lrt;\"  -u '_ACCOUNT_,bogus' sftp://DOMAIN.com";
  $sdir = 'uvwxyz';
  $fd = array(
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w"),  // stderr
  );
  $proc = proc_open($prog, $fd, $pipes, $ldir, null);
  $stdout = stream_get_contents($pipes[1]);
  fclose($pipes[1]);
  $stderr = explode("\n",stream_get_contents($pipes[2]));
  fclose($pipes[2]);
  proc_close($proc);
  $err = $stderr[0];
  if ($sdir != '/') {
    $str = "cd ok, cwd={$sdir}";
    if ($stderr[0] == $str) $err = $stderr[1];
  }
  if ($err > '') return $err;
  return 0;
}
// Set timeout timer
function set_timeout() {
  global $CHILD_PID;
  global $PID;
  global $TIMEOUT;
  $CHILD_PID = pcntl_fork();
  if($CHILD_PID == 0) {
    sleep($TIMEOUT);
    posix_kill($PID, SIGTERM);
    echo "ERROR: extProg() stopped after {$TIMEOUT} seconds:  ";
    exit(999);
  }
}
?>

Of course, I have "dummied up" the lftp connection parameters - suffice it to say that, with my real world parameters, this script throws an error, stops the lftp process and exits as I see fit

 

Any ideas how to silence the errant text output?

 

~ Mike

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.