MDL-69521 core: Move all comments in code from 4.0 to 3.10
[moodle.git] / lib / classes / lock / db_record_lock_factory.php
CommitLineData
9843e5ec
DW
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/**
18 * This is a db record locking factory.
19 *
20 * @package core
21 * @category lock
22 * @copyright Damyon Wiese 2013
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26namespace core\lock;
27
28defined('MOODLE_INTERNAL') || die();
29
30/**
31 * This is a db record locking factory.
32 *
33 * This lock factory uses record locks relying on sql of the form "SET XXX where YYY" and checking if the
34 * value was set. It supports timeouts, autorelease and can work on any DB. The downside - is this
35 * will always be slower than some shared memory type locking function.
36 *
37 * @package core
38 * @category lock
39 * @copyright Damyon Wiese 2013
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 */
42class db_record_lock_factory implements lock_factory {
43
34027f15 44 /** @var \moodle_database $db Hold a reference to the global $DB */
9843e5ec
DW
45 protected $db;
46
47 /** @var string $type Used to prefix lock keys */
48 protected $type;
49
50 /** @var array $openlocks - List of held locks - used by auto-release */
51 protected $openlocks = array();
52
53 /**
54 * Is available.
55 * @return boolean - True if this lock type is available in this environment.
56 */
57 public function is_available() {
58 return true;
59 }
60
61 /**
62 * Almighty constructor.
63 * @param string $type - Used to prefix lock keys.
64 */
65 public function __construct($type) {
66 global $DB;
67
68 $this->type = $type;
69 // Save a reference to the global $DB so it will not be released while we still have open locks.
70 $this->db = $DB;
71
72 \core_shutdown_manager::register_function(array($this, 'auto_release'));
73 }
74
75 /**
76 * Return information about the blocking behaviour of the lock type on this platform.
77 * @return boolean - True
78 */
79 public function supports_timeout() {
80 return true;
81 }
82
83 /**
84 * Will this lock type will be automatically released when a process ends.
85 *
86 * @return boolean - True (shutdown handler)
87 */
88 public function supports_auto_release() {
89 return true;
90 }
91
92 /**
93 * Multiple locks for the same resource can be held by a single process.
4b71cdcd 94 *
709b46db 95 * @deprecated since Moodle 3.10.
9843e5ec
DW
96 * @return boolean - False - not process specific.
97 */
98 public function supports_recursion() {
4b71cdcd
MG
99 debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
100 DEBUG_DEVELOPER);
9843e5ec
DW
101 return false;
102 }
103
104 /**
105 * This function generates a unique token for the lock to use.
34027f15 106 * It is important that this token is not solely based on time as this could lead
9843e5ec
DW
107 * to duplicates in a clustered environment (especially on VMs due to poor time precision).
108 */
109 protected function generate_unique_token() {
c7321899 110 return \core\uuid::generate();
9843e5ec
DW
111 }
112
9843e5ec
DW
113 /**
114 * Create and get a lock
115 * @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
116 * @param int $timeout - The number of seconds to wait for a lock before giving up.
117 * @param int $maxlifetime - Unused by this lock type.
118 * @return boolean - true if a lock was obtained.
119 */
120 public function get_lock($resource, $timeout, $maxlifetime = 86400) {
121
122 $token = $this->generate_unique_token();
123 $now = time();
124 $giveuptime = $now + $timeout;
125 $expires = $now + $maxlifetime;
126
7e086935
BH
127 $resourcekey = $this->type . '_' . $resource;
128
129 if (!$this->db->record_exists('lock_db', array('resourcekey' => $resourcekey))) {
9843e5ec 130 $record = new \stdClass();
7e086935 131 $record->resourcekey = $resourcekey;
9843e5ec
DW
132 $result = $this->db->insert_record('lock_db', $record);
133 }
134
135 $params = array('expires' => $expires,
136 'token' => $token,
7e086935 137 'resourcekey' => $resourcekey,
9843e5ec
DW
138 'now' => $now);
139 $sql = 'UPDATE {lock_db}
140 SET
141 expires = :expires,
142 owner = :token
143 WHERE
144 resourcekey = :resourcekey AND
145 (owner IS NULL OR expires < :now)';
146
147 do {
148 $now = time();
149 $params['now'] = $now;
150 $this->db->execute($sql, $params);
151
7e086935 152 $countparams = array('owner' => $token, 'resourcekey' => $resourcekey);
9843e5ec
DW
153 $result = $this->db->count_records('lock_db', $countparams);
154 $locked = $result === 1;
ecbe9206 155 if (!$locked && $timeout > 0) {
9843e5ec
DW
156 usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds.
157 }
158 // Try until the giveup time.
159 } while (!$locked && $now < $giveuptime);
160
161 if ($locked) {
162 $this->openlocks[$token] = 1;
163 return new lock($token, $this);
164 }
165
166 return false;
167 }
168
169 /**
170 * Release a lock that was previously obtained with @lock.
171 * @param lock $lock - a lock obtained from this factory.
172 * @return boolean - true if the lock is no longer held (including if it was never held).
173 */
174 public function release_lock(lock $lock) {
175 $params = array('noexpires' => null,
176 'token' => $lock->get_key(),
177 'noowner' => null);
178
179 $sql = 'UPDATE {lock_db}
180 SET
181 expires = :noexpires,
182 owner = :noowner
183 WHERE
184 owner = :token';
185 $result = $this->db->execute($sql, $params);
186 if ($result) {
187 unset($this->openlocks[$lock->get_key()]);
188 }
189 return $result;
190 }
191
192 /**
193 * Extend a lock that was previously obtained with @lock.
80736a93 194 *
709b46db 195 * @deprecated since Moodle 3.10.
9843e5ec
DW
196 * @param lock $lock - a lock obtained from this factory.
197 * @param int $maxlifetime - the new lifetime for the lock (in seconds).
198 * @return boolean - true if the lock was extended.
199 */
200 public function extend_lock(lock $lock, $maxlifetime = 86400) {
80736a93
MG
201 debugging('The function extend_lock() is deprecated, please do not use it anymore.',
202 DEBUG_DEVELOPER);
203
9843e5ec
DW
204 $now = time();
205 $expires = $now + $maxlifetime;
206 $params = array('expires' => $expires,
207 'token' => $lock->get_key());
208
209 $sql = 'UPDATE {lock_db}
210 SET
211 expires = :expires,
212 WHERE
213 owner = :token';
214
215 $this->db->execute($sql, $params);
216 $countparams = array('owner' => $lock->get_key());
217 $result = $this->count_records('lock_db', $countparams);
218
219 return $result === 0;
220 }
221
222 /**
223 * Auto release any open locks on shutdown.
224 * This is required, because we may be using persistent DB connections.
225 */
226 public function auto_release() {
227 // Called from the shutdown handler. Must release all open locks.
228 foreach ($this->openlocks as $key => $unused) {
229 $lock = new lock($key, $this);
894f36ab 230 $lock->release();
9843e5ec
DW
231 }
232 }
233}