From: Tim Starling Date: Fri, 4 Mar 2011 06:01:30 +0000 (+0000) Subject: * Added an Ehcache client. X-Git-Tag: 1.31.0-rc.0~31659 X-Git-Url: http://git.cyclocoop.org/ecrire?a=commitdiff_plain;h=f509ce8060cbbf0a4c5e8d99c9e5fd0e15be5936;p=lhc%2Fweb%2Fwiklou.git * Added an Ehcache client. * Fixed encoding of spaces in memcached keys, in r83140 they would have been encoded as "+". --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 961f180afd..1fe21fd306 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -505,6 +505,7 @@ $wgAutoloadLocalClasses = array( 'BagOStuff' => 'includes/objectcache/BagOStuff.php', 'DBABagOStuff' => 'includes/objectcache/DBABagOStuff.php', 'eAccelBagOStuff' => 'includes/objectcache/eAccelBagOStuff.php', + 'EhcacheBagOStuff' => 'includes/objectcache/EhcacheBagOStuff.php', 'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php', 'FakeMemCachedClient' => 'includes/objectcache/EmptyBagOStuff.php', 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php', diff --git a/includes/objectcache/EhcacheBagOStuff.php b/includes/objectcache/EhcacheBagOStuff.php new file mode 100644 index 0000000000..2ac013a0e1 --- /dev/null +++ b/includes/objectcache/EhcacheBagOStuff.php @@ -0,0 +1,226 @@ +servers = $params['servers']; + $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw'; + $this->connectTimeout = isset( $params['connectTimeout'] ) + ? $params['connectTimeout'] : 1; + $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1; + $this->curlOptions = array( + CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ), + CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ), + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_POST => 0, + CURLOPT_POSTFIELDS => '', + CURLOPT_HTTPHEADER => array(), + ); + } + + public function get( $key ) { + wfProfileIn( __METHOD__ ); + $response = $this->doItemRequest( $key ); + if ( !$response || $response['http_code'] == 404 ) { + wfProfileOut( __METHOD__ ); + return false; + } + if ( $response['http_code'] >= 300 ) { + wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" ); + wfProfileOut( __METHOD__ ); + return false; + } + $body = $response['body']; + $type = $response['content_type']; + if ( $type == 'application/vnd.php.serialized+deflate' ) { + $body = gzinflate( $body ); + if ( !$body ) { + wfDebug( __METHOD__.": error inflating $key\n" ); + wfProfileOut( __METHOD__ ); + return false; + } + $data = unserialize( $body ); + } elseif ( $type == 'application/vnd.php.serialized' ) { + $data = unserialize( $body ); + } else { + wfDebug( __METHOD__.": unknown content type \"$type\"\n" ); + wfProfileOut( __METHOD__ ); + return false; + } + + wfProfileOut( __METHOD__ ); + return $data; + } + + public function set( $key, $value, $expiry = 0 ) { + wfProfileIn( __METHOD__ ); + $expiry = $this->convertExpiry( $expiry ); + $ttl = $expiry ? $expiry - time() : 2147483647; + $blob = serialize( $value ); + if ( strlen( $blob ) > 100 ) { + $blob = gzdeflate( $blob ); + $contentType = 'application/vnd.php.serialized+deflate'; + } else { + $contentType = 'application/vnd.php.serialized'; + } + + $code = $this->attemptPut( $key, $blob, $contentType, $ttl ); + + if ( $code == 404 ) { + // Maybe the cache does not exist yet, let's try creating it + if ( !$this->createCache( $key ) ) { + wfDebug( __METHOD__.": cache creation failed\n" ); + wfProfileOut( __METHOD__ ); + return false; + } + $code = $this->attemptPut( $key, $blob, $contentType, $ttl ); + } + + $result = false; + if ( !$code ) { + wfDebug( __METHOD__.": PUT failure for key $key\n" ); + } elseif ( $code >= 300 ) { + wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" ); + } else { + $result = true; + } + + wfProfileOut( __METHOD__ ); + return $result; + } + + public function delete( $key, $time = 0 ) { + wfProfileIn( __METHOD__ ); + $response = $this->doItemRequest( $key, + array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) ); + $code = isset( $response['http_code'] ) ? $response['http_code'] : 0; + if ( !$response || ( $code != 404 && $code >= 300 ) ) { + wfDebug( __METHOD__.": DELETE failure for key $key\n" ); + $result = false; + } else { + $result = true; + } + wfProfileOut( __METHOD__ ); + return $result; + } + + protected function getCacheUrl( $key ) { + if ( count( $this->servers ) == 1 ) { + $server = reset( $this->servers ); + } else { + // Use consistent hashing + $hashes = array(); + foreach ( $this->servers as $server ) { + $hashes[$server] = md5( $server . '/' . $key ); + } + asort( $hashes ); + reset( $hashes ); + $server = key( $hashes ); + } + return "http://$server/ehcache/rest/{$this->cacheName}"; + } + + /** + * Get a cURL handle for the given cache URL. + * We cache the handles to allow keepalive. + */ + protected function getCurl( $cacheUrl ) { + if ( !isset( $this->curls[$cacheUrl] ) ) { + $this->curls[$cacheUrl] = curl_init(); + } + return $this->curls[$cacheUrl]; + } + + protected function attemptPut( $key, $data, $type, $ttl ) { + // In initial benchmarking, it was 30 times faster to use CURLOPT_POST + // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because + // CURLOPT_UPLOAD was pushing the request headers first, then waiting + // for an ACK packet, then sending the data, whereas CURLOPT_POST just + // sends the headers and the data in a single send(). + $response = $this->doItemRequest( $key, + array( + CURLOPT_POST => 1, + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_POSTFIELDS => $data, + CURLOPT_HTTPHEADER => array( + 'Content-Type: ' . $type, + 'ehcacheTimeToLiveSeconds: ' . $ttl + ) + ) + ); + if ( !$response ) { + return 0; + } else { + return $response['http_code']; + } + } + + protected function createCache( $key ) { + wfDebug( __METHOD__.": creating cache for $key\n" ); + $response = $this->doCacheRequest( $key, + array( + CURLOPT_POST => 1, + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_POSTFIELDS => '', + ) ); + if ( !$response ) { + wfDebug( __CLASS__.": failed to create cache for $key\n" ); + return false; + } + if ( $response['http_code'] == 201 /* created */ + || $response['http_code'] == 409 /* already there */ ) + { + return true; + } else { + return false; + } + } + + protected function doCacheRequest( $key, $curlOptions = array() ) { + $cacheUrl = $this->getCacheUrl( $key ); + $curl = $this->getCurl( $cacheUrl ); + return $this->doRequest( $curl, $cacheUrl, $curlOptions ); + } + + protected function doItemRequest( $key, $curlOptions = array() ) { + $cacheUrl = $this->getCacheUrl( $key ); + $curl = $this->getCurl( $cacheUrl ); + $url = $cacheUrl . '/' . rawurlencode( $key ); + return $this->doRequest( $curl, $url, $curlOptions ); + } + + protected function doRequest( $curl, $url, $curlOptions = array() ) { + if ( array_diff_key( $curlOptions, $this->curlOptions ) ) { + var_dump( array_diff_key( $curlOptions, $this->curlOptions ) ); + throw new MWException( __METHOD__.": to prevent options set in one doRequest() " . + "call from affecting subsequent doRequest() calls, only options listed " . + "in \$this->curlOptions may be specified in the \$curlOptions parameter." ); + } + $curlOptions += $this->curlOptions; + $curlOptions[CURLOPT_URL] = $url; + + curl_setopt_array( $curl, $curlOptions ); + $result = curl_exec( $curl ); + if ( $result === false ) { + wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" ); + return false; + } + $info = curl_getinfo( $curl ); + $info['body'] = $result; + return $info; + } +} diff --git a/includes/objectcache/MemcachedPhpBagOStuff.php b/includes/objectcache/MemcachedPhpBagOStuff.php index 4538bb0d51..7bac31964d 100644 --- a/includes/objectcache/MemcachedPhpBagOStuff.php +++ b/includes/objectcache/MemcachedPhpBagOStuff.php @@ -102,7 +102,7 @@ class MemcachedPhpBagOStuff extends BagOStuff { } protected function encodeKeyCallback( $m ) { - return urlencode( $m[0] ); + return rawurlencode( $m[0] ); } /**