MediaWiki  REL1_22
MemcachedClient.php
Go to the documentation of this file.
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 }