MDL-41106 cache: several fixes for the session cache.
[moodle.git] / cache / stores / session / lib.php
index 8221502..d5e245e 100644 (file)
@@ -118,7 +118,7 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
 
     /**
      * The maximum size for the store, or false if there isn't one.
-     * @var bool
+     * @var bool|int
      */
     protected $maxsize = false;
 
@@ -202,7 +202,7 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      */
     public function initialise(cache_definition $definition) {
         $this->storeid = $definition->generate_definition_hash();
-        $this->store = &self::register_store_id($definition->get_id());
+        $this->store = &self::register_store_id($this->name.'-'.$definition->get_id());
         $this->ttl = $definition->get_ttl();
         $maxsize = $definition->get_maxsize();
         if ($maxsize !== null) {
@@ -210,6 +210,7 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
             $this->maxsize = abs((int)$maxsize);
             $this->storecount = count($this->store);
         }
+        $this->check_ttl();
     }
 
     /**
@@ -238,9 +239,17 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
     public function get($key) {
         if (isset($this->store[$key])) {
             if ($this->ttl == 0) {
-                return $this->store[$key][0];
+                $value = $this->store[$key][0];
+                if ($this->maxsize !== false) {
+                    // Make sure the element is now in the end of array.
+                    $this->set($key, $value);
+                }
+                return $value;
             } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
                 return $this->store[$key][0];
+            } else {
+                // Element is present but has expired.
+                $this->check_ttl();
             }
         }
         return false;
@@ -257,20 +266,32 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      */
     public function get_many($keys) {
         $return = array();
+        $maxtime = 0;
         if ($this->ttl != 0) {
             $maxtime = cache::now() - $this->ttl;
         }
 
+        $hasexpiredelements = false;
         foreach ($keys as $key) {
             $return[$key] = false;
             if (isset($this->store[$key])) {
                 if ($this->ttl == 0) {
                     $return[$key] = $this->store[$key][0];
+                    if ($this->maxsize !== false) {
+                        // Make sure the element is now in the end of array.
+                        $this->set($key, $return[$key], false);
+                    }
                 } else if ($this->store[$key][1] >= $maxtime) {
                     $return[$key] = $this->store[$key][0];
+                } else {
+                    $hasexpiredelements = true;
                 }
             }
         }
+        if ($hasexpiredelements) {
+            // There are some elements that are present but have expired.
+            $this->check_ttl();
+        }
         return $return;
     }
 
@@ -279,24 +300,27 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      *
      * @param string $key The key to use.
      * @param mixed $data The data to set.
-     * @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required.
+     * @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required. If this is set to false you will
+     *      need to perform these checks yourself. This allows for bulk set's to be performed and maxsize tests performed once.
      * @return bool True if the operation was a success false otherwise.
      */
     public function set($key, $data, $testmaxsize = true) {
         $testmaxsize = ($testmaxsize && $this->maxsize !== false);
-        if ($testmaxsize) {
-            $increment = (!isset($this->store[$key]));
+        $increment = $this->maxsize !== false && !isset($this->store[$key]);
+        if (($this->maxsize !== false && !$increment) || $this->ttl != 0) {
+            // Make sure the element is added to the end of $this->store array.
+            unset($this->store[$key]);
         }
-        if ($this->ttl == 0) {
-            $this->store[$key][0] = $data;
+        if ($this->ttl === 0) {
+            $this->store[$key] = array($data, 0);
         } else {
             $this->store[$key] = array($data, cache::now());
         }
-        if ($testmaxsize && $increment) {
+        if ($increment) {
             $this->storecount++;
-            if ($this->storecount > $this->maxsize) {
-                $this->reduce_for_maxsize();
-            }
+        }
+        if ($testmaxsize && $this->storecount > $this->maxsize) {
+            $this->reduce_for_maxsize();
         }
         return true;
     }
@@ -311,12 +335,26 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      */
     public function set_many(array $keyvaluearray) {
         $count = 0;
+        $increment = 0;
         foreach ($keyvaluearray as $pair) {
-            $this->set($pair['key'], $pair['value'], false);
+            $key = $pair['key'];
+            $data = $pair['value'];
             $count++;
+            if ($this->maxsize !== false || $this->ttl !== 0) {
+                // Make sure the element is added to the end of $this->store array.
+                $this->delete($key);
+                $increment++;
+            } else if (!isset($this->store[$key])) {
+                $increment++;
+            }
+            if ($this->ttl === 0) {
+                $this->store[$key] = array($data, 0);
+            } else {
+                $this->store[$key] = array($data, cache::now());
+            }
         }
         if ($this->maxsize !== false) {
-            $this->storecount += $count;
+            $this->storecount += $increment;
             if ($this->storecount > $this->maxsize) {
                 $this->reduce_for_maxsize();
             }
@@ -348,6 +386,7 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      * @return bool
      */
     public function has_all(array $keys) {
+        $maxtime = 0;
         if ($this->ttl != 0) {
             $maxtime = cache::now() - $this->ttl;
         }
@@ -370,6 +409,7 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      * @return bool
      */
     public function has_any(array $keys) {
+        $maxtime = 0;
         if ($this->ttl != 0) {
             $maxtime = cache::now() - $this->ttl;
         }
@@ -389,12 +429,14 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      * @return bool Returns true if the operation was a success, false otherwise.
      */
     public function delete($key) {
-        $result = isset($this->store[$key]);
+        if (!isset($this->store[$key])) {
+            return false;
+        }
         unset($this->store[$key]);
         if ($this->maxsize !== false) {
             $this->storecount--;
         }
-        return $result;
+        return true;
     }
 
     /**
@@ -404,17 +446,18 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      * @return int The number of items successfully deleted.
      */
     public function delete_many(array $keys) {
-        $count = 0;
+        // The number of items that have actually being removed.
+        $reduction = 0;
         foreach ($keys as $key) {
             if (isset($this->store[$key])) {
-                $count++;
+                $reduction++;
             }
             unset($this->store[$key]);
         }
         if ($this->maxsize !== false) {
-            $this->storecount -= $count;
+            $this->storecount -= $reduction;
         }
-        return $count;
+        return $reduction;
     }
 
     /**
@@ -489,12 +532,40 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
         return $this->name;
     }
 
+    /**
+     * Removes expired elements.
+     * @return int number of removed elements
+     */
+    protected function check_ttl() {
+        if ($this->ttl === 0) {
+            return 0;
+        }
+        $maxtime = cache::now() - $this->ttl;
+        $count = 0;
+        for ($value = reset($this->store); $value !== false; $value = next($this->store)) {
+            if ($value[1] >= $maxtime) {
+                // We know that elements are sorted by ttl so no need to continue.
+                break;
+            }
+            $count++;
+        }
+        if ($count) {
+            // Remove first $count elements as they are expired.
+            $this->store = array_slice($this->store, $count, null, true);
+            if ($this->maxsize !== false) {
+                $this->storecount -= $count;
+            }
+        }
+        return $count;
+    }
+
     /**
      * Finds all of the keys being stored in the cache store instance.
      *
      * @return array
      */
     public function find_all() {
+        $this->check_ttl();
         return array_keys($this->store);
     }
 
@@ -502,6 +573,7 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
      * Finds all of the keys whose keys start with the given prefix.
      *
      * @param string $prefix
+     * @return array An array of keys.
      */
     public function find_by_prefix($prefix) {
         $return = array();
@@ -512,4 +584,12 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
         }
         return $return;
     }
+
+    /**
+     * This store supports native TTL handling.
+     * @return bool
+     */
+    public function store_supports_native_ttl() {
+        return true;
+    }
 }