If I try to exec this code, an infinite loop starts. But, ... How can I send SIGTERM signal to stop the execution?
<?php
$flag = false;
pcntl_signal('SIGTERM', function () use (&$flag) {
$flag = true;
});
$shouldStop = function() use (&$flag) {
pcntl_signal_dispatch();
return $flag;
};
while (!$shouldStop($flag)) {
sleep(1);
echo "\n" . time();
}
Related
I got the following two functions that I use to lock a Redis key. I am trying to prevent concurrency execution of a block of code using Redis. So what I do is use the following functions in such a way that they prevent the execution of the same code by different threads.
lockRedisKey("ABC");
CODE THAT I DON'T WANT TO RUN CONCURRENTLY!
unlockRedisKey("ABC");
Unfortunately, it doesn't seem to work and causes an infinitely loop at lockRedisKey() until exit_time is reached. What could be wrong?
static public function lockRedisKey($key, $value = "true") {
$redis = RedisClient::getInstance();
$time = microtime(true);
$exit_time = $time + 10;
$sleep = 10000;
do {
// Lock Redis with PX and NX
$lock = $redis->setnx("lock:" . $key, $value);
if ($lock == 1) {
$redis->expire("lock:" . $key, "10");
return true;
}
usleep($sleep);
} while (microtime(true) < $exit_time);
return false;
}
static public function unlockRedisKey($key) {
$redis = RedisClient::getInstance();
$redis->del("lock:" . $key);
}
I'm aware that I might be facing deadlocks, so I decided to use transactions, but I continue to face the issue.
static public function lockRedisKey($key, $value = "true") {
$redis = RedisClient::getInstance();
$time = microtime(true);
$exit_time = $time + 10;
$sleep = 10000;
do {
// Lock Redis with PX and NX
$redis->multi();
$redis->set('lock:' . $key, $value, array('nx', 'ex' => 10));
$ret = $redis->exec();
if ($ret[0] == true) {
return true;
}
usleep($sleep);
} while (microtime(true) < $exit_time);
return false;
}
static public function unlockRedisKey($key) {
$redis = RedisClient::getInstance();
$redis->multi();
$redis->del("lock:" . $key);
$redis->exec();
}
The locking is working fine. It's just the code between the locking that is crashing and causing the lock not to be released :-)
First of all, PHP isn't async script language as JS f.e. You haven't any control on the script execution ordering, so it means you can't run code that below lockRedisKey() call in concurent way.
I assume you're not completely understand what usleep function is actually doing, it's just delay process and not postpone it, so it just blocks process for $sleep time and after this continue script execution. So you're not performing concurrent write by calling your script.
I'd like to execute a shell command asynchronously in PHP. I.e. PHP shouldn't wait for the command to be finished to continue execution. However in contrast to the numerous question about that topic on Stackoverflow I do care about the output of the program. In particular I would like to do something like this:
exec("some command", $output_array, $has_finished);
while(count($output_array) > 0 && !$has_finished)
{
if(count($output_array) > 0)
{
$line = array_shift($output_array);
do_something_with_that($line);
} else
sleep(1);
}
do_something_with_that($line)
{
echo $line."\n";
flush();
}
The above code would work if exec would return immediately while still adding elements to the array and if there was a method to check if the process has terminated or not.
Is there a way to do that?
I've solved the problem by piping the output STDIN to a temporary file and then reading from it.
Here's my
Implementation
class ExecAsync {
public function __construct($cmd) {
$this->cmd = $cmd;
$this->cacheFile = ".cache-pipe-".uniqid();
$this->lineNumber = 0;
}
public function getLine() {
$file = new SplFileObject($this->cacheFile);
$file->seek($this->lineNumber);
if($file->valid())
{
$this->lineNumber++;
$current = $file->current();
return $current;
} else
return NULL;
}
public function hasFinished() {
if(file_exists(".status-".$this->cacheFile) ||
(!file_exists(".status-".$this->cacheFile) && !file_exists($this->cacheFile)))
{
unlink($this->cacheFile);
unlink(".status-".$this->cacheFile);
$this->lineNumber = 0;
return TRUE;
} else
return FALSE;
}
public function run() {
if($this->cmd) {
$out = exec('{ '.$this->cmd." > ".$this->cacheFile." && echo finished > .status-".$this->cacheFile.";} > /dev/null 2>/dev/null &");
}
}
}
Usage
$command = new ExecAsync("command to execute");
//run the command
$command->run();
/*We want to read from the command output as long as
*there are still lines left to read
*and the command hasn't finished yet
*if getLine returns NULL it means that we have caught up
*and there are no more lines left to read
*/
while(($line = $command->getLine()) || !$command->hasFinished())
{
if($line !== NULL)
{
echo $line."\n";
flush();
} else
{
usleep(10);
}
}
Let's say I have a simple code:
while(1) {
myend();
}
function myend() {
echo rand(0,10);
echo "<br>";
if(rand(0,10) < 3) break;
}
This will not work with error code 'Fatal error: Cannot break/continue 1 level on line'.
So is there any possibility to terminate the loop during a subfunctin execution?
Make the loop condition depend upon the return value of the function:
$continue = true;
while( $continue) {
$continue = myend();
}
Then, change your function to be something like:
function myend() {
echo rand(0,10);
echo "<br>";
return (rand(0,10) < 3) ? false : true;
}
There isn't. Not should there be; if your function is called somewhere where you're not in a loop, your code will stop dead. In the example above, your calling code should check the return of the function and then decide whether to stop looping itself. For example:
while(1) {
if (myend())
break;
}
function myend() {
echo rand(0,10);
echo "<br>";
return rand(0,10) < 3;
}
Use:
$cond = true;
while($cond) {
$cond = myend();
}
function myend() {
echo rand(0,10);
echo "<br>";
if(rand(0,10) < 3) return false;
}
Following is my code in which i am facing only one difficulty that when i run the following script then due to comparison failure the $flag doesnot echo kindly let me know how to fix this?
$s = "iph4on comes";
$se = "4gb comes in iphone";
$f = 0;
$tf = explode(" ",$searching);
$ms= explode(" ",$search_in);
foreach($tf as $word)
{
if (!preg_match("/$word/i", $search_in))
//if (!strpos($search_in, $word));
return false;
}
{
$f = 1;
}
echo $f;
//Due to return flase above i am not echoing
echo "Comparison Failed";
return terminates the currently executing code block and "returns" to whatever called that code. if you execute a return in the top level of the code, it's essentially an exit() call and your echo will never be reached.
Why not just put an echo $flag before the return statement?
if (!preg_match("/$word/i", $search_in)){
echo $flag
return false;
}
You need to put the echo before the return. return gets the execution back to the calling function, hence any code after that won't be executed.
if (!preg_match("/$word/i", $search_in)) {
$flag = 1;
echo $flag;
echo "Comparison Failed";
return false;
}
Problem: I want to implement several php-worker processes who are listening on a MQ-server queue for asynchronous jobs. The problem now is that simply running this processes as daemons on a server doesn't really give me any level of control over the instances (Load, Status, locked up)...except maybe for dumping ps -aux.
Because of that I'm looking for a runtime environment of some kind that lets me monitor and control the instances, either on system (process) level or on a higher layer (some kind of Java-style appserver)
Any pointers?
Here's some code that may be useful.
<?
define('WANT_PROCESSORS', 5);
define('PROCESSOR_EXECUTABLE', '/path/to/your/processor');
set_time_limit(0);
$cycles = 0;
$run = true;
$reload = false;
declare(ticks = 30);
function signal_handler($signal) {
switch($signal) {
case SIGTERM :
global $run;
$run = false;
break;
case SIGHUP :
global $reload;
$reload = true;
break;
}
}
pcntl_signal(SIGTERM, 'signal_handler');
pcntl_signal(SIGHUP, 'signal_handler');
function spawn_processor() {
$pid = pcntl_fork();
if($pid) {
global $processors;
$processors[] = $pid;
} else {
if(posix_setsid() == -1)
die("Forked process could not detach from terminal\n");
fclose(stdin);
fclose(stdout);
fclose(stderr);
pcntl_exec(PROCESSOR_EXECUTABLE);
die('Failed to fork ' . PROCESSOR_EXECUTABLE . "\n");
}
}
function spawn_processors() {
global $processors;
if($processors)
kill_processors();
$processors = array();
for($ix = 0; $ix < WANT_PROCESSORS; $ix++)
spawn_processor();
}
function kill_processors() {
global $processors;
foreach($processors as $processor)
posix_kill($processor, SIGTERM);
foreach($processors as $processor)
pcntl_waitpid($processor);
unset($processors);
}
function check_processors() {
global $processors;
$valid = array();
foreach($processors as $processor) {
pcntl_waitpid($processor, $status, WNOHANG);
if(posix_getsid($processor))
$valid[] = $processor;
}
$processors = $valid;
if(count($processors) > WANT_PROCESSORS) {
for($ix = count($processors) - 1; $ix >= WANT_PROCESSORS; $ix--)
posix_kill($processors[$ix], SIGTERM);
for($ix = count($processors) - 1; $ix >= WANT_PROCESSORS; $ix--)
pcntl_waitpid($processors[$ix]);
} elseif(count($processors) < WANT_PROCESSORS) {
for($ix = count($processors); $ix < WANT_PROCESSORS; $ix++)
spawn_processor();
}
}
spawn_processors();
while($run) {
$cycles++;
if($reload) {
$reload = false;
kill_processors();
spawn_processors();
} else {
check_processors();
}
usleep(150000);
}
kill_processors();
pcntl_wait();
?>
It sounds like you already have a MQ up and running on a *nix system and just want a way to manage workers.
A very simple way to do so is to use GNU screen. To start 10 workers you can use:
#!/bin/sh
for x in `seq 1 10` ; do
screen -dmS worker_$x php /path/to/script.php worker$x
end
This will start 10 workers in the background using screens named worker_1,2,3 and so on.
You can reattach to the screens by running screen -r worker_ and list the running workers by using screen -list.
For more info this guide may be of help:
http://www.kuro5hin.org/story/2004/3/9/16838/14935
Also try:
screen --help
man screen
or google.
For production servers I would normally recommend using the normal system startup scripts, but I have been running screen commands from the startup scripts for years with no problems.
Do you actually need it to be continuously running?
If you only want to spawn new process on request, you can register it as a service in xinetd.
a pcntl plugin type server daemon for PHP
http://dev.pedemont.com/sonic/
Bellow is our working implementation of #chaos answer. Code to handle signals was removed as this script lives usually just few milliseconds.
Also, in code we added 2 functions to save pids between calls: restore_processors_state() and save_processors_state(). We've used redis there, but you can decide to use implementation on files.
We run this script every minute using cron. Cron checks if all processes alive. If not - it re-run them and then dies. If we want to kill existing processes then we simply run this script with argument kill: php script.php kill.
Very handy way of running workers without injecting scripts into init.d.
<?php
include_once dirname( __FILE__ ) . '/path/to/bootstrap.php';
define('WANT_PROCESSORS', 5);
define('PROCESSOR_EXECUTABLE', '' . dirname(__FILE__) . '/path/to/worker.php');
set_time_limit(0);
$run = true;
$reload = false;
declare(ticks = 30);
function restore_processors_state()
{
global $processors;
$redis = Zend_Registry::get('redis');
$pids = $redis->hget('worker_procs', 'pids');
if( !$pids )
{
$processors = array();
}
else
{
$processors = json_decode($pids, true);
}
}
function save_processors_state()
{
global $processors;
$redis = Zend_Registry::get('redis');
$redis->hset('worker_procs', 'pids', json_encode($processors));
}
function spawn_processor() {
$pid = pcntl_fork();
if($pid) {
global $processors;
$processors[] = $pid;
} else {
if(posix_setsid() == -1)
die("Forked process could not detach from terminal\n");
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
pcntl_exec('/usr/bin/php', array(PROCESSOR_EXECUTABLE));
die('Failed to fork ' . PROCESSOR_EXECUTABLE . "\n");
}
}
function spawn_processors() {
restore_processors_state();
check_processors();
save_processors_state();
}
function kill_processors() {
global $processors;
foreach($processors as $processor)
posix_kill($processor, SIGTERM);
foreach($processors as $processor)
pcntl_waitpid($processor, $trash);
unset($processors);
}
function check_processors() {
global $processors;
$valid = array();
foreach($processors as $processor) {
pcntl_waitpid($processor, $status, WNOHANG);
if(posix_getsid($processor))
$valid[] = $processor;
}
$processors = $valid;
if(count($processors) > WANT_PROCESSORS) {
for($ix = count($processors) - 1; $ix >= WANT_PROCESSORS; $ix--)
posix_kill($processors[$ix], SIGTERM);
for($ix = count($processors) - 1; $ix >= WANT_PROCESSORS; $ix--)
pcntl_waitpid($processors[$ix], $trash);
}
elseif(count($processors) < WANT_PROCESSORS) {
for($ix = count($processors); $ix < WANT_PROCESSORS; $ix++)
spawn_processor();
}
}
if( isset($argv) && count($argv) > 1 ) {
if( $argv[1] == 'kill' ) {
restore_processors_state();
kill_processors();
save_processors_state();
exit(0);
}
}
spawn_processors();