Index: trunk/phase3/includes/objectcache/MemcachedPhpBagOStuff.php |
— | — | @@ -102,7 +102,7 @@ |
103 | 103 | } |
104 | 104 | |
105 | 105 | protected function encodeKeyCallback( $m ) { |
106 | | - return urlencode( $m[0] ); |
| 106 | + return rawurlencode( $m[0] ); |
107 | 107 | } |
108 | 108 | |
109 | 109 | /** |
Index: trunk/phase3/includes/objectcache/EhcacheBagOStuff.php |
— | — | @@ -0,0 +1,226 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class EhcacheBagOStuff extends BagOStuff { |
| 5 | + var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions, |
| 6 | + $requestData, $requestDataPos; |
| 7 | + |
| 8 | + var $curls = array(); |
| 9 | + |
| 10 | + function __construct( $params ) { |
| 11 | + if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) { |
| 12 | + throw new MWException( __CLASS__.' requires curl version 7.16.2 or later.' ); |
| 13 | + } |
| 14 | + if ( !extension_loaded( 'zlib' ) ) { |
| 15 | + throw new MWException( __CLASS__.' requires the zlib extension' ); |
| 16 | + } |
| 17 | + if ( !isset( $params['servers'] ) ) { |
| 18 | + throw new MWException( __METHOD__.': servers parameter is required' ); |
| 19 | + } |
| 20 | + $this->servers = $params['servers']; |
| 21 | + $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw'; |
| 22 | + $this->connectTimeout = isset( $params['connectTimeout'] ) |
| 23 | + ? $params['connectTimeout'] : 1; |
| 24 | + $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1; |
| 25 | + $this->curlOptions = array( |
| 26 | + CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ), |
| 27 | + CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ), |
| 28 | + CURLOPT_RETURNTRANSFER => 1, |
| 29 | + CURLOPT_CUSTOMREQUEST => 'GET', |
| 30 | + CURLOPT_POST => 0, |
| 31 | + CURLOPT_POSTFIELDS => '', |
| 32 | + CURLOPT_HTTPHEADER => array(), |
| 33 | + ); |
| 34 | + } |
| 35 | + |
| 36 | + public function get( $key ) { |
| 37 | + wfProfileIn( __METHOD__ ); |
| 38 | + $response = $this->doItemRequest( $key ); |
| 39 | + if ( !$response || $response['http_code'] == 404 ) { |
| 40 | + wfProfileOut( __METHOD__ ); |
| 41 | + return false; |
| 42 | + } |
| 43 | + if ( $response['http_code'] >= 300 ) { |
| 44 | + wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" ); |
| 45 | + wfProfileOut( __METHOD__ ); |
| 46 | + return false; |
| 47 | + } |
| 48 | + $body = $response['body']; |
| 49 | + $type = $response['content_type']; |
| 50 | + if ( $type == 'application/vnd.php.serialized+deflate' ) { |
| 51 | + $body = gzinflate( $body ); |
| 52 | + if ( !$body ) { |
| 53 | + wfDebug( __METHOD__.": error inflating $key\n" ); |
| 54 | + wfProfileOut( __METHOD__ ); |
| 55 | + return false; |
| 56 | + } |
| 57 | + $data = unserialize( $body ); |
| 58 | + } elseif ( $type == 'application/vnd.php.serialized' ) { |
| 59 | + $data = unserialize( $body ); |
| 60 | + } else { |
| 61 | + wfDebug( __METHOD__.": unknown content type \"$type\"\n" ); |
| 62 | + wfProfileOut( __METHOD__ ); |
| 63 | + return false; |
| 64 | + } |
| 65 | + |
| 66 | + wfProfileOut( __METHOD__ ); |
| 67 | + return $data; |
| 68 | + } |
| 69 | + |
| 70 | + public function set( $key, $value, $expiry = 0 ) { |
| 71 | + wfProfileIn( __METHOD__ ); |
| 72 | + $expiry = $this->convertExpiry( $expiry ); |
| 73 | + $ttl = $expiry ? $expiry - time() : 2147483647; |
| 74 | + $blob = serialize( $value ); |
| 75 | + if ( strlen( $blob ) > 100 ) { |
| 76 | + $blob = gzdeflate( $blob ); |
| 77 | + $contentType = 'application/vnd.php.serialized+deflate'; |
| 78 | + } else { |
| 79 | + $contentType = 'application/vnd.php.serialized'; |
| 80 | + } |
| 81 | + |
| 82 | + $code = $this->attemptPut( $key, $blob, $contentType, $ttl ); |
| 83 | + |
| 84 | + if ( $code == 404 ) { |
| 85 | + // Maybe the cache does not exist yet, let's try creating it |
| 86 | + if ( !$this->createCache( $key ) ) { |
| 87 | + wfDebug( __METHOD__.": cache creation failed\n" ); |
| 88 | + wfProfileOut( __METHOD__ ); |
| 89 | + return false; |
| 90 | + } |
| 91 | + $code = $this->attemptPut( $key, $blob, $contentType, $ttl ); |
| 92 | + } |
| 93 | + |
| 94 | + $result = false; |
| 95 | + if ( !$code ) { |
| 96 | + wfDebug( __METHOD__.": PUT failure for key $key\n" ); |
| 97 | + } elseif ( $code >= 300 ) { |
| 98 | + wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" ); |
| 99 | + } else { |
| 100 | + $result = true; |
| 101 | + } |
| 102 | + |
| 103 | + wfProfileOut( __METHOD__ ); |
| 104 | + return $result; |
| 105 | + } |
| 106 | + |
| 107 | + public function delete( $key, $time = 0 ) { |
| 108 | + wfProfileIn( __METHOD__ ); |
| 109 | + $response = $this->doItemRequest( $key, |
| 110 | + array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) ); |
| 111 | + $code = isset( $response['http_code'] ) ? $response['http_code'] : 0; |
| 112 | + if ( !$response || ( $code != 404 && $code >= 300 ) ) { |
| 113 | + wfDebug( __METHOD__.": DELETE failure for key $key\n" ); |
| 114 | + $result = false; |
| 115 | + } else { |
| 116 | + $result = true; |
| 117 | + } |
| 118 | + wfProfileOut( __METHOD__ ); |
| 119 | + return $result; |
| 120 | + } |
| 121 | + |
| 122 | + protected function getCacheUrl( $key ) { |
| 123 | + if ( count( $this->servers ) == 1 ) { |
| 124 | + $server = reset( $this->servers ); |
| 125 | + } else { |
| 126 | + // Use consistent hashing |
| 127 | + $hashes = array(); |
| 128 | + foreach ( $this->servers as $server ) { |
| 129 | + $hashes[$server] = md5( $server . '/' . $key ); |
| 130 | + } |
| 131 | + asort( $hashes ); |
| 132 | + reset( $hashes ); |
| 133 | + $server = key( $hashes ); |
| 134 | + } |
| 135 | + return "http://$server/ehcache/rest/{$this->cacheName}"; |
| 136 | + } |
| 137 | + |
| 138 | + /** |
| 139 | + * Get a cURL handle for the given cache URL. |
| 140 | + * We cache the handles to allow keepalive. |
| 141 | + */ |
| 142 | + protected function getCurl( $cacheUrl ) { |
| 143 | + if ( !isset( $this->curls[$cacheUrl] ) ) { |
| 144 | + $this->curls[$cacheUrl] = curl_init(); |
| 145 | + } |
| 146 | + return $this->curls[$cacheUrl]; |
| 147 | + } |
| 148 | + |
| 149 | + protected function attemptPut( $key, $data, $type, $ttl ) { |
| 150 | + // In initial benchmarking, it was 30 times faster to use CURLOPT_POST |
| 151 | + // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because |
| 152 | + // CURLOPT_UPLOAD was pushing the request headers first, then waiting |
| 153 | + // for an ACK packet, then sending the data, whereas CURLOPT_POST just |
| 154 | + // sends the headers and the data in a single send(). |
| 155 | + $response = $this->doItemRequest( $key, |
| 156 | + array( |
| 157 | + CURLOPT_POST => 1, |
| 158 | + CURLOPT_CUSTOMREQUEST => 'PUT', |
| 159 | + CURLOPT_POSTFIELDS => $data, |
| 160 | + CURLOPT_HTTPHEADER => array( |
| 161 | + 'Content-Type: ' . $type, |
| 162 | + 'ehcacheTimeToLiveSeconds: ' . $ttl |
| 163 | + ) |
| 164 | + ) |
| 165 | + ); |
| 166 | + if ( !$response ) { |
| 167 | + return 0; |
| 168 | + } else { |
| 169 | + return $response['http_code']; |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + protected function createCache( $key ) { |
| 174 | + wfDebug( __METHOD__.": creating cache for $key\n" ); |
| 175 | + $response = $this->doCacheRequest( $key, |
| 176 | + array( |
| 177 | + CURLOPT_POST => 1, |
| 178 | + CURLOPT_CUSTOMREQUEST => 'PUT', |
| 179 | + CURLOPT_POSTFIELDS => '', |
| 180 | + ) ); |
| 181 | + if ( !$response ) { |
| 182 | + wfDebug( __CLASS__.": failed to create cache for $key\n" ); |
| 183 | + return false; |
| 184 | + } |
| 185 | + if ( $response['http_code'] == 201 /* created */ |
| 186 | + || $response['http_code'] == 409 /* already there */ ) |
| 187 | + { |
| 188 | + return true; |
| 189 | + } else { |
| 190 | + return false; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + protected function doCacheRequest( $key, $curlOptions = array() ) { |
| 195 | + $cacheUrl = $this->getCacheUrl( $key ); |
| 196 | + $curl = $this->getCurl( $cacheUrl ); |
| 197 | + return $this->doRequest( $curl, $cacheUrl, $curlOptions ); |
| 198 | + } |
| 199 | + |
| 200 | + protected function doItemRequest( $key, $curlOptions = array() ) { |
| 201 | + $cacheUrl = $this->getCacheUrl( $key ); |
| 202 | + $curl = $this->getCurl( $cacheUrl ); |
| 203 | + $url = $cacheUrl . '/' . rawurlencode( $key ); |
| 204 | + return $this->doRequest( $curl, $url, $curlOptions ); |
| 205 | + } |
| 206 | + |
| 207 | + protected function doRequest( $curl, $url, $curlOptions = array() ) { |
| 208 | + if ( array_diff_key( $curlOptions, $this->curlOptions ) ) { |
| 209 | + var_dump( array_diff_key( $curlOptions, $this->curlOptions ) ); |
| 210 | + throw new MWException( __METHOD__.": to prevent options set in one doRequest() " . |
| 211 | + "call from affecting subsequent doRequest() calls, only options listed " . |
| 212 | + "in \$this->curlOptions may be specified in the \$curlOptions parameter." ); |
| 213 | + } |
| 214 | + $curlOptions += $this->curlOptions; |
| 215 | + $curlOptions[CURLOPT_URL] = $url; |
| 216 | + |
| 217 | + curl_setopt_array( $curl, $curlOptions ); |
| 218 | + $result = curl_exec( $curl ); |
| 219 | + if ( $result === false ) { |
| 220 | + wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" ); |
| 221 | + return false; |
| 222 | + } |
| 223 | + $info = curl_getinfo( $curl ); |
| 224 | + $info['body'] = $result; |
| 225 | + return $info; |
| 226 | + } |
| 227 | +} |
Property changes on: trunk/phase3/includes/objectcache/EhcacheBagOStuff.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 228 | + native |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -505,6 +505,7 @@ |
506 | 506 | 'BagOStuff' => 'includes/objectcache/BagOStuff.php', |
507 | 507 | 'DBABagOStuff' => 'includes/objectcache/DBABagOStuff.php', |
508 | 508 | 'eAccelBagOStuff' => 'includes/objectcache/eAccelBagOStuff.php', |
| 509 | + 'EhcacheBagOStuff' => 'includes/objectcache/EhcacheBagOStuff.php', |
509 | 510 | 'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php', |
510 | 511 | 'FakeMemCachedClient' => 'includes/objectcache/EmptyBagOStuff.php', |
511 | 512 | 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php', |