Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core\lock;
  18  
  19  use coding_exception;
  20  
  21  /**
  22   * Flock based file locking factory.
  23   *
  24   * The file lock factory returns file locks locked with the flock function. Works OK, except on some
  25   * NFS, exotic shared storage and exotic server OSes (like windows). On windows, a second attempt to get a
  26   * lock will block indefinitely instead of timing out.
  27   *
  28   * @package   core
  29   * @category  lock
  30   * @copyright Damyon Wiese 2013
  31   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   */
  33  class file_lock_factory implements lock_factory {
  34  
  35      /** @var string $type - The type of lock, e.g. cache, cron, session. */
  36      protected $type;
  37  
  38      /** @var string $lockdirectory - Full system path to the directory used to store file locks. */
  39      protected $lockdirectory;
  40  
  41      /** @var boolean $verbose - If true, debugging info about the owner of the lock will be written to the lock file. */
  42      protected $verbose;
  43  
  44      /**
  45       * Create this lock factory.
  46       *
  47       * @param string $type - The type, e.g. cron, cache, session
  48       * @param string|null $lockdirectory - Optional path to the lock directory, to override defaults.
  49       */
  50      public function __construct($type, ?string $lockdirectory = null) {
  51          global $CFG;
  52  
  53          $this->type = $type;
  54          if (!is_null($lockdirectory)) {
  55              $this->lockdirectory = $lockdirectory;
  56          } else if (!isset($CFG->file_lock_root)) {
  57              $this->lockdirectory = $CFG->dataroot . '/lock';
  58          } else {
  59              $this->lockdirectory = $CFG->file_lock_root;
  60          }
  61          $this->verbose = false;
  62          if ($CFG->debugdeveloper) {
  63              $this->verbose = true;
  64          }
  65      }
  66  
  67      /**
  68       * Return information about the blocking behaviour of the lock type on this platform.
  69       * @return boolean - False if attempting to get a lock will block indefinitely.
  70       */
  71      public function supports_timeout() {
  72          global $CFG;
  73  
  74          return $CFG->ostype !== 'WINDOWS';
  75      }
  76  
  77      /**
  78       * This lock type will be automatically released when a process ends.
  79       * @return boolean - True
  80       */
  81      public function supports_auto_release() {
  82          return true;
  83      }
  84  
  85      /**
  86       * Is available.
  87       * @return boolean - True if preventfilelocking is not set - or the file_lock_root is not in dataroot.
  88       */
  89      public function is_available() {
  90          global $CFG;
  91          $preventfilelocking = !empty($CFG->preventfilelocking);
  92          $lockdirisdataroot = true;
  93          if (strpos($this->lockdirectory, $CFG->dataroot) !== 0) {
  94              $lockdirisdataroot = false;
  95          }
  96          return !$preventfilelocking || !$lockdirisdataroot;
  97      }
  98  
  99      /**
 100       * @deprecated since Moodle 3.10.
 101       */
 102      public function supports_recursion() {
 103          throw new coding_exception('The function supports_recursion() has been removed, please do not use it anymore.');
 104      }
 105  
 106      /**
 107       * Get some info that might be useful for debugging.
 108       * @return boolean - string
 109       */
 110      protected function get_debug_info() {
 111          return 'host:' . php_uname('n') . ', pid:' . getmypid() . ', time:' . time();
 112      }
 113  
 114      /**
 115       * Get a lock within the specified timeout or return false.
 116       * @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
 117       * @param int $timeout - The number of seconds to wait for a lock before giving up.
 118       * @param int $maxlifetime - Unused by this lock type.
 119       * @return boolean - true if a lock was obtained.
 120       */
 121      public function get_lock($resource, $timeout, $maxlifetime = 86400) {
 122          $giveuptime = time() + $timeout;
 123  
 124          $hash = md5($this->type . '_' . $resource);
 125          $lockdir = $this->lockdirectory . '/' . substr($hash, 0, 2);
 126  
 127          if (!check_dir_exists($lockdir, true, true)) {
 128              return false;
 129          }
 130  
 131          $lockfilename = $lockdir . '/' . $hash;
 132  
 133          $filehandle = fopen($lockfilename, "wb");
 134  
 135          // Could not open the lock file.
 136          if (!$filehandle) {
 137              return false;
 138          }
 139  
 140          do {
 141              // Will block on windows. So sad.
 142              $wouldblock = false;
 143              $locked = flock($filehandle, LOCK_EX | LOCK_NB, $wouldblock);
 144              if (!$locked && $wouldblock && $timeout > 0) {
 145                  usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds.
 146              }
 147              // Try until the giveup time.
 148          } while (!$locked && $wouldblock && time() < $giveuptime);
 149  
 150          if (!$locked) {
 151              fclose($filehandle);
 152              return false;
 153          }
 154          if ($this->verbose) {
 155              fwrite($filehandle, $this->get_debug_info());
 156          }
 157          return new lock($filehandle, $this);
 158      }
 159  
 160      /**
 161       * Release a lock that was previously obtained with @lock.
 162       * @param lock $lock - A lock obtained from this factory.
 163       * @return boolean - true if the lock is no longer held (including if it was never held).
 164       */
 165      public function release_lock(lock $lock) {
 166          $handle = $lock->get_key();
 167  
 168          if (!$handle) {
 169              // We didn't have a lock.
 170              return false;
 171          }
 172  
 173          $result = flock($handle, LOCK_UN);
 174          fclose($handle);
 175          return $result;
 176      }
 177  
 178      /**
 179       * @deprecated since Moodle 3.10.
 180       */
 181      public function extend_lock() {
 182          throw new coding_exception('The function extend_lock() has been removed, please do not use it anymore.');
 183      }
 184  
 185  }