https://www.mediawiki.org/wiki/Special:Code/MediaWiki/104116

Revision: 104116
Author:   aaron
Date:     2011-11-24 00:26:17 +0000 (Thu, 24 Nov 2011)
Log Message:
-----------
* Made FSFileLockManager create the lock dir if needed
* Optimized FSFileBackend::move() and FSFileBackend::concatenate() a bit for 
UNIX
* Simplified DBFileLockManager logic as the servers for a bucket are all piers
* Made DBFileLockManger more resistent to connection timeouts and server 
restarts/query loss
* Fixed mispelled wfMkdirParents() calls

Modified Paths:
--------------
    branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
    branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php

Modified: 
branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php
===================================================================
--- branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php     
2011-11-24 00:05:15 UTC (rev 104115)
+++ branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php     
2011-11-24 00:26:17 UTC (rev 104116)
@@ -55,7 +55,7 @@
                                return $status;
                        }
                } else {
-                       wfMakeDirParents( $dest );
+                       wfMkdirParents( $dest );
                }
 
                wfSuppressWarnings();
@@ -95,10 +95,13 @@
 
                if ( file_exists( $dest ) ) {
                        if ( isset( $params['overwriteDest'] ) ) {
-                               $ok = unlink( $dest );
-                               if ( !$ok ) {
-                                       $status->fatal( 'backend-fail-delete', 
$params['dest'] );
-                                       return $status;
+                               // Windows does not support moving over 
existing files
+                               if ( wfIsWindows() ) {
+                                       $ok = unlink( $dest );
+                                       if ( !$ok ) {
+                                               $status->fatal( 
'backend-fail-delete', $params['dest'] );
+                                               return $status;
+                                       }
                                }
                        } elseif ( isset( $params['overwriteSame'] ) ) {
                                if ( !$this->filesAreSame( $source, $dest ) ) {
@@ -110,7 +113,7 @@
                                return $status;
                        }
                } else {
-                       wfMakeDirParents( $dest );
+                       wfMkdirParents( $dest );
                }
 
                wfSuppressWarnings();
@@ -213,12 +216,15 @@
                // Note that we already checked if no overwrite params were set 
above.
                if ( $destExists ) {
                        if ( isset( $params['overwriteDest'] ) ) {
-                               wfSuppressWarnings();
-                               $ok = unlink( $dest );
-                               wfRestoreWarnings();
-                               if ( !$ok ) {
-                                       $status->fatal( 'backend-fail-delete', 
$params['dest'] );
-                                       return $status;
+                               // Windows does not support moving over 
existing files
+                               if ( wfIsWindows() ) {
+                                       wfSuppressWarnings();
+                                       $ok = unlink( $dest );
+                                       wfRestoreWarnings();
+                                       if ( !$ok ) {
+                                               $status->fatal( 
'backend-fail-delete', $params['dest'] );
+                                               return $status;
+                                       }
                                }
                        } elseif ( isset( $params['overwriteSame'] ) ) {
                                if ( !$this->filesAreSame( $tmpPath, $dest ) ) {
@@ -228,7 +234,7 @@
                        }
                } else {
                        // Make sure destination directory exists
-                       wfMakeDirParents( $dest );
+                       wfMkdirParents( $dest );
                }
 
                // Rename the temporary file to the destination path

Modified: 
branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php
===================================================================
--- branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php   
2011-11-24 00:05:15 UTC (rev 104115)
+++ branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php   
2011-11-24 00:26:17 UTC (rev 104116)
@@ -128,6 +128,13 @@
                } else {
                        wfSuppressWarnings();
                        $handle = fopen( "{$this->lockDir}/{$key}", 'c' );
+                       wfRestoreWarnings();
+                       if ( !$handle ) { // lock dir missing?
+                               wfMkdirParents( "{$this->lockDir}/{$key}" );
+                               wfSuppressWarnings();
+                               $handle = fopen( "{$this->lockDir}/{$key}", 'c' 
); // try again
+                               wfRestoreWarnings();
+                       }
                        if ( $handle ) {
                                // Either a shared or exclusive lock
                                $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : 
LOCK_EX;
@@ -140,7 +147,6 @@
                        } else {
                                $status->fatal( 'lockmanager-fail-openlock', 
$key );
                        }
-                       wfRestoreWarnings();
                }
 
                return $status;
@@ -203,19 +209,20 @@
 /**
  * Version of FileLockManager based on using DB table locks.
  *
- * This is meant for multi-wiki systems that may share share some files.
- * One or several lock database servers are set up having a `file_locking`
+ * This is meant for multi-wiki systems that may share share files.
+ * One or several database servers are set up having a `file_locking`
  * table with one field, fl_key, the PRIMARY KEY. The table engine should
- * have row-level locking. For performance, deadlock detection should be
- * disabled and a low lock-wait timeout should be set via server config.
- *
- * All lock requests for an item (identified by an abstract key string) will
- * map to one bucket. Each bucket maps to a single server, though each server
- * can have several fallback servers.
- *
- * Fallback servers recieve the same lock statements as the servers they 
standby for.
+ * have row-level locking. All lock requests for a resource, identified by
+ * a hash string, will map to one bucket. Each bucket maps to a single server.
+ * 
+ * Each bucket can also have several fallback servers.
+ * Fallback servers get the same lock statements as the primary bucket server.
  * This propagation is only best-effort; lock requests will not be blocked just
- * because a fallback server cannot recieve a copy of the lock request.
+ * because a fallback server cannot be contacted.
+ * 
+ * For performance, deadlock detection should be disabled and a small
+ * lock-wait timeout should be set via server config. In innoDB, this can
+ * done via the innodb_deadlock_detect and innodb_lock_wait_timeout settings.
  */
 class DBFileLockManager extends FileLockManager {
        /** @var Array Map of bucket indexes to server names */
@@ -273,30 +280,24 @@
                $lockedKeys = array(); // files locked in this attempt
                // Attempt to acquire these locks...
                foreach ( $keysToLock as $bucket => $keys ) {
-                       $server = $this->serverMap[$bucket][0]; // primary lock 
server
-                       $propagateToFallbacks = true; // give lock statements 
to fallback servers
                        // Acquire the locks for this server. Three main cases 
can happen:
-                       // (a) Server is up; common case
-                       // (b) Server is down but a fallback is up
-                       // (c) Server is down and no fallbacks are up (or none 
defined)
-                       try {
-                               $this->lockingSelect( $server, $keys, $type );
-                       } catch ( DBError $e ) {
-                               // Can we manage to lock on any of the fallback 
servers?
-                               if ( $this->lockingSelectFallbacks( $bucket, 
$keys, $type ) ) {
-                                       // Recovered; a fallback server is up
-                                       $propagateToFallbacks = false; // done 
already
-                               } else {
-                                       // Abort and unlock everything we just 
locked
-                                       $status->fatal( 'lockmanager-fail-db', 
$bucket );
-                                       $status->merge( $this->doUnlock( 
$lockedKeys, $type ) );
-                                       return $status;
-                               }
+                       // (a) First server is up; common case
+                       // (b) First server is down but a fallback is up
+                       // (c) First server is down and no fallbacks are up (or 
none defined)
+                       $count = $this->doLockingSelectAll( $bucket, $keys, 
$type );
+                       if ( $count == -1 ) {
+                               // Resources already locked by another process.
+                               // Abort and unlock everything we just locked.
+                               $status->fatal( 'lockmanager-fail-acquirelocks' 
);
+                               $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
+                               return $status;
+                       } elseif ( $count <= 0 ) {
+                               // Couldn't contact any servers for this bucket.
+                               // Abort and unlock everything we just locked.
+                               $status->fatal( 'lockmanager-fail-db', $bucket 
);
+                               $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
+                               return $status; // error
                        }
-                       // Propagate any locks to the fallback servers (best 
effort)
-                       if ( $propagateToFallbacks ) {
-                               $this->lockingSelectFallbacks( $bucket, $keys, 
$type );
-                       }
                        // Record locks as active
                        foreach ( $keys as $key ) {
                                $this->locksHeld[$key][$type] = 1; // locked
@@ -342,11 +343,22 @@
         * @param $type integer FileLockManager::LOCK_EX or 
FileLockManager::LOCK_SH
         * @return void
         */
-       protected function lockingSelect( $server, array $keys, $type ) {
+       protected function doLockingSelect( $server, array $keys, $type ) {
                if ( !isset( $this->activeConns[$server] ) ) {
                        $this->activeConns[$server] = wfGetDB( DB_MASTER, 
array(), $server );
                        $this->activeConns[$server]->begin(); // start 
transaction
+                       # If the connection drops, try to avoid letting the DB 
rollback
+                       # and release the locks before the file operations are 
finished.
+                       # This won't handle the case of server reboots however.
+                       $options = array();
+                       if ( php_sapi_name() == 'cli' ) { // maintenance scripts
+                               $options['connTimeout'] = 60; // some sane 
amount
+                       } else { // web requests
+                               $options['connTimeout'] = ini_get( 
'max_execution_time' );
+                       }
+                       $this->activeConns[$server]->setSessionOptions( 
$options );
                }
+               # Try to get the locks...this should be the last query of this 
function
                $lockingClause = ( $type == self::LOCK_SH )
                        ? 'LOCK IN SHARE MODE' // reader lock
                        : 'FOR UPDATE'; // writer lock
@@ -360,23 +372,28 @@
        }
 
        /**
-        * Propagate any locks to the fallback servers for a bucket.
+        * Attept to acquire a lock on the primary server as well
+        * as all fallback servers for a bucket. Returns the number
+        * of servers with locks made or -1 if any of them claimed
+        * that any of the keys were already locked by another process.
         * This should avoid throwing any exceptions.
         *
         * @param $bucket integer
         * @param $keys Array
         * @param $type integer FileLockManager::LOCK_EX or 
FileLockManager::LOCK_SH
-        * @return bool Locks made on at least one fallback server
+        * @return integer
         */
-       protected function lockingSelectFallbacks( $bucket, array $keys, $type 
) {
-               $locksMade = false;
-               // Start at $i=1 to only include fallback servers
-               for ( $i=1; $i < count( $this->serverMap[$bucket] ); $i++ ) {
+       protected function doLockingSelectAll( $bucket, array $keys, $type ) {
+               $locksMade = 0;
+               for ( $i=0; $i < count( $this->serverMap[$bucket] ); $i++ ) {
                        $server = $this->serverMap[$bucket][$i];
                        try {
                                $this->doLockingSelect( $server, $keys, $type );
-                               $locksMade = true; // success for this fallback
+                               ++$locksMade; // success for this fallback
                        } catch ( DBError $e ) {
+                               if ( $this->lastErrorIndicatesLocked( $server ) 
) {
+                                       return -1; // resource locked
+                               }
                                // oh well; best effort (@TODO: logging?)
                        }
                }
@@ -394,13 +411,29 @@
                        try {
                                $db->commit(); // finish transaction
                        } catch ( DBError $e ) {
-                               // oh well
+                               // oh well; best effort (@TODO: logging?)
                        }
                }
                $this->activeConns = array();
        }
 
        /**
+        * Check if the last DB error for $server indicates
+        * that a requested resource was locked by another process.
+        * This should avoid throwing any exceptions.
+        * 
+        * @param $server string
+        * @return bool
+        */
+       protected function lastErrorIndicatesLocked( $server ) {
+               if ( isset( $this->activeConns[$server] ) ) { // sanity
+                       $db = $this->activeConns[$server];
+                       return ( $db->wasDeadlock() || $db->wasLockTimeout() );
+               }
+               return false;
+       }
+
+       /**
         * Get the bucket for lock key.
         * This should avoid throwing any exceptions.
         *


_______________________________________________
MediaWiki-CVS mailing list
MediaWiki-CVS@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to