Docs for gerrit 47419
[lhc/web/wiklou.git] / includes / objectcache / MemcachedClient.php
index 787a168..f8ce0d0 100644 (file)
@@ -99,7 +99,6 @@ class MWMemcached {
 
        // }}}
 
-
        /**
         * Command statistics
         *
@@ -408,10 +407,11 @@ class MWMemcached {
         * Retrieves the value associated with the key from the memcache server
         *
         * @param $key array|string key to retrieve
+        * @param $casToken[optional] Float
         *
         * @return Mixed
         */
-       public function get( $key ) {
+       public function get( $key, &$casToken = null ) {
                wfProfileIn( __METHOD__ );
 
                if ( $this->_debug ) {
@@ -437,14 +437,14 @@ class MWMemcached {
                        $this->stats['get'] = 1;
                }
 
-               $cmd = "get $key\r\n";
+               $cmd = "gets $key\r\n";
                if ( !$this->_fwrite( $sock, $cmd ) ) {
                        wfProfileOut( __METHOD__ );
                        return false;
                }
 
                $val = array();
-               $this->_load_items( $sock, $val );
+               $this->_load_items( $sock, $val, $casToken );
 
                if ( $this->_debug ) {
                        foreach ( $val as $k => $v ) {
@@ -498,7 +498,7 @@ class MWMemcached {
                $gather = array();
                // Send out the requests
                foreach ( $socks as $sock ) {
-                       $cmd = 'get';
+                       $cmd = 'gets';
                        foreach ( $sock_keys[ intval( $sock ) ] as $key ) {
                                $cmd .= ' ' . $key;
                        }
@@ -512,7 +512,7 @@ class MWMemcached {
                // Parse responses
                $val = array();
                foreach ( $gather as $sock ) {
-                       $this->_load_items( $sock, $val );
+                       $this->_load_items( $sock, $val, $casToken );
                }
 
                if ( $this->_debug ) {
@@ -617,6 +617,28 @@ class MWMemcached {
                return $this->_set( 'set', $key, $value, $exp );
        }
 
+       // }}}
+       // {{{ cas()
+
+       /**
+        * Sets a key to a given value in the memcache if the current value still corresponds
+        * to a known, given value.  Returns true if set successfully.
+        *
+        * @param $casToken Float: current known value
+        * @param $key String: key to set value as
+        * @param $value Mixed: value to set
+        * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+        * to cache for (up to 30 days inclusive).  Any timespans of 30 days + 1 second or
+        * longer must be the timestamp of the time at which the mapping should expire. It
+        * is safe to use timestamps in all cases, regardless of exipration
+        * eg: strtotime("+3 hour")
+        *
+        * @return Boolean: TRUE on success
+        */
+       public function cas( $casToken, $key, $value, $exp = 0 ) {
+               return $this->_set( 'cas', $key, $value, $exp, $casToken );
+       }
+
        // }}}
        // {{{ set_compress_threshold()
 
@@ -879,39 +901,77 @@ class MWMemcached {
         *
         * @param $sock Resource: socket to read from
         * @param $ret Array: returned values
+        * @param $casToken[optional] Float
         * @return boolean True for success, false for failure
         *
         * @access private
         */
-       function _load_items( $sock, &$ret ) {
+       function _load_items( $sock, &$ret, &$casToken = null ) {
+               $results = array();
+
                while ( 1 ) {
                        $decl = $this->_fgets( $sock );
+
                        if( $decl === false ) {
+                               /*
+                                * If nothing can be read, something is wrong because we know exactly when
+                                * to stop reading (right after "END") and we return right after that.
+                                */
                                return false;
+                       } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
+                               /*
+                                * Read all data returned. This can be either one or multiple values.
+                                * Save all that data (in an array) to be processed later: we'll first
+                                * want to continue reading until "END" before doing anything else,
+                                * to make sure that we don't leave our client in a state where it's
+                                * output is not yet fully read.
+                                */
+                               $results[] = array(
+                                       $match[1], // rkey
+                                       $match[2], // flags
+                                       $match[3], // len
+                                       $match[4], // casToken
+                                       $this->_fread( $sock, $match[3] + 2 ), // data
+                               );
                        } elseif ( $decl == "END" ) {
-                               return true;
-                       } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+)$/', $decl, $match ) ) {
-                               list( $rkey, $flags, $len ) = array( $match[1], $match[2], $match[3] );
-                               $data = $this->_fread( $sock, $len + 2 );
-                               if ( $data === false ) {
-                                       return false;
-                               }
-                               if ( substr( $data, -2 ) !== "\r\n" ) {
-                                       $this->_handle_error( $sock,
-                                               'line ending missing from data block from $1' );
+                               if ( count( $results ) == 0 ) {
                                        return false;
                                }
-                               $data = substr( $data, 0, -2 );
-                               $ret[$rkey] = $data;
 
-                               if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
-                                       $ret[$rkey] = gzuncompress( $ret[$rkey] );
-                               }
+                               /**
+                                * All data has been read, time to process the data and build
+                                * meaningful return values.
+                                */
+                               foreach ( $results as $vars ) {
+                                       list( $rkey, $flags, $len, $casToken, $data ) = $vars;
+
+                                       if ( $data === false || substr( $data, -2 ) !== "\r\n" ) {
+                                               $this->_handle_error( $sock,
+                                                       'line ending missing from data block from $1' );
+                                               return false;
+                                       }
+                                       $data = substr( $data, 0, -2 );
+                                       $ret[$rkey] = $data;
+
+                                       if ( $this->_have_zlib && $flags & self::COMPRESSED ) {
+                                               $ret[$rkey] = gzuncompress( $ret[$rkey] );
+                                       }
 
-                               if ( $flags & self::SERIALIZED ) {
-                                       $ret[$rkey] = unserialize( $ret[$rkey] );
+                                       /*
+                                        * This unserialize is the exact reason that we only want to
+                                        * process data after having read until "END" (instead of doing
+                                        * this right away): "unserialize" can trigger outside code:
+                                        * in the event that $ret[$rkey] is a serialized object,
+                                        * unserializing it will trigger __wakeup() if present. If that
+                                        * function attempted to read from memcached (while we did not
+                                        * yet read "END"), these 2 calls would collide.
+                                        */
+                                       if ( $flags & self::SERIALIZED ) {
+                                               $ret[$rkey] = unserialize( $ret[$rkey] );
+                                       }
                                }
 
+                               return true;
                        } else {
                                $this->_handle_error( $sock, 'Error parsing response from $1' );
                                return false;
@@ -933,11 +993,12 @@ class MWMemcached {
         * longer must be the timestamp of the time at which the mapping should expire. It
         * is safe to use timestamps in all cases, regardless of exipration
         * eg: strtotime("+3 hour")
+        * @param $casToken[optional] Float
         *
         * @return Boolean
         * @access private
         */
-       function _set( $cmd, $key, $val, $exp ) {
+       function _set( $cmd, $key, $val, $exp, $casToken = null ) {
                if ( !$this->_active ) {
                        return false;
                }
@@ -966,7 +1027,7 @@ class MWMemcached {
                $len = strlen( $val );
 
                if ( $this->_have_zlib && $this->_compress_enable &&
-                        $this->_compress_threshold && $len >= $this->_compress_threshold )
+                       $this->_compress_threshold && $len >= $this->_compress_threshold )
                {
                        $c_val = gzcompress( $val, 9 );
                        $c_len = strlen( $c_val );
@@ -980,7 +1041,13 @@ class MWMemcached {
                                $flags |= self::COMPRESSED;
                        }
                }
-               if ( !$this->_fwrite( $sock, "$cmd $key $flags $exp $len\r\n$val\r\n" ) ) {
+
+               $command = "$cmd $key $flags $exp $len";
+               if ( $casToken ) {
+                       $command .= " $casToken";
+               }
+
+               if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
                        return false;
                }
 
@@ -1036,11 +1103,6 @@ class MWMemcached {
         * @param $text string
         */
        function _debugprint( $text ) {
-               global $wgDebugLogGroups;
-               if( !isset( $wgDebugLogGroups['memcached'] ) ) {
-                       # Prefix message since it will end up in main debug log file
-                       $text = "memcached: $text";
-               }
                wfDebugLog( 'memcached', $text );
        }
 
@@ -1185,7 +1247,6 @@ class MWMemcached {
        // }}}
 }
 
-
 // }}}
 
 class MemCachedClientforWiki extends MWMemcached {