Restrict multiple php script instances - php

I have a script that is running multiple times cause the validation is taking longer and allowing multiple instance of the script. It is supposed to run about once a day but yesterday script_start() ran 18 times all right around the same time.
add_action('init', 'time_validator');
function time_validator() {
$last = get_option( 'last_update' );
$interval = get_option( 'interval' );
$slop = get_option( 'interval_slop' );
if ( ( time() - $last ) > ( $interval + rand( 0, $slop ) ) ) {
update_option( 'last_update', time() );
script_start();
}
}

It sounds messy, that you've detected 18 instances of your script running although you don't want that. You should fix the code which calls those script instances.
However, you can implement this check into the script itself. To make sure that the script runs only once you should use flock(). I' ll give an example:
Add this to the top of your code that should run only once a time:
// open the lock file
$fd = fopen('lock.file', 'w+');
// try to obtain an exclusive lock. If another instance is currently
// obtaining the lock we'll just exit. (LOCK_NB makes flock not blocking)
if(!flock($fd, LOCK_EX | LOCK_NB)) {
die('process is already running');
}
... and this and the end of the critical code:
// release the lock
flock($fd, LOCK_UN);
// close the file
fclose($fd);
The method described is safe against race conditions, it really makes sure that a critical section runs only once.

Related

PHP File locking problem with multiple writes

I'm using a very simple script where some users data are stored in a text file beside a database because this file needs to be read by a specific process on the system .
The php expire date update code for a line in the following format like this :
U: w w { enddate=2022-10-18 }
The function code goes like this:
$date = "enddate=";
$date .= setExpireDate($duration);
$Read = file($fileDir);
foreach ($Read as $LineNum => $line) {
$LineParts = explode(' ', $line);
if (count($LineParts) == 1) {continue;}
if ($LineParts[1] == $username) {
$LineParts[4] = $date;
$Read[$LineNum] = implode(' ', $LineParts);
break;
}
}
file_put_contents($fileDir, $Read, LOCK_EX);
some users have reported problems , after doing some search i found that the data in the database is updated fine , but the not in the text file as the data is not updated for some users .
as i think it is a file locking problem , that prevents some users under heavy load to write on the file , but the database can handle multiple writes just fine .
So is there anyway to handle the multiple writes on the file at the same time better than this ?
and is there a way to simulate this in php like locking the file by a form submit ?
i tried this :
function lock(){
$fp = fopen("file.txt", "r+");
flock($fp, LOCK_EX);
echo "file is locked <br>";
}
but it is not working at all ,and others can write to the file .
P.S: I know there are a lot of questions about file locking ,but non of the answers did help !
Regards
Here is an example of the file locking approach
First you attempt to lock the Lock file, if you can all well and good proceed to edit the real file. If you cannot get a lock you retry until you can.
// this is not the file you are attempting to update, its just the lock file
// it must already exist and its bext if you place it in a specific directory,
// not accessable from the web.
$fplock = fopen("/some/path/lock.txt", "r+");
// attempt to get an exclusive lock on the lock file
while ( ! flock($fplock, LOCK_EX) ) {
// this has failed to get the lock so do nothing but retry,
// it should be only milli seconds until the other process finishes
// and releases the lock
}
// the lock must have been released by the other process when you get here
// or you got a lock on your first attempt and never ran the wait loop
// at this point this script has the lock
$date = "enddate=";
$date .= setExpireDate($duration);
$Read = file($fileDir);
foreach ($Read as $LineNum => $line) {
$LineParts = explode(' ', $line);
if (count($LineParts) == 1) {continue;}
if ($LineParts[1] == $username) {
$LineParts[4] = $date;
$Read[$LineNum] = implode(' ', $LineParts);
break;
}
}
file_put_contents($fileDir, $Read, LOCK_EX);
/*
closing the lock file has the effect of releasing the lock, or simply finishing this
script will do the same, but do it yourself as soon as you have finished updating the
real file, so other process's dont have to wait until the script finishes,
I assume you may have other code after this
*/
fclose($fplock);
Of course ALL scripts that access the Real File, must also attempt to lock the lock file before accessing the real file, or this all falls down in a mess.

php: time limit exceeded `Success' # fatal/cache.c/GetImagePixelCache/2042

I'm import data from a CRM Server by JSON to Wordpress.
I know that the load may take several minutes, so the script runs outside Wordpress. And I execute "php load_data.php"
But when the script reaches the part where we upload the images, it throws an error:
php: time limit exceeded `Success' # fatal/cache.c/GetImagePixelCache/2042.
and it stops.
This is my code to upload image to media:
<?php
function upload_image_to_media($postid, $image_url, $set_featured=0) {
$tmp = download_url( $image_url );
// fix filename for query strings
preg_match( '/[^\?]+\.(jpg|jpe|jpeg|gif|png)/i', $image_url, $matches );
$before_name = $postid == 0 ? 'upload' : $postid;
$file_array = array(
'name' => $before_name . '_' . basename( $matches[0] ),
'tmp_name' => $tmp
);
// Check for download errors
if ( is_wp_error( $tmp ) ) {
#unlink( $file_array['tmp_name'] );
return false;
}
$media_id = media_handle_sideload( $file_array, $postid );
// Check for handle sideload errors.
if ( is_wp_error( $media_id ) ) {
#unlink( $file_array['tmp_name'] );
return false;
}
if( $postid != 0 && $set_featured == 1 )
set_post_thumbnail( $postid, $media_id );
return $media_id;
}
?>
They are like 50 posts and each one has 10 large images.
Regards
The default execution time is 30 seconds so looks you are exceeding that. We have a similar script that downloads up to a couple thousand photos per run. Adding set_time_limit(60) to reset timer each loop fixed timeout issues. In your case you can probably just add at the beginning of the function. Just be very careful you don't get any infinite loops as they will run forever (or until the next reboot).
To make sure it works you can add the below as the first line inside your upload function
set_time_limit(0)
this will allow it to run until it's finished, but watch it as this will let it run forever which WILL hurt your servers available memory. But to see if the script works put that in there, then adjust to proper time if need be.
If you get another or the same error it will at least verify its not a time issue (error messages are not always factual).
The other possibility is that you are on a shared server and are exceeding their time allotment for you server. (continuous processor use for more then 30 seconds, as an example).

PHP Prevent simultaneous function execution (throttling limits via PHP)

I have a function that send HTTP request via CURL to www.server.com
My task is to make sure that www.server.com gets no more than one request every 2 seconds.
Possible solution:
Create a function checktime() that will store current call time in database and check with database on every next call and make system pause for 2 seconds:
$oldTime = $this->getTimeFromDatabase();
if ($oldTime < (time() - 2) ) { // if its been 2 seconds
$this->setNewTimeInDatabase();
return true;
} else {
sleep(2);
return false;
}
The problem/question:
Lets say, the last request to www.server.com was on 1361951000. Then 10 other users attempt to do request on 1361951001 (1 seconds later). checktime() Function will be called.
As since it only has been 1 second, the function will return false. All 10 users will wait 2 seconds. Does it means that on 1361951003 there are 10 requests will be sent simultaneously? And is it possible that the time of last request will not be changed in database, because of the missed call of $this->setNewTimeInDatabase() in checktime()?
Thank you!
UPDATE:
I have just been told that using a loop might solve the problem:
for($i=0;$i<300;$i++)
{
$oldTime = $this->getTimeFromDatabase();
if ($oldTime < (time() - 2) ) { // if its been 2 seconds
$this->setNewTimeInDatabase();
return true;
} else {
sleep(2);
return false;
}
}
But i don't really see logic in it.
I believe you need some implementation of a semaphore. The database could work, as long as you can guarantee that only one thread gets to write to the db and then make the request.
For example, you might use an update request to the db and then check for the updated rows (in order to check whether the update actually happened). If the update was succesful you can assume you got the mutex lock and then make the request (assuming the time is right to make it). Something like this:
$oldTime = $this->getTimeFromDatabase();
if ($oldTime < (time() - 2) && $this->getLock()) { // if its been 2 seconds
$this->setNewTimeInDatabase();
$this->releaseLock();
return true;
} else {
sleep(2);
return false;
}
function getLock()
{
return $mysqli->query('UPDATE locktable set locked = 1 WHERE locked = 0');
}
function releaseLock()
{
$mysqli->query('UPDATE locktable set locked = 0');
}
I'm not sure about the mysql functions, but I believe it's ok to get the general idea.
Watch out with using a database. For example MySQL is not always 100% in sync with its sessions, and for that reason it is not safe to rely on that for locking purposes.
You could use a file-lock through the method flock, where you would save the access time in. Then you could be sure to lock the file, so no two or more processes would ever access it at the same time.
It would probably go something like this:
$filepath = "lockfile_for_source";
touch($filepath);
$fp = fopen("lockfile_for_resource", "r") or die("Could not open file.");
while(true){
while(!flock($fp, LOCK_EX)){
sleep(0.25); //wait to get file-lock.
}
$time = file_get_contents($filepath);
$diff = time() - $time;
if ($diff >= 2){
break;
}else{
flock($fp, LOCK_UN);
}
}
//Following code would never be executed simultaneously by two scripts.
//You should access and use your resource here.
fwrite($fp, time());
fflush($fp);
flock($fp, LOCK_UN); //remove lock on file.
fclose($fp);
Please be aware that I have not tested the code.

PHP - Call external programs and show progress

I have a PHP script that takes the fields of a form to make 3 alternated program calls, where the output of the first one is the input of the second one and so on.
The problem is that I need to display the progress of each program call, nothing to complicated, I just like to show 3 different messages:
System call N waiting.
System call N in execution.
System call N finished.
I´m trying to do that with the different PHP functions like exec(), popen() or proc_open() but with these ones the browser waits until each call finish.
The whole system calls don´t take more than 5 minutes, maybe 3 or 4, so, it would also be good to place a timer in each call, maybe 1.5 minutes, and if the call takes more that that time, kill the current system call, skip the following calls and show an error message.
Do you have any idea? Maybe a combination of ajax and javascript can be a solution. Thanks in advance.
<?php
/*
System Calls
This file is required in another main script
$projectPath and $projectName defined in the main script
*/
//$mainHome = getcwd();
$home = $projectPath . $projectName;
$temp = $home . "/temp/";
$calls = $temp . "CALLS";
$threads = array();
if(is_dir($temp)){
//chdir($temp);
$FILE = fopen($calls, "r");
while(($call = fgetcsv($FILE)) !== FALSE) {
//print_r($call);
$threads[] = implode(" ", $call);
}
}
//print_r($threads);
$descriptorspec = array(0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","./phpError.log","a")
);
for ($a=0; $a<count($threads); $a++) {
print $threads[$a] . "<br/><br/>";
exec($threads[$a])
//$res = proc_open($threads[$a], $descriptorspec, $pipes, $temp);
}
//chdir($mainHome);
?>
Thank you very much! finally I develop a python CGI based solution, a bit more static than I thought but meets the expectations of the team.
Greetings

How can I prevent PHP from executing code multiple times?

I have a WordPress plugin with a backup script that executes on a schedule. The catch is, if someone hits the page multiple times in succession it can execute the backup script multiple times. Any thoughts on how to prevent multiple executions?
global $bwpsoptions;
if ( get_transient( 'bit51_bwps_backup' ) === false ) {
set_transient( 'bit51_bwps_backup', '1', 300 );
if ( $bwpsoptions['backup_enabled'] == 1 ) {
$nextbackup = $bwpsoptions['backup_next']; //get next schedule
$lastbackup = $bwpsoptions['backup_last']; //get last backup
switch ( $bwpsoptions['backup_interval'] ) { //schedule backup at appropriate time
case '0':
$next = 60 * 60 * $bwpsoptions['backup_time'];
break;
case '1':
$next = 60 * 60 * 24 * $bwpsoptions['backup_time'];
break;
case '2':
$next = 60 * 60 * 24 * 7 * $bwpsoptions['backup_time'];
break;
}
if ( ( $lastbackup == '' || $nextbackup < time() ) && get_transient( 'bit51_bwps_backup' ) === false ) {
$bwpsoptions['backup_last'] = time();
if ( $lastbackup == '' ) {
$bwpsoptions['backup_next'] = ( time() + $next );
} else {
$bwpsoptions['backup_next'] = ( $lastbackup + $next );
}
update_option( $this->primarysettings, $bwpsoptions );
$this->execute_backup(); //execute backup
}
}
}
Create a file at the start of the code.
When the code finishes running delete the file.
At the beginning of the code make sure thefile doesn't exist before running.
Sort of like the apt-get lock in linux.
If your site is very busy and basic locking mechanism arn't working (I personally can't imagine that but oh well!), you can try the solution from PHP session's garbage collector.
Just randomly choose a number between 0 and 10 and if the number is 0, do the backup. If now 10 user's call your backup script at nearly the same time, statistically only one will actually execute the backup.
define("BACKUP_PROBABILITY", 10);
if (mt_rand(0, BACKUP_PROBABILITY) == 0)
doBackup();
You can increase the maximum (the 10) if your site is very highly frequented.
If in those 10 visits none got the 0, the next 10 visitors will get their chance.
You will need of course some kind of locking mechanism and it is still possible (though unplausible) that you will end up with more than one or even 10 backups.
I found this question about mutexes (locks) in PHP. Might be helpful: PHP mutual exclusion (mutex)
Store the last backup date/time in some external file on server or into database, and use a check against that value!
I assume that this backup thing makes a backup somewhere.
So check the metadata on the latest backup, and if it's creation time is not far enough in the past, don't do the backup.
I assume there's a good reason why this isn't a cron job?

Categories