[FileBackend] Added auth token/url cache to SwiftFileBackend.
authorAaron <aschulz@wikimedia.org>
Thu, 16 Aug 2012 22:46:19 +0000 (15:46 -0700)
committerAaron <aschulz@wikimedia.org>
Fri, 17 Aug 2012 17:51:39 +0000 (10:51 -0700)
Change-Id: I5a053dec59730f642ac51817a1af57a1e257d3fe

includes/filebackend/SwiftFileBackend.php

index d13ac22..e84f854 100644 (file)
@@ -47,8 +47,14 @@ class SwiftFileBackend extends FileBackendStore {
 
        /** @var CF_Connection */
        protected $conn; // Swift connection handle
-       protected $connStarted = 0; // integer UNIX timestamp
-       protected $connException; // CloudFiles exception
+       protected $sessionStarted = 0; // integer UNIX timestamp
+
+       /** @var CloudFilesException */
+       protected $connException;
+       protected $connErrorTime = 0; // UNIX timestamp
+
+       /** @var BagOStuff */
+       protected $srvCache;
 
        /** @var ProcessCacheLRU */
        protected $connContainerCache; // container object cache
@@ -75,6 +81,8 @@ class SwiftFileBackend extends FileBackendStore {
         *                             - levels : the number of hash levels (and digits)
         *                             - repeat : hash subdirectories are prefixed with all the
         *                                        parent hash directory names (e.g. "a/ab/abc")
+        *   - cacheAuthInfo      : Whether to cache authentication tokens in APC/XCache.
+        *                          This is probably insecure in shared hosting environments.
         */
        public function __construct( array $config ) {
                parent::__construct( $config );
@@ -107,10 +115,17 @@ class SwiftFileBackend extends FileBackendStore {
                $this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
                        ? $config['swiftCDNPurgable']
                        : true;
-               // Cache container info to mask latency
+               // Cache container information to mask latency
                $this->memCache = wfGetMainCache();
                // Process cache for container info
                $this->connContainerCache = new ProcessCacheLRU( 300 );
+               // Cache auth token information to avoid RTTs
+               if ( !empty( $config['cacheAuthInfo'] ) ) {
+                       try { // look for APC, XCache, WinCache, ect...
+                               $this->srvCache = ObjectCache::newAccelerator( array() );
+                       } catch ( Exception $e ) {}
+               }
+               $this->srvCache = $this->srvCache ? $this->srvCache : new EmptyBagOStuff();
        }
 
        /**
@@ -1122,36 +1137,62 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        /**
-        * Get a connection to the Swift proxy
+        * Get an authenticated connection handle to the Swift proxy
         *
         * @return CF_Connection|bool False on failure
         * @throws CloudFilesException
         */
        protected function getConnection() {
-               if ( $this->connException instanceof Exception ) {
-                       throw $this->connException; // failed last attempt
+               if ( $this->connException instanceof CloudFilesException ) {
+                       if ( ( time() - $this->connErrorTime ) < 60 ) {
+                               throw $this->connException; // failed last attempt; don't bother
+                       } else { // actually retry this time
+                               $this->connException = null;
+                               $this->connErrorTime = 0;
+                       }
                }
                // Session keys expire after a while, so we renew them periodically
-               if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) {
-                       $this->conn->close(); // close active cURL connections
-                       $this->conn = null;
-               }
+               $reAuth = ( ( time() - $this->sessionStarted ) > $this->authTTL );
                // Authenticate with proxy and get a session key...
-               if ( !$this->conn ) {
-                       $this->connStarted = 0;
+               if ( !$this->conn || $reAuth ) {
+                       $this->sessionStarted = 0;
                        $this->connContainerCache->clear();
-                       try {
-                               $this->auth->authenticate();
-                               $this->conn = new CF_Connection( $this->auth );
-                               $this->connStarted = time();
-                       } catch ( CloudFilesException $e ) {
-                               $this->connException = $e; // don't keep re-trying
-                               throw $e; // throw it back
+                       $cacheKey = $this->getCredsCacheKey( $this->auth->username );
+                       $creds = $this->srvCache->get( $cacheKey ); // credentials
+                       if ( is_array( $creds ) ) { // cache hit
+                               $this->auth->load_cached_credentials(
+                                       $creds['auth_token'], $creds['storage_url'], $creds['cdnm_url'] );
+                               $this->sessionStarted = time() - ceil( $this->authTTL/2 ); // skew for worst case
+                       } else { // cache miss
+                               try {
+                                       $this->auth->authenticate();
+                                       $creds = $this->auth->export_credentials();
+                                       $this->srvCache->add( $cacheKey, $creds, ceil( $this->authTTL/2 ) ); // cache
+                                       $this->sessionStarted = time();
+                               } catch ( CloudFilesException $e ) {
+                                       $this->connException = $e; // don't keep re-trying
+                                       $this->connErrorTime = time();
+                                       throw $e; // throw it back
+                               }
+                       }
+                       if ( $this->conn ) { // re-authorizing?
+                               $this->conn->close(); // close active cURL handles in CF_Http object
                        }
+                       $this->conn = new CF_Connection( $this->auth );
                }
                return $this->conn;
        }
 
+       /**
+        * Get the cache key for a container
+        *
+        * @param $username string
+        * @return string
+        */
+       private function getCredsCacheKey( $username ) {
+               return wfMemcKey( 'backend', $this->getName(), 'usercreds', $username );
+       }
+
        /**
         * @see FileBackendStore::doClearCache()
         */