PHP flock() alternative

后端 未结 7 1794
一个人的身影
一个人的身影 2020-12-09 11:40

PHP\'s documentation page for flock() indicates that it\'s not safe to use under IIS. If I can\'t rely on flock under all circumstances, is there another way I

7条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-09 11:57

    Here is my "PHP flock() alternative" - build on mkdir().

    The idea to do it with mkdir() came from here and here.

    My version

    • checks if I already have got lock-access. It also prevents blocking myself if I create and use the class multiple times for the same basedir.name
    • checks if my locking-file, with which I am asking for lock-access, was created
    • lets me get lock-access in the order I came to ask for it
    • stops waiting and looping if it could not get lock-access in the time I specified
    • removes dead lock-files (= files where the SID of the PID does not exist any more)

    You can use the PHP-class like this:

    //$dir        (string) = base-directory for the lock-files (with 'files' I mean directories => mode 0644)
    // 2       (float/int) = time to wait for lock-access before returning unsuccessful (default is 0 <= try once and return)
    //'.my_lock'  (string) = the way you want to name your locking-dirs (default is '.fLock')
    $lock = new FileLock($dir, 2, '.my_lock');
    
    //start lock - a locking directory will be created looking like this:
    //$dir/.my_lock-1536166146.4997-22796
    if ($lock->lock()) {
        //open your file - modify it - write it back
    } else { /* write alert-email to admin */ }
    
    //check if I had locked before
    if ($lock->is_locked) { /* do something else with your locked file */ }
    
    //unlock - the created dir will be removed (rmdir)
    $lock->unlock();
    

    Here is the working class:

    //build a file-locking class
    define('LOCKFILE_NONE', 0);
    define('LOCKFILE_LOCKED', 1);
    define('LOCKFILE_ALREADY_LOCKED', 2);
    define('LOCKFILE_ALREADY_LOCKED_IN_OTHER_CLASS', 3);
    define('LOCKFILE_FAILED_TO_OBTAIN_LOCK', false);
    define('LOCKFILE_FAILED_TO_OBTAIN_LOCK_BY_TIMEOUT', '');
    
    
    class FileLock {
        //FileLock assumes that there are no other directories or files in the
        //lock-base-directory named "$name-(float)-(int)"
        //FileLock uses mkdir() to lock. Why?
        //- mkdir() is atomic, so the lock is atomic and faster then saving files.
        //  Apparently it is faster than flock(), that requires several calls to the
        //  file system.
        //- flock() depends on the system, mkdir() works everywhere.
    
        private static $locked_memory = array();
    
        public function __construct($lockbasedir, $wait_sec=0, $name='.fLock') {
            $this->lockbasedir = (string)$lockbasedir;
            $this->wait        = (float)$wait_sec;
            $this->name        = (string)$name;
    
            $this->pid         = (int)getmypid();
    
            //if this basedir.name was locked before and is still locked don't try to lock again
            $this->is_locked   = empty(self::$locked_memory[$this->lockbasedir . $this->name]) ? LOCKFILE_NONE : LOCKFILE_ALREADY_LOCKED;
        }
    
        public function lock() {
            if ($this->is_locked) return $this->is_locked;
    
            $break_time = microtime(true);
    
            //create the directory as lock-file NOW
            $this->lockdir = "{$this->name}-" . number_format($break_time, 4, '.', '') . "-{$this->pid}";
            @mkdir("{$this->lockbasedir}/{$this->lockdir}", 0644);
    
            $break_time += $this->wait;
    
            //try to get locked
            while ($this->wait == 0 || microtime(true) < $break_time) {
    
                //get all locks with $this->name
                $files = preg_grep("/^{$this->name}-\d+\.\d+-\d+$/", scandir($this->lockbasedir));
    
                //since scandir() is sorted asc by default
                //$first_file is the next directory to obtain lock
                $first_file = reset($files);
    
                if (!$first_file) {
                    //no lock-files at all
                    return $this->is_locked = LOCKFILE_FAILED_TO_OBTAIN_LOCK;
                } elseif ($first_file == $this->lockdir) {
                    //Its me!! I'm getting locked :)
                    self::$locked_memory[$this->lockbasedir . $this->name] = 1;
                    return $this->is_locked = LOCKFILE_LOCKED;
                } elseif (preg_match("/^{$this->name}-\d+\.\d+-{$this->pid}$/", $first_file)) {
                    //my process-ID already locked $this->name in another class before
                    rmdir("{$this->lockbasedir}/{$this->lockdir}");
                    $this->lockdir = $first_file;
                    self::$locked_memory[$this->lockbasedir . $this->name] = 1;
                    return $this->is_locked = LOCKFILE_ALREADY_LOCKED_IN_OTHER_CLASS;
                }
    
                //missing lock-file for this job
                if (array_search($this->lockdir, $files) === false) return LOCKFILE_FAILED_TO_OBTAIN_LOCK;
    
                //run only once
                if ($this->wait == 0) break;
    
                //check if process at first place has died
                if (!posix_getsid(explode('-', $first_file)[2])) {
                    //remove dead lock
                    @rmdir("{$this->lockbasedir}/$first_file");
                } else {
                    //wait and try again after 0.1 seconds
                    usleep(100000);
                }
            }
    
            return $this->is_locked = LOCKFILE_FAILED_TO_OBTAIN_LOCK_BY_TIMEOUT;
        }
    
        public function unlock($force=false) {
            if ($force || $this->is_locked == 1) {
                rmdir("{$this->lockbasedir}/{$this->lockdir}");
                self::$locked_memory[$this->lockbasedir . $this->name] = $this->is_locked = LOCKFILE_NONE;
            }
        }
    }
    

提交回复
热议问题