From bd035b98fdf182d2b517944f6cde3588523dee04 Mon Sep 17 00:00:00 2001 From: Fred Woolard Date: Mon, 23 Oct 2017 13:22:42 -0400 Subject: [PATCH] MDL-57477 core: If memcached 3.x mimic 2.2.0 lock acquisition behavior --- config-dist.php | 2 ++ lib/classes/session/memcached.php | 42 ++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/config-dist.php b/config-dist.php index 094ffb49dd3..6c60e81b690 100644 --- a/config-dist.php +++ b/config-dist.php @@ -259,6 +259,8 @@ $CFG->admin = 'admin'; // $CFG->session_memcached_prefix = 'memc.sess.key.'; // $CFG->session_memcached_acquire_lock_timeout = 120; // $CFG->session_memcached_lock_expire = 7200; // Ignored if PECL memcached is below version 2.2.0 +// $CFG->session_memcached_lock_retry_sleep = 150; // Spin-lock retry sleeptime (msec). Only effective +// // for tuning php-memcached 3.0.x (PHP 7) // // Redis session handler (requires redis server and redis extension): // $CFG->session_handler_class = '\core\session\redis'; diff --git a/lib/classes/session/memcached.php b/lib/classes/session/memcached.php index 5a1fd4df107..c7853379007 100644 --- a/lib/classes/session/memcached.php +++ b/lib/classes/session/memcached.php @@ -47,6 +47,13 @@ class memcached extends handler { * may continue execution, ignored if PECL memcached is below version 2.2.0. */ protected $lockexpire = 7200; + /** + * + * @var integer $lockretrysleep Used for memcached 3.x (PHP7), the amount of time to + * sleep between attempts to acquire the session lock. Mimics the deprecated config + * memcached.sess_lock_wait. + */ + protected $lockretrysleep = 150; /** * Create new instance of handler. @@ -79,6 +86,10 @@ class memcached extends handler { if (!empty($CFG->session_memcached_lock_expire)) { $this->lockexpire = (int)$CFG->session_memcached_lock_expire; } + + if (!empty($CFG->session_memcached_lock_retry_sleep)) { + $this->lockretrysleep = (int)$CFG->session_memcached_lock_retry_sleep; + } } /** @@ -98,6 +109,7 @@ class memcached extends handler { $result = parent::start(); + // MDL-53713... // If session_start returned TRUE, but it took as long // as the timeout value, and the $_SESSION returned is // empty when should not have been (isnewsession false) @@ -134,15 +146,39 @@ class memcached extends handler { ini_set('session.save_path', $this->savepath); ini_set('memcached.sess_prefix', $this->prefix); ini_set('memcached.sess_locking', '1'); // Locking is required! + ini_set('memcached.sess_lock_expire', $this->lockexpire); - // Try to configure lock and expire timeouts - ignored if memcached is before version 2.2.0. + // MDL-57477, MDL-53713... if (version_compare($version, '3.0.0-dev') >= 0) { - ini_set('memcached.sess_lock_wait_max', $this->acquiretimeout * 1000); + // With memcached 3.x (PHP 7) we configure the max retries to make + // and the time to sleep between each retry. There are two sleep + // config values, an initial and a max value. After each attempt + // the memcached module adjusts the sleep value to be the lesser of + // the configured max value, or 2X the previous value. With default + // memcached.ini configs (5, 1s, 2s) the result is only 5 attempts + // to lock over 9 sec. To mimic the behavior of the 2.2.x module so + // we get more attempts and much more frequently, config both sleep + // values to the old default value of 150 msec (making it constant) + // and calculate number of retries using the existing Moodle config + // $CFG->session_memcached_acquire_lock_timeout. Doing this so + // admins configure session lock attempt timeout in familiar terms, + // and more straight-forward to detect if lock attempt timeout has + // occurred in start(). If _min and _max values are not equal, the + // actual lock acquire timeout will not be the expected configured + // value in $CFG->session_memcached_acquire_lock_timeout; this will + // cause session data loss when failure to acquire the lock is not + // detected. + ini_set('memcached.sess_lock_wait_min', $this->lockretrysleep); + ini_set('memcached.sess_lock_wait_max', $this->lockretrysleep); + ini_set('memcached.sess_lock_retries', (int)(($this->acquiretimeout * 1000) / $this->lockretrysleep) + 1); } else { + // With memcached 2.2.x we configure max time to attempt lock, and + // accept default value (in memcached.ini) for sleep time between + // each attempt (usually 150 msec), then memcached calculates the + // max number of retries to make. ini_set('memcached.sess_lock_max_wait', $this->acquiretimeout); } - ini_set('memcached.sess_lock_expire', $this->lockexpire); } /**