As it turns out, all these years I was lucky that my shared web hosting on ionos (Apache) ensured that when the same PHP script was accessed simultaneously by 2 separate requests, access was serialized; the first call would complete fully before the second was allowed to run. Effectively this means that the log file accessed from the PHP script was never written by more than 1 process. But there was a single (non-essential) utility file that was used by two different PHP scripts (say SCRIPT1.PHP and SCRIPT_2.PHP). My webhost allows scripts with different names to be executed simultaneously, and under heavy load the file became corrupt.
Red-faced, I rushed into a crash course on PHP file locking, using flock function. Before writing a log file you must acquire an exclusive lock to it such as flock($fp, LOCK_EX). At the end unlock the file using LOCK_UN. Only one process can hold the file lock; other processes that attempt to flock are blocked till the locking process releases the lock. Note fopen on a locked file succeeds. So a secure way to access a shared log file would be as this sample PHP code:
# fopen on locked file succeeds and returns immediately $fp = fopen('log.txt', 'a'); if($fp) { # microtime shows also milliseconds echo "open time: " . date('H:i:s') . "<br>"; // this waits (blocks) till the lock becomes available if (flock($fp, LOCK_EX)) { echo "lock time: " . date('H:i:s'). "<br>"; fwrite($fp, "test\n"); // append log line fflush($fp); // Flush output before releasing the lock sleep(5); // simulate long execution flock($fp, LOCK_UN); // Release the lock } else echo "Could not lock the file!"; // very rare fault fclose($fp); } else echo "error opening file"; echo "exit time: " . date('H:i:s') . "<br>";
If you observe the time stamps shown by the above script ("open time" etc) you will establish if your webhost serializes PHP execution automatically (the start time of the 2nd instance will be the termination of the first). If it does, save the same script under a different name (script1.php and script2.php) and observe the effect of locking (they'll both start together but one script will wait till the other one unlocks LOG.TXT.
Using LOCK_NB one can implement non-blocking lock tests, time-outs and extra frills. But I leave that to PHP experts :)
Post a comment on this topic »