Completed
Branch master (b92a94)
by
unknown
34:34
created

DBLockManager::getConnection()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 6
eloc 21
c 1
b 1
f 0
nc 8
nop 1
dl 0
loc 34
rs 8.439
1
<?php
2
/**
3
 * Version of LockManager based on using DB table locks.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup LockManager
22
 */
23
24
/**
25
 * Version of LockManager based on using named/row DB locks.
26
 *
27
 * This is meant for multi-wiki systems that may share files.
28
 *
29
 * All lock requests for a resource, identified by a hash string, will map to one bucket.
30
 * Each bucket maps to one or several peer DBs, each on their own server.
31
 * A majority of peer DBs must agree for a lock to be acquired.
32
 *
33
 * Caching is used to avoid hitting servers that are down.
34
 *
35
 * @ingroup LockManager
36
 * @since 1.19
37
 */
38
abstract class DBLockManager extends QuorumLockManager {
39
	/** @var array[] Map of DB names to server config */
40
	protected $dbServers; // (DB name => server config array)
41
	/** @var BagOStuff */
42
	protected $statusCache;
43
44
	protected $lockExpiry; // integer number of seconds
45
	protected $safeDelay; // integer number of seconds
46
47
	protected $session = 0; // random integer
48
	/** @var IDatabase[] Map Database connections (DB name => Database) */
49
	protected $conns = [];
50
51
	/**
52
	 * Construct a new instance from configuration.
53
	 *
54
	 * @param array $config Parameters include:
55
	 *   - dbServers   : Associative array of DB names to server configuration.
56
	 *                   Configuration is an associative array that includes:
57
	 *                     - host        : DB server name
58
	 *                     - dbname      : DB name
59
	 *                     - type        : DB type (mysql,postgres,...)
60
	 *                     - user        : DB user
61
	 *                     - password    : DB user password
62
	 *                     - tablePrefix : DB table prefix
63
	 *                     - flags       : DB flags (see DatabaseBase)
64
	 *   - dbsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
65
	 *                   each having an odd-numbered list of DB names (peers) as values.
66
	 *                   Any DB named 'localDBMaster' will automatically use the DB master
67
	 *                   settings for this wiki (without the need for a dbServers entry).
68
	 *                   Only use 'localDBMaster' if the domain is a valid wiki ID.
69
	 *   - lockExpiry  : Lock timeout (seconds) for dropped connections. [optional]
70
	 *                   This tells the DB server how long to wait before assuming
71
	 *                   connection failure and releasing all the locks for a session.
72
	 */
73
	public function __construct( array $config ) {
74
		parent::__construct( $config );
75
76
		$this->dbServers = isset( $config['dbServers'] )
77
			? $config['dbServers']
78
			: []; // likely just using 'localDBMaster'
79
		// Sanitize srvsByBucket config to prevent PHP errors
80
		$this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
81
		$this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
82
83
		if ( isset( $config['lockExpiry'] ) ) {
84
			$this->lockExpiry = $config['lockExpiry'];
85
		} else {
86
			$met = ini_get( 'max_execution_time' );
87
			$this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
88
		}
89
		$this->safeDelay = ( $this->lockExpiry <= 0 )
90
			? 60 // pick a safe-ish number to match DB timeout default
91
			: $this->lockExpiry; // cover worst case
92
93
		foreach ( $this->srvsByBucket as $bucket ) {
94
			if ( count( $bucket ) > 1 ) { // multiple peers
95
				// Tracks peers that couldn't be queried recently to avoid lengthy
96
				// connection timeouts. This is useless if each bucket has one peer.
97
				$this->statusCache = ObjectCache::getLocalServerInstance();
98
				break;
99
			}
100
		}
101
102
		$this->session = wfRandomString( 31 );
0 ignored issues
show
Documentation Bug introduced by
The property $session was declared of type integer, but wfRandomString(31) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
103
	}
104
105
	// @todo change this code to work in one batch
106 View Code Duplication
	protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
107
		$status = Status::newGood();
108
		foreach ( $pathsByType as $type => $paths ) {
109
			$status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
110
		}
111
112
		return $status;
113
	}
114
115
	abstract protected function doGetLocksOnServer( $lockSrv, array $paths, $type );
116
117
	protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
118
		return Status::newGood();
119
	}
120
121
	/**
122
	 * @see QuorumLockManager::isServerUp()
123
	 * @param string $lockSrv
124
	 * @return bool
125
	 */
126
	protected function isServerUp( $lockSrv ) {
127
		if ( !$this->cacheCheckFailures( $lockSrv ) ) {
128
			return false; // recent failure to connect
129
		}
130
		try {
131
			$this->getConnection( $lockSrv );
132
		} catch ( DBError $e ) {
133
			$this->cacheRecordFailure( $lockSrv );
134
135
			return false; // failed to connect
136
		}
137
138
		return true;
139
	}
140
141
	/**
142
	 * Get (or reuse) a connection to a lock DB
143
	 *
144
	 * @param string $lockDb
145
	 * @return IDatabase
146
	 * @throws DBError
147
	 * @throws UnexpectedValueException
148
	 */
149
	protected function getConnection( $lockDb ) {
150
		if ( !isset( $this->conns[$lockDb] ) ) {
151
			if ( $lockDb === 'localDBMaster' ) {
152
				$lb = $this->getLocalLB();
153
				$db = $lb->getConnection( DB_MASTER, [], $this->domain );
154
				# Do not mess with settings if the LoadBalancer is the main singleton
155
				# to avoid clobbering the settings of handles from wfGetDB( DB_MASTER ).
156
				$init = ( wfGetLB() !== $lb );
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
157
			} elseif ( isset( $this->dbServers[$lockDb] ) ) {
158
				$config = $this->dbServers[$lockDb];
159
				$db = DatabaseBase::factory( $config['type'], $config );
160
				$init = true;
161
			} else {
162
				throw new UnexpectedValueException( "No server called '$lockDb'." );
163
			}
164
165
			if ( $init ) {
166
				$db->clearFlag( DBO_TRX );
167
				# If the connection drops, try to avoid letting the DB rollback
168
				# and release the locks before the file operations are finished.
169
				# This won't handle the case of DB server restarts however.
170
				$options = [];
171
				if ( $this->lockExpiry > 0 ) {
172
					$options['connTimeout'] = $this->lockExpiry;
173
				}
174
				$db->setSessionOptions( $options );
175
				$this->initConnection( $lockDb, $db );
176
			}
177
178
			$this->conns[$lockDb] = $db;
179
		}
180
181
		return $this->conns[$lockDb];
182
	}
183
184
	/**
185
	 * @return LoadBalancer
186
	 */
187
	protected function getLocalLB() {
188
		return wfGetLBFactory()->getMainLB( $this->domain );
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLBFactory() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
189
	}
190
191
	/**
192
	 * Do additional initialization for new lock DB connection
193
	 *
194
	 * @param string $lockDb
195
	 * @param IDatabase $db
196
	 * @throws DBError
197
	 */
198
	protected function initConnection( $lockDb, IDatabase $db ) {
199
	}
200
201
	/**
202
	 * Checks if the DB has not recently had connection/query errors.
203
	 * This just avoids wasting time on doomed connection attempts.
204
	 *
205
	 * @param string $lockDb
206
	 * @return bool
207
	 */
208
	protected function cacheCheckFailures( $lockDb ) {
209
		return ( $this->statusCache && $this->safeDelay > 0 )
210
			? !$this->statusCache->get( $this->getMissKey( $lockDb ) )
211
			: true;
212
	}
213
214
	/**
215
	 * Log a lock request failure to the cache
216
	 *
217
	 * @param string $lockDb
218
	 * @return bool Success
219
	 */
220
	protected function cacheRecordFailure( $lockDb ) {
221
		return ( $this->statusCache && $this->safeDelay > 0 )
222
			? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay )
223
			: true;
224
	}
225
226
	/**
227
	 * Get a cache key for recent query misses for a DB
228
	 *
229
	 * @param string $lockDb
230
	 * @return string
231
	 */
232
	protected function getMissKey( $lockDb ) {
233
		$lockDb = ( $lockDb === 'localDBMaster' ) ? wfWikiID() : $lockDb; // non-relative
234
		return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb );
235
	}
236
237
	/**
238
	 * Make sure remaining locks get cleared for sanity
239
	 */
240
	function __destruct() {
241
		$this->releaseAllLocks();
242
		foreach ( $this->conns as $db ) {
243
			$db->close();
244
		}
245
	}
246
}
247