Fixed bug #9645: Memcached backend is not working properly
authorDmitry Dulepov <dmitry.dulepov@gmail.com>
Mon, 1 Dec 2008 16:10:09 +0000 (16:10 +0000)
committerDmitry Dulepov <dmitry.dulepov@gmail.com>
Mon, 1 Dec 2008 16:10:09 +0000 (16:10 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@4512 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/cache/backend/class.t3lib_cache_backend_memcached.php

index 888ac9c..824d88b 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2008-12-01  Dmitry Dulepov  <dmitry@typo3.org>
+
+       * Fixed bug #9645: Memcached backend is not working properly
+
 2008-12-01  Steffen Kamper  <info@sk-typo3.de>
 
        * #9864: felogin: Drop languages from locallang.xml
index 68c2fa6..5647e4a 100644 (file)
 
 
 /**
- *  A caching backend which stores cache entries by using Memcached
+ * A caching backend which stores cache entries by using Memcached.
  *
- * This file is a backport from FLOW3
+ * This backend uses the following types of Memcache keys:
+ * - tag_xxx
+ *   xxx is tag name, value is array of associated identifiers identifier. This
+ *   is "forward" tag index. It is mainly used for obtaining content by tag
+ *   (get identifier by tag -> get content by identifier)
+ * - ident_xxx
+ *   xxx is identifier, value is array of associated tags. This is "reverse" tag
+ *   index. It provides quick access for all tags associated with this identifier
+ *   and used when removing the identifier
+ * - tagIndex
+ *   Value is a List of all tags (array)
+ * Each key is prepended with a prefix. By default prefix consists from two parts
+ * separated by underscore character and ends in yet another underscore character:
+ * - "TYPO3"
+ * - Current site path obtained from the PATH_site constant
+ * This prefix makes sure that keys from the different installations do not
+ * conflict.
+ *
+ * This file is a backport from FLOW3 by Ingo Renner.
  *
  * @package TYPO3
  * @subpackage t3lib_cache
 class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
 
        /**
+        * Instance of the PHP Memcache class
+        *
         * @var Memcache
         */
        protected $memcache;
 
        /**
+        * Array of Memcache server configurations
+        *
         * @var array
         */
        protected $servers = array();
 
        /**
-        * @var boolean whether the memcache uses compression or not (requires zlib)
+        * Indicates whether the memcache uses compression or not (requires zlib)
+        *
+        * @var boolean
         */
        protected $useCompressed;
 
        /**
-        * @var string A prefix to seperate stored data from other data possible stored in the memcache
+        * A prefix to seperate stored data from other data possibly stored in the
+        * memcache. This prefix must be unique for each site in the tree. Default
+        * implementation uses MD5 of the current site path to make identifier prefix
+        * unique.
+        *
+        * @var string
         */
        protected $identifierPrefix;
 
        /**
-        * @var string  The ID of this TYPO3 server. If many sites are using the same memcached, it prevents conflicts
-        */
-       protected $serverId;
-
-       /**
         * Constructs this backend
         *
-        * @param string $context FLOW3's application context
         * @param mixed $options Configuration options - depends on the actual backend
         * @author Robert Lemke <robert@typo3.org>
         */
        public function __construct($options = array()) {
                if (!extension_loaded('memcache')) {
                        throw new t3lib_cache_Exception(
-                               'The PHP extension "memcached" must be installed and loaded in order to use the Memcached backend.',
+                               'The PHP extension "memcached" must be installed and loaded in ' .
+                               'order to use the Memcached backend.',
                                1213987706
                        );
                }
 
-               // Set default value for the server ID
-               $this->serverId = t3lib_div::getIndpEnv('HTTP_HOST');
-
                parent::__construct($options);
 
                $this->memcache = new Memcache();
-               $this->identifierPrefix = 'TYPO3_' . md5(
-                       t3lib_div::getIndpEnv('SCRIPT_FILENAME')
-                       . php_sapi_name()
-                       . $this->serverId
-               ) . '_';
+               $this->identifierPrefix = $this->getIdentifierPrefix();
 
                if (!count($this->servers)) {
                        throw new t3lib_cache_Exception(
@@ -100,33 +117,22 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
        }
 
        /**
-        * Setter for serverId property.
+        * Sets servers property.
         *
-        * @param       int     $serverId       The value of the property
+        * @param       array   An array of servers to add (format: "host:port")
         * @return      void
-        */
-       protected function setServerId($serverId) {
-               $this->serverId = $serverId;
-       }
-
-       /**
-        * setter for servers property
-        * should be an array of entries like host:port
-        *
-        * @param array An array of servers to add
-        * @return void
-        * @author Christian Jul Jensen <julle@typo3.org>
+        * @author      Christian Jul Jensen <julle@typo3.org>
         */
        protected function setServers(array $servers) {
                $this->servers = $servers;
        }
 
        /**
-        * Setter for useCompressed
+        * Sets for compression flag
         *
-        * @param boolean $enableCompression
-        * @return void
-        * @author Christian Jul Jensen <julle@typo3.org>
+        * @param       boolean $enableCompression      New value of compression flag
+        * @return      void
+        * @author      Christian Jul Jensen <julle@typo3.org>
         */
        protected function setCompression($enableCompression) {
                $this->useCompressed = $enableCompression;
@@ -163,7 +169,8 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
 
                if (!is_string($data)) {
                        throw new t3lib_cache_Exception_InvalidData(
-                               'The specified data is of type "' . gettype($data) . '" but a string is expected.',
+                               'The specified data is of type "' . gettype($data) .
+                               '" but a string is expected.',
                                1207149231
                        );
                }
@@ -198,7 +205,8 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
                        $this->addIdentifierToTags($entryIdentifier, $tags);
                } catch(Exception $exception) {
                        throw new t3lib_cache_Exception(
-                               'Memcache was unable to connect to any server. ' . $exception->getMessage(),
+                               'Memcache was unable to connect to any server. ' .
+                               $exception->getMessage(),
                                1207208100
                        );
                }
@@ -225,7 +233,7 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         * @author Karsten Dambekalns <karsten@typo3.org>
         */
        public function has($entryIdentifier) {
-               return (boolean) $this->memcache->get($this->identifierPrefix . $entryIdentifier);
+               return $this->memcache->get($this->identifierPrefix . $entryIdentifier) !== false;
        }
 
        /**
@@ -240,6 +248,7 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         */
        public function remove($entryIdentifier) {
                $this->removeIdentifierFromAllTags($entryIdentifier);
+               $this->memcache->delete($this->identifierPrefix . 'ident_' . $entryIdentifier);
                return $this->memcache->delete($this->identifierPrefix . $entryIdentifier);
        }
 
@@ -260,7 +269,7 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
                        );
                }
 
-               $entries     = array();
+               $entries = array();
                $identifiers = $this->findIdentifiersTaggedWith($tag);
                foreach($identifiers as $identifier) {
                        $entries[] = $this->get($identifier);
@@ -346,7 +355,8 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         * @author Karsten Dambekalns <karsten@typo3.org>
         */
        protected function getTagIndex() {
-               return (array) $this->memcache->get($this->identifierPrefix . '_tagIndex');
+               $tagIndex = $this->memcache->get($this->identifierPrefix . 'tagIndex');
+               return ($tagIndex == false ? array() : (array)$tagIndex);
        }
 
        /**
@@ -356,12 +366,7 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         * @author Karsten Dambekalns <karsten@typo3.org>
         */
        protected function setTagIndex(array $tags) {
-               $this->memcache->set(
-                       $this->identifierPrefix . '_tagIndex',
-                       array_unique($tags),
-                       0,
-                       0
-               );
+               $this->memcache->set($this->identifierPrefix . 'tagIndex', array_unique($tags), 0, 0);
        }
 
        /**
@@ -396,15 +401,23 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         * @param string $entryIdentifier
         * @param array Array of tags
         * @author Karsten Dambekalns <karsten@typo3.org>
+        * @author      Dmitry Dulepov
         */
        protected function addIdentifierToTags($entryIdentifier, array $tags) {
                foreach($tags as $tag) {
-                       $identifiers   = $this->findIdentifiersTaggedWith($tag);
-                       $identifiers[] = $entryIdentifier;
-                       $this->memcache->set(
-                               $this->identifierPrefix . '_tag_' . $tag,
-                               array_unique($identifiers)
-                       );
+                       // Update tag-to-identifier index
+                       $identifiers = $this->findIdentifiersTaggedWith($tag);
+                       if (array_search($entryIdentifier, $identifiers) === false) {
+                               $identifiers[] = $entryIdentifier;
+                               $this->memcache->set($this->identifierPrefix . 'tag_' . $tag,
+                                       $identifiers);
+                       }
+                       // Update identifier-to-tag index
+                       $existingTags = $this->findTagsForIdentifier($entryIdentifier);
+                       if (array_search($entryIdentifier, $existingTags) === false) {
+                               $this->memcache->set($this->identifierPrefix . 'ident_' . $entryIdentifier,
+                                       array_merge($existingTags, $tags));
+                       }
                }
        }
 
@@ -414,27 +427,35 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         * @param string $entryIdentifier
         * @param array Array of tags
         * @author Karsten Dambekalns <karsten@typo3.org>
+        * @author      Dmitry Dulepov
         */
        protected function removeIdentifierFromAllTags($entryIdentifier) {
-               $tags = $this->getTagIndex();
-
-               foreach($tags as $tag) {
+               // Get tags for this identifier
+               $tags = $this->findTagsForIdentifier($entryIdentifier);
+               // Deassociate tags with this identifier
+               foreach ($tags as $tag) {
                        $identifiers = $this->findIdentifiersTaggedWith($tag);
-
-                       if(array_search($entryIdentifier, $identifiers) !== FALSE) {
-                               unset($identifiers[array_search($entryIdentifier, $identifiers)]);
-                       }
-
-                       if(count($identifiers)) {
-                               $this->memcache->set(
-                                       $this->identifierPrefix . '_tag_' . $tag,
-                                       array_unique($identifiers)
-                               );
-                       } else {
-                               $this->removeTagsFromTagIndex(array($tag));
-                               $this->memcache->delete($this->identifierPrefix . '_tag_' . $tag);
+                       // Formally array_search() below should never return false due to
+                       // the behavior of findTagsForIdentifier(). But if reverse index is
+                       // corrupted, we still can get 'false' from array_search(). This is
+                       // not a problem because we are removing this identifier from
+                       // anywhere.
+                       if (($key = array_search($entryIdentifier, $identifiers)) !== false) {
+                               unset($identifiers[$key]);
+
+                               if(count($identifiers)) {
+                                       $this->memcache->set(
+                                               $this->identifierPrefix . 'tag_' . $tag,
+                                               $identifiers
+                                       );
+                               } else {
+                                       $this->removeTagsFromTagIndex(array($tag));
+                                       $this->memcache->delete($this->identifierPrefix . 'tag_' . $tag);
+                               }
                        }
                }
+               // Clear reverse tag index for this identifier
+               $this->memcache->delete($this->identifierPrefix . 'ident_' . $entryIdentifier);
        }
 
        /**
@@ -445,13 +466,34 @@ class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
         * @author Karsten Dambekalns <karsten@typo3.org>
         */
        public function findIdentifiersTaggedWith($tag) {
-               $identifiers = $this->memcache->get($this->identifierPrefix . '_tag_' . $tag);
+               $identifiers = $this->memcache->get($this->identifierPrefix . 'tag_' . $tag);
+               return ($identifiers === false ? array() : $identifiers);
+       }
 
-               if($identifiers !== FALSE) {
-                       return (array) $identifiers;
-               } else {
-                       return array();
-               }
+       /**
+        * Finds all tags for the given identifier. This function uses reverse tag
+        * index to search for tags.
+        *
+        * @param       string  $identifier     Identifier to search tags for
+        * @return      array   Array with tags
+        */
+       protected function findTagsForIdentifier($identifier) {
+               $tags = $this->memcache->get($this->identifierPrefix . 'ident_' . $identifier);
+               return ($tags == false ? array() : (array)$tags);
+       }
+
+       /**
+        * Returns idenfier prefix. Extensions can override this function to provide
+        * another identifier prefix if it is necessary for special purposes.
+        * Default identifier prefix is based on PATH_site only. In most cases
+        * it is enough because different installations use different paths and page
+        * IDs in the same installation never repeat.
+        *
+        * @return      string  Identifier prefix, ending with underscore
+        * @author      Dmitry Dulepov
+        */
+       protected function getIdentifierPrefix() {
+               return 'TYPO3_' . md5(PATH_site) . '_';
        }
 }