/**
* The here's-one-I-made-earlier option: do the submission if
- * posted, or display the form with or without funky valiation
+ * posted, or display the form with or without funky validation
* errors
* @return Bool or Status whether submission was successful.
*/
$this->prepareForm();
$result = $this->tryAuthorizedSubmit();
- if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){
+ if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
return $result;
}
return ChangesList::showCharacterDifference( $old, $new );
}
- public static function checkIPAddress( $ip ) {
+ private static function checkIPAddress( $ip ) {
global $wgRequest;
if ( $ip ) {
if ( !IP::isIPAddress( $ip ) ) {
$dbType = strtolower( $dbType );
$class = 'Database' . ucfirst( $dbType );
- if( in_array( $dbType, $canonicalDBTypes ) ) {
+ if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
isset( $p['flags'] ) ? $p['flags'] : 0,
isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
);
- } elseif ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- return new $class( $p );
} else {
return null;
}
*/
final public function createInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
$status = $this->doCreateInternal( $params );
$this->clearCache( array( $params['dst'] ) );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function storeInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-store', $params['dst'] );
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function copyInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function deleteInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doDeleteInternal( $params );
$this->clearCache( array( $params['src'] ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function moveInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function concatenate( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
// Try to lock the source files for the scope of this function
$status->merge( $this->doConcatenate( $params ) );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final protected function doPrepare( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
}
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final protected function doSecure( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
}
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final protected function doClean( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
$filesLockEx = array( $params['dir'] );
$scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // abort
}
}
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function fileExists( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return ( $stat === null ) ? null : (bool)$stat; // null => failure
}
*/
final public function getFileTimestamp( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $stat ? $stat['mtime'] : false;
}
*/
final public function getFileSize( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $stat ? $stat['size'] : false;
}
*/
final public function getFileStat( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return false; // invalid storage path
}
// If we want the latest data, check that this cached
// value was in fact fetched with the latest available data.
if ( !$latest || $this->cache[$path]['stat']['latest'] ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $this->cache[$path]['stat'];
}
}
wfProfileIn( __METHOD__ . '-miss' );
+ wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$stat = $this->doGetFileStat( $params );
+ wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
if ( is_array( $stat ) ) { // don't cache negatives
$this->trimCache(); // limit memory
$this->cache[$path]['stat'] = $stat;
$this->cache[$path]['stat']['latest'] = $latest;
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $stat;
}
*/
public function getFileContents( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$tmpFile = $this->getLocalReference( $params );
if ( !$tmpFile ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return false;
}
wfSuppressWarnings();
$data = file_get_contents( $tmpFile->getPath() );
wfRestoreWarnings();
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $data;
}
*/
final public function getFileSha1Base36( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$path = $params['src'];
if ( isset( $this->cache[$path]['sha1'] ) ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $this->cache[$path]['sha1'];
}
wfProfileIn( __METHOD__ . '-miss' );
+ wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$hash = $this->doGetFileSha1Base36( $params );
+ wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
if ( $hash ) { // don't cache negatives
$this->trimCache(); // limit memory
$this->cache[$path]['sha1'] = $hash;
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $hash;
}
*/
final public function getFileProps( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$fsFile = $this->getLocalReference( $params );
$props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $props;
}
*/
public function getLocalReference( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$path = $params['src'];
if ( isset( $this->expensiveCache[$path]['localRef'] ) ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $this->expensiveCache[$path]['localRef'];
}
$this->trimExpensiveCache(); // limit memory
$this->expensiveCache[$path]['localRef'] = $tmpFile;
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $tmpFile;
}
*/
final public function streamFile( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
$info = $this->getFileStat( $params );
// do nothing; client cache is up to date
} elseif ( $res == StreamFile::READY_STREAM ) {
wfProfileIn( __METHOD__ . '-send' );
+ wfProfileIn( __METHOD__ . '-send-' . $this->name );
$status = $this->doStreamFile( $params );
+ wfProfileOut( __METHOD__ . '-send-' . $this->name );
wfProfileOut( __METHOD__ . '-send' );
} else {
$status->fatal( 'backend-fail-stream', $params['src'] );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
protected function doOperationsInternal( array $ops, array $opts ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
// Build up a list of FileOps...
$scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager::LOCK_UW, $status );
$scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // abort
}
$status->merge( $subStatus );
$status->success = $subStatus->success; // not done in merge()
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
return false;
}
+ /**
+ * @param $key
+ * @param $timeout int
+ * @return bool
+ */
public function lock( $key, $timeout = 0 ) {
/* stub */
return true;
}
+ /**
+ * @param $key
+ * @return bool
+ */
public function unlock( $key ) {
/* stub */
return true;
$this->stats['get_multi'] = 1;
}
$sock_keys = array();
-
+ $socks = array();
foreach ( $keys as $key ) {
$sock = $this->get_sock( $key );
if ( !is_resource( $sock ) ) {
$sock_keys[$sock][] = $key;
}
+ $gather = array();
// Send out the requests
foreach ( $socks as $sock ) {
$cmd = 'get';
return array();
}
+ $ret = array();
while ( true ) {
$res = fgets( $sock );
$ret[] = $res;
$this->_dead_host( $host );
}
+ /**
+ * @param $host
+ */
function _dead_host( $host ) {
$parts = explode( ':', $host );
$ip = $parts[0];
}
$hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
-
if ( $this->_buckets === null ) {
+ $bu = array();
foreach ( $this->_servers as $v ) {
if ( is_array( $v ) ) {
for( $i = 0; $i < $v[1]; $i++ ) {
$this->stats[$cmd] = 1;
}
if ( !$this->_safe_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
- return $this->_dead_sock( $sock );
+ $this->_dead_sock( $sock );
+ return null;
}
$line = fgets( $sock );
}
}
if ( !$this->_safe_fwrite( $sock, "$cmd $key $flags $exp $len\r\n$val\r\n" ) ) {
- return $this->_dead_sock( $sock );
+ $this->_dead_sock( $sock );
+ return false;
}
$line = trim( fgets( $sock ) );
}
if ( !$this->_connect_sock( $sock, $host ) ) {
- return $this->_dead_host( $host );
+ $this->_dead_host( $host );
+ return null;
}
// Do not buffer writes
return $this->_cache_sock[$host];
}
+ /**
+ * @param $str string
+ */
function _debugprint( $str ) {
print( $str );
}
/**
* Original behaviour
+ * @param $f
+ * @param $buf
+ * @param $len bool
* @return int
*/
function _safe_fwrite( $f, $buf, $len = false ) {
/**
* Flush the read buffer of a stream
+ * @param $f Resource
*/
function _flush_read_buffer( $f ) {
if ( !is_resource( $f ) ) {
/**
* Decode an expiry (block, protection, etc) which has come from the DB
- *
+ *
* @FIXME: why are we returnings DBMS-dependent strings???
*
* @param $expiry String: Database expiry String
}
/**
- * It fixes issue with lcfirst for transforming 'I' to 'ı'
+ * It fixes issue with lcfirst for transforming 'I' to 'ı'
*
* @param $string string
*
- * @return string
+ * @return mixed|string
*/
function lcfirst ( $string ) {
if ( substr( $string, 0, 1 ) === 'I' ) {
$dbw->commit( __METHOD__ );
}
- $this->output( 'done!\n', 'msg' );
+ $this->output( "done!\n", 'msg' );
}
}
var $prefetchCountLast = 0;
var $fetchCountLast = 0;
- var $failures = 0;
var $maxFailures = 5;
- var $failedTextRetrievals = 0;
var $maxConsecutiveFailedTextRetrievals = 200;
var $failureTimeout = 5; // Seconds to sleep after db failure
*/
protected $db;
+
+ /**
+ * Drop the database connection $this->db and try to get a new one.
+ *
+ * This function tries to get a /different/ connection if this is
+ * possible. Hence, (if this is possible) it switches to a different
+ * failover upon each call.
+ *
+ * This function resets $this->lb and closes all connections on it.
+ *
+ * @throws MWException
+ */
+ function rotateDb() {
+ // Cleaning up old connections
+ if ( isset( $this->lb ) ) {
+ $this->lb->closeAll();
+ unset( $this->lb );
+ }
+
+ if ( isset( $this->db ) && $this->db->isOpen() ) {
+ throw new MWException( 'DB is set and has not been closed by the Load Balancer' );
+ }
+
+ unset( $this->db );
+
+ // Trying to set up new connection.
+ // We do /not/ retry upon failure, but delegate to encapsulating logic, to avoid
+ // individually retrying at different layers of code.
+
+ // 1. The LoadBalancer.
+ try {
+ $this->lb = wfGetLBFactory()->newMainLB();
+ } catch ( Exception $e ) {
+ throw new MWException( __METHOD__ . " rotating DB failed to obtain new load balancer (" . $e->getMessage() . ")" );
+ }
+
+
+ // 2. The Connection, through the load balancer.
+ try {
+ $this->db = $this->lb->getConnection( DB_SLAVE, 'backup' );
+ } catch ( Exception $e ) {
+ throw new MWException( __METHOD__ . " rotating DB failed to obtain new database (" . $e->getMessage() . ")" );
+ }
+ }
+
+
function initProgress( $history ) {
parent::initProgress();
$this->timeOfCheckpoint = $this->startTime;
$this->initProgress( $this->history );
- $this->db = $this->backupDb();
+ // We are trying to get an initial database connection to avoid that the
+ // first try of this request's first call to getText fails. However, if
+ // obtaining a good DB connection fails it's not a serious issue, as
+ // getText does retry upon failure and can start without having a working
+ // DB connection.
+ try {
+ $this->rotateDb();
+ } catch ( Exception $e ) {
+ // We do not even count this as failure. Just let eventual
+ // watchdogs know.
+ $this->progress( "Getting initial DB connection failed (" .
+ $e->getMessage() . ")" );
+ }
$this->egress = new ExportProgressFilter( $this->sink, $this );
$this->input = $url;
break;
case 'maxtime':
- $this->maxTimeAllowed = intval($val)*60;
+ $this->maxTimeAllowed = intval( $val ) * 60;
break;
case 'checkpointfile':
$this->checkpointFiles[] = $val;
}
function processFileOpt( $val, $param ) {
- $fileURIs = explode(';',$param);
+ $fileURIs = explode( ';', $param );
foreach ( $fileURIs as $URI ) {
switch( $val ) {
case "file":
function finalOptionCheck() {
if ( ( $this->checkpointFiles && ! $this->maxTimeAllowed ) ||
( $this->maxTimeAllowed && !$this->checkpointFiles ) ) {
- throw new MWException("Options checkpointfile and maxtime must be specified together.\n");
+ throw new MWException( "Options checkpointfile and maxtime must be specified together.\n" );
}
- foreach ($this->checkpointFiles as $checkpointFile) {
- $count = substr_count ( $checkpointFile,"%s" );
+ foreach ( $this->checkpointFiles as $checkpointFile ) {
+ $count = substr_count ( $checkpointFile, "%s" );
if ( $count != 2 ) {
- throw new MWException("Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n");
+ throw new MWException( "Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n" );
}
}
if ( $this->checkpointFiles ) {
$filenameList = (array)$this->egress->getFilenames();
if ( count( $filenameList ) != count( $this->checkpointFiles ) ) {
- throw new MWException("One checkpointfile must be specified for each output option, if maxtime is used.\n");
+ throw new MWException( "One checkpointfile must be specified for each output option, if maxtime is used.\n" );
}
}
}
$offset = 0; // for context extraction on error reporting
$bufferSize = 512 * 1024;
do {
- if ($this->checkIfTimeExceeded()) {
+ if ( $this->checkIfTimeExceeded() ) {
$this->setTimeExceeded();
}
$chunk = fread( $input, $bufferSize );
}
$offset += strlen( $chunk );
} while ( $chunk !== false && !feof( $input ) );
- if ($this->maxTimeAllowed) {
+ if ( $this->maxTimeAllowed ) {
$filenameList = (array)$this->egress->getFilenames();
// we wrote some stuff after last checkpoint that needs renamed
- if (file_exists($filenameList[0])) {
+ if ( file_exists( $filenameList[0] ) ) {
$newFilenames = array();
# we might have just written the header and footer and had no
# pages or revisions written... perhaps they were all deleted
# there's no pageID 0 so we use that. the caller is responsible
# for deciding what to do with a file containing only the
# siteinfo information and the mw tags.
- if (! $this->firstPageWritten) {
- $firstPageID = str_pad(0,9,"0",STR_PAD_LEFT);
- $lastPageID = str_pad(0,9,"0",STR_PAD_LEFT);
+ if ( ! $this->firstPageWritten ) {
+ $firstPageID = str_pad( 0, 9, "0", STR_PAD_LEFT );
+ $lastPageID = str_pad( 0, 9, "0", STR_PAD_LEFT );
}
else {
- $firstPageID = str_pad($this->firstPageWritten,9,"0",STR_PAD_LEFT);
- $lastPageID = str_pad($this->lastPageWritten,9,"0",STR_PAD_LEFT);
+ $firstPageID = str_pad( $this->firstPageWritten, 9, "0", STR_PAD_LEFT );
+ $lastPageID = str_pad( $this->lastPageWritten, 9, "0", STR_PAD_LEFT );
}
for ( $i = 0; $i < count( $filenameList ); $i++ ) {
$checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
- $fileinfo = pathinfo($filenameList[$i]);
+ $fileinfo = pathinfo( $filenameList[$i] );
$newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn;
}
$this->egress->closeAndRename( $newFilenames );
return true;
}
+ /**
+ * Tries to get the revision text for a revision id.
+ *
+ * Upon errors, retries (Up to $this->maxFailures tries each call).
+ * If still no good revision get could be found even after this retrying, "" is returned.
+ * If no good revision text could be returned for
+ * $this->maxConsecutiveFailedTextRetrievals consecutive calls to getText, MWException
+ * is thrown.
+ *
+ * @param $id string The revision id to get the text for
+ *
+ * @return string The revision text for $id, or ""
+ * @throws MWException
+ */
function getText( $id ) {
+ $prefetchNotTried = true; // Whether or not we already tried to get the text via prefetch.
+ $text = false; // The candidate for a good text. false if no proper value.
+ $failures = 0; // The number of times, this invocation of getText already failed.
+
+ static $consecutiveFailedTextRetrievals = 0; // The number of times getText failed without
+ // yielding a good text in between.
+
$this->fetchCount++;
- if ( isset( $this->prefetch ) ) {
- $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev );
- if ( $text !== null ) { // Entry missing from prefetch dump
- $dbr = wfGetDB( DB_SLAVE );
+
+ // To allow to simply return on success and do not have to worry about book keeping,
+ // we assume, this fetch works (possible after some retries). Nevertheless, we koop
+ // the old value, so we can restore it, if problems occur (See after the while loop).
+ $oldConsecutiveFailedTextRetrievals = $consecutiveFailedTextRetrievals;
+ $consecutiveFailedTextRetrievals = 0;
+
+ while ( $failures < $this->maxFailures ) {
+
+ // As soon as we found a good text for the $id, we will return immediately.
+ // Hence, if we make it past the try catch block, we know that we did not
+ // find a good text.
+
+ try {
+ // Step 1: Get some text (or reuse from previous iteratuon if checking
+ // for plausibility failed)
+
+ // Trying to get prefetch, if it has not been tried before
+ if ( $text === false && isset( $this->prefetch ) && $prefetchNotTried ) {
+ $prefetchNotTried = false;
+ $tryIsPrefetch = true;
+ $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev );
+ if ( $text === null ) {
+ $text = false;
+ }
+ }
+
+ if ( $text === false ) {
+ // Fallback to asking the database
+ $tryIsPrefetch = false;
+ if ( $this->spawn ) {
+ $text = $this->getTextSpawned( $id );
+ } else {
+ $text = $this->getTextDb( $id );
+ }
+ }
+
+ if ( $text === false ) {
+ throw new MWException( "Generic error while obtaining text for id " . $id );
+ }
+
+ // We received a good candidate for the text of $id via some method
+
+ // Step 2: Checking for plausibility and return the text if it is
+ // plausible
$revID = intval( $this->thisRev );
- $revLength = $dbr->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) );
- // if length of rev text in file doesn't match length in db, we reload
- // this avoids carrying forward broken data from previous xml dumps
- if( strlen( $text ) == $revLength ) {
- $this->prefetchCount++;
+ if ( ! isset( $this->db ) ) {
+ throw new MWException( "No database available" );
+ }
+ $revLength = $this->db->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) );
+ if ( strlen( $text ) == $revLength ) {
+ if ( $tryIsPrefetch ) {
+ $this->prefetchCount++;
+ }
return $text;
}
+
+ $text = false;
+ throw new MWException( "Received text is unplausible for id " . $id );
+
+ } catch ( Exception $e ) {
+ $msg = "getting/checking text " . $id . " failed (" . $e->getMessage() . ")";
+ if ( $failures + 1 < $this->maxFailures ) {
+ $msg .= " (Will retry " . ( $this->maxFailures - $failures - 1 ) . " more times)";
+ }
+ $this->progress( $msg );
}
- }
- return $this->doGetText( $id );
- }
- private function doGetText( $id ) {
- $id = intval( $id );
- $this->failures = 0;
- $ex = new MWException( "Graceful storage failure" );
- while (true) {
- if ( $this->spawn ) {
- if ($this->failures) {
- // we don't know why it failed, could be the child process
- // borked, could be db entry busted, could be db server out to lunch,
- // so cover all bases
+ // Something went wrong; we did not a text that was plausible :(
+ $failures++;
+
+
+ // After backing off for some time, we try to reboot the whole process as
+ // much as possible to not carry over failures from one part to the other
+ // parts
+ sleep( $this->failureTimeout );
+ try {
+ $this->rotateDb();
+ if ( $this->spawn ) {
$this->closeSpawn();
$this->openSpawn();
}
- $text = $this->getTextSpawned( $id );
- } else {
- $text = $this->getTextDbSafe( $id );
- }
- if ( $text === false ) {
- $this->failures++;
- if ( $this->failures > $this->maxFailures) {
- $this->progress( "Failed to retrieve revision text for text id ".
- "$id after $this->maxFailures tries, giving up" );
- // were there so many bad retrievals in a row we want to bail?
- // at some point we have to declare the dump irretrievably broken
- $this->failedTextRetrievals++;
- if ($this->failedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals) {
- throw $ex;
- } else {
- // would be nice to return something better to the caller someday,
- // log what we know about the failure and about the revision
- return "";
- }
- } else {
- $this->progress( "Error $this->failures " .
- "of allowed $this->maxFailures retrieving revision text for text id $id! " .
- "Pausing $this->failureTimeout seconds before retry..." );
- sleep( $this->failureTimeout );
- }
- } else {
- $this->failedTextRetrievals= 0;
- return $text;
+ } catch ( Exception $e ) {
+ $this->progress( "Rebooting getText infrastructure failed (" . $e->getMessage() . ")" .
+ " Trying to continue anyways" );
}
}
- return '';
- }
- /**
- * Fetch a text revision from the database, retrying in case of failure.
- * This may survive some transitory errors by reconnecting, but
- * may not survive a long-term server outage.
- *
- * FIXME: WTF? Why is it using a loop and then returning unconditionally?
- * @param $id int
- * @return bool|string
- */
- private function getTextDbSafe( $id ) {
- while ( true ) {
- try {
- $text = $this->getTextDb( $id );
- } catch ( DBQueryError $ex ) {
- $text = false;
- }
- return $text;
+ // Retirieving a good text for $id failed (at least) maxFailures times.
+ // We abort for this $id.
+
+ // Restoring the consecutive failures, and maybe aborting, if the dump
+ // is too broken.
+ $consecutiveFailedTextRetrievals = $oldConsecutiveFailedTextRetrievals + 1;
+ if ( $consecutiveFailedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals ) {
+ throw new MWException( "Graceful storage failure" );
}
+
+ return "";
}
+
/**
* May throw a database error if, say, the server dies during query.
* @param $id
* @return bool|string
+ * @throws MWException
*/
private function getTextDb( $id ) {
global $wgContLang;
+ if ( ! isset( $this->db ) ) {
+ throw new MWException( __METHOD__ . "No database available" );
+ }
$row = $this->db->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $id ),
$nbytes = intval( $len );
// actual error, not zero-length text
- if ($nbytes < 0 ) return false;
+ if ( $nbytes < 0 ) return false;
$text = "";
$this->buffer = "";
$this->thisRev = "";
} elseif ( $name == 'page' ) {
- if (! $this->firstPageWritten) {
- $this->firstPageWritten = trim($this->thisPage);
+ if ( ! $this->firstPageWritten ) {
+ $this->firstPageWritten = trim( $this->thisPage );
}
- $this->lastPageWritten = trim($this->thisPage);
- if ($this->timeExceeded) {
+ $this->lastPageWritten = trim( $this->thisPage );
+ if ( $this->timeExceeded ) {
$this->egress->writeClosePage( $this->buffer );
// nasty hack, we can't just write the chardata after the
// page tag, it will include leading blanks from the next line
- $this->egress->sink->write("\n");
+ $this->egress->sink->write( "\n" );
$this->buffer = $this->xmlwriterobj->closeStream();
$this->egress->writeCloseStream( $this->buffer );
$filenameList = (array)$this->egress->getFilenames();
$newFilenames = array();
- $firstPageID = str_pad($this->firstPageWritten,9,"0",STR_PAD_LEFT);
- $lastPageID = str_pad($this->lastPageWritten,9,"0",STR_PAD_LEFT);
+ $firstPageID = str_pad( $this->firstPageWritten, 9, "0", STR_PAD_LEFT );
+ $lastPageID = str_pad( $this->lastPageWritten, 9, "0", STR_PAD_LEFT );
for ( $i = 0; $i < count( $filenameList ); $i++ ) {
$checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
- $fileinfo = pathinfo($filenameList[$i]);
+ $fileinfo = pathinfo( $filenameList[$i] );
$newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn;
}
$this->egress->closeRenameAndReopen( $newFilenames );
}
// have to skip the newline left over from closepagetag line of
// end of checkpoint files. nasty hack!!
- if ($this->checkpointJustWritten) {
- if ($data[0] == "\n") {
- $data = substr($data,1);
+ if ( $this->checkpointJustWritten ) {
+ if ( $data[0] == "\n" ) {
+ $data = substr( $data, 1 );
}
$this->checkpointJustWritten = false;
}
'convertDoubleWidth() with the full alphabet and digits'
);
}
-
+
/** @dataProvider provideFormattableTimes */
function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
$this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc );
}
-
+
function provideFormattableTimes() {
return array(
array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ),
array( 176460.55, array(), '2d 1h 1m 1s', 'formatTimePeriod() rounding, recursion, (>48h)' ),
array( 176460.55, array( 'noabbrevs' => true ), '2 days 1 hour 1 minute 1 second', 'formatTimePeriod() rounding, recursion, (>48h)' ),
);
-
+
}
function testTruncate() {
"sprintfDate('$format', '$ts'): $msg"
);
- date_default_timezone_set( $oldTZ );
+ date_default_timezone_set( $oldTZ );
}
function provideSprintfDateSamples() {