function to check if php is already executed for cron - php

Ealier I used function in a seperate file that could be call by script for cron.
function processExists($file = false) {
$exists = false;
$file = $file ? $file : basename(__FILE__);
$command = "ps -ef | grep -v grep | grep $file";
exec($command, $pids);
if (count($pids) > 1) {
$exists = true;
}
return $exists;
}
It could be executed from any php script as:
if (processExists(basename(__FILE__))) {
echo 'Process in already running ';
exit(0);
}
For some reason it doesn't work in Debian6.
Now I add in the beginning of every script:
$fh = fopen(__FILE__, 'r');
if (!flock($fh, LOCK_EX | LOCK_NB)) {
echo 'Script is already running!!!' . "\n";
exit(0);
}
It causes duplication of code in every script I tried to create function in a seperate file to call from any php script when neccessary, for example something like this:
function stopIfRunning($file)
{
$fh = fopen($file, 'r');
if(!flock($fh, LOCK_EX | LOCK_NB)) {
echo 'Script is already running!!!' . "\n";
exit(0);
}
}
And calling from php script:
stopIfRunning(__FILE__);
But it doesn't work in this case. Could you please explain why it doesn't work in this case?

If you need to make sure the service is running, use Daemontools or Monit.
And personally, I would use plain Bash, not PHP, if custom implementation needed.
BTW, you can easily debug your ps command in terminal.

FILE it is name of running file.
stopIfRunning(__FILE__);
function stopIfRunning($file)
{
$fh = fopen($file, 'r');
....
}
Here you tried open this executed file for lock.

It seems the problem is with $fh on stopIfRunning function. If the file with with functiona changed it with:
$fh = null;
function stopIfRunning($file)
{
global $fh;
echo $file, "\n";
$fh = fopen($file, 'r');
if (!$fh) {
echo 'Failed to open file', "\n";
}
if(!flock($fh, LOCK_EX | LOCK_NB)) {
echo 'Script is already running!!!' . "\n";
exit(0);
}
}
And call it in the file that should be launched using cron:
stopIfRunning(__FILE__);
Here global variable is used, which is not good. It would be great if somebody suggest a solution without global variable.

Related

PHP. How to read a file, if it is writing without a problem with "a+", but is not readable with "r"?

I have two scripts: one of them writes the value of a variable to a file. In another script, I try to read it. It is written without problems, but it is not readable.
Here I write to a file:
$peer_id=2000000001;
$fileLocation = getenv("DOCUMENT_ROOT") . "/peer_id.txt";
$file = fopen($fileLocation,"a+");
fwrite($file, $peer_id);
fclose($file);
Here I read the file:
$fileLocation = getenv("DOCUMENT_ROOT") . "/peer_id.txt";
$file = fopen($fileLocation,"r");
if(file_exists($fileLocation)){
// Result is TRUE
}
if(is_readable ($file)){
// Result is FALSE
}
// an empty variables, because the file is not readable
$peer_id = fread($file);
$peer_id = fileread($file);
$peer_id = file_get_contents($file);
fclose($file);
The code runs on "sprinthost" hosting, if that makes a difference. There are suspicions that this is because of that hosting.
file_get_contents in short runs the fopen, fread, and fclose. You don't use a pointer with it. You should just use:
$peer_id = file_get_contents($fileLocation);
That is the same for is_readable:
if(is_readable($fileLocation)){
// Result is FALSE
}
So full code should be something like:
$fileLocation = getenv("DOCUMENT_ROOT") . "/peer_id.txt";
if(file_exists($fileLocation) && is_readable($fileLocation)) {
$peer_id = file_get_contents($fileLocation);
} else {
echo 'Error message about file being inaccessible here';
}
The file_get_contents has an inverse function for writing; https://www.php.net/manual/en/function.file-put-contents.php. Use that with the append constant and you should have the same functionality your first code block had:
file_put_contents($fileLocation, $peer_id, FILE_APPEND | LOCK_EX);

When using flock to check if another process is running, sometimes the file is used by MySQL process

I use PHP function flock to check if another process is running, but sometimes the file is used by MySQL process. This is the result for lsof command :
And this is my php script:
$list = [];
$handle_list = fopen("$backup path/log/list.txt", "a+");
if(flock($handle_list, LOCK_EX | LOCK_NB))
{
while(($buffer = fgets($handle_list, 4096)) !== false)
{
$buffer = str_replace(["\r", "\n"], '', $buffer);
$list[] = $buffer;
}
}
else
{
echo date('Y-m-d H:i:s'). " : ERROR : Another process running\n\n\n";
exit(-1);
}
Without MySQL process locking the file, the script work fine. I don't know when can MySQL use a file?

How to prevent PHP script running more than once?

Currently, I tried to prevent an onlytask.php script from running more than once:
$fp = fopen("/tmp/"."onlyme.lock", "a+");
if (flock($fp, LOCK_EX | LOCK_NB)) {
echo "task started\n";
//
while (true) {
// do something lengthy
sleep(10);
}
//
flock($fp, LOCK_UN);
} else {
echo "task already running\n";
}
fclose($fp);
and there is a cron job to execute the above script every minute:
* * * * * php /usr/local/src/onlytask.php
It works for a while. After a few day, when I do:
ps auxwww | grep onlytask
I found that there are two instances running! Not three or more, not one. I killed one of the instances. After a few days, there are two instances again.
What's wrong in the code? Are there other alternatives to limit only one instance of the onlytask.php is running?
p.s. my /tmp/ folder is not cleaned up. ls -al /tmp/*.lock show the lock file was created in day one:
-rw-r--r-- 1 root root 0 Dec 4 04:03 onlyme.lock
You should use x flag when opening the lock file:
<?php
$lock = '/tmp/myscript.lock';
$f = fopen($lock, 'x');
if ($f === false) {
die("\nCan't acquire lock\n");
} else {
// Do processing
while (true) {
echo "Working\n";
sleep(2);
}
fclose($f);
unlink($lock);
}
Note from the PHP manual
'x' - Create and open for writing only; place the file pointer at the
beginning of the file. If the file already exists, the fopen() call
will fail by returning FALSE and generating an error of level
E_WARNING. If the file does not exist, attempt to create it. This is
equivalent to specifying O_EXCL|O_CREAT flags for the underlying
open(2) system call.
And here is O_EXCL explanation from man page:
O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file
exists. The check for the existence of the file and the creation of
the file if it does not exist shall be atomic with respect to other
threads executing open() naming the same filename in the same
directory with O_EXCL and O_CREAT set. If O_EXCL and O_CREAT are set,
and path names a symbolic link, open() shall fail and set errno to
[EEXIST], regardless of the contents of the symbolic link. If O_EXCL
is set and O_CREAT is not set, the result is undefined.
UPDATE:
More reliable approach - run main script, which acquires lock, runs worker script and releases the lock.
<?php
// File: main.php
$lock = '/tmp/myscript.lock';
$f = fopen($lock, 'x');
if ($f === false) {
die("\nCan't acquire lock\n");
} else {
// Spawn worker which does processing (redirect stderr to stdout)
$worker = './worker 2>&1';
$output = array();
$retval = 0;
exec($worker, $output, $retval);
echo "Worker exited with code: $retval\n";
echo "Output:\n";
echo implode("\n", $output) . "\n";
// Cleanup the lock
fclose($f);
unlink($lock);
}
Here goes the worker. Let's raise a fake fatal error in it:
#!/usr/bin/env php
<?php
// File: worker (must be executable +x)
for ($i = 0; $i < 3; $i++) {
echo "Processing $i\n";
if ($i == 2) {
// Fake fatal error
trigger_error("Oh, fatal error!", E_USER_ERROR);
}
sleep(1);
}
Here is the output I got:
galymzhan#atom:~$ php main.php
Worker exited with code: 255
Output:
Processing 0
Processing 1
Processing 2
PHP Fatal error: Oh, fatal error! in /home/galymzhan/worker on line 8
PHP Stack trace:
PHP 1. {main}() /home/galymzhan/worker:0
PHP 2. trigger_error() /home/galymzhan/worker:8
The main point is that the lock file is cleaned up properly so you can run main.php again without problems.
Now I check whether the process is running by ps and warp the php script by a bash script:
#!/bin/bash
PIDS=`ps aux | grep onlytask.php | grep -v grep`
if [ -z "$PIDS" ]; then
echo "Starting onlytask.php ..."
php /usr/local/src/onlytask.php >> /var/log/onlytask.log &
else
echo "onlytask.php already running."
fi
and run the bash script by cron every minute.
<?php
$sLock = '/tmp/yourScript.lock';
if( file_exist($sLock) ) {
die( 'There is a lock file' );
}
file_put_content( $sLock, 1 );
// A lot of code
unlink( $sLock );
You can add an extra check by writing the pid and then check it within file_exist-statement.
To secure it even more you can fetch all running applications by "ps fax" end check if this file is in the list.
try using the presence of the file and not its flock flag :
$lockFile = "/tmp/"."onlyme.lock";
if (!file_exists($lockFile)) {
touch($lockFile);
echo "task started\n";
//
// do something lengthy
//
unlink($lockFile);
} else {
echo "task already running\n";
}
You can use lock files, as some have suggested, but what you are really looking for is the PHP Semaphore functions. These are kind of like file locks, but designed specifically for what you are doing, restricting access to shared resources.
Never use unlink for lock files or other functions like rename. It's break your LOCK_EX on Linux. For example, after unlink or rename lock file, any other script always get true from flock().
Best way to detect previous valid end - write to lock file few bytes on the end lock, before LOCK_UN to handle. And after LOCK_EX read few bytes from lock files and ftruncate handle.
Important note: All tested on PHP 5.4.17 on Linux and 5.4.22 on Windows 7.
Example code:
set semaphore:
$handle = fopen($lockFile, 'c+');
if (!is_resource($handle) || !flock($handle, LOCK_EX | LOCK_NB)) {
if (is_resource($handle)) {
fclose($handle);
}
$handle = false;
echo SEMAPHORE_DENY;
exit;
} else {
$data = fread($handle, 2);
if ($data !== 'OK') {
$timePreviousEnter = fileatime($lockFile);
echo SEMAPHORE_ALLOW_AFTER_FAIL;
} else {
echo SEMAPHORE_ALLOW;
}
fseek($handle, 0);
ftruncate($handle, 0);
}
leave semaphore (better call in shutdown handler):
if (is_resource($handle)) {
fwrite($handle, 'OK');
flock($handle, LOCK_UN);
fclose($handle);
$handle = false;
}
Added a check for old stale locks to galimzhan's answer (not enough *s to comment), so that if the process dies, old lock files would be cleared after three minutes and let cron start the process again. That's what I use:
<?php
$lock = '/tmp/myscript.lock';
if(time()-filemtime($lock) > 180){
// remove stale locks older than 180 seconds
unlink($lock);
}
$f = fopen($lock, 'x');
if ($f === false) {
die("\nCan't acquire lock\n");
} else {
// Do processing
while (true) {
echo "Working\n";
sleep(2);
}
fclose($f);
unlink($lock);
}
You can also add a timeout to the cron job so that the php process will be killed after, let's say 60 seconds, with something like:
* * * * * user timeout -s 9 60 php /dir/process.php >/dev/null

Starting a bash script from a php script

I'm trying to run a bash script using shell_exec but It doesnt seem to work. (Nothing seems to happen) I'm using nginx and the latest php5-cgi. Heres what the php file looks like:
<?php
$startserver = "./startserver.sh";
$startserver = shell_exec($startserver);
$getprocess = "pidof hlds_amd";
$pid = shell_exec($getprocess);
$fh = fopen('closeserver.sh', 'w');
$command = "kill -9 $pid";
fwrite($fh, $command);
fclose($fh);
$string = "at -f closeserver.sh now + 1 hour";
$closer = shell_exec($string);
?>
and this is what the bash script looks like:
#!/bin/bash
cd /home/kraffs/srcds
./hlds_run -game cstrike -autoupdate +maxplayers 12 +map de_dust2 > hlds.log 2>&1 &
Theres no errors in the phpscript and the file gets created just fine but $startserver doesnt seem to get executed and $pid is empty. Did I miss something in the php file or do I need to change permissions for an user? Thanks for your help.
replace shell_exec, with below function and try again
<?php
function runcmd($EXEC_CMD)
$handle = popen ($EXEC_CMD, 'r');
$output = "";
if ($handle) {
while(! feof ($handle)) {
$read = fgets ($handle);
$output .= $read;
}
pclose($handle);
}
return $output;
}
?>

How To watch a file write in PHP?

I want to make movement such as the tail command with PHP,
but how may watch append to the file?
I don't believe that there's some magical way to do it. You just have to continuously poll the file size and output any new data. This is actually quite easy, and the only real thing to watch out for is that file sizes and other stat data is cached in php. The solution to this is to call clearstatcache() before outputting any data.
Here's a quick sample, that doesn't include any error handling:
function follow($file)
{
$size = 0;
while (true) {
clearstatcache();
$currentSize = filesize($file);
if ($size == $currentSize) {
usleep(100);
continue;
}
$fh = fopen($file, "r");
fseek($fh, $size);
while ($d = fgets($fh)) {
echo $d;
}
fclose($fh);
$size = $currentSize;
}
}
follow("file.txt");
$handle = popen("tail -f /var/log/your_file.log 2>&1", 'r');
while(!feof($handle)) {
$buffer = fgets($handle);
echo "$buffer\n";
flush();
}
pclose($handle);
Checkout php-tail on Google code. It's a 2 file implementation with PHP and Javascript and it has very little overhead in my testing.
It even supports filtering with a grep keyword (useful for ffmpeg which spits out frame rate etc every second).
$handler = fopen('somefile.txt', 'r');
// move you at the end of file
fseek($handler, filesize( ));
// move you at the begining of file
fseek($handler, 0);
And probably you will want to consider a use of stream_get_line
Instead of polling filesize you regular checking the file modification time: filemtime
Below is what I adapted from above. Call it periodically with an ajax call and append to your 'holder' (textarea)... Hope this helps... thank you to all of you who contribute to stackoverflow and other such forums!
/* Used by the programming module to output debug.txt */
session_start();
$_SESSION['tailSize'] = filesize("./debugLog.txt");
if($_SESSION['tailPrevSize'] == '' || $_SESSION['tailPrevSize'] > $_SESSION['tailSize'])
{
$_SESSION['tailPrevSize'] = $_SESSION['tailSize'];
}
$tailDiff = $_SESSION['tailSize'] - $_SESSION['tailPrevSize'];
$_SESSION['tailPrevSize'] = $_SESSION['tailSize'];
/* Include your own security checks (valid user, etc) if required here */
if(!$valid_user) {
echo "Invalid system mode for this page.";
}
$handle = popen("tail -c ".$tailDiff." ./debugLog.txt 2>&1", 'r');
while(!feof($handle)) {
$buffer = fgets($handle);
echo "$buffer";
flush();
}
pclose($handle);

Categories