MediaWiki  REL1_22
EhcacheBagOStuff.php
Go to the documentation of this file.
00001 <?php
00030 class EhcacheBagOStuff extends BagOStuff {
00031     var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions,
00032         $requestData, $requestDataPos;
00033 
00034     var $curls = array();
00035 
00040     function __construct( $params ) {
00041         if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
00042             throw new MWException( __CLASS__ . ' requires curl version 7.16.2 or later.' );
00043         }
00044         if ( !extension_loaded( 'zlib' ) ) {
00045             throw new MWException( __CLASS__ . ' requires the zlib extension' );
00046         }
00047         if ( !isset( $params['servers'] ) ) {
00048             throw new MWException( __METHOD__ . ': servers parameter is required' );
00049         }
00050         $this->servers = $params['servers'];
00051         $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
00052         $this->connectTimeout = isset( $params['connectTimeout'] )
00053             ? $params['connectTimeout'] : 1;
00054         $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
00055         $this->curlOptions = array(
00056             CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ),
00057             CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ),
00058             CURLOPT_RETURNTRANSFER => 1,
00059             CURLOPT_CUSTOMREQUEST => 'GET',
00060             CURLOPT_POST => 0,
00061             CURLOPT_POSTFIELDS => '',
00062             CURLOPT_HTTPHEADER => array(),
00063         );
00064     }
00065 
00071     public function get( $key, &$casToken = null ) {
00072         wfProfileIn( __METHOD__ );
00073         $response = $this->doItemRequest( $key );
00074         if ( !$response || $response['http_code'] == 404 ) {
00075             wfProfileOut( __METHOD__ );
00076             return false;
00077         }
00078         if ( $response['http_code'] >= 300 ) {
00079             wfDebug( __METHOD__ . ": GET failure, got HTTP {$response['http_code']}\n" );
00080             wfProfileOut( __METHOD__ );
00081             return false;
00082         }
00083         $body = $response['body'];
00084         $type = $response['content_type'];
00085         if ( $type == 'application/vnd.php.serialized+deflate' ) {
00086             $body = gzinflate( $body );
00087             if ( !$body ) {
00088                 wfDebug( __METHOD__ . ": error inflating $key\n" );
00089                 wfProfileOut( __METHOD__ );
00090                 return false;
00091             }
00092             $data = unserialize( $body );
00093         } elseif ( $type == 'application/vnd.php.serialized' ) {
00094             $data = unserialize( $body );
00095         } else {
00096             wfDebug( __METHOD__ . ": unknown content type \"$type\"\n" );
00097             wfProfileOut( __METHOD__ );
00098             return false;
00099         }
00100 
00101         $casToken = $body;
00102 
00103         wfProfileOut( __METHOD__ );
00104         return $data;
00105     }
00106 
00113     public function set( $key, $value, $expiry = 0 ) {
00114         wfProfileIn( __METHOD__ );
00115         $expiry = $this->convertExpiry( $expiry );
00116         $ttl = $expiry ? $expiry - time() : 2147483647;
00117         $blob = serialize( $value );
00118         if ( strlen( $blob ) > 100 ) {
00119             $blob = gzdeflate( $blob );
00120             $contentType = 'application/vnd.php.serialized+deflate';
00121         } else {
00122             $contentType = 'application/vnd.php.serialized';
00123         }
00124 
00125         $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
00126 
00127         if ( $code == 404 ) {
00128             // Maybe the cache does not exist yet, let's try creating it
00129             if ( !$this->createCache( $key ) ) {
00130                 wfDebug( __METHOD__ . ": cache creation failed\n" );
00131                 wfProfileOut( __METHOD__ );
00132                 return false;
00133             }
00134             $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
00135         }
00136 
00137         $result = false;
00138         if ( !$code ) {
00139             wfDebug( __METHOD__ . ": PUT failure for key $key\n" );
00140         } elseif ( $code >= 300 ) {
00141             wfDebug( __METHOD__ . ": PUT failure for key $key: HTTP $code\n" );
00142         } else {
00143             $result = true;
00144         }
00145 
00146         wfProfileOut( __METHOD__ );
00147         return $result;
00148     }
00149 
00157     public function cas( $casToken, $key, $value, $exptime = 0 ) {
00158         // Not sure if we can implement CAS for ehcache. There appears to be CAS-support per
00159         // http://ehcache.org/documentation/get-started/consistency-options#cas-cache-operations,
00160         // but I can't find any docs for our current implementation.
00161         throw new MWException( "CAS is not implemented in " . __CLASS__ );
00162     }
00163 
00169     public function delete( $key, $time = 0 ) {
00170         wfProfileIn( __METHOD__ );
00171         $response = $this->doItemRequest( $key,
00172             array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
00173         $code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
00174         if ( !$response || ( $code != 404 && $code >= 300 ) ) {
00175             wfDebug( __METHOD__ . ": DELETE failure for key $key\n" );
00176             $result = false;
00177         } else {
00178             $result = true;
00179         }
00180         wfProfileOut( __METHOD__ );
00181         return $result;
00182     }
00183 
00188     public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
00189         return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
00190     }
00191 
00196     protected function getCacheUrl( $key ) {
00197         if ( count( $this->servers ) == 1 ) {
00198             $server = reset( $this->servers );
00199         } else {
00200             // Use consistent hashing
00201             $hashes = array();
00202             foreach ( $this->servers as $server ) {
00203                 $hashes[$server] = md5( $server . '/' . $key );
00204             }
00205             asort( $hashes );
00206             reset( $hashes );
00207             $server = key( $hashes );
00208         }
00209         return "http://$server/ehcache/rest/{$this->cacheName}";
00210     }
00211 
00216     protected function getCurl( $cacheUrl ) {
00217         if ( !isset( $this->curls[$cacheUrl] ) ) {
00218             $this->curls[$cacheUrl] = curl_init();
00219         }
00220         return $this->curls[$cacheUrl];
00221     }
00222 
00230     protected function attemptPut( $key, $data, $type, $ttl ) {
00231         // In initial benchmarking, it was 30 times faster to use CURLOPT_POST
00232         // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
00233         // CURLOPT_UPLOAD was pushing the request headers first, then waiting
00234         // for an ACK packet, then sending the data, whereas CURLOPT_POST just
00235         // sends the headers and the data in a single send().
00236         $response = $this->doItemRequest( $key,
00237             array(
00238                 CURLOPT_POST => 1,
00239                 CURLOPT_CUSTOMREQUEST => 'PUT',
00240                 CURLOPT_POSTFIELDS => $data,
00241                 CURLOPT_HTTPHEADER => array(
00242                     'Content-Type: ' . $type,
00243                     'ehcacheTimeToLiveSeconds: ' . $ttl
00244                 )
00245             )
00246         );
00247         if ( !$response ) {
00248             return 0;
00249         } else {
00250             return $response['http_code'];
00251         }
00252     }
00253 
00258     protected function createCache( $key ) {
00259         wfDebug( __METHOD__ . ": creating cache for $key\n" );
00260         $response = $this->doCacheRequest( $key,
00261             array(
00262                 CURLOPT_POST => 1,
00263                 CURLOPT_CUSTOMREQUEST => 'PUT',
00264                 CURLOPT_POSTFIELDS => '',
00265             ) );
00266         if ( !$response ) {
00267             wfDebug( __CLASS__ . ": failed to create cache for $key\n" );
00268             return false;
00269         }
00270         return ( $response['http_code'] == 201 /* created */
00271             || $response['http_code'] == 409 /* already there */ );
00272     }
00273 
00279     protected function doCacheRequest( $key, $curlOptions = array() ) {
00280         $cacheUrl = $this->getCacheUrl( $key );
00281         $curl = $this->getCurl( $cacheUrl );
00282         return $this->doRequest( $curl, $cacheUrl, $curlOptions );
00283     }
00284 
00290     protected function doItemRequest( $key, $curlOptions = array() ) {
00291         $cacheUrl = $this->getCacheUrl( $key );
00292         $curl = $this->getCurl( $cacheUrl );
00293         $url = $cacheUrl . '/' . rawurlencode( $key );
00294         return $this->doRequest( $curl, $url, $curlOptions );
00295     }
00296 
00304     protected function doRequest( $curl, $url, $curlOptions = array() ) {
00305         if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
00306             // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
00307             throw new MWException( __METHOD__ . ": to prevent options set in one doRequest() " .
00308                 "call from affecting subsequent doRequest() calls, only options listed " .
00309                 "in \$this->curlOptions may be specified in the \$curlOptions parameter." );
00310         }
00311         $curlOptions += $this->curlOptions;
00312         $curlOptions[CURLOPT_URL] = $url;
00313 
00314         curl_setopt_array( $curl, $curlOptions );
00315         $result = curl_exec( $curl );
00316         if ( $result === false ) {
00317             wfDebug( __CLASS__ . ": curl error: " . curl_error( $curl ) . "\n" );
00318             return false;
00319         }
00320         $info = curl_getinfo( $curl );
00321         $info['body'] = $result;
00322         return $info;
00323     }
00324 }