MediaWiki
REL1_22
|
00001 <?php 00066 // {{{ requirements 00067 // }}} 00068 00069 // {{{ class MWMemcached 00076 class MWMemcached { 00077 // {{{ properties 00078 // {{{ public 00079 00080 // {{{ constants 00081 // {{{ flags 00082 00086 const SERIALIZED = 1; 00087 00091 const COMPRESSED = 2; 00092 00093 // }}} 00094 00098 const COMPRESSION_SAVINGS = 0.20; 00099 00100 // }}} 00101 00108 var $stats; 00109 00110 // }}} 00111 // {{{ private 00112 00119 var $_cache_sock; 00120 00127 var $_debug; 00128 00135 var $_host_dead; 00136 00143 var $_have_zlib; 00144 00151 var $_compress_enable; 00152 00159 var $_compress_threshold; 00160 00167 var $_persistent; 00168 00175 var $_single_sock; 00176 00183 var $_servers; 00184 00191 var $_buckets; 00192 00199 var $_bucketcount; 00200 00207 var $_active; 00208 00215 var $_timeout_seconds; 00216 00223 var $_timeout_microseconds; 00224 00228 var $_connect_timeout; 00229 00233 var $_connect_attempts; 00234 00235 // }}} 00236 // }}} 00237 // {{{ methods 00238 // {{{ public functions 00239 // {{{ memcached() 00240 00248 public function __construct( $args ) { 00249 $this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() ); 00250 $this->_debug = isset( $args['debug'] ) ? $args['debug'] : false; 00251 $this->stats = array(); 00252 $this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0; 00253 $this->_persistent = isset( $args['persistent'] ) ? $args['persistent'] : false; 00254 $this->_compress_enable = true; 00255 $this->_have_zlib = function_exists( 'gzcompress' ); 00256 00257 $this->_cache_sock = array(); 00258 $this->_host_dead = array(); 00259 00260 $this->_timeout_seconds = 0; 00261 $this->_timeout_microseconds = isset( $args['timeout'] ) ? $args['timeout'] : 500000; 00262 00263 $this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1; 00264 $this->_connect_attempts = 2; 00265 } 00266 00267 // }}} 00268 // {{{ add() 00269 00284 public function add( $key, $val, $exp = 0 ) { 00285 return $this->_set( 'add', $key, $val, $exp ); 00286 } 00287 00288 // }}} 00289 // {{{ decr() 00290 00299 public function decr( $key, $amt = 1 ) { 00300 return $this->_incrdecr( 'decr', $key, $amt ); 00301 } 00302 00303 // }}} 00304 // {{{ delete() 00305 00314 public function delete( $key, $time = 0 ) { 00315 if ( !$this->_active ) { 00316 return false; 00317 } 00318 00319 $sock = $this->get_sock( $key ); 00320 if ( !is_resource( $sock ) ) { 00321 return false; 00322 } 00323 00324 $key = is_array( $key ) ? $key[1] : $key; 00325 00326 if ( isset( $this->stats['delete'] ) ) { 00327 $this->stats['delete']++; 00328 } else { 00329 $this->stats['delete'] = 1; 00330 } 00331 $cmd = "delete $key $time\r\n"; 00332 if ( !$this->_fwrite( $sock, $cmd ) ) { 00333 return false; 00334 } 00335 $res = $this->_fgets( $sock ); 00336 00337 if ( $this->_debug ) { 00338 $this->_debugprint( sprintf( "MemCache: delete %s (%s)\n", $key, $res ) ); 00339 } 00340 00341 if ( $res == "DELETED" || $res == "NOT_FOUND" ) { 00342 return true; 00343 } 00344 00345 return false; 00346 } 00347 00353 public function lock( $key, $timeout = 0 ) { 00354 /* stub */ 00355 return true; 00356 } 00357 00362 public function unlock( $key ) { 00363 /* stub */ 00364 return true; 00365 } 00366 00367 // }}} 00368 // {{{ disconnect_all() 00369 00373 public function disconnect_all() { 00374 foreach ( $this->_cache_sock as $sock ) { 00375 fclose( $sock ); 00376 } 00377 00378 $this->_cache_sock = array(); 00379 } 00380 00381 // }}} 00382 // {{{ enable_compress() 00383 00389 public function enable_compress( $enable ) { 00390 $this->_compress_enable = $enable; 00391 } 00392 00393 // }}} 00394 // {{{ forget_dead_hosts() 00395 00399 public function forget_dead_hosts() { 00400 $this->_host_dead = array(); 00401 } 00402 00403 // }}} 00404 // {{{ get() 00405 00414 public function get( $key, &$casToken = null ) { 00415 wfProfileIn( __METHOD__ ); 00416 00417 if ( $this->_debug ) { 00418 $this->_debugprint( "get($key)\n" ); 00419 } 00420 00421 if ( !$this->_active ) { 00422 wfProfileOut( __METHOD__ ); 00423 return false; 00424 } 00425 00426 $sock = $this->get_sock( $key ); 00427 00428 if ( !is_resource( $sock ) ) { 00429 wfProfileOut( __METHOD__ ); 00430 return false; 00431 } 00432 00433 $key = is_array( $key ) ? $key[1] : $key; 00434 if ( isset( $this->stats['get'] ) ) { 00435 $this->stats['get']++; 00436 } else { 00437 $this->stats['get'] = 1; 00438 } 00439 00440 $cmd = "gets $key\r\n"; 00441 if ( !$this->_fwrite( $sock, $cmd ) ) { 00442 wfProfileOut( __METHOD__ ); 00443 return false; 00444 } 00445 00446 $val = array(); 00447 $this->_load_items( $sock, $val, $casToken ); 00448 00449 if ( $this->_debug ) { 00450 foreach ( $val as $k => $v ) { 00451 $this->_debugprint( sprintf( "MemCache: sock %s got %s\n", serialize( $sock ), $k ) ); 00452 } 00453 } 00454 00455 $value = false; 00456 if ( isset( $val[$key] ) ) { 00457 $value = $val[$key]; 00458 } 00459 wfProfileOut( __METHOD__ ); 00460 return $value; 00461 } 00462 00463 // }}} 00464 // {{{ get_multi() 00465 00473 public function get_multi( $keys ) { 00474 if ( !$this->_active ) { 00475 return false; 00476 } 00477 00478 if ( isset( $this->stats['get_multi'] ) ) { 00479 $this->stats['get_multi']++; 00480 } else { 00481 $this->stats['get_multi'] = 1; 00482 } 00483 $sock_keys = array(); 00484 $socks = array(); 00485 foreach ( $keys as $key ) { 00486 $sock = $this->get_sock( $key ); 00487 if ( !is_resource( $sock ) ) { 00488 continue; 00489 } 00490 $key = is_array( $key ) ? $key[1] : $key; 00491 if ( !isset( $sock_keys[$sock] ) ) { 00492 $sock_keys[intval( $sock )] = array(); 00493 $socks[] = $sock; 00494 } 00495 $sock_keys[intval( $sock )][] = $key; 00496 } 00497 00498 $gather = array(); 00499 // Send out the requests 00500 foreach ( $socks as $sock ) { 00501 $cmd = 'gets'; 00502 foreach ( $sock_keys[intval( $sock )] as $key ) { 00503 $cmd .= ' ' . $key; 00504 } 00505 $cmd .= "\r\n"; 00506 00507 if ( $this->_fwrite( $sock, $cmd ) ) { 00508 $gather[] = $sock; 00509 } 00510 } 00511 00512 // Parse responses 00513 $val = array(); 00514 foreach ( $gather as $sock ) { 00515 $this->_load_items( $sock, $val, $casToken ); 00516 } 00517 00518 if ( $this->_debug ) { 00519 foreach ( $val as $k => $v ) { 00520 $this->_debugprint( sprintf( "MemCache: got %s\n", $k ) ); 00521 } 00522 } 00523 00524 return $val; 00525 } 00526 00527 // }}} 00528 // {{{ incr() 00529 00540 public function incr( $key, $amt = 1 ) { 00541 return $this->_incrdecr( 'incr', $key, $amt ); 00542 } 00543 00544 // }}} 00545 // {{{ replace() 00546 00560 public function replace( $key, $value, $exp = 0 ) { 00561 return $this->_set( 'replace', $key, $value, $exp ); 00562 } 00563 00564 // }}} 00565 // {{{ run_command() 00566 00576 public function run_command( $sock, $cmd ) { 00577 if ( !is_resource( $sock ) ) { 00578 return array(); 00579 } 00580 00581 if ( !$this->_fwrite( $sock, $cmd ) ) { 00582 return array(); 00583 } 00584 00585 $ret = array(); 00586 while ( true ) { 00587 $res = $this->_fgets( $sock ); 00588 $ret[] = $res; 00589 if ( preg_match( '/^END/', $res ) ) { 00590 break; 00591 } 00592 if ( strlen( $res ) == 0 ) { 00593 break; 00594 } 00595 } 00596 return $ret; 00597 } 00598 00599 // }}} 00600 // {{{ set() 00601 00616 public function set( $key, $value, $exp = 0 ) { 00617 return $this->_set( 'set', $key, $value, $exp ); 00618 } 00619 00620 // }}} 00621 // {{{ cas() 00622 00638 public function cas( $casToken, $key, $value, $exp = 0 ) { 00639 return $this->_set( 'cas', $key, $value, $exp, $casToken ); 00640 } 00641 00642 // }}} 00643 // {{{ set_compress_threshold() 00644 00650 public function set_compress_threshold( $thresh ) { 00651 $this->_compress_threshold = $thresh; 00652 } 00653 00654 // }}} 00655 // {{{ set_debug() 00656 00664 public function set_debug( $dbg ) { 00665 $this->_debug = $dbg; 00666 } 00667 00668 // }}} 00669 // {{{ set_servers() 00670 00678 public function set_servers( $list ) { 00679 $this->_servers = $list; 00680 $this->_active = count( $list ); 00681 $this->_buckets = null; 00682 $this->_bucketcount = 0; 00683 00684 $this->_single_sock = null; 00685 if ( $this->_active == 1 ) { 00686 $this->_single_sock = $this->_servers[0]; 00687 } 00688 } 00689 00696 public function set_timeout( $seconds, $microseconds ) { 00697 $this->_timeout_seconds = $seconds; 00698 $this->_timeout_microseconds = $microseconds; 00699 } 00700 00701 // }}} 00702 // }}} 00703 // {{{ private methods 00704 // {{{ _close_sock() 00705 00713 function _close_sock( $sock ) { 00714 $host = array_search( $sock, $this->_cache_sock ); 00715 fclose( $this->_cache_sock[$host] ); 00716 unset( $this->_cache_sock[$host] ); 00717 } 00718 00719 // }}} 00720 // {{{ _connect_sock() 00721 00731 function _connect_sock( &$sock, $host ) { 00732 list( $ip, $port ) = explode( ':', $host ); 00733 $sock = false; 00734 $timeout = $this->_connect_timeout; 00735 $errno = $errstr = null; 00736 for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) { 00737 wfSuppressWarnings(); 00738 if ( $this->_persistent == 1 ) { 00739 $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout ); 00740 } else { 00741 $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout ); 00742 } 00743 wfRestoreWarnings(); 00744 } 00745 if ( !$sock ) { 00746 $this->_error_log( "Error connecting to $host: $errstr\n" ); 00747 $this->_dead_host( $host ); 00748 return false; 00749 } 00750 00751 // Initialise timeout 00752 stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds ); 00753 00754 // If the connection was persistent, flush the read buffer in case there 00755 // was a previous incomplete request on this connection 00756 if ( $this->_persistent ) { 00757 $this->_flush_read_buffer( $sock ); 00758 } 00759 return true; 00760 } 00761 00762 // }}} 00763 // {{{ _dead_sock() 00764 00772 function _dead_sock( $sock ) { 00773 $host = array_search( $sock, $this->_cache_sock ); 00774 $this->_dead_host( $host ); 00775 } 00776 00780 function _dead_host( $host ) { 00781 $parts = explode( ':', $host ); 00782 $ip = $parts[0]; 00783 $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) ); 00784 $this->_host_dead[$host] = $this->_host_dead[$ip]; 00785 unset( $this->_cache_sock[$host] ); 00786 } 00787 00788 // }}} 00789 // {{{ get_sock() 00790 00799 function get_sock( $key ) { 00800 if ( !$this->_active ) { 00801 return false; 00802 } 00803 00804 if ( $this->_single_sock !== null ) { 00805 return $this->sock_to_host( $this->_single_sock ); 00806 } 00807 00808 $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key ); 00809 if ( $this->_buckets === null ) { 00810 $bu = array(); 00811 foreach ( $this->_servers as $v ) { 00812 if ( is_array( $v ) ) { 00813 for ( $i = 0; $i < $v[1]; $i++ ) { 00814 $bu[] = $v[0]; 00815 } 00816 } else { 00817 $bu[] = $v; 00818 } 00819 } 00820 $this->_buckets = $bu; 00821 $this->_bucketcount = count( $bu ); 00822 } 00823 00824 $realkey = is_array( $key ) ? $key[1] : $key; 00825 for ( $tries = 0; $tries < 20; $tries++ ) { 00826 $host = $this->_buckets[$hv % $this->_bucketcount]; 00827 $sock = $this->sock_to_host( $host ); 00828 if ( is_resource( $sock ) ) { 00829 return $sock; 00830 } 00831 $hv = $this->_hashfunc( $hv . $realkey ); 00832 } 00833 00834 return false; 00835 } 00836 00837 // }}} 00838 // {{{ _hashfunc() 00839 00848 function _hashfunc( $key ) { 00849 # Hash function must be in [0,0x7ffffff] 00850 # We take the first 31 bits of the MD5 hash, which unlike the hash 00851 # function used in a previous version of this client, works 00852 return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff; 00853 } 00854 00855 // }}} 00856 // {{{ _incrdecr() 00857 00868 function _incrdecr( $cmd, $key, $amt = 1 ) { 00869 if ( !$this->_active ) { 00870 return null; 00871 } 00872 00873 $sock = $this->get_sock( $key ); 00874 if ( !is_resource( $sock ) ) { 00875 return null; 00876 } 00877 00878 $key = is_array( $key ) ? $key[1] : $key; 00879 if ( isset( $this->stats[$cmd] ) ) { 00880 $this->stats[$cmd]++; 00881 } else { 00882 $this->stats[$cmd] = 1; 00883 } 00884 if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) { 00885 return null; 00886 } 00887 00888 $line = $this->_fgets( $sock ); 00889 $match = array(); 00890 if ( !preg_match( '/^(\d+)/', $line, $match ) ) { 00891 return null; 00892 } 00893 return $match[1]; 00894 } 00895 00896 // }}} 00897 // {{{ _load_items() 00898 00909 function _load_items( $sock, &$ret, &$casToken = null ) { 00910 $results = array(); 00911 00912 while ( 1 ) { 00913 $decl = $this->_fgets( $sock ); 00914 00915 if ( $decl === false ) { 00916 /* 00917 * If nothing can be read, something is wrong because we know exactly when 00918 * to stop reading (right after "END") and we return right after that. 00919 */ 00920 return false; 00921 } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) { 00922 /* 00923 * Read all data returned. This can be either one or multiple values. 00924 * Save all that data (in an array) to be processed later: we'll first 00925 * want to continue reading until "END" before doing anything else, 00926 * to make sure that we don't leave our client in a state where it's 00927 * output is not yet fully read. 00928 */ 00929 $results[] = array( 00930 $match[1], // rkey 00931 $match[2], // flags 00932 $match[3], // len 00933 $match[4], // casToken 00934 $this->_fread( $sock, $match[3] + 2 ), // data 00935 ); 00936 } elseif ( $decl == "END" ) { 00937 if ( count( $results ) == 0 ) { 00938 return false; 00939 } 00940 00945 foreach ( $results as $vars ) { 00946 list( $rkey, $flags, $len, $casToken, $data ) = $vars; 00947 00948 if ( $data === false || substr( $data, -2 ) !== "\r\n" ) { 00949 $this->_handle_error( $sock, 00950 'line ending missing from data block from $1' ); 00951 return false; 00952 } 00953 $data = substr( $data, 0, -2 ); 00954 $ret[$rkey] = $data; 00955 00956 if ( $this->_have_zlib && $flags & self::COMPRESSED ) { 00957 $ret[$rkey] = gzuncompress( $ret[$rkey] ); 00958 } 00959 00960 /* 00961 * This unserialize is the exact reason that we only want to 00962 * process data after having read until "END" (instead of doing 00963 * this right away): "unserialize" can trigger outside code: 00964 * in the event that $ret[$rkey] is a serialized object, 00965 * unserializing it will trigger __wakeup() if present. If that 00966 * function attempted to read from memcached (while we did not 00967 * yet read "END"), these 2 calls would collide. 00968 */ 00969 if ( $flags & self::SERIALIZED ) { 00970 $ret[$rkey] = unserialize( $ret[$rkey] ); 00971 } 00972 } 00973 00974 return true; 00975 } else { 00976 $this->_handle_error( $sock, 'Error parsing response from $1' ); 00977 return false; 00978 } 00979 } 00980 } 00981 00982 // }}} 00983 // {{{ _set() 00984 01001 function _set( $cmd, $key, $val, $exp, $casToken = null ) { 01002 if ( !$this->_active ) { 01003 return false; 01004 } 01005 01006 $sock = $this->get_sock( $key ); 01007 if ( !is_resource( $sock ) ) { 01008 return false; 01009 } 01010 01011 if ( isset( $this->stats[$cmd] ) ) { 01012 $this->stats[$cmd]++; 01013 } else { 01014 $this->stats[$cmd] = 1; 01015 } 01016 01017 $flags = 0; 01018 01019 if ( !is_scalar( $val ) ) { 01020 $val = serialize( $val ); 01021 $flags |= self::SERIALIZED; 01022 if ( $this->_debug ) { 01023 $this->_debugprint( sprintf( "client: serializing data as it is not scalar\n" ) ); 01024 } 01025 } 01026 01027 $len = strlen( $val ); 01028 01029 if ( $this->_have_zlib && $this->_compress_enable && 01030 $this->_compress_threshold && $len >= $this->_compress_threshold ) 01031 { 01032 $c_val = gzcompress( $val, 9 ); 01033 $c_len = strlen( $c_val ); 01034 01035 if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) { 01036 if ( $this->_debug ) { 01037 $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len ) ); 01038 } 01039 $val = $c_val; 01040 $len = $c_len; 01041 $flags |= self::COMPRESSED; 01042 } 01043 } 01044 01045 $command = "$cmd $key $flags $exp $len"; 01046 if ( $casToken ) { 01047 $command .= " $casToken"; 01048 } 01049 01050 if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) { 01051 return false; 01052 } 01053 01054 $line = $this->_fgets( $sock ); 01055 01056 if ( $this->_debug ) { 01057 $this->_debugprint( sprintf( "%s %s (%s)\n", $cmd, $key, $line ) ); 01058 } 01059 if ( $line == "STORED" ) { 01060 return true; 01061 } 01062 return false; 01063 } 01064 01065 // }}} 01066 // {{{ sock_to_host() 01067 01076 function sock_to_host( $host ) { 01077 if ( isset( $this->_cache_sock[$host] ) ) { 01078 return $this->_cache_sock[$host]; 01079 } 01080 01081 $sock = null; 01082 $now = time(); 01083 list( $ip, /* $port */) = explode( ':', $host ); 01084 if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now || 01085 isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now 01086 ) { 01087 return null; 01088 } 01089 01090 if ( !$this->_connect_sock( $sock, $host ) ) { 01091 return null; 01092 } 01093 01094 // Do not buffer writes 01095 stream_set_write_buffer( $sock, 0 ); 01096 01097 $this->_cache_sock[$host] = $sock; 01098 01099 return $this->_cache_sock[$host]; 01100 } 01101 01105 function _debugprint( $text ) { 01106 wfDebugLog( 'memcached', $text ); 01107 } 01108 01112 function _error_log( $text ) { 01113 wfDebugLog( 'memcached-serious', "Memcached error: $text" ); 01114 } 01115 01123 function _fwrite( $sock, $buf ) { 01124 $bytesWritten = 0; 01125 $bufSize = strlen( $buf ); 01126 while ( $bytesWritten < $bufSize ) { 01127 $result = fwrite( $sock, $buf ); 01128 $data = stream_get_meta_data( $sock ); 01129 if ( $data['timed_out'] ) { 01130 $this->_handle_error( $sock, 'timeout writing to $1' ); 01131 return false; 01132 } 01133 // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3. 01134 if ( $result === false || $result === 0 ) { 01135 $this->_handle_error( $sock, 'error writing to $1' ); 01136 return false; 01137 } 01138 $bytesWritten += $result; 01139 } 01140 01141 return true; 01142 } 01143 01147 function _handle_error( $sock, $msg ) { 01148 $peer = stream_socket_get_name( $sock, true ); 01149 if ( strval( $peer ) === '' ) { 01150 $peer = array_search( $sock, $this->_cache_sock ); 01151 if ( $peer === false ) { 01152 $peer = '[unknown host]'; 01153 } 01154 } 01155 $msg = str_replace( '$1', $peer, $msg ); 01156 $this->_error_log( "$msg\n" ); 01157 $this->_dead_sock( $sock ); 01158 } 01159 01168 function _fread( $sock, $len ) { 01169 $buf = ''; 01170 while ( $len > 0 ) { 01171 $result = fread( $sock, $len ); 01172 $data = stream_get_meta_data( $sock ); 01173 if ( $data['timed_out'] ) { 01174 $this->_handle_error( $sock, 'timeout reading from $1' ); 01175 return false; 01176 } 01177 if ( $result === false ) { 01178 $this->_handle_error( $sock, 'error reading buffer from $1' ); 01179 return false; 01180 } 01181 if ( $result === '' ) { 01182 // This will happen if the remote end of the socket is shut down 01183 $this->_handle_error( $sock, 'unexpected end of file reading from $1' ); 01184 return false; 01185 } 01186 $len -= strlen( $result ); 01187 $buf .= $result; 01188 } 01189 return $buf; 01190 } 01191 01199 function _fgets( $sock ) { 01200 $result = fgets( $sock ); 01201 // fgets() may return a partial line if there is a select timeout after 01202 // a successful recv(), so we have to check for a timeout even if we 01203 // got a string response. 01204 $data = stream_get_meta_data( $sock ); 01205 if ( $data['timed_out'] ) { 01206 $this->_handle_error( $sock, 'timeout reading line from $1' ); 01207 return false; 01208 } 01209 if ( $result === false ) { 01210 $this->_handle_error( $sock, 'error reading line from $1' ); 01211 return false; 01212 } 01213 if ( substr( $result, -2 ) === "\r\n" ) { 01214 $result = substr( $result, 0, -2 ); 01215 } elseif ( substr( $result, -1 ) === "\n" ) { 01216 $result = substr( $result, 0, -1 ); 01217 } else { 01218 $this->_handle_error( $sock, 'line ending missing in response from $1' ); 01219 return false; 01220 } 01221 return $result; 01222 } 01223 01228 function _flush_read_buffer( $f ) { 01229 if ( !is_resource( $f ) ) { 01230 return; 01231 } 01232 $r = array( $f ); 01233 $w = null; 01234 $e = null; 01235 $n = stream_select( $r, $w, $e, 0, 0 ); 01236 while ( $n == 1 && !feof( $f ) ) { 01237 fread( $f, 1024 ); 01238 $r = array( $f ); 01239 $w = null; 01240 $e = null; 01241 $n = stream_select( $r, $w, $e, 0, 0 ); 01242 } 01243 } 01244 01245 // }}} 01246 // }}} 01247 // }}} 01248 } 01249 01250 // }}} 01251 01252 class MemCachedClientforWiki extends MWMemcached { 01253 }