/**
* List for revision table items for a single page
*/
-abstract class RevisionListBase extends ContextSource {
+abstract class RevisionListBase extends ContextSource implements Iterator {
/** @var Title */
public $title;
return $this->current;
}
+ public function rewind() {
+ $this->reset();
+ }
+
/**
* Get the current list item, or false if we are at the end
* @return Revision
return $this->current;
}
+ public function key() {
+ return $this->res ? $this->res->key(): 0;
+ }
+
+ public function valid() {
+ return $this->res ? $this->res->valid() : false;
+ }
+
/**
* Get the number of items in the list.
* @return int
&& strlen( serialize( $this->metadata ) ) <= self::CACHE_FIELD_MAX_LEN;
}
+ /**
+ * @return Status
+ * @since 1.28
+ */
+ public function acquireFileLock() {
+ return $this->getRepo()->getBackend()->lockFiles(
+ [ $this->getPath() ], LockManager::LOCK_EX, 10
+ );
+ }
+
+ /**
+ * @return Status
+ * @since 1.28
+ */
+ public function releaseFileLock() {
+ return $this->getRepo()->getBackend()->unlockFiles(
+ [ $this->getPath() ], LockManager::LOCK_EX
+ );
+ }
+
/**
* Start an atomic DB section and lock the image for update
* or increments a reference counter if the lock is already held
*
+ * This method should not be used outside of LocalFile/LocalFile*Batch
+ *
* @throws LocalFileLockError Throws an error if the lock was not acquired
* @return bool Whether the file lock owns/spawned the DB transaction
*/
- function lock() {
+ public function lock() {
if ( !$this->locked ) {
$logger = LoggerFactory::getInstance( 'LocalFile' );
// Bug 54736: use simple lock to handle when the file does not exist.
// SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
// Also, that would cause contention on INSERT of similarly named rows.
- $backend = $this->getRepo()->getBackend();
- $lockPaths = [ $this->getPath() ]; // represents all versions of the file
- $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 10 );
+ $status = $this->acquireFileLock(); // represents all versions of the file
if ( !$status->isGood() ) {
$dbw->endAtomic( self::ATOMIC_SECTION_LOCK );
$logger->warning( "Failed to lock '{file}'", [ 'file' => $this->name ] );
}
// Release the lock *after* commit to avoid row-level contention.
// Make sure it triggers on rollback() as well as commit() (T132921).
- $dbw->onTransactionResolution( function () use ( $backend, $lockPaths, $logger ) {
- $status = $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX );
+ $dbw->onTransactionResolution( function () use ( $logger ) {
+ $status = $this->releaseFileLock();
if ( !$status->isGood() ) {
$logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
}
/**
* Decrement the lock reference count and end the atomic section if it reaches zero
*
+ * This method should not be used outside of LocalFile/LocalFile*Batch
+ *
* The commit and loc release will happen when no atomic sections are active, which
* may happen immediately or at some point after calling this
*/
- function unlock() {
+ public function unlock() {
if ( $this->locked ) {
--$this->locked;
if ( !$this->locked ) {
* Item class for a filearchive table row
*/
class RevDelArchivedFileItem extends RevDelFileItem {
+ /** @var RevDelArchivedFileList */
+ protected $list;
+ /** @var ArchivedFile */
+ protected $file;
+ /** @var LocalFile */
+ protected $lockFile;
+
public function __construct( $list, $row ) {
RevDelItem::__construct( $list, $row );
$this->file = ArchivedFile::newFromRow( $row );
+ $this->lockFile = RepoGroup::singleton()->getLocalRepo()->newFile( $row->fa_name );
}
public function getIdField() {
return $ret;
}
+
+ public function lock() {
+ return $this->lockFile->acquireFileLock();
+ }
+
+ public function unlock() {
+ return $this->lockFile->releaseFileLock();
+ }
}
* Item class for an oldimage table row
*/
class RevDelFileItem extends RevDelItem {
- /** @var File */
+ /** @var OldLocalFile */
public $file;
+ /** @var RevDelFileList */
+ protected $list;
public function __construct( $list, $row ) {
parent::__construct( $list, $row );
return $ret;
}
+
+ public function lock() {
+ return $this->file->acquireFileLock();
+ }
+
+ public function unlock() {
+ return $this->file->releaseFileLock();
+ }
}
* @return array Data for the API result
*/
abstract public function getApiData( ApiResult $result );
+
+ /**
+ * Lock the item against changes outside of the DB
+ * @return Status
+ * @since 1.28
+ */
+ public function lock() {
+ return Status::newGood();
+ }
+
+ /**
+ * Unlock the item against changes outside of the DB
+ * @return Status
+ * @since 1.28
+ */
+ public function unlock() {
+ return Status::newGood();
+ }
}
public function areAnySuppressed() {
$bit = $this->getSuppressBit();
- // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
- for ( $this->reset(); $this->current(); $this->next() ) {
- // @codingStandardsIgnoreEnd
- $item = $this->current();
+ /** @var $item RevDelItem */
+ foreach ( $this as $item ) {
if ( $item->getBits() & $bit ) {
return true;
}
}
+
return false;
}
* @since 1.23 Added 'perItemStatus' param
*/
public function setVisibility( array $params ) {
+ $status = Status::newGood();
+
$bitPars = $params['value'];
$comment = $params['comment'];
$perItemStatus = isset( $params['perItemStatus'] ) ? $params['perItemStatus'] : false;
$dbw = wfGetDB( DB_MASTER );
$this->res = $this->doQuery( $dbw );
+ $status->merge( $this->acquireItemLocks() );
+ if ( !$status->isGood() ) {
+ return $status;
+ }
+
$dbw->startAtomic( __METHOD__ );
+ $dbw->onTransactionResolution( function () {
+ // Release locks on commit or error
+ $this->releaseItemLocks();
+ } );
- $status = Status::newGood();
$missing = array_flip( $this->ids );
$this->clearFileOps();
$idsForLog = [];
// passed to doPostCommitUpdates().
$visibilityChangeMap = [];
- // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
- for ( $this->reset(); $this->current(); $this->next() ) {
- // @codingStandardsIgnoreEnd
- /** @var $item RevDelItem */
- $item = $this->current();
+ /** @var $item RevDelItem */
+ foreach ( $this as $item ) {
unset( $missing[$item->getId()] );
if ( $perItemStatus ) {
return $status;
}
+ final protected function acquireItemLocks() {
+ $status = Status::newGood();
+ /** @var $item RevDelItem */
+ foreach ( $this as $item ) {
+ $status->merge( $item->lock() );
+ }
+
+ return $status;
+ }
+
+ final protected function releaseItemLocks() {
+ $status = Status::newGood();
+ /** @var $item RevDelItem */
+ foreach ( $this as $item ) {
+ $status->merge( $item->unlock() );
+ }
+
+ return $status;
+ }
+
/**
* Reload the list data from the master DB. This can be done after setVisibility()
* to allow $item->getHTML() to show the new data.