summaryrefslogtreecommitdiff
path: root/includes/filebackend/lockmanager/LockManager.php
blob: 0512a01b1eb1dfaf98873be2af3ae44bbbcce49d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<?php
/**
 * @defgroup LockManager Lock management
 * @ingroup FileBackend
 */

/**
 * Resource locking handling.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 * @ingroup LockManager
 * @author Aaron Schulz
 */

/**
 * @brief Class for handling resource locking.
 *
 * Locks on resource keys can either be shared or exclusive.
 *
 * Implementations must keep track of what is locked by this proccess
 * in-memory and support nested locking calls (using reference counting).
 * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
 * Locks should either be non-blocking or have low wait timeouts.
 *
 * Subclasses should avoid throwing exceptions at all costs.
 *
 * @ingroup LockManager
 * @since 1.19
 */
abstract class LockManager {
	/** @var Array Mapping of lock types to the type actually used */
	protected $lockTypeMap = array(
		self::LOCK_SH => self::LOCK_SH,
		self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
		self::LOCK_EX => self::LOCK_EX
	);

	/** @var Array Map of (resource path => lock type => count) */
	protected $locksHeld = array();

	protected $domain; // string; domain (usually wiki ID)
	protected $lockTTL; // integer; maximum time locks can be held

	/* Lock types; stronger locks have higher values */
	const LOCK_SH = 1; // shared lock (for reads)
	const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
	const LOCK_EX = 3; // exclusive lock (for writes)

	/**
	 * Construct a new instance from configuration
	 *
	 * $config paramaters include:
	 *   - domain  : Domain (usually wiki ID) that all resources are relative to [optional]
	 *   - lockTTL : Age (in seconds) at which resource locks should expire.
	 *               This only applies if locks are not tied to a connection/process.
	 *
	 * @param $config Array
	 */
	public function __construct( array $config ) {
		$this->domain = isset( $config['domain'] ) ? $config['domain'] : wfWikiID();
		if ( isset( $config['lockTTL'] ) ) {
			$this->lockTTL = max( 1, $config['lockTTL'] );
		} elseif ( PHP_SAPI === 'cli' ) {
			$this->lockTTL = 2*3600;
		} else {
			$met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
			$this->lockTTL = max( 5*60, 2*(int)$met );
		}
	}

	/**
	 * Lock the resources at the given abstract paths
	 *
	 * @param array $paths List of resource names
	 * @param $type integer LockManager::LOCK_* constant
	 * @return Status
	 */
	final public function lock( array $paths, $type = self::LOCK_EX ) {
		wfProfileIn( __METHOD__ );
		$status = $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] );
		wfProfileOut( __METHOD__ );
		return $status;
	}

	/**
	 * Unlock the resources at the given abstract paths
	 *
	 * @param array $paths List of storage paths
	 * @param $type integer LockManager::LOCK_* constant
	 * @return Status
	 */
	final public function unlock( array $paths, $type = self::LOCK_EX ) {
		wfProfileIn( __METHOD__ );
		$status = $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] );
		wfProfileOut( __METHOD__ );
		return $status;
	}

	/**
	 * Get the base 36 SHA-1 of a string, padded to 31 digits.
	 * Before hashing, the path will be prefixed with the domain ID.
	 * This should be used interally for lock key or file names.
	 *
	 * @param $path string
	 * @return string
	 */
	final protected function sha1Base36Absolute( $path ) {
		return wfBaseConvert( sha1( "{$this->domain}:{$path}" ), 16, 36, 31 );
	}

	/**
	 * Get the base 16 SHA-1 of a string, padded to 31 digits.
	 * Before hashing, the path will be prefixed with the domain ID.
	 * This should be used interally for lock key or file names.
	 *
	 * @param $path string
	 * @return string
	 */
	final protected function sha1Base16Absolute( $path ) {
		return sha1( "{$this->domain}:{$path}" );
	}

	/**
	 * Lock resources with the given keys and lock type
	 *
	 * @param array $paths List of storage paths
	 * @param $type integer LockManager::LOCK_* constant
	 * @return string
	 */
	abstract protected function doLock( array $paths, $type );

	/**
	 * Unlock resources with the given keys and lock type
	 *
	 * @param array $paths List of storage paths
	 * @param $type integer LockManager::LOCK_* constant
	 * @return string
	 */
	abstract protected function doUnlock( array $paths, $type );
}

/**
 * Simple version of LockManager that does nothing
 * @since 1.19
 */
class NullLockManager extends LockManager {
	/**
	 * @see LockManager::doLock()
	 * @param $paths array
	 * @param $type int
	 * @return Status
	 */
	protected function doLock( array $paths, $type ) {
		return Status::newGood();
	}

	/**
	 * @see LockManager::doUnlock()
	 * @param $paths array
	 * @param $type int
	 * @return Status
	 */
	protected function doUnlock( array $paths, $type ) {
		return Status::newGood();
	}
}