MediaWiki  REL1_24
MemcachedPeclBagOStuff.php
Go to the documentation of this file.
00001 <?php
00029 class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
00030 
00047     function __construct( $params ) {
00048         $params = $this->applyDefaultParams( $params );
00049 
00050         if ( $params['persistent'] ) {
00051             // The pool ID must be unique to the server/option combination.
00052             // The Memcached object is essentially shared for each pool ID.
00053             // We can only reuse a pool ID if we keep the config consistent.
00054             $this->client = new Memcached( md5( serialize( $params ) ) );
00055             if ( count( $this->client->getServerList() ) ) {
00056                 wfDebug( __METHOD__ . ": persistent Memcached object already loaded.\n" );
00057                 return; // already initialized; don't add duplicate servers
00058             }
00059         } else {
00060             $this->client = new Memcached;
00061         }
00062 
00063         if ( !isset( $params['serializer'] ) ) {
00064             $params['serializer'] = 'php';
00065         }
00066 
00067         if ( isset( $params['retry_timeout'] ) ) {
00068             $this->client->setOption( Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout'] );
00069         }
00070 
00071         if ( isset( $params['server_failure_limit'] ) ) {
00072             $this->client->setOption( Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit'] );
00073         }
00074 
00075         // The compression threshold is an undocumented php.ini option for some
00076         // reason. There's probably not much harm in setting it globally, for
00077         // compatibility with the settings for the PHP client.
00078         ini_set( 'memcached.compression_threshold', $params['compress_threshold'] );
00079 
00080         // Set timeouts
00081         $this->client->setOption( Memcached::OPT_CONNECT_TIMEOUT, $params['connect_timeout'] * 1000 );
00082         $this->client->setOption( Memcached::OPT_SEND_TIMEOUT, $params['timeout'] );
00083         $this->client->setOption( Memcached::OPT_RECV_TIMEOUT, $params['timeout'] );
00084         $this->client->setOption( Memcached::OPT_POLL_TIMEOUT, $params['timeout'] / 1000 );
00085 
00086         // Set libketama mode since it's recommended by the documentation and
00087         // is as good as any. There's no way to configure libmemcached to use
00088         // hashes identical to the ones currently in use by the PHP client, and
00089         // even implementing one of the libmemcached hashes in pure PHP for
00090         // forwards compatibility would require MWMemcached::get_sock() to be
00091         // rewritten.
00092         $this->client->setOption( Memcached::OPT_LIBKETAMA_COMPATIBLE, true );
00093 
00094         // Set the serializer
00095         switch ( $params['serializer'] ) {
00096             case 'php':
00097                 $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP );
00098                 break;
00099             case 'igbinary':
00100                 if ( !Memcached::HAVE_IGBINARY ) {
00101                     throw new MWException( __CLASS__ . ': the igbinary extension is not available ' .
00102                         'but igbinary serialization was requested.' );
00103                 }
00104                 $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
00105                 break;
00106             default:
00107                 throw new MWException( __CLASS__ . ': invalid value for serializer parameter' );
00108         }
00109         $servers = array();
00110         foreach ( $params['servers'] as $host ) {
00111             $servers[] = IP::splitHostAndPort( $host ); // (ip, port)
00112         }
00113         $this->client->addServers( $servers );
00114     }
00115 
00121     public function get( $key, &$casToken = null ) {
00122         wfProfileIn( __METHOD__ );
00123         $this->debugLog( "get($key)" );
00124         $result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
00125         $result = $this->checkResult( $key, $result );
00126         wfProfileOut( __METHOD__ );
00127         return $result;
00128     }
00129 
00136     public function set( $key, $value, $exptime = 0 ) {
00137         $this->debugLog( "set($key)" );
00138         return $this->checkResult( $key, parent::set( $key, $value, $exptime ) );
00139     }
00140 
00148     public function cas( $casToken, $key, $value, $exptime = 0 ) {
00149         $this->debugLog( "cas($key)" );
00150         return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
00151     }
00152 
00158     public function delete( $key, $time = 0 ) {
00159         $this->debugLog( "delete($key)" );
00160         $result = parent::delete( $key, $time );
00161         if ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) {
00162             // "Not found" is counted as success in our interface
00163             return true;
00164         } else {
00165             return $this->checkResult( $key, $result );
00166         }
00167     }
00168 
00175     public function add( $key, $value, $exptime = 0 ) {
00176         $this->debugLog( "add($key)" );
00177         return $this->checkResult( $key, parent::add( $key, $value, $exptime ) );
00178     }
00179 
00185     public function incr( $key, $value = 1 ) {
00186         $this->debugLog( "incr($key)" );
00187         $result = $this->client->increment( $key, $value );
00188         return $this->checkResult( $key, $result );
00189     }
00190 
00196     public function decr( $key, $value = 1 ) {
00197         $this->debugLog( "decr($key)" );
00198         $result = $this->client->decrement( $key, $value );
00199         return $this->checkResult( $key, $result );
00200     }
00201 
00213     protected function checkResult( $key, $result ) {
00214         if ( $result !== false ) {
00215             return $result;
00216         }
00217         switch ( $this->client->getResultCode() ) {
00218             case Memcached::RES_SUCCESS:
00219                 break;
00220             case Memcached::RES_DATA_EXISTS:
00221             case Memcached::RES_NOTSTORED:
00222             case Memcached::RES_NOTFOUND:
00223                 $this->debugLog( "result: " . $this->client->getResultMessage() );
00224                 break;
00225             default:
00226                 $msg = $this->client->getResultMessage();
00227                 if ( $key !== false ) {
00228                     $server = $this->client->getServerByKey( $key );
00229                     $serverName = "{$server['host']}:{$server['port']}";
00230                     $msg = "Memcached error for key \"$key\" on server \"$serverName\": $msg";
00231                 } else {
00232                     $msg = "Memcached error: $msg";
00233                 }
00234                 wfDebugLog( 'memcached-serious', $msg );
00235                 $this->setLastError( BagOStuff::ERR_UNEXPECTED );
00236         }
00237         return $result;
00238     }
00239 
00244     public function getMulti( array $keys ) {
00245         wfProfileIn( __METHOD__ );
00246         $this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
00247         $callback = array( $this, 'encodeKey' );
00248         $result = $this->client->getMulti( array_map( $callback, $keys ) );
00249         wfProfileOut( __METHOD__ );
00250         $result = $result ?: array(); // must be an array
00251         return $this->checkResult( false, $result );
00252     }
00253 
00259     public function setMulti( array $data, $exptime = 0 ) {
00260         wfProfileIn( __METHOD__ );
00261         foreach ( $data as $key => $value ) {
00262             $encKey = $this->encodeKey( $key );
00263             if ( $encKey !== $key ) {
00264                 $data[$encKey] = $value;
00265                 unset( $data[$key] );
00266             }
00267         }
00268         $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
00269         $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
00270         wfProfileOut( __METHOD__ );
00271         return $this->checkResult( false, $result );
00272     }
00273 }