Merge "Removed useless keys() function from BagOStuff."
authorDemon <chadh@wikimedia.org>
Mon, 14 Jan 2013 11:48:31 +0000 (11:48 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 14 Jan 2013 11:48:31 +0000 (11:48 +0000)
1  2 
includes/objectcache/APCBagOStuff.php
includes/objectcache/BagOStuff.php
includes/objectcache/DBABagOStuff.php
includes/objectcache/HashBagOStuff.php
includes/objectcache/SqlBagOStuff.php
includes/objectcache/WinCacheBagOStuff.php

  class APCBagOStuff extends BagOStuff {
        /**
         * @param $key string
 +       * @param $casToken[optional] int
         * @return mixed
         */
 -      public function get( $key ) {
 +      public function get( $key, &$casToken = null ) {
                $val = apc_fetch( $key );
  
 +              $casToken = $val;
 +
                if ( is_string( $val ) ) {
                        if ( $this->isInteger( $val ) ) {
                                $val = intval( $val );
                return true;
        }
  
 +      /**
 +       * @param $casToken mixed
 +       * @param $key string
 +       * @param $value mixed
 +       * @param $exptime int
 +       * @return bool
 +       */
 +      public function cas( $casToken, $key, $value, $exptime = 0 ) {
 +              // APC's CAS functions only work on integers
 +              throw new MWException( "CAS is not implemented in " . __CLASS__ );
 +      }
 +
        /**
         * @param $key string
         * @param $time int
                return true;
        }
  
 +      /**
 +       * @param $key string
 +       * @param $callback closure Callback method to be executed
 +       * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
 +       * @param $attempts int The amount of times to attempt a merge in case of failure
 +       * @return bool success
 +       */
 +      public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
 +              return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
 +      }
 +
        public function incr( $key, $value = 1 ) {
                return apc_inc( $key, $value );
        }
        public function decr( $key, $value = 1 ) {
                return apc_dec( $key, $value );
        }
-       /**
-        * @return Array
-        */
-       public function keys() {
-               $info = apc_cache_info( 'user' );
-               $list = $info['cache_list'];
-               $keys = array();
-               foreach ( $list as $entry ) {
-                       $keys[] = $entry['info'];
-               }
-               return $keys;
-       }
  }
@@@ -56,10 -56,9 +56,10 @@@ abstract class BagOStuff 
        /**
         * Get an item with the given key. Returns false if it does not exist.
         * @param $key string
 +       * @param $casToken[optional] mixed
         * @return mixed Returns false on failure
         */
 -      abstract public function get( $key );
 +      abstract public function get( $key, &$casToken = null );
  
        /**
         * Set an item.
         */
        abstract public function set( $key, $value, $exptime = 0 );
  
 +      /**
 +       * Check and set an item.
 +       * @param $casToken mixed
 +       * @param $key string
 +       * @param $value mixed
 +       * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
 +       * @return bool success
 +       */
 +      abstract public function cas( $casToken, $key, $value, $exptime = 0 );
 +
        /**
         * Delete an item.
         * @param $key string
        abstract public function delete( $key, $time = 0 );
  
        /**
 +       * Merge changes into the existing cache value (possibly creating a new one)
 +       *
         * @param $key string
 -       * @param $timeout integer
 +       * @param $callback closure Callback method to be executed
 +       * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
 +       * @param $attempts int The amount of times to attempt a merge in case of failure
         * @return bool success
         */
 -      public function lock( $key, $timeout = 0 ) {
 -              /* stub */
 -              return true;
 +      public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
 +              return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
 +      }
 +
 +      /**
 +       * @see BagOStuff::merge()
 +       *
 +       * @param $key string
 +       * @param $callback closure Callback method to be executed
 +       * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
 +       * @param $attempts int The amount of times to attempt a merge in case of failure
 +       * @return bool success
 +       */
 +      protected function mergeViaCas( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
 +              do {
 +                      $casToken = null; // passed by reference
 +                      $currentValue = $this->get( $key, $casToken ); // get the old value
 +                      $value = $callback( $this, $key, $currentValue ); // derive the new value
 +
 +                      if ( $value === false ) {
 +                              $success = true; // do nothing
 +                      } elseif ( $currentValue === false ) {
 +                              // Try to create the key, failing if it gets created in the meantime
 +                              $success = $this->add( $key, $value, $exptime );
 +                      } else {
 +                              // Try to update the key, failing if it gets changed in the meantime
 +                              $success = $this->cas( $casToken, $key, $value, $exptime );
 +                      }
 +              } while ( !$success && --$attempts );
 +
 +              return $success;
 +      }
 +
 +      /**
 +       * @see BagOStuff::merge()
 +       *
 +       * @param $key string
 +       * @param $callback closure Callback method to be executed
 +       * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
 +       * @param $attempts int The amount of times to attempt a merge in case of failure
 +       * @return bool success
 +       */
 +      protected function mergeViaLock( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
 +              if ( !$this->lock( $key, 60 ) ) {
 +                      return false;
 +              }
 +
 +              $currentValue = $this->get( $key ); // get the old value
 +              $value = $callback( $this, $key, $currentValue ); // derive the new value
 +
 +              if ( $value === false ) {
 +                      $success = true; // do nothing
 +              } else {
 +                      $success = $this->set( $key, $value, $exptime ); // set the new value
 +              }
 +
 +              if ( !$this->unlock( $key ) ) {
 +                      // this should never happen
 +                      trigger_error( "Could not release lock for key '$key'." );
 +              }
 +
 +              return $success;
 +      }
 +
 +      /**
 +       * @param $key string
 +       * @param $timeout integer [optional]
 +       * @return bool success
 +       */
 +      public function lock( $key, $timeout = 60 ) {
 +              $timestamp = microtime( true ); // starting UNIX timestamp
 +              if ( $this->add( "{$key}:lock", $timeout ) ) {
 +                      return true;
 +              }
 +
 +              $uRTT  = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
 +              $sleep = 2*$uRTT; // rough time to do get()+set()
 +
 +              $locked   = false; // lock acquired
 +              $attempts = 0; // failed attempts
 +              do {
 +                      if ( ++$attempts >= 3 && $sleep <= 1e6 ) {
 +                              // Exponentially back off after failed attempts to avoid network spam.
 +                              // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
 +                              $sleep *= 2;
 +                      }
 +                      usleep( $sleep ); // back off
 +                      $locked = $this->add( "{$key}:lock", $timeout );
 +              } while( !$locked );
 +
 +              return $locked;
        }
  
        /**
         * @return bool success
         */
        public function unlock( $key ) {
 -              /* stub */
 -              return true;
 +              return $this->delete( "{$key}:lock" );
        }
  
-       /**
-        * @todo: what is this?
-        * @return Array
-        */
-       public function keys() {
-               /* stub */
-               return array();
-       }
        /**
         * Delete all objects expiring before a certain date.
         * @param $date string The reference date in MW format
@@@ -111,10 -111,9 +111,10 @@@ class DBABagOStuff extends BagOStuff 
  
        /**
         * @param $key string
 +       * @param $casToken[optional] mixed
         * @return mixed
         */
 -      public function get( $key ) {
 +      public function get( $key, &$casToken = null ) {
                wfProfileIn( __METHOD__ );
                wfDebug( __METHOD__ . "($key)\n" );
  
                        $val = false;
                }
  
 +              $casToken = $val;
 +
                wfProfileOut( __METHOD__ );
 +
                return $val;
        }
  
                return $ret;
        }
  
 +      /**
 +       * @param $casToken mixed
 +       * @param $key string
 +       * @param $value mixed
 +       * @param $exptime int
 +       * @return bool
 +       */
 +      public function cas( $casToken, $key, $value, $exptime = 0 ) {
 +              wfProfileIn( __METHOD__ );
 +              wfDebug( __METHOD__ . "($key)\n" );
 +
 +              $blob = $this->encode( $value, $exptime );
 +
 +              $handle = $this->getWriter();
 +              if ( !$handle ) {
 +                      wfProfileOut( __METHOD__ );
 +                      return false;
 +              }
 +
 +              // DBA is locked to any other write connection, so we can safely
 +              // compare the current & previous value before saving new value
 +              $val = dba_fetch( $key, $handle );
 +              list( $val, $exptime ) = $this->decode( $val );
 +              if ( $casToken !== $val ) {
 +                      dba_close( $handle );
 +                      wfProfileOut( __METHOD__ );
 +                      return false;
 +              }
 +
 +              $ret = dba_replace( $key, $blob, $handle );
 +              dba_close( $handle );
 +
 +              wfProfileOut( __METHOD__ );
 +              return $ret;
 +      }
 +
        /**
         * @param $key string
         * @param $time int
  
                return ( $value === false ) ? false : (int)$value;
        }
-       function keys() {
-               $reader = $this->getReader();
-               $k1 = dba_firstkey( $reader );
-               if ( !$k1 ) {
-                       return array();
-               }
-               $result[] = $k1;
-               $key = dba_nextkey( $reader );
-               while ( $key ) {
-                       $result[] = $key;
-                       $key = dba_nextkey( $reader );
-               }
-               return $result;
-       }
  }
@@@ -52,10 -52,9 +52,10 @@@ class HashBagOStuff extends BagOStuff 
  
        /**
         * @param $key string
 +       * @param $casToken[optional] mixed
         * @return bool|mixed
         */
 -      function get( $key ) {
 +      function get( $key, &$casToken = null ) {
                if ( !isset( $this->bag[$key] ) ) {
                        return false;
                }
@@@ -64,8 -63,6 +64,8 @@@
                        return false;
                }
  
 +              $casToken = $this->bag[$key][0];
 +
                return $this->bag[$key][0];
        }
  
                return true;
        }
  
 +      /**
 +       * @param $casToken mixed
 +       * @param $key string
 +       * @param $value mixed
 +       * @param $exptime int
 +       * @return bool
 +       */
 +      function cas( $casToken, $key, $value, $exptime = 0 ) {
 +              if ( $this->get( $key ) === $casToken ) {
 +                      return $this->set( $key, $value, $exptime );
 +              }
 +
 +              return false;
 +      }
 +
        /**
         * @param $key string
         * @param $time int
  
                return true;
        }
-       /**
-        * @return array
-        */
-       function keys() {
-               return array_keys( $this->bag );
-       }
  }
  
@@@ -49,7 -49,7 +49,7 @@@ class SqlBagOStuff extends BagOStuff 
         *   - server:      A server info structure in the format required by each
         *                  element in $wgDBServers.
         *
-        *   - servers:     An array of server info structures describing a set of 
+        *   - servers:     An array of server info structures describing a set of
         *                  database servers to distribute keys to. If this is
         *                  specified, the "server" option will be ignored.
         *
@@@ -62,7 -62,7 +62,7 @@@
         *
         *   - tableName:   The table name to use, default is "objectcache".
         *
-        *   - shards:      The number of tables to use for data storage on each server. 
+        *   - shards:      The number of tables to use for data storage on each server.
         *                  If this is more than 1, table names will be formed in the style
         *                  objectcacheNNN where NNN is the shard index, between 0 and
         *                  shards-1. The number of digits will be the minimum number
                        }
  
                        # Don't keep timing out trying to connect for each call if the DB is down
-                       if ( isset( $this->connFailureErrors[$serverIndex] ) 
-                               && ( time() - $this->connFailureTimes[$serverIndex] ) < 60 ) 
+                       if ( isset( $this->connFailureErrors[$serverIndex] )
+                               && ( time() - $this->connFailureTimes[$serverIndex] ) < 60 )
                        {
                                throw $this->connFailureErrors[$serverIndex];
                        }
  
        /**
         * @param $key string
 +       * @param $casToken[optional] mixed
         * @return mixed
         */
 -      public function get( $key ) {
 +      public function get( $key, &$casToken = null ) {
                $values = $this->getMulti( array( $key ) );
 -              return array_key_exists( $key, $values ) ? $values[$key] : false;
 +              if ( array_key_exists( $key, $values ) ) {
 +                      $casToken = $values[$key];
 +                      return $values[$key];
 +              }
 +              return false;
        }
  
        /**
                return true;
        }
  
 +      /**
 +       * @param $casToken mixed
 +       * @param $key string
 +       * @param $value mixed
 +       * @param $exptime int
 +       * @return bool
 +       */
 +      public function cas( $casToken, $key, $value, $exptime = 0 ) {
 +              $db = $this->getDB();
 +              $exptime = intval( $exptime );
 +
 +              if ( $exptime < 0 ) {
 +                      $exptime = 0;
 +              }
 +
 +              if ( $exptime == 0 ) {
 +                      $encExpiry = $this->getMaxDateTime();
 +              } else {
 +                      if ( $exptime < 3.16e8 ) { # ~10 years
 +                              $exptime += time();
 +                      }
 +
 +                      $encExpiry = $db->timestamp( $exptime );
 +              }
 +              try {
 +                      $db->begin( __METHOD__ );
 +                      // (bug 24425) use a replace if the db supports it instead of
 +                      // delete/insert to avoid clashes with conflicting keynames
 +                      $db->update(
 +                              $this->getTableByKey( $key ),
 +                              array(
 +                                      'keyname' => $key,
 +                                      'value' => $db->encodeBlob( $this->serialize( $value ) ),
 +                                      'exptime' => $encExpiry
 +                              ),
 +                              array(
 +                                      'keyname' => $key,
 +                                      'value' => $db->encodeBlob( $this->serialize( $casToken ) )
 +                              ), __METHOD__ );
 +                      $db->commit( __METHOD__ );
 +              } catch ( DBQueryError $e ) {
 +                      $this->handleWriteError( $e );
 +
 +                      return false;
 +              }
 +
 +              return (bool) $db->affectedRows();
 +      }
 +
        /**
         * @param $key string
         * @param $time int
                return $newValue;
        }
  
-       /**
-        * @return Array
-        */
-       public function keys() {
-               $result = array();
-               for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
-                       try {
-                               $db = $this->getDB( $serverIndex );
-                               for ( $i = 0; $i < $this->shards; $i++ ) {
-                                       $res = $db->select( $this->getTableNameByShard( $i ),
-                                               array( 'keyname' ), false, __METHOD__ );
-                                       foreach ( $res as $row ) {
-                                               $result[] = $row->keyname;
-                                       }
-                               }
-                       } catch ( DBError $e ) {
-                               $this->handleReadError( $e, $serverIndex );
-                       }
-               }
-               return $result;
-       }
        /**
         * @param $exptime string
         * @return bool
@@@ -33,14 -33,11 +33,14 @@@ class WinCacheBagOStuff extends BagOStu
         * Get a value from the WinCache object cache
         *
         * @param $key String: cache key
 +       * @param $casToken[optional] int: cas token
         * @return mixed
         */
 -      public function get( $key ) {
 +      public function get( $key, &$casToken = null ) {
                $val = wincache_ucache_get( $key );
  
 +              $casToken = $val;
 +
                if ( is_string( $val ) ) {
                        $val = unserialize( $val );
                }
                return ( is_array( $result ) && $result === array() ) || $result;
        }
  
 +      /**
 +       * Store a value in the WinCache object cache, race condition-safe
 +       *
 +       * @param $casToken int: cas token
 +       * @param $key String: cache key
 +       * @param $value int: object to store
 +       * @param $exptime Int: expiration time
 +       * @return bool
 +       */
 +      public function cas( $casToken, $key, $value, $exptime = 0 ) {
 +              return wincache_ucache_cas( $key, $casToken, serialize( $value ) );
 +      }
 +
        /**
         * Remove a value from the WinCache object cache
         *
  
                return true;
        }
-       /**
-        * @return Array
-        */
-       public function keys() {
-               $info = wincache_ucache_info();
-               $list = $info['ucache_entries'];
-               $keys = array();
-               if ( is_null( $list ) ) {
-                       return array();
-               }
-               foreach ( $list as $entry ) {
-                       $keys[] = $entry['key_name'];
-               }
-               return $keys;
-       }
  }