1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Generator of database load balancing objects. |
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 Database |
22
|
|
|
*/ |
23
|
|
|
|
24
|
|
|
use MediaWiki\MediaWikiServices; |
25
|
|
|
use MediaWiki\Services\DestructibleService; |
26
|
|
|
use Psr\Log\LoggerInterface; |
27
|
|
|
use MediaWiki\Logger\LoggerFactory; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* An interface for generating database load balancers |
31
|
|
|
* @ingroup Database |
32
|
|
|
*/ |
33
|
|
|
abstract class LBFactory implements DestructibleService { |
34
|
|
|
|
35
|
|
|
/** @var ChronologyProtector */ |
36
|
|
|
protected $chronProt; |
37
|
|
|
|
38
|
|
|
/** @var TransactionProfiler */ |
39
|
|
|
protected $trxProfiler; |
40
|
|
|
|
41
|
|
|
/** @var LoggerInterface */ |
42
|
|
|
protected $logger; |
43
|
|
|
|
44
|
|
|
/** @var string|bool Reason all LBs are read-only or false if not */ |
45
|
|
|
protected $readOnlyReason = false; |
46
|
|
|
|
47
|
|
|
const SHUTDOWN_NO_CHRONPROT = 1; // don't save ChronologyProtector positions (for async code) |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf) |
51
|
|
|
* @param array $conf |
52
|
|
|
*/ |
53
|
|
|
public function __construct( array $conf ) { |
54
|
|
View Code Duplication |
if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) { |
55
|
|
|
$this->readOnlyReason = $conf['readOnlyReason']; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
$this->chronProt = $this->newChronologyProtector(); |
59
|
|
|
$this->trxProfiler = Profiler::instance()->getTransactionProfiler(); |
60
|
|
|
$this->logger = LoggerFactory::getInstance( 'DBTransaction' ); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Disables all load balancers. All connections are closed, and any attempt to |
65
|
|
|
* open a new connection will result in a DBAccessError. |
66
|
|
|
* @see LoadBalancer::disable() |
67
|
|
|
*/ |
68
|
|
|
public function destroy() { |
69
|
|
|
$this->shutdown(); |
70
|
|
|
$this->forEachLBCallMethod( 'disable' ); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Disables all access to the load balancer, will cause all database access |
75
|
|
|
* to throw a DBAccessError |
76
|
|
|
*/ |
77
|
|
|
public static function disableBackend() { |
78
|
|
|
MediaWikiServices::disableStorageBackend(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Get an LBFactory instance |
83
|
|
|
* |
84
|
|
|
* @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead. |
85
|
|
|
* |
86
|
|
|
* @return LBFactory |
87
|
|
|
*/ |
88
|
|
|
public static function singleton() { |
89
|
|
|
return MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Returns the LBFactory class to use and the load balancer configuration. |
94
|
|
|
* |
95
|
|
|
* @todo instead of this, use a ServiceContainer for managing the different implementations. |
96
|
|
|
* |
97
|
|
|
* @param array $config (e.g. $wgLBFactoryConf) |
98
|
|
|
* @return string Class name |
99
|
|
|
*/ |
100
|
|
|
public static function getLBFactoryClass( array $config ) { |
101
|
|
|
// For configuration backward compatibility after removing |
102
|
|
|
// underscores from class names in MediaWiki 1.23. |
103
|
|
|
$bcClasses = [ |
104
|
|
|
'LBFactory_Simple' => 'LBFactorySimple', |
105
|
|
|
'LBFactory_Single' => 'LBFactorySingle', |
106
|
|
|
'LBFactory_Multi' => 'LBFactoryMulti', |
107
|
|
|
'LBFactory_Fake' => 'LBFactoryFake', |
108
|
|
|
]; |
109
|
|
|
|
110
|
|
|
$class = $config['class']; |
111
|
|
|
|
112
|
|
|
if ( isset( $bcClasses[$class] ) ) { |
113
|
|
|
$class = $bcClasses[$class]; |
114
|
|
|
wfDeprecated( |
115
|
|
|
'$wgLBFactoryConf must be updated. See RELEASE-NOTES for details', |
116
|
|
|
'1.23' |
117
|
|
|
); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return $class; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Shut down, close connections and destroy the cached instance. |
125
|
|
|
* |
126
|
|
|
* @deprecated since 1.27, use LBFactory::destroy() |
127
|
|
|
*/ |
128
|
|
|
public static function destroyInstance() { |
129
|
|
|
self::singleton()->destroy(); |
|
|
|
|
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Create a new load balancer object. The resulting object will be untracked, |
134
|
|
|
* not chronology-protected, and the caller is responsible for cleaning it up. |
135
|
|
|
* |
136
|
|
|
* @param bool|string $wiki Wiki ID, or false for the current wiki |
137
|
|
|
* @return LoadBalancer |
138
|
|
|
*/ |
139
|
|
|
abstract public function newMainLB( $wiki = false ); |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Get a cached (tracked) load balancer object. |
143
|
|
|
* |
144
|
|
|
* @param bool|string $wiki Wiki ID, or false for the current wiki |
145
|
|
|
* @return LoadBalancer |
146
|
|
|
*/ |
147
|
|
|
abstract public function getMainLB( $wiki = false ); |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Create a new load balancer for external storage. The resulting object will be |
151
|
|
|
* untracked, not chronology-protected, and the caller is responsible for |
152
|
|
|
* cleaning it up. |
153
|
|
|
* |
154
|
|
|
* @param string $cluster External storage cluster, or false for core |
155
|
|
|
* @param bool|string $wiki Wiki ID, or false for the current wiki |
156
|
|
|
* @return LoadBalancer |
157
|
|
|
*/ |
158
|
|
|
abstract protected function newExternalLB( $cluster, $wiki = false ); |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Get a cached (tracked) load balancer for external storage |
162
|
|
|
* |
163
|
|
|
* @param string $cluster External storage cluster, or false for core |
164
|
|
|
* @param bool|string $wiki Wiki ID, or false for the current wiki |
165
|
|
|
* @return LoadBalancer |
166
|
|
|
*/ |
167
|
|
|
abstract public function &getExternalLB( $cluster, $wiki = false ); |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Execute a function for each tracked load balancer |
171
|
|
|
* The callback is called with the load balancer as the first parameter, |
172
|
|
|
* and $params passed as the subsequent parameters. |
173
|
|
|
* |
174
|
|
|
* @param callable $callback |
175
|
|
|
* @param array $params |
176
|
|
|
*/ |
177
|
|
|
abstract public function forEachLB( $callback, array $params = [] ); |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Prepare all tracked load balancers for shutdown |
181
|
|
|
* @param integer $flags Supports SHUTDOWN_* flags |
182
|
|
|
* STUB |
183
|
|
|
*/ |
184
|
|
|
public function shutdown( $flags = 0 ) { |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Call a method of each tracked load balancer |
189
|
|
|
* |
190
|
|
|
* @param string $methodName |
191
|
|
|
* @param array $args |
192
|
|
|
*/ |
193
|
|
|
private function forEachLBCallMethod( $methodName, array $args = [] ) { |
194
|
|
|
$this->forEachLB( |
195
|
|
|
function ( LoadBalancer $loadBalancer, $methodName, array $args ) { |
196
|
|
|
call_user_func_array( [ $loadBalancer, $methodName ], $args ); |
197
|
|
|
}, |
198
|
|
|
[ $methodName, $args ] |
199
|
|
|
); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Commit on all connections. Done for two reasons: |
204
|
|
|
* 1. To commit changes to the masters. |
205
|
|
|
* 2. To release the snapshot on all connections, master and slave. |
206
|
|
|
* @param string $fname Caller name |
207
|
|
|
*/ |
208
|
|
|
public function commitAll( $fname = __METHOD__ ) { |
209
|
|
|
$this->logMultiDbTransaction(); |
210
|
|
|
|
211
|
|
|
$start = microtime( true ); |
212
|
|
|
$this->forEachLBCallMethod( 'commitAll', [ $fname ] ); |
213
|
|
|
$timeMs = 1000 * ( microtime( true ) - $start ); |
214
|
|
|
|
215
|
|
|
RequestContext::getMain()->getStats()->timing( "db.commit-all", $timeMs ); |
|
|
|
|
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Commit changes on all master connections |
220
|
|
|
* @param string $fname Caller name |
221
|
|
|
* @param array $options Options map: |
222
|
|
|
* - maxWriteDuration: abort if more than this much time was spent in write queries |
223
|
|
|
*/ |
224
|
|
|
public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) { |
225
|
|
|
$limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0; |
226
|
|
|
|
227
|
|
|
$this->logMultiDbTransaction(); |
228
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( $limit ) { |
229
|
|
|
$lb->forEachOpenConnection( function ( IDatabase $db ) use ( $limit ) { |
230
|
|
|
$time = $db->pendingWriteQueryDuration(); |
231
|
|
|
if ( $limit > 0 && $time > $limit ) { |
232
|
|
|
throw new DBTransactionError( |
233
|
|
|
$db, |
234
|
|
|
wfMessage( 'transaction-duration-limit-exceeded', $time, $limit )->text() |
235
|
|
|
); |
236
|
|
|
} |
237
|
|
|
} ); |
238
|
|
|
} ); |
239
|
|
|
|
240
|
|
|
$this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] ); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Rollback changes on all master connections |
245
|
|
|
* @param string $fname Caller name |
246
|
|
|
* @since 1.23 |
247
|
|
|
*/ |
248
|
|
|
public function rollbackMasterChanges( $fname = __METHOD__ ) { |
249
|
|
|
$this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] ); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Log query info if multi DB transactions are going to be committed now |
254
|
|
|
*/ |
255
|
|
|
private function logMultiDbTransaction() { |
256
|
|
|
$callersByDB = []; |
257
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( &$callersByDB ) { |
258
|
|
|
$masterName = $lb->getServerName( $lb->getWriterIndex() ); |
259
|
|
|
$callers = $lb->pendingMasterChangeCallers(); |
260
|
|
|
if ( $callers ) { |
261
|
|
|
$callersByDB[$masterName] = $callers; |
262
|
|
|
} |
263
|
|
|
} ); |
264
|
|
|
|
265
|
|
|
if ( count( $callersByDB ) >= 2 ) { |
266
|
|
|
$dbs = implode( ', ', array_keys( $callersByDB ) ); |
267
|
|
|
$msg = "Multi-DB transaction [{$dbs}]:\n"; |
268
|
|
|
foreach ( $callersByDB as $db => $callers ) { |
269
|
|
|
$msg .= "$db: " . implode( '; ', $callers ) . "\n"; |
270
|
|
|
} |
271
|
|
|
$this->logger->info( $msg ); |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Determine if any master connection has pending changes |
277
|
|
|
* @return bool |
278
|
|
|
* @since 1.23 |
279
|
|
|
*/ |
280
|
|
|
public function hasMasterChanges() { |
281
|
|
|
$ret = false; |
282
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) { |
283
|
|
|
$ret = $ret || $lb->hasMasterChanges(); |
284
|
|
|
} ); |
285
|
|
|
|
286
|
|
|
return $ret; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Detemine if any lagged slave connection was used |
291
|
|
|
* @since 1.27 |
292
|
|
|
* @return bool |
293
|
|
|
*/ |
294
|
|
|
public function laggedSlaveUsed() { |
295
|
|
|
$ret = false; |
296
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) { |
297
|
|
|
$ret = $ret || $lb->laggedSlaveUsed(); |
298
|
|
|
} ); |
299
|
|
|
|
300
|
|
|
return $ret; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Determine if any master connection has pending/written changes from this request |
305
|
|
|
* @return bool |
306
|
|
|
* @since 1.27 |
307
|
|
|
*/ |
308
|
|
|
public function hasOrMadeRecentMasterChanges() { |
309
|
|
|
$ret = false; |
310
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) { |
311
|
|
|
$ret = $ret || $lb->hasOrMadeRecentMasterChanges(); |
312
|
|
|
} ); |
313
|
|
|
return $ret; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Waits for the slave DBs to catch up to the current master position |
318
|
|
|
* |
319
|
|
|
* Use this when updating very large numbers of rows, as in maintenance scripts, |
320
|
|
|
* to avoid causing too much lag. Of course, this is a no-op if there are no slaves. |
321
|
|
|
* |
322
|
|
|
* By default this waits on all DB clusters actually used in this request. |
323
|
|
|
* This makes sense when lag being waiting on is caused by the code that does this check. |
324
|
|
|
* In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters |
325
|
|
|
* that were not changed since the last wait check. To forcefully wait on a specific cluster |
326
|
|
|
* for a given wiki, use the 'wiki' parameter. To forcefully wait on an "external" cluster, |
327
|
|
|
* use the "cluster" parameter. |
328
|
|
|
* |
329
|
|
|
* Never call this function after a large DB write that is *still* in a transaction. |
330
|
|
|
* It only makes sense to call this after the possible lag inducing changes were committed. |
331
|
|
|
* |
332
|
|
|
* @param array $opts Optional fields that include: |
333
|
|
|
* - wiki : wait on the load balancer DBs that handles the given wiki |
334
|
|
|
* - cluster : wait on the given external load balancer DBs |
335
|
|
|
* - timeout : Max wait time. Default: ~60 seconds |
336
|
|
|
* - ifWritesSince: Only wait if writes were done since this UNIX timestamp |
337
|
|
|
* @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster |
338
|
|
|
* @since 1.27 |
339
|
|
|
*/ |
340
|
|
|
public function waitForReplication( array $opts = [] ) { |
341
|
|
|
$opts += [ |
342
|
|
|
'wiki' => false, |
343
|
|
|
'cluster' => false, |
344
|
|
|
'timeout' => 60, |
345
|
|
|
'ifWritesSince' => null |
346
|
|
|
]; |
347
|
|
|
|
348
|
|
|
// Figure out which clusters need to be checked |
349
|
|
|
/** @var LoadBalancer[] $lbs */ |
350
|
|
|
$lbs = []; |
351
|
|
|
if ( $opts['cluster'] !== false ) { |
352
|
|
|
$lbs[] = $this->getExternalLB( $opts['cluster'] ); |
353
|
|
|
} elseif ( $opts['wiki'] !== false ) { |
354
|
|
|
$lbs[] = $this->getMainLB( $opts['wiki'] ); |
355
|
|
|
} else { |
356
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) { |
357
|
|
|
$lbs[] = $lb; |
358
|
|
|
} ); |
359
|
|
|
if ( !$lbs ) { |
360
|
|
|
return; // nothing actually used |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
// Get all the master positions of applicable DBs right now. |
365
|
|
|
// This can be faster since waiting on one cluster reduces the |
366
|
|
|
// time needed to wait on the next clusters. |
367
|
|
|
$masterPositions = array_fill( 0, count( $lbs ), false ); |
368
|
|
|
foreach ( $lbs as $i => $lb ) { |
369
|
|
|
if ( $lb->getServerCount() <= 1 ) { |
370
|
|
|
// Bug 27975 - Don't try to wait for slaves if there are none |
371
|
|
|
// Prevents permission error when getting master position |
372
|
|
|
continue; |
373
|
|
|
} elseif ( $opts['ifWritesSince'] |
374
|
|
|
&& $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince'] |
375
|
|
|
) { |
376
|
|
|
continue; // no writes since the last wait |
377
|
|
|
} |
378
|
|
|
$masterPositions[$i] = $lb->getMasterPos(); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
$failed = []; |
382
|
|
|
foreach ( $lbs as $i => $lb ) { |
383
|
|
|
if ( $masterPositions[$i] ) { |
384
|
|
|
// The DBMS may not support getMasterPos() or the whole |
385
|
|
|
// load balancer might be fake (e.g. $wgAllDBsAreLocalhost). |
386
|
|
|
if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) { |
387
|
|
|
$failed[] = $lb->getServerName( $lb->getWriterIndex() ); |
388
|
|
|
} |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
if ( $failed ) { |
393
|
|
|
throw new DBReplicationWaitError( |
394
|
|
|
"Could not wait for slaves to catch up to " . |
395
|
|
|
implode( ', ', $failed ) |
396
|
|
|
); |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
/** |
401
|
|
|
* Disable the ChronologyProtector for all load balancers |
402
|
|
|
* |
403
|
|
|
* This can be called at the start of special API entry points |
404
|
|
|
* |
405
|
|
|
* @since 1.27 |
406
|
|
|
*/ |
407
|
|
|
public function disableChronologyProtection() { |
408
|
|
|
$this->chronProt->setEnabled( false ); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
/** |
412
|
|
|
* @return ChronologyProtector |
413
|
|
|
*/ |
414
|
|
|
protected function newChronologyProtector() { |
415
|
|
|
$request = RequestContext::getMain()->getRequest(); |
416
|
|
|
$chronProt = new ChronologyProtector( |
417
|
|
|
ObjectCache::getMainStashInstance(), |
418
|
|
|
[ |
419
|
|
|
'ip' => $request->getIP(), |
420
|
|
|
'agent' => $request->getHeader( 'User-Agent' ) |
421
|
|
|
] |
422
|
|
|
); |
423
|
|
|
if ( PHP_SAPI === 'cli' ) { |
424
|
|
|
$chronProt->setEnabled( false ); |
425
|
|
|
} elseif ( $request->getHeader( 'ChronologyProtection' ) === 'false' ) { |
426
|
|
|
// Request opted out of using position wait logic. This is useful for requests |
427
|
|
|
// done by the job queue or background ETL that do not have a meaningful session. |
428
|
|
|
$chronProt->setWaitEnabled( false ); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
return $chronProt; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* @param ChronologyProtector $cp |
436
|
|
|
*/ |
437
|
|
|
protected function shutdownChronologyProtector( ChronologyProtector $cp ) { |
438
|
|
|
// Get all the master positions needed |
439
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( $cp ) { |
440
|
|
|
$cp->shutdownLB( $lb ); |
441
|
|
|
} ); |
442
|
|
|
// Write them to the stash |
443
|
|
|
$unsavedPositions = $cp->shutdown(); |
444
|
|
|
// If the positions failed to write to the stash, at least wait on local datacenter |
445
|
|
|
// slaves to catch up before responding. Even if there are several DCs, this increases |
446
|
|
|
// the chance that the user will see their own changes immediately afterwards. As long |
447
|
|
|
// as the sticky DC cookie applies (same domain), this is not even an issue. |
448
|
|
|
$this->forEachLB( function ( LoadBalancer $lb ) use ( $unsavedPositions ) { |
449
|
|
|
$masterName = $lb->getServerName( $lb->getWriterIndex() ); |
450
|
|
|
if ( isset( $unsavedPositions[$masterName] ) ) { |
451
|
|
|
$lb->waitForAll( $unsavedPositions[$masterName] ); |
452
|
|
|
} |
453
|
|
|
} ); |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
/** |
457
|
|
|
* Close all open database connections on all open load balancers. |
458
|
|
|
* @since 1.28 |
459
|
|
|
*/ |
460
|
|
|
public function closeAll() { |
461
|
|
|
$this->forEachLBCallMethod( 'closeAll', [] ); |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Exception class for attempted DB access |
468
|
|
|
*/ |
469
|
|
|
class DBAccessError extends MWException { |
470
|
|
|
public function __construct() { |
471
|
|
|
parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " . |
472
|
|
|
"This is not allowed, because database access has been disabled." ); |
473
|
|
|
} |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Exception class for replica DB wait timeouts |
478
|
|
|
*/ |
479
|
|
|
class DBReplicationWaitError extends Exception { |
480
|
|
|
} |
481
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.