From 9d05b2e9796d156cdcdc107f09b7b652cf2fb7aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaime=20Pe=CC=81rez=20Crespo?= <jaime.perez@uninett.no>
Date: Wed, 18 Mar 2020 11:04:31 +0100
Subject: [PATCH] Restore persistence for memcached

This was available for the memcache extension, but when we moved to memcached it went away since the API is different and required changes. The issue with persitence in memcached is that persistent connections require a common identifier, which shouldn't be fixed. Therefore, we need to change a bit the way the memcache servers are configured in config.php as well.
---
 config-templates/config.php | 36 ++++++++++++++++++++++++++++++++++++
 lib/SimpleSAML/Memcache.php | 29 ++++++++++++++++++++---------
 2 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/config-templates/config.php b/config-templates/config.php
index 2769afe6a..c284fe1ee 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -627,6 +627,9 @@ $config = [
      *  - 'port': This is the port number of the memcache server. If this
      *    option isn't set, then we will use the 'memcache.default_port'
      *    ini setting. This is 11211 by default.
+     *
+     * When using the "memcache" extension, the following options are also
+     * supported:
      *  - 'weight': This sets the weight of this server in this server
      *    group. http://php.net/manual/en/function.Memcache-addServer.php
      *    contains more information about the weight option.
@@ -660,6 +663,39 @@ $config = [
      *     ],
      * ],
      *
+     * Additionally, when using the "memcached" extension, unique keys must
+     * be provided for each group of servers if persistent connections are
+     * desired. Each server group can also have an "options" indexed array
+     * with the options desired for the given group:
+     *
+     * 'memcache_store.servers' => [
+     *     [
+     *         'memcache_group_1' => [
+     *             'options' => [
+     *                  Memcached::OPT_BINARY_PROTOCOL => true,
+     *                  Memcached::OPT_NO_BLOCK => true,
+     *                  Memcached::OPT_TCP_NODELAY => true,
+     *                  Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
+     *                  Memcached::OPT_BUFFER_WRITES => true,
+     *             ],
+     *             ['hostname' => '127.0.0.1', 'port' => 11211],
+     *             ['hostname' => '127.0.0.2', 'port' => 11211],
+     *         ],
+     *
+     *         'memcache_group_2' => [
+     *             'options' => [
+     *                  Memcached::OPT_BINARY_PROTOCOL => true,
+     *                  Memcached::OPT_NO_BLOCK => true,
+     *                  Memcached::OPT_TCP_NODELAY => true,
+     *                  Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
+     *                  Memcached::OPT_BUFFER_WRITES => true,
+     *             ],
+     *             ['hostname' => '127.0.0.3', 'port' => 11211],
+     *             ['hostname' => '127.0.0.4', 'port' => 11211],
+     *         ],
+     *     ],
+     * ],
+     *
      */
     'memcache_store.servers' => [
         [
diff --git a/lib/SimpleSAML/Memcache.php b/lib/SimpleSAML/Memcache.php
index 15c24b9a3..ac9106d58 100644
--- a/lib/SimpleSAML/Memcache.php
+++ b/lib/SimpleSAML/Memcache.php
@@ -289,14 +289,29 @@ class Memcache
      * creates a Memcache object from the servers in the group.
      *
      * @param array $group Array of servers which should be created as a group.
+     * @param string $index The index for this group. Specify if persistent connections are desired.
      *
      * @return \Memcached A Memcache object of the servers in the group
      *
      * @throws \Exception If the servers configuration is invalid.
      */
-    private static function loadMemcacheServerGroup(array $group)
+    private static function loadMemcacheServerGroup(array $group, $index = null)
     {
-        $memcache = new \Memcached('persistent');
+        if (is_string($index)) {
+            $memcache = new \Memcached($index);
+        } else {
+            $memcache = new \Memcached();
+        }
+        if (array_key_exists('options', $group)) {
+            $memcache->setOptions($group['options']);
+            unset($group['options']);
+        }
+
+        $servers = $memcache->getServerList();
+        if (count($servers) === count($group) && !$memcache->isPristine()) {
+            return $memcache;
+        }
+        $memcache->resetServerList();
 
         // iterate over all the servers in the group and add them to the Memcache object
         foreach ($group as $index => $server) {
@@ -353,12 +368,8 @@ class Memcache
         // iterate over all the groups in the 'memcache_store.servers' configuration option
         foreach ($groups as $index => $group) {
             // make sure that the group doesn't have an index. An index would be a sign of invalid configuration
-            if (!is_int($index)) {
-                throw new \Exception(
-                    "Invalid index on element in the 'memcache_store.servers'" .
-                    ' configuration option. Perhaps you have forgotten to add an array(...)' .
-                    ' around one of the server groups? The invalid index was: ' . $index
-                );
+            if (is_int($index)) {
+                $index = null;
             }
 
             /*
@@ -374,7 +385,7 @@ class Memcache
             }
 
             // parse and add this group to the server group list
-            self::$serverGroups[] = self::loadMemcacheServerGroup($group);
+            self::$serverGroups[] = self::loadMemcacheServerGroup($group, $index);
         }
 
         return self::$serverGroups;
-- 
GitLab