MediaWiki
REL1_24
|
00001 <?php 00002 // @codingStandardsIgnoreFile It's an external lib and it isn't. Let's not bother. 00067 // {{{ requirements 00068 // }}} 00069 00070 // {{{ class MWMemcached 00077 class MWMemcached { 00078 // {{{ properties 00079 // {{{ public 00080 00081 // {{{ constants 00082 // {{{ flags 00083 00087 const SERIALIZED = 1; 00088 00092 const COMPRESSED = 2; 00093 00094 // }}} 00095 00099 const COMPRESSION_SAVINGS = 0.20; 00100 00101 // }}} 00102 00109 public $stats; 00110 00111 // }}} 00112 // {{{ private 00113 00120 public $_cache_sock; 00121 00128 public $_debug; 00129 00136 public $_host_dead; 00137 00144 public $_have_zlib; 00145 00152 public $_compress_enable; 00153 00160 public $_compress_threshold; 00161 00168 public $_persistent; 00169 00176 public $_single_sock; 00177 00184 public $_servers; 00185 00192 public $_buckets; 00193 00200 public $_bucketcount; 00201 00208 public $_active; 00209 00216 public $_timeout_seconds; 00217 00224 public $_timeout_microseconds; 00225 00229 public $_connect_timeout; 00230 00234 public $_connect_attempts; 00235 00236 // }}} 00237 // }}} 00238 // {{{ methods 00239 // {{{ public functions 00240 // {{{ memcached() 00241 00249 public function __construct( $args ) { 00250 $this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() ); 00251 $this->_debug = isset( $args['debug'] ) ? $args['debug'] : false; 00252 $this->stats = array(); 00253 $this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0; 00254 $this->_persistent = isset( $args['persistent'] ) ? $args['persistent'] : false; 00255 $this->_compress_enable = true; 00256 $this->_have_zlib = function_exists( 'gzcompress' ); 00257 00258 $this->_cache_sock = array(); 00259 $this->_host_dead = array(); 00260 00261 $this->_timeout_seconds = 0; 00262 $this->_timeout_microseconds = isset( $args['timeout'] ) ? $args['timeout'] : 500000; 00263 00264 $this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1; 00265 $this->_connect_attempts = 2; 00266 } 00267 00268 // }}} 00269 // {{{ add() 00270 00285 public function add( $key, $val, $exp = 0 ) { 00286 return $this->_set( 'add', $key, $val, $exp ); 00287 } 00288 00289 // }}} 00290 // {{{ decr() 00291 00300 public function decr( $key, $amt = 1 ) { 00301 return $this->_incrdecr( 'decr', $key, $amt ); 00302 } 00303 00304 // }}} 00305 // {{{ delete() 00306 00315 public function delete( $key, $time = 0 ) { 00316 if ( !$this->_active ) { 00317 return false; 00318 } 00319 00320 $sock = $this->get_sock( $key ); 00321 if ( !is_resource( $sock ) ) { 00322 return false; 00323 } 00324 00325 $key = is_array( $key ) ? $key[1] : $key; 00326 00327 if ( isset( $this->stats['delete'] ) ) { 00328 $this->stats['delete']++; 00329 } else { 00330 $this->stats['delete'] = 1; 00331 } 00332 $cmd = "delete $key $time\r\n"; 00333 if ( !$this->_fwrite( $sock, $cmd ) ) { 00334 return false; 00335 } 00336 $res = $this->_fgets( $sock ); 00337 00338 if ( $this->_debug ) { 00339 $this->_debugprint( sprintf( "MemCache: delete %s (%s)\n", $key, $res ) ); 00340 } 00341 00342 if ( $res == "DELETED" || $res == "NOT_FOUND" ) { 00343 return true; 00344 } 00345 00346 return false; 00347 } 00348 00354 public function lock( $key, $timeout = 0 ) { 00355 /* stub */ 00356 return true; 00357 } 00358 00363 public function unlock( $key ) { 00364 /* stub */ 00365 return true; 00366 } 00367 00368 // }}} 00369 // {{{ disconnect_all() 00370 00374 public function disconnect_all() { 00375 foreach ( $this->_cache_sock as $sock ) { 00376 fclose( $sock ); 00377 } 00378 00379 $this->_cache_sock = array(); 00380 } 00381 00382 // }}} 00383 // {{{ enable_compress() 00384 00390 public function enable_compress( $enable ) { 00391 $this->_compress_enable = $enable; 00392 } 00393 00394 // }}} 00395 // {{{ forget_dead_hosts() 00396 00400 public function forget_dead_hosts() { 00401 $this->_host_dead = array(); 00402 } 00403 00404 // }}} 00405 // {{{ get() 00406 00415 public function get( $key, &$casToken = null ) { 00416 wfProfileIn( __METHOD__ ); 00417 00418 if ( $this->_debug ) { 00419 $this->_debugprint( "get($key)\n" ); 00420 } 00421 00422 if ( !is_array( $key ) && strval( $key ) === '' ) { 00423 $this->_debugprint( "Skipping key which equals to an empty string" ); 00424 wfProfileOut( __METHOD__ ); 00425 return false; 00426 } 00427 00428 if ( !$this->_active ) { 00429 wfProfileOut( __METHOD__ ); 00430 return false; 00431 } 00432 00433 $sock = $this->get_sock( $key ); 00434 00435 if ( !is_resource( $sock ) ) { 00436 wfProfileOut( __METHOD__ ); 00437 return false; 00438 } 00439 00440 $key = is_array( $key ) ? $key[1] : $key; 00441 if ( isset( $this->stats['get'] ) ) { 00442 $this->stats['get']++; 00443 } else { 00444 $this->stats['get'] = 1; 00445 } 00446 00447 $cmd = "gets $key\r\n"; 00448 if ( !$this->_fwrite( $sock, $cmd ) ) { 00449 wfProfileOut( __METHOD__ ); 00450 return false; 00451 } 00452 00453 $val = array(); 00454 $this->_load_items( $sock, $val, $casToken ); 00455 00456 if ( $this->_debug ) { 00457 foreach ( $val as $k => $v ) { 00458 $this->_debugprint( sprintf( "MemCache: sock %s got %s\n", serialize( $sock ), $k ) ); 00459 } 00460 } 00461 00462 $value = false; 00463 if ( isset( $val[$key] ) ) { 00464 $value = $val[$key]; 00465 } 00466 wfProfileOut( __METHOD__ ); 00467 return $value; 00468 } 00469 00470 // }}} 00471 // {{{ get_multi() 00472 00480 public function get_multi( $keys ) { 00481 if ( !$this->_active ) { 00482 return false; 00483 } 00484 00485 if ( isset( $this->stats['get_multi'] ) ) { 00486 $this->stats['get_multi']++; 00487 } else { 00488 $this->stats['get_multi'] = 1; 00489 } 00490 $sock_keys = array(); 00491 $socks = array(); 00492 foreach ( $keys as $key ) { 00493 $sock = $this->get_sock( $key ); 00494 if ( !is_resource( $sock ) ) { 00495 continue; 00496 } 00497 $key = is_array( $key ) ? $key[1] : $key; 00498 if ( !isset( $sock_keys[$sock] ) ) { 00499 $sock_keys[intval( $sock )] = array(); 00500 $socks[] = $sock; 00501 } 00502 $sock_keys[intval( $sock )][] = $key; 00503 } 00504 00505 $gather = array(); 00506 // Send out the requests 00507 foreach ( $socks as $sock ) { 00508 $cmd = 'gets'; 00509 foreach ( $sock_keys[intval( $sock )] as $key ) { 00510 $cmd .= ' ' . $key; 00511 } 00512 $cmd .= "\r\n"; 00513 00514 if ( $this->_fwrite( $sock, $cmd ) ) { 00515 $gather[] = $sock; 00516 } 00517 } 00518 00519 // Parse responses 00520 $val = array(); 00521 foreach ( $gather as $sock ) { 00522 $this->_load_items( $sock, $val, $casToken ); 00523 } 00524 00525 if ( $this->_debug ) { 00526 foreach ( $val as $k => $v ) { 00527 $this->_debugprint( sprintf( "MemCache: got %s\n", $k ) ); 00528 } 00529 } 00530 00531 return $val; 00532 } 00533 00534 // }}} 00535 // {{{ incr() 00536 00547 public function incr( $key, $amt = 1 ) { 00548 return $this->_incrdecr( 'incr', $key, $amt ); 00549 } 00550 00551 // }}} 00552 // {{{ replace() 00553 00567 public function replace( $key, $value, $exp = 0 ) { 00568 return $this->_set( 'replace', $key, $value, $exp ); 00569 } 00570 00571 // }}} 00572 // {{{ run_command() 00573 00583 public function run_command( $sock, $cmd ) { 00584 if ( !is_resource( $sock ) ) { 00585 return array(); 00586 } 00587 00588 if ( !$this->_fwrite( $sock, $cmd ) ) { 00589 return array(); 00590 } 00591 00592 $ret = array(); 00593 while ( true ) { 00594 $res = $this->_fgets( $sock ); 00595 $ret[] = $res; 00596 if ( preg_match( '/^END/', $res ) ) { 00597 break; 00598 } 00599 if ( strlen( $res ) == 0 ) { 00600 break; 00601 } 00602 } 00603 return $ret; 00604 } 00605 00606 // }}} 00607 // {{{ set() 00608 00623 public function set( $key, $value, $exp = 0 ) { 00624 return $this->_set( 'set', $key, $value, $exp ); 00625 } 00626 00627 // }}} 00628 // {{{ cas() 00629 00645 public function cas( $casToken, $key, $value, $exp = 0 ) { 00646 return $this->_set( 'cas', $key, $value, $exp, $casToken ); 00647 } 00648 00649 // }}} 00650 // {{{ set_compress_threshold() 00651 00657 public function set_compress_threshold( $thresh ) { 00658 $this->_compress_threshold = $thresh; 00659 } 00660 00661 // }}} 00662 // {{{ set_debug() 00663 00671 public function set_debug( $dbg ) { 00672 $this->_debug = $dbg; 00673 } 00674 00675 // }}} 00676 // {{{ set_servers() 00677 00685 public function set_servers( $list ) { 00686 $this->_servers = $list; 00687 $this->_active = count( $list ); 00688 $this->_buckets = null; 00689 $this->_bucketcount = 0; 00690 00691 $this->_single_sock = null; 00692 if ( $this->_active == 1 ) { 00693 $this->_single_sock = $this->_servers[0]; 00694 } 00695 } 00696 00703 public function set_timeout( $seconds, $microseconds ) { 00704 $this->_timeout_seconds = $seconds; 00705 $this->_timeout_microseconds = $microseconds; 00706 } 00707 00708 // }}} 00709 // }}} 00710 // {{{ private methods 00711 // {{{ _close_sock() 00712 00720 function _close_sock( $sock ) { 00721 $host = array_search( $sock, $this->_cache_sock ); 00722 fclose( $this->_cache_sock[$host] ); 00723 unset( $this->_cache_sock[$host] ); 00724 } 00725 00726 // }}} 00727 // {{{ _connect_sock() 00728 00738 function _connect_sock( &$sock, $host ) { 00739 list( $ip, $port ) = preg_split( '/:(?=\d)/', $host ); 00740 $sock = false; 00741 $timeout = $this->_connect_timeout; 00742 $errno = $errstr = null; 00743 for ( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) { 00744 wfSuppressWarnings(); 00745 if ( $this->_persistent == 1 ) { 00746 $sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout ); 00747 } else { 00748 $sock = fsockopen( $ip, $port, $errno, $errstr, $timeout ); 00749 } 00750 wfRestoreWarnings(); 00751 } 00752 if ( !$sock ) { 00753 $this->_error_log( "Error connecting to $host: $errstr\n" ); 00754 $this->_dead_host( $host ); 00755 return false; 00756 } 00757 00758 // Initialise timeout 00759 stream_set_timeout( $sock, $this->_timeout_seconds, $this->_timeout_microseconds ); 00760 00761 // If the connection was persistent, flush the read buffer in case there 00762 // was a previous incomplete request on this connection 00763 if ( $this->_persistent ) { 00764 $this->_flush_read_buffer( $sock ); 00765 } 00766 return true; 00767 } 00768 00769 // }}} 00770 // {{{ _dead_sock() 00771 00779 function _dead_sock( $sock ) { 00780 $host = array_search( $sock, $this->_cache_sock ); 00781 $this->_dead_host( $host ); 00782 } 00783 00787 function _dead_host( $host ) { 00788 $parts = explode( ':', $host ); 00789 $ip = $parts[0]; 00790 $this->_host_dead[$ip] = time() + 30 + intval( rand( 0, 10 ) ); 00791 $this->_host_dead[$host] = $this->_host_dead[$ip]; 00792 unset( $this->_cache_sock[$host] ); 00793 } 00794 00795 // }}} 00796 // {{{ get_sock() 00797 00806 function get_sock( $key ) { 00807 if ( !$this->_active ) { 00808 return false; 00809 } 00810 00811 if ( $this->_single_sock !== null ) { 00812 return $this->sock_to_host( $this->_single_sock ); 00813 } 00814 00815 $hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key ); 00816 if ( $this->_buckets === null ) { 00817 $bu = array(); 00818 foreach ( $this->_servers as $v ) { 00819 if ( is_array( $v ) ) { 00820 for ( $i = 0; $i < $v[1]; $i++ ) { 00821 $bu[] = $v[0]; 00822 } 00823 } else { 00824 $bu[] = $v; 00825 } 00826 } 00827 $this->_buckets = $bu; 00828 $this->_bucketcount = count( $bu ); 00829 } 00830 00831 $realkey = is_array( $key ) ? $key[1] : $key; 00832 for ( $tries = 0; $tries < 20; $tries++ ) { 00833 $host = $this->_buckets[$hv % $this->_bucketcount]; 00834 $sock = $this->sock_to_host( $host ); 00835 if ( is_resource( $sock ) ) { 00836 return $sock; 00837 } 00838 $hv = $this->_hashfunc( $hv . $realkey ); 00839 } 00840 00841 return false; 00842 } 00843 00844 // }}} 00845 // {{{ _hashfunc() 00846 00855 function _hashfunc( $key ) { 00856 # Hash function must be in [0,0x7ffffff] 00857 # We take the first 31 bits of the MD5 hash, which unlike the hash 00858 # function used in a previous version of this client, works 00859 return hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff; 00860 } 00861 00862 // }}} 00863 // {{{ _incrdecr() 00864 00875 function _incrdecr( $cmd, $key, $amt = 1 ) { 00876 if ( !$this->_active ) { 00877 return null; 00878 } 00879 00880 $sock = $this->get_sock( $key ); 00881 if ( !is_resource( $sock ) ) { 00882 return null; 00883 } 00884 00885 $key = is_array( $key ) ? $key[1] : $key; 00886 if ( isset( $this->stats[$cmd] ) ) { 00887 $this->stats[$cmd]++; 00888 } else { 00889 $this->stats[$cmd] = 1; 00890 } 00891 if ( !$this->_fwrite( $sock, "$cmd $key $amt\r\n" ) ) { 00892 return null; 00893 } 00894 00895 $line = $this->_fgets( $sock ); 00896 $match = array(); 00897 if ( !preg_match( '/^(\d+)/', $line, $match ) ) { 00898 return null; 00899 } 00900 return $match[1]; 00901 } 00902 00903 // }}} 00904 // {{{ _load_items() 00905 00916 function _load_items( $sock, &$ret, &$casToken = null ) { 00917 $results = array(); 00918 00919 while ( 1 ) { 00920 $decl = $this->_fgets( $sock ); 00921 00922 if ( $decl === false ) { 00923 /* 00924 * If nothing can be read, something is wrong because we know exactly when 00925 * to stop reading (right after "END") and we return right after that. 00926 */ 00927 return false; 00928 } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) { 00929 /* 00930 * Read all data returned. This can be either one or multiple values. 00931 * Save all that data (in an array) to be processed later: we'll first 00932 * want to continue reading until "END" before doing anything else, 00933 * to make sure that we don't leave our client in a state where it's 00934 * output is not yet fully read. 00935 */ 00936 $results[] = array( 00937 $match[1], // rkey 00938 $match[2], // flags 00939 $match[3], // len 00940 $match[4], // casToken 00941 $this->_fread( $sock, $match[3] + 2 ), // data 00942 ); 00943 } elseif ( $decl == "END" ) { 00944 if ( count( $results ) == 0 ) { 00945 return false; 00946 } 00947 00952 foreach ( $results as $vars ) { 00953 list( $rkey, $flags, $len, $casToken, $data ) = $vars; 00954 00955 if ( $data === false || substr( $data, -2 ) !== "\r\n" ) { 00956 $this->_handle_error( $sock, 00957 'line ending missing from data block from $1' ); 00958 return false; 00959 } 00960 $data = substr( $data, 0, -2 ); 00961 $ret[$rkey] = $data; 00962 00963 if ( $this->_have_zlib && $flags & self::COMPRESSED ) { 00964 $ret[$rkey] = gzuncompress( $ret[$rkey] ); 00965 } 00966 00967 /* 00968 * This unserialize is the exact reason that we only want to 00969 * process data after having read until "END" (instead of doing 00970 * this right away): "unserialize" can trigger outside code: 00971 * in the event that $ret[$rkey] is a serialized object, 00972 * unserializing it will trigger __wakeup() if present. If that 00973 * function attempted to read from memcached (while we did not 00974 * yet read "END"), these 2 calls would collide. 00975 */ 00976 if ( $flags & self::SERIALIZED ) { 00977 $ret[$rkey] = unserialize( $ret[$rkey] ); 00978 } 00979 } 00980 00981 return true; 00982 } else { 00983 $this->_handle_error( $sock, 'Error parsing response from $1' ); 00984 return false; 00985 } 00986 } 00987 } 00988 00989 // }}} 00990 // {{{ _set() 00991 01008 function _set( $cmd, $key, $val, $exp, $casToken = null ) { 01009 if ( !$this->_active ) { 01010 return false; 01011 } 01012 01013 $sock = $this->get_sock( $key ); 01014 if ( !is_resource( $sock ) ) { 01015 return false; 01016 } 01017 01018 if ( isset( $this->stats[$cmd] ) ) { 01019 $this->stats[$cmd]++; 01020 } else { 01021 $this->stats[$cmd] = 1; 01022 } 01023 01024 $flags = 0; 01025 01026 if ( !is_scalar( $val ) ) { 01027 $val = serialize( $val ); 01028 $flags |= self::SERIALIZED; 01029 if ( $this->_debug ) { 01030 $this->_debugprint( sprintf( "client: serializing data as it is not scalar\n" ) ); 01031 } 01032 } 01033 01034 $len = strlen( $val ); 01035 01036 if ( $this->_have_zlib && $this->_compress_enable 01037 && $this->_compress_threshold && $len >= $this->_compress_threshold 01038 ) { 01039 $c_val = gzcompress( $val, 9 ); 01040 $c_len = strlen( $c_val ); 01041 01042 if ( $c_len < $len * ( 1 - self::COMPRESSION_SAVINGS ) ) { 01043 if ( $this->_debug ) { 01044 $this->_debugprint( sprintf( "client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len ) ); 01045 } 01046 $val = $c_val; 01047 $len = $c_len; 01048 $flags |= self::COMPRESSED; 01049 } 01050 } 01051 01052 $command = "$cmd $key $flags $exp $len"; 01053 if ( $casToken ) { 01054 $command .= " $casToken"; 01055 } 01056 01057 if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) { 01058 return false; 01059 } 01060 01061 $line = $this->_fgets( $sock ); 01062 01063 if ( $this->_debug ) { 01064 $this->_debugprint( sprintf( "%s %s (%s)\n", $cmd, $key, $line ) ); 01065 } 01066 if ( $line == "STORED" ) { 01067 return true; 01068 } 01069 return false; 01070 } 01071 01072 // }}} 01073 // {{{ sock_to_host() 01074 01083 function sock_to_host( $host ) { 01084 if ( isset( $this->_cache_sock[$host] ) ) { 01085 return $this->_cache_sock[$host]; 01086 } 01087 01088 $sock = null; 01089 $now = time(); 01090 list( $ip, /* $port */) = explode( ':', $host ); 01091 if ( isset( $this->_host_dead[$host] ) && $this->_host_dead[$host] > $now || 01092 isset( $this->_host_dead[$ip] ) && $this->_host_dead[$ip] > $now 01093 ) { 01094 return null; 01095 } 01096 01097 if ( !$this->_connect_sock( $sock, $host ) ) { 01098 return null; 01099 } 01100 01101 // Do not buffer writes 01102 stream_set_write_buffer( $sock, 0 ); 01103 01104 $this->_cache_sock[$host] = $sock; 01105 01106 return $this->_cache_sock[$host]; 01107 } 01108 01112 function _debugprint( $text ) { 01113 wfDebugLog( 'memcached', $text ); 01114 } 01115 01119 function _error_log( $text ) { 01120 wfDebugLog( 'memcached-serious', "Memcached error: $text" ); 01121 } 01122 01130 function _fwrite( $sock, $buf ) { 01131 $bytesWritten = 0; 01132 $bufSize = strlen( $buf ); 01133 while ( $bytesWritten < $bufSize ) { 01134 $result = fwrite( $sock, $buf ); 01135 $data = stream_get_meta_data( $sock ); 01136 if ( $data['timed_out'] ) { 01137 $this->_handle_error( $sock, 'timeout writing to $1' ); 01138 return false; 01139 } 01140 // Contrary to the documentation, fwrite() returns zero on error in PHP 5.3. 01141 if ( $result === false || $result === 0 ) { 01142 $this->_handle_error( $sock, 'error writing to $1' ); 01143 return false; 01144 } 01145 $bytesWritten += $result; 01146 } 01147 01148 return true; 01149 } 01150 01157 function _handle_error( $sock, $msg ) { 01158 $peer = stream_socket_get_name( $sock, true ); 01159 if ( strval( $peer ) === '' ) { 01160 $peer = array_search( $sock, $this->_cache_sock ); 01161 if ( $peer === false ) { 01162 $peer = '[unknown host]'; 01163 } 01164 } 01165 $msg = str_replace( '$1', $peer, $msg ); 01166 $this->_error_log( "$msg\n" ); 01167 $this->_dead_sock( $sock ); 01168 } 01169 01178 function _fread( $sock, $len ) { 01179 $buf = ''; 01180 while ( $len > 0 ) { 01181 $result = fread( $sock, $len ); 01182 $data = stream_get_meta_data( $sock ); 01183 if ( $data['timed_out'] ) { 01184 $this->_handle_error( $sock, 'timeout reading from $1' ); 01185 return false; 01186 } 01187 if ( $result === false ) { 01188 $this->_handle_error( $sock, 'error reading buffer from $1' ); 01189 return false; 01190 } 01191 if ( $result === '' ) { 01192 // This will happen if the remote end of the socket is shut down 01193 $this->_handle_error( $sock, 'unexpected end of file reading from $1' ); 01194 return false; 01195 } 01196 $len -= strlen( $result ); 01197 $buf .= $result; 01198 } 01199 return $buf; 01200 } 01201 01209 function _fgets( $sock ) { 01210 $result = fgets( $sock ); 01211 // fgets() may return a partial line if there is a select timeout after 01212 // a successful recv(), so we have to check for a timeout even if we 01213 // got a string response. 01214 $data = stream_get_meta_data( $sock ); 01215 if ( $data['timed_out'] ) { 01216 $this->_handle_error( $sock, 'timeout reading line from $1' ); 01217 return false; 01218 } 01219 if ( $result === false ) { 01220 $this->_handle_error( $sock, 'error reading line from $1' ); 01221 return false; 01222 } 01223 if ( substr( $result, -2 ) === "\r\n" ) { 01224 $result = substr( $result, 0, -2 ); 01225 } elseif ( substr( $result, -1 ) === "\n" ) { 01226 $result = substr( $result, 0, -1 ); 01227 } else { 01228 $this->_handle_error( $sock, 'line ending missing in response from $1' ); 01229 return false; 01230 } 01231 return $result; 01232 } 01233 01238 function _flush_read_buffer( $f ) { 01239 if ( !is_resource( $f ) ) { 01240 return; 01241 } 01242 $r = array( $f ); 01243 $w = null; 01244 $e = null; 01245 $n = stream_select( $r, $w, $e, 0, 0 ); 01246 while ( $n == 1 && !feof( $f ) ) { 01247 fread( $f, 1024 ); 01248 $r = array( $f ); 01249 $w = null; 01250 $e = null; 01251 $n = stream_select( $r, $w, $e, 0, 0 ); 01252 } 01253 } 01254 01255 // }}} 01256 // }}} 01257 // }}} 01258 } 01259 01260 // }}} 01261 01262 class MemCachedClientforWiki extends MWMemcached { 01263 }