php mutex for ram based wordpress cache in php

Im trying to implement a cache for a high traffic wp site in php. so far ive managed to store the results to a ramfs and load them directly from the htaccess. however during peak hours there are mora than one process generatin certain page and is becoming an issue

i was thinking that a mutex would help and i was wondering if there is a better way than system(“mkdir cache.mutex”)

Related posts

Leave a Reply

2 comments

  1. From what I understand you want to make sure only a single process at a time is running a certain piece of code. A mutex or similar mechanism could be used for this. I myself use lockfiles to have a solution that works on many platforms and doesn’t rely on a specific library only available on Linux etc.

    For that, I have written a small Lock class. Do note that it uses some non-standard functions from my library, for instance, to get where to store temporary files etc. But you could easily change that.

    <?php   
        class Lock
        {
            private $_owned             = false;
    
            private $_name              = null;
            private $_lockFile          = null;
            private $_lockFilePointer   = null;
    
            public function __construct($name)
            {
                $this->_name = $name;
                $this->_lockFile = PluginManager::getInstance()->getCorePlugin()->getTempDir('locks') . $name . '-' . sha1($name . PluginManager::getInstance()->getCorePlugin()->getPreference('EncryptionKey')->getValue()).'.lock';
            }
    
            public function __destruct()
            {
                $this->release();
            }
    
            /**
             * Acquires a lock
             *
             * Returns true on success and false on failure.
             * Could be told to wait (block) and if so for a max amount of seconds or return false right away.
             *
             * @param bool $wait
             * @param null $maxWaitTime
             * @return bool
             * @throws Exception
             */
            public function acquire($wait = false, $maxWaitTime = null) {
                $this->_lockFilePointer = fopen($this->_lockFile, 'c');
                if(!$this->_lockFilePointer) {
                    throw new RuntimeException(__('Unable to create lock file', 'dliCore'));
                }
    
                if($wait && $maxWaitTime === null) {
                    $flags = LOCK_EX;
                }
                else {
                    $flags = LOCK_EX | LOCK_NB;
                }
    
                $startTime = time();
    
                while(1) {
                    if (flock($this->_lockFilePointer, $flags)) {
                        $this->_owned = true;
                        return true;
                    } else {
                        if($maxWaitTime === null || time() - $startTime > $maxWaitTime) {
                            fclose($this->_lockFilePointer);
                            return false;
                        }
                        sleep(1);
                    }
                }
            }
    
            /**
             * Releases the lock
             */
            public function release()
            {
                if($this->_owned) {
                    @flock($this->_lockFilePointer, LOCK_UN);
                    @fclose($this->_lockFilePointer);
                    @unlink($this->_lockFile);
                    $this->_owned = false;
                }
            }
        }
    

    Usage

    Now you can have two process that run at the same time and execute the same script

    Process 1

    $lock = new Lock('runExpensiveFunction');
    
    if($lock->acquire()) {
      // Some expensive function that should only run one at a time
      runExpensiveFunction();
      $lock->release();
    }
    

    Process 2

    $lock = new Lock('runExpensiveFunction');
    
    // Check will be false since the lock will already be held by someone else so the function is skipped
    if($lock->acquire()) {
      // Some expensive function that should only run one at a time
      runExpensiveFunction();
      $lock->release();
    }
    

    Another alternative would be to have the second process wait for the first one to finish instead of skipping the code.

    $lock = new Lock('runExpensiveFunction');
    
    // Process will now wait for the lock to become available. A max wait time can be set if needed.
    if($lock->acquire(true)) {
      // Some expensive function that should only run one at a time
      runExpensiveFunction();
      $lock->release();
    }
    

    Ram disk

    To limit the number of writes to your HDD/SSD with the lockfiles you could create a RAM disk to store them in.

    On Linux you could add something like the following to /etc/fstab

    tmpfs       /mnt/ramdisk tmpfs   nodev,nosuid,noexec,nodiratime,size=1024M   0 0
    

    On Windows you can download something like ImDisk Toolkit and create a ramdisk with that.

    ImDisk RamDisk Configuration Tool

  2. I agree with @gries, a reverse proxy is going to be a really good bang-for-the-buck way to get high performance out of a high-volume WordPress site. I’ve leveraged Varnish with quite a lot of success, though I suspect you can do so with nginx as well.