Hakim
Hakim

Reputation: 3437

Dont run a cron php task until last one has finished

I have a php-cli script that is run by cron every 5 minutes. Because this interval is short, multiple processes are run at the same time. That's not what I want, since this script has to write inside a text file a numeric id that is incremented each time. It happens that writers are writing at the same time on this text file, and the value written is incorrect.

I have tried to use php's flock function to block writing in the file, when another process is writing on it but it doesnt work.

$fw = fopen($path, 'r+');
if (flock($fw, LOCK_EX)) {
    ftruncate($fw, 0);
    fwrite($fw, $latestid);
    fflush($fw);
    flock($fw, LOCK_UN);
}
fclose($fw);

So I suppose that the solution to this is create a bash script that verifies if there is an instance of this php script that is running, if so it should wait until it finished. But I dont know how to do it, any ideas?

Upvotes: 3

Views: 3439

Answers (5)

marekful
marekful

Reputation: 15351

I don't really understand how incrementing a counter every 5 minutes will result in multiple processes trying to write the counter file at the same time, but...

A much simpler approach is to use a simple locking mechanism similar to the below:

<?php

$lock_filename = 'nobodyshouldincrementthecounterwhenthisfileishere';

if(file_exists($lock_filename)) {
  return;
}

touch($lock_filename);

// your stuff...

unlink($lock_filename);

This as a simple approach will not deal with a situation when the script breaks before it could remove the lock file, in which case it would never run again until it is removed.

More sophisticated approaches are also possible as you suggest, e.g. fork the job in its own process, write the PID into a file, then before running the job it could be checked whether that PID is still running.

Upvotes: 4

Mart Rivilis
Mart Rivilis

Reputation: 122

To prevent start of a next session of any program until the previous session still running, such as next cron job, I recommend to use either built into your program or external check of running process of this program. Just execute before starting of your program

 ps -ef|grep <process_name>|grep -v grep|wc -l

and check, if its result will be 0. Only in this case your program could be started. I suppose, that you must guarantee an absence of 3rd party process having similar name. (For this purpose give your program a longer and unique name). And a name of your program must not contain pattern "grep".

This work good in combination with normal regular starting of your program, that is configured in a cron table, by cron daemon. For the case if your check is written as an external script, an entry in the crontab might look like

 <time_specification>  <your_starter_script>  <your_program> ...

2 important remarks: Exit code of your_starter_script must be 0 in case of not starting of your program and it would be better to completely prohibit writing to stdout or stderr by this script.

Such starter is very short and a simple programming exercise. Therefore I don't feel a need to provide its complete code.

Upvotes: 1

mti2935
mti2935

Reputation: 12027

Or, perhaps even simpler than my previous answer (using at to schedule the script to run again in 5 minutes) is make your script a daemon, by using a non-terminating loop, like so:

while(1) {
  // whatever your script does here....
  sleep(300) //wait 5 minutes
}

Then, you can do away with scheduling by way of cron or at altogether. Just simply run your script in the background from the command line, like so:

/path/to/your/script &

Or, add /path/to/your/script in /etc/rc.local to make your script start automatically when the machine boots.

Upvotes: 0

mti2935
mti2935

Reputation: 12027

Instead of using cron to run your script every 5 minutes, how about using at to schedule your script to run again, 5 minutes after it finishes. Near the end of your script, you can use shell_exec() to run an at command to schedule your script to run again in 5 minutes, like so:

at now + 5 minutes /path/to/script

Upvotes: 0

Hakim
Hakim

Reputation: 3437

The solution I'm using with a bash script is this:

exec 9>/path/to/lock/file
if ! flock -n 9  ; then
    echo "another instance is running";
    exit 1
fi
# this now runs under the lock until 9 is closed (it will be closed automatically when the script ends)  

A file descriptor 9> is created in /var/lock/file, and flock will exit a new process that's trying to run, unless there is no other instance of the script that is running.

How can I ensure that only one instance of a script is running at a time (mutual exclusion)?

Upvotes: 4

Related Questions