MediaWiki
REL1_24
|
00001 <?php 00025 $optionsWithArgs = RecompressTracked::getOptionsWithArgs(); 00026 require __DIR__ . '/../commandLine.inc'; 00027 00028 if ( count( $args ) < 1 ) { 00029 echo "Usage: php recompressTracked.php [options] <cluster> [... <cluster>...] 00030 Moves blobs indexed by trackBlobs.php to a specified list of destination clusters, 00031 and recompresses them in the process. Restartable. 00032 00033 Options: 00034 --procs <procs> Set the number of child processes (default 1) 00035 --copy-only Copy only, do not update the text table. Restart 00036 without this option to complete. 00037 --debug-log <file> Log debugging data to the specified file 00038 --info-log <file> Log progress messages to the specified file 00039 --critical-log <file> Log error messages to the specified file 00040 "; 00041 exit( 1 ); 00042 } 00043 00044 $job = RecompressTracked::newFromCommandLine( $args, $options ); 00045 $job->execute(); 00046 00053 class RecompressTracked { 00054 public $destClusters; 00055 public $batchSize = 1000; 00056 public $orphanBatchSize = 1000; 00057 public $reportingInterval = 10; 00058 public $numProcs = 1; 00059 public $useDiff, $pageBlobClass, $orphanBlobClass; 00060 public $slavePipes, $slaveProcs, $prevSlaveId; 00061 public $copyOnly = false; 00062 public $isChild = false; 00063 public $slaveId = false; 00064 public $noCount = false; 00065 public $debugLog, $infoLog, $criticalLog; 00066 public $store; 00067 00068 private static $optionsWithArgs = array( 00069 'procs', 00070 'slave-id', 00071 'debug-log', 00072 'info-log', 00073 'critical-log' 00074 ); 00075 00076 private static $cmdLineOptionMap = array( 00077 'no-count' => 'noCount', 00078 'procs' => 'numProcs', 00079 'copy-only' => 'copyOnly', 00080 'child' => 'isChild', 00081 'slave-id' => 'slaveId', 00082 'debug-log' => 'debugLog', 00083 'info-log' => 'infoLog', 00084 'critical-log' => 'criticalLog', 00085 ); 00086 00087 static function getOptionsWithArgs() { 00088 return self::$optionsWithArgs; 00089 } 00090 00091 static function newFromCommandLine( $args, $options ) { 00092 $jobOptions = array( 'destClusters' => $args ); 00093 foreach ( self::$cmdLineOptionMap as $cmdOption => $classOption ) { 00094 if ( isset( $options[$cmdOption] ) ) { 00095 $jobOptions[$classOption] = $options[$cmdOption]; 00096 } 00097 } 00098 00099 return new self( $jobOptions ); 00100 } 00101 00102 function __construct( $options ) { 00103 foreach ( $options as $name => $value ) { 00104 $this->$name = $value; 00105 } 00106 $this->store = new ExternalStoreDB; 00107 if ( !$this->isChild ) { 00108 $GLOBALS['wgDebugLogPrefix'] = "RCT M: "; 00109 } elseif ( $this->slaveId !== false ) { 00110 $GLOBALS['wgDebugLogPrefix'] = "RCT {$this->slaveId}: "; 00111 } 00112 $this->useDiff = function_exists( 'xdiff_string_bdiff' ); 00113 $this->pageBlobClass = $this->useDiff ? 'DiffHistoryBlob' : 'ConcatenatedGzipHistoryBlob'; 00114 $this->orphanBlobClass = 'ConcatenatedGzipHistoryBlob'; 00115 } 00116 00117 function debug( $msg ) { 00118 wfDebug( "$msg\n" ); 00119 if ( $this->debugLog ) { 00120 $this->logToFile( $msg, $this->debugLog ); 00121 } 00122 } 00123 00124 function info( $msg ) { 00125 echo "$msg\n"; 00126 if ( $this->infoLog ) { 00127 $this->logToFile( $msg, $this->infoLog ); 00128 } 00129 } 00130 00131 function critical( $msg ) { 00132 echo "$msg\n"; 00133 if ( $this->criticalLog ) { 00134 $this->logToFile( $msg, $this->criticalLog ); 00135 } 00136 } 00137 00138 function logToFile( $msg, $file ) { 00139 $header = '[' . date( 'd\TH:i:s' ) . '] ' . wfHostname() . ' ' . posix_getpid(); 00140 if ( $this->slaveId !== false ) { 00141 $header .= "({$this->slaveId})"; 00142 } 00143 $header .= ' ' . wfWikiID(); 00144 wfErrorLog( sprintf( "%-50s %s\n", $header, $msg ), $file ); 00145 } 00146 00152 function syncDBs() { 00153 $dbw = wfGetDB( DB_MASTER ); 00154 $dbr = wfGetDB( DB_SLAVE ); 00155 $pos = $dbw->getMasterPos(); 00156 $dbr->masterPosWait( $pos, 100000 ); 00157 } 00158 00162 function execute() { 00163 if ( $this->isChild ) { 00164 $this->executeChild(); 00165 } else { 00166 $this->executeParent(); 00167 } 00168 } 00169 00173 function executeParent() { 00174 if ( !$this->checkTrackingTable() ) { 00175 return; 00176 } 00177 00178 $this->syncDBs(); 00179 $this->startSlaveProcs(); 00180 $this->doAllPages(); 00181 $this->doAllOrphans(); 00182 $this->killSlaveProcs(); 00183 } 00184 00189 function checkTrackingTable() { 00190 $dbr = wfGetDB( DB_SLAVE ); 00191 if ( !$dbr->tableExists( 'blob_tracking' ) ) { 00192 $this->critical( "Error: blob_tracking table does not exist" ); 00193 00194 return false; 00195 } 00196 $row = $dbr->selectRow( 'blob_tracking', '*', false, __METHOD__ ); 00197 if ( !$row ) { 00198 $this->info( "Warning: blob_tracking table contains no rows, skipping this wiki." ); 00199 00200 return false; 00201 } 00202 00203 return true; 00204 } 00205 00212 function startSlaveProcs() { 00213 $cmd = 'php ' . wfEscapeShellArg( __FILE__ ); 00214 foreach ( self::$cmdLineOptionMap as $cmdOption => $classOption ) { 00215 if ( $cmdOption == 'slave-id' ) { 00216 continue; 00217 } elseif ( in_array( $cmdOption, self::$optionsWithArgs ) && isset( $this->$classOption ) ) { 00218 $cmd .= " --$cmdOption " . wfEscapeShellArg( $this->$classOption ); 00219 } elseif ( $this->$classOption ) { 00220 $cmd .= " --$cmdOption"; 00221 } 00222 } 00223 $cmd .= ' --child' . 00224 ' --wiki ' . wfEscapeShellArg( wfWikiID() ) . 00225 ' ' . call_user_func_array( 'wfEscapeShellArg', $this->destClusters ); 00226 00227 $this->slavePipes = $this->slaveProcs = array(); 00228 for ( $i = 0; $i < $this->numProcs; $i++ ) { 00229 $pipes = false; 00230 $spec = array( 00231 array( 'pipe', 'r' ), 00232 array( 'file', 'php://stdout', 'w' ), 00233 array( 'file', 'php://stderr', 'w' ) 00234 ); 00235 wfSuppressWarnings(); 00236 $proc = proc_open( "$cmd --slave-id $i", $spec, $pipes ); 00237 wfRestoreWarnings(); 00238 if ( !$proc ) { 00239 $this->critical( "Error opening slave process: $cmd" ); 00240 exit( 1 ); 00241 } 00242 $this->slaveProcs[$i] = $proc; 00243 $this->slavePipes[$i] = $pipes[0]; 00244 } 00245 $this->prevSlaveId = -1; 00246 } 00247 00251 function killSlaveProcs() { 00252 $this->info( "Waiting for slave processes to finish..." ); 00253 for ( $i = 0; $i < $this->numProcs; $i++ ) { 00254 $this->dispatchToSlave( $i, 'quit' ); 00255 } 00256 for ( $i = 0; $i < $this->numProcs; $i++ ) { 00257 $status = proc_close( $this->slaveProcs[$i] ); 00258 if ( $status ) { 00259 $this->critical( "Warning: child #$i exited with status $status" ); 00260 } 00261 } 00262 $this->info( "Done." ); 00263 } 00264 00269 function dispatch( /*...*/ ) { 00270 $args = func_get_args(); 00271 $pipes = $this->slavePipes; 00272 $numPipes = stream_select( $x = array(), $pipes, $y = array(), 3600 ); 00273 if ( !$numPipes ) { 00274 $this->critical( "Error waiting to write to slaves. Aborting" ); 00275 exit( 1 ); 00276 } 00277 for ( $i = 0; $i < $this->numProcs; $i++ ) { 00278 $slaveId = ( $i + $this->prevSlaveId + 1 ) % $this->numProcs; 00279 if ( isset( $pipes[$slaveId] ) ) { 00280 $this->prevSlaveId = $slaveId; 00281 $this->dispatchToSlave( $slaveId, $args ); 00282 00283 return; 00284 } 00285 } 00286 $this->critical( "Unreachable" ); 00287 exit( 1 ); 00288 } 00289 00295 function dispatchToSlave( $slaveId, $args ) { 00296 $args = (array)$args; 00297 $cmd = implode( ' ', $args ); 00298 fwrite( $this->slavePipes[$slaveId], "$cmd\n" ); 00299 } 00300 00304 function doAllPages() { 00305 $dbr = wfGetDB( DB_SLAVE ); 00306 $i = 0; 00307 $startId = 0; 00308 if ( $this->noCount ) { 00309 $numPages = '[unknown]'; 00310 } else { 00311 $numPages = $dbr->selectField( 'blob_tracking', 00312 'COUNT(DISTINCT bt_page)', 00313 # A condition is required so that this query uses the index 00314 array( 'bt_moved' => 0 ), 00315 __METHOD__ 00316 ); 00317 } 00318 if ( $this->copyOnly ) { 00319 $this->info( "Copying pages..." ); 00320 } else { 00321 $this->info( "Moving pages..." ); 00322 } 00323 while ( true ) { 00324 $res = $dbr->select( 'blob_tracking', 00325 array( 'bt_page' ), 00326 array( 00327 'bt_moved' => 0, 00328 'bt_page > ' . $dbr->addQuotes( $startId ) 00329 ), 00330 __METHOD__, 00331 array( 00332 'DISTINCT', 00333 'ORDER BY' => 'bt_page', 00334 'LIMIT' => $this->batchSize, 00335 ) 00336 ); 00337 if ( !$res->numRows() ) { 00338 break; 00339 } 00340 foreach ( $res as $row ) { 00341 $this->dispatch( 'doPage', $row->bt_page ); 00342 $i++; 00343 } 00344 $startId = $row->bt_page; 00345 $this->report( 'pages', $i, $numPages ); 00346 } 00347 $this->report( 'pages', $i, $numPages ); 00348 if ( $this->copyOnly ) { 00349 $this->info( "All page copies queued." ); 00350 } else { 00351 $this->info( "All page moves queued." ); 00352 } 00353 } 00354 00361 function report( $label, $current, $end ) { 00362 $this->numBatches++; 00363 if ( $current == $end || $this->numBatches >= $this->reportingInterval ) { 00364 $this->numBatches = 0; 00365 $this->info( "$label: $current / $end" ); 00366 $this->waitForSlaves(); 00367 } 00368 } 00369 00373 function doAllOrphans() { 00374 $dbr = wfGetDB( DB_SLAVE ); 00375 $startId = 0; 00376 $i = 0; 00377 if ( $this->noCount ) { 00378 $numOrphans = '[unknown]'; 00379 } else { 00380 $numOrphans = $dbr->selectField( 'blob_tracking', 00381 'COUNT(DISTINCT bt_text_id)', 00382 array( 'bt_moved' => 0, 'bt_page' => 0 ), 00383 __METHOD__ ); 00384 if ( !$numOrphans ) { 00385 return; 00386 } 00387 } 00388 if ( $this->copyOnly ) { 00389 $this->info( "Copying orphans..." ); 00390 } else { 00391 $this->info( "Moving orphans..." ); 00392 } 00393 00394 while ( true ) { 00395 $res = $dbr->select( 'blob_tracking', 00396 array( 'bt_text_id' ), 00397 array( 00398 'bt_moved' => 0, 00399 'bt_page' => 0, 00400 'bt_text_id > ' . $dbr->addQuotes( $startId ) 00401 ), 00402 __METHOD__, 00403 array( 00404 'DISTINCT', 00405 'ORDER BY' => 'bt_text_id', 00406 'LIMIT' => $this->batchSize 00407 ) 00408 ); 00409 if ( !$res->numRows() ) { 00410 break; 00411 } 00412 $ids = array(); 00413 foreach ( $res as $row ) { 00414 $ids[] = $row->bt_text_id; 00415 $i++; 00416 } 00417 // Need to send enough orphan IDs to the child at a time to fill a blob, 00418 // so orphanBatchSize needs to be at least ~100. 00419 // batchSize can be smaller or larger. 00420 while ( count( $ids ) > $this->orphanBatchSize ) { 00421 $args = array_slice( $ids, 0, $this->orphanBatchSize ); 00422 $ids = array_slice( $ids, $this->orphanBatchSize ); 00423 array_unshift( $args, 'doOrphanList' ); 00424 call_user_func_array( array( $this, 'dispatch' ), $args ); 00425 } 00426 if ( count( $ids ) ) { 00427 $args = $ids; 00428 array_unshift( $args, 'doOrphanList' ); 00429 call_user_func_array( array( $this, 'dispatch' ), $args ); 00430 } 00431 00432 $startId = $row->bt_text_id; 00433 $this->report( 'orphans', $i, $numOrphans ); 00434 } 00435 $this->report( 'orphans', $i, $numOrphans ); 00436 $this->info( "All orphans queued." ); 00437 } 00438 00442 function executeChild() { 00443 $this->debug( 'starting' ); 00444 $this->syncDBs(); 00445 00446 while ( !feof( STDIN ) ) { 00447 $line = rtrim( fgets( STDIN ) ); 00448 if ( $line == '' ) { 00449 continue; 00450 } 00451 $this->debug( $line ); 00452 $args = explode( ' ', $line ); 00453 $cmd = array_shift( $args ); 00454 switch ( $cmd ) { 00455 case 'doPage': 00456 $this->doPage( intval( $args[0] ) ); 00457 break; 00458 case 'doOrphanList': 00459 $this->doOrphanList( array_map( 'intval', $args ) ); 00460 break; 00461 case 'quit': 00462 return; 00463 } 00464 $this->waitForSlaves(); 00465 } 00466 } 00467 00473 function doPage( $pageId ) { 00474 $title = Title::newFromId( $pageId ); 00475 if ( $title ) { 00476 $titleText = $title->getPrefixedText(); 00477 } else { 00478 $titleText = '[deleted]'; 00479 } 00480 $dbr = wfGetDB( DB_SLAVE ); 00481 00482 // Finish any incomplete transactions 00483 if ( !$this->copyOnly ) { 00484 $this->finishIncompleteMoves( array( 'bt_page' => $pageId ) ); 00485 $this->syncDBs(); 00486 } 00487 00488 $startId = 0; 00489 $trx = new CgzCopyTransaction( $this, $this->pageBlobClass ); 00490 00491 while ( true ) { 00492 $res = $dbr->select( 00493 array( 'blob_tracking', 'text' ), 00494 '*', 00495 array( 00496 'bt_page' => $pageId, 00497 'bt_text_id > ' . $dbr->addQuotes( $startId ), 00498 'bt_moved' => 0, 00499 'bt_new_url IS NULL', 00500 'bt_text_id=old_id', 00501 ), 00502 __METHOD__, 00503 array( 00504 'ORDER BY' => 'bt_text_id', 00505 'LIMIT' => $this->batchSize 00506 ) 00507 ); 00508 if ( !$res->numRows() ) { 00509 break; 00510 } 00511 00512 $lastTextId = 0; 00513 foreach ( $res as $row ) { 00514 if ( $lastTextId == $row->bt_text_id ) { 00515 // Duplicate (null edit) 00516 continue; 00517 } 00518 $lastTextId = $row->bt_text_id; 00519 // Load the text 00520 $text = Revision::getRevisionText( $row ); 00521 if ( $text === false ) { 00522 $this->critical( "Error loading {$row->bt_rev_id}/{$row->bt_text_id}" ); 00523 continue; 00524 } 00525 00526 // Queue it 00527 if ( !$trx->addItem( $text, $row->bt_text_id ) ) { 00528 $this->debug( "$titleText: committing blob with " . $trx->getSize() . " items" ); 00529 $trx->commit(); 00530 $trx = new CgzCopyTransaction( $this, $this->pageBlobClass ); 00531 $this->waitForSlaves(); 00532 } 00533 } 00534 $startId = $row->bt_text_id; 00535 } 00536 00537 $this->debug( "$titleText: committing blob with " . $trx->getSize() . " items" ); 00538 $trx->commit(); 00539 } 00540 00554 function moveTextRow( $textId, $url ) { 00555 if ( $this->copyOnly ) { 00556 $this->critical( "Internal error: can't call moveTextRow() in --copy-only mode" ); 00557 exit( 1 ); 00558 } 00559 $dbw = wfGetDB( DB_MASTER ); 00560 $dbw->begin( __METHOD__ ); 00561 $dbw->update( 'text', 00562 array( // set 00563 'old_text' => $url, 00564 'old_flags' => 'external,utf-8', 00565 ), 00566 array( // where 00567 'old_id' => $textId 00568 ), 00569 __METHOD__ 00570 ); 00571 $dbw->update( 'blob_tracking', 00572 array( 'bt_moved' => 1 ), 00573 array( 'bt_text_id' => $textId ), 00574 __METHOD__ 00575 ); 00576 $dbw->commit( __METHOD__ ); 00577 } 00578 00589 function finishIncompleteMoves( $conds ) { 00590 $dbr = wfGetDB( DB_SLAVE ); 00591 00592 $startId = 0; 00593 $conds = array_merge( $conds, array( 00594 'bt_moved' => 0, 00595 'bt_new_url IS NOT NULL' 00596 ) ); 00597 while ( true ) { 00598 $res = $dbr->select( 'blob_tracking', 00599 '*', 00600 array_merge( $conds, array( 'bt_text_id > ' . $dbr->addQuotes( $startId ) ) ), 00601 __METHOD__, 00602 array( 00603 'ORDER BY' => 'bt_text_id', 00604 'LIMIT' => $this->batchSize, 00605 ) 00606 ); 00607 if ( !$res->numRows() ) { 00608 break; 00609 } 00610 $this->debug( 'Incomplete: ' . $res->numRows() . ' rows' ); 00611 foreach ( $res as $row ) { 00612 $this->moveTextRow( $row->bt_text_id, $row->bt_new_url ); 00613 if ( $row->bt_text_id % 10 == 0 ) { 00614 $this->waitForSlaves(); 00615 } 00616 } 00617 $startId = $row->bt_text_id; 00618 } 00619 } 00620 00625 function getTargetCluster() { 00626 $cluster = next( $this->destClusters ); 00627 if ( $cluster === false ) { 00628 $cluster = reset( $this->destClusters ); 00629 } 00630 00631 return $cluster; 00632 } 00633 00639 function getExtDB( $cluster ) { 00640 $lb = wfGetLBFactory()->getExternalLB( $cluster ); 00641 00642 return $lb->getConnection( DB_MASTER ); 00643 } 00644 00650 function doOrphanList( $textIds ) { 00651 // Finish incomplete moves 00652 if ( !$this->copyOnly ) { 00653 $this->finishIncompleteMoves( array( 'bt_text_id' => $textIds ) ); 00654 $this->syncDBs(); 00655 } 00656 00657 $trx = new CgzCopyTransaction( $this, $this->orphanBlobClass ); 00658 00659 $res = wfGetDB( DB_SLAVE )->select( 00660 array( 'text', 'blob_tracking' ), 00661 array( 'old_id', 'old_text', 'old_flags' ), 00662 array( 00663 'old_id' => $textIds, 00664 'bt_text_id=old_id', 00665 'bt_moved' => 0, 00666 ), 00667 __METHOD__, 00668 array( 'DISTINCT' ) 00669 ); 00670 00671 foreach ( $res as $row ) { 00672 $text = Revision::getRevisionText( $row ); 00673 if ( $text === false ) { 00674 $this->critical( "Error: cannot load revision text for old_id={$row->old_id}" ); 00675 continue; 00676 } 00677 00678 if ( !$trx->addItem( $text, $row->old_id ) ) { 00679 $this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" ); 00680 $trx->commit(); 00681 $trx = new CgzCopyTransaction( $this, $this->orphanBlobClass ); 00682 $this->waitForSlaves(); 00683 } 00684 } 00685 $this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" ); 00686 $trx->commit(); 00687 } 00688 00692 function waitForSlaves() { 00693 $lb = wfGetLB(); 00694 while ( true ) { 00695 list( $host, $maxLag ) = $lb->getMaxLag(); 00696 if ( $maxLag < 2 ) { 00697 break; 00698 } 00699 sleep( 5 ); 00700 } 00701 } 00702 } 00703 00707 class CgzCopyTransaction { 00708 public $parent; 00709 public $blobClass; 00710 public $cgz; 00711 public $referrers; 00712 00718 function __construct( $parent, $blobClass ) { 00719 $this->blobClass = $blobClass; 00720 $this->cgz = false; 00721 $this->texts = array(); 00722 $this->parent = $parent; 00723 } 00724 00732 function addItem( $text, $textId ) { 00733 if ( !$this->cgz ) { 00734 $class = $this->blobClass; 00735 $this->cgz = new $class; 00736 } 00737 $hash = $this->cgz->addItem( $text ); 00738 $this->referrers[$textId] = $hash; 00739 $this->texts[$textId] = $text; 00740 00741 return $this->cgz->isHappy(); 00742 } 00743 00744 function getSize() { 00745 return count( $this->texts ); 00746 } 00747 00751 function recompress() { 00752 $class = $this->blobClass; 00753 $this->cgz = new $class; 00754 $this->referrers = array(); 00755 foreach ( $this->texts as $textId => $text ) { 00756 $hash = $this->cgz->addItem( $text ); 00757 $this->referrers[$textId] = $hash; 00758 } 00759 } 00760 00766 function commit() { 00767 $originalCount = count( $this->texts ); 00768 if ( !$originalCount ) { 00769 return; 00770 } 00771 00772 // Check to see if the target text_ids have been moved already. 00773 // 00774 // We originally read from the slave, so this can happen when a single 00775 // text_id is shared between multiple pages. It's rare, but possible 00776 // if a delete/move/undelete cycle splits up a null edit. 00777 // 00778 // We do a locking read to prevent closer-run race conditions. 00779 $dbw = wfGetDB( DB_MASTER ); 00780 $dbw->begin( __METHOD__ ); 00781 $res = $dbw->select( 'blob_tracking', 00782 array( 'bt_text_id', 'bt_moved' ), 00783 array( 'bt_text_id' => array_keys( $this->referrers ) ), 00784 __METHOD__, array( 'FOR UPDATE' ) ); 00785 $dirty = false; 00786 foreach ( $res as $row ) { 00787 if ( $row->bt_moved ) { 00788 # This row has already been moved, remove it 00789 $this->parent->debug( "TRX: conflict detected in old_id={$row->bt_text_id}" ); 00790 unset( $this->texts[$row->bt_text_id] ); 00791 $dirty = true; 00792 } 00793 } 00794 00795 // Recompress the blob if necessary 00796 if ( $dirty ) { 00797 if ( !count( $this->texts ) ) { 00798 // All have been moved already 00799 if ( $originalCount > 1 ) { 00800 // This is suspcious, make noise 00801 $this->critical( "Warning: concurrent operation detected, are there two conflicting " . 00802 "processes running, doing the same job?" ); 00803 } 00804 00805 return; 00806 } 00807 $this->recompress(); 00808 } 00809 00810 // Insert the data into the destination cluster 00811 $targetCluster = $this->parent->getTargetCluster(); 00812 $store = $this->parent->store; 00813 $targetDB = $store->getMaster( $targetCluster ); 00814 $targetDB->clearFlag( DBO_TRX ); // we manage the transactions 00815 $targetDB->begin( __METHOD__ ); 00816 $baseUrl = $this->parent->store->store( $targetCluster, serialize( $this->cgz ) ); 00817 00818 // Write the new URLs to the blob_tracking table 00819 foreach ( $this->referrers as $textId => $hash ) { 00820 $url = $baseUrl . '/' . $hash; 00821 $dbw->update( 'blob_tracking', 00822 array( 'bt_new_url' => $url ), 00823 array( 00824 'bt_text_id' => $textId, 00825 'bt_moved' => 0, # Check for concurrent conflicting update 00826 ), 00827 __METHOD__ 00828 ); 00829 } 00830 00831 $targetDB->commit( __METHOD__ ); 00832 // Critical section here: interruption at this point causes blob duplication 00833 // Reversing the order of the commits would cause data loss instead 00834 $dbw->commit( __METHOD__ ); 00835 00836 // Write the new URLs to the text table and set the moved flag 00837 if ( !$this->parent->copyOnly ) { 00838 foreach ( $this->referrers as $textId => $hash ) { 00839 $url = $baseUrl . '/' . $hash; 00840 $this->parent->moveTextRow( $textId, $url ); 00841 } 00842 } 00843 } 00844 }