diff --git a/config-dist.php b/config-dist.php index e3402daa23d..47ffb73416f 100644 --- a/config-dist.php +++ b/config-dist.php @@ -240,6 +240,14 @@ $CFG->admin = 'admin'; // $CFG->session_memcached_acquire_lock_timeout = 120; // $CFG->session_memcached_lock_expire = 7200; // Ignored if memcached extension <= 2.1.0 // +// Memcache session handler (requires memcached server and memcache extension): +// $CFG->session_handler_class = '\core\session\memcache'; +// $CFG->session_memcache_save_path = '127.0.0.1:11211'; +// $CFG->session_memcache_acquire_lock_timeout = 120; +// ** NOTE: Memcache extension has less features than memcached and may be +// less reliable. Use memcached where possible or if you encounter +// session problems. ** +// // Following setting allows you to alter how frequently is timemodified updated in sessions table. // $CFG->session_update_timemodified_frequency = 20; // In seconds. // diff --git a/lib/classes/session/memcache.php b/lib/classes/session/memcache.php new file mode 100644 index 00000000000..03b2cc3b842 --- /dev/null +++ b/lib/classes/session/memcache.php @@ -0,0 +1,170 @@ +. + +/** + * Memcache based session handler. + * + * This is based on the memcached code. It lacks some features, such as + * locking options, but appears to work in practice. + * + * @package core + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\session; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Memcache based session handler. + * + * @package core + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class memcache extends handler { + /** + * Creates new instance of handler. + */ + public function __construct() { + global $CFG; + + if (empty($CFG->session_memcache_save_path)) { + $this->savepath = ''; + } else { + $this->savepath = $CFG->session_memcache_save_path; + } + + if (empty($this->savepath)) { + $this->servers = array(); + } else { + $this->servers = util::connection_string_to_memcache_servers($this->savepath); + } + + if (!empty($CFG->session_memcache_acquire_lock_timeout)) { + $this->acquiretimeout = (int)$CFG->session_memcache_acquire_lock_timeout; + } + } + + /** + * Starts the session. + * + * @return bool success + */ + public function start() { + $default = ini_get('max_execution_time'); + set_time_limit($this->acquiretimeout); + + $result = parent::start(); + + set_time_limit($default); + return $result; + } + + /** + * Inits session handler. + */ + public function init() { + if (!extension_loaded('memcache')) { + throw new exception('sessionhandlerproblem', 'error', '', null, + 'memcache extension is not loaded'); + } + $version = phpversion('memcache'); + if (!$version or version_compare($version, '2.2') < 0) { + throw new exception('sessionhandlerproblem', 'error', '', null, + 'memcache extension version must be at least 2.2'); + } + if (empty($this->savepath)) { + throw new exception('sessionhandlerproblem', 'error', '', null, + '$CFG->session_memcache_save_path must be specified in config.php'); + } + + ini_set('session.save_handler', 'memcache'); + ini_set('session.save_path', $this->savepath); + } + + /** + * Checks for existing session with given id. + * + * Note: this verifies the storage backend only, not the actual session records. + * + * @param string $sid PHP session ID + * @return bool true if session found. + */ + public function session_exists($sid) { + if (!$this->servers) { + return false; + } + + $memcache = $this->get_memcache(); + $value = $memcache->get($sid); + $memcache->close(); + + return ($value !== false); + } + + /** + * Gets the memcache object with all the servers added to it. + * + * @return \Memcache Initialised memcache object + */ + protected function get_memcache() { + $memcache = new \Memcache(); + foreach ($this->servers as $server) { + $memcache->addServer($server[0], $server[1]); + } + return $memcache; + } + + /** + * Kills all active sessions, the core sessions table is purged afterwards. + */ + public function kill_all_sessions() { + global $DB; + if (!$this->servers) { + return; + } + + $memcache = $this->get_memcache(); + + // Note: this can be significantly improved by fetching keys from memcache, + // but we need to make sure we are not deleting somebody else's sessions. + + $rs = $DB->get_recordset('sessions', array(), 'id DESC', 'id, sid'); + foreach ($rs as $record) { + $memcache->delete($record->sid); + } + $rs->close(); + + $memcache->close(); + } + + /** + * Kills one session, the session record is removed afterwards. + * + * @param string $sid PHP session ID + */ + public function kill_session($sid) { + if (!$this->servers) { + return; + } + + $memcache = $this->get_memcache(); + $memcache->delete($sid); + $memcache->close(); + } +} diff --git a/lib/classes/session/memcached.php b/lib/classes/session/memcached.php index 4e07f3c38cd..73bf1bb343c 100644 --- a/lib/classes/session/memcached.php +++ b/lib/classes/session/memcached.php @@ -63,7 +63,7 @@ class memcached extends handler { if (empty($this->savepath)) { $this->servers = array(); } else { - $this->servers = self::connection_string_to_servers($this->savepath); + $this->servers = util::connection_string_to_memcache_servers($this->savepath); } if (empty($CFG->session_memcached_prefix)) { @@ -186,38 +186,4 @@ class memcached extends handler { $memcached->quit(); } - /** - * Convert a connection string to an array of servers - * - * EG: Converts: "abc:123, xyz:789" to - * - * array( - * array('abc', '123'), - * array('xyz', '789'), - * ) - * - * @copyright 2013 Moodlerooms Inc. (http://www.moodlerooms.com) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @author Mark Nielsen - * - * @param string $str save_path value containing memcached connection string - * @return array - */ - protected static function connection_string_to_servers($str) { - $servers = array(); - $parts = explode(',', $str); - foreach ($parts as $part) { - $part = trim($part); - $pos = strrpos($part, ':'); - if ($pos !== false) { - $host = substr($part, 0, $pos); - $port = substr($part, ($pos + 1)); - } else { - $host = $part; - $port = 11211; - } - $servers[] = array($host, $port); - } - return $servers; - } } diff --git a/lib/classes/session/util.php b/lib/classes/session/util.php new file mode 100644 index 00000000000..72c476a805d --- /dev/null +++ b/lib/classes/session/util.php @@ -0,0 +1,75 @@ +. + +/** + * Shared utility functions for session handlers. + * + * This contains functions that are shared between two or more handlers. + * + * @package core + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\session; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Shared utility functions for session handlers. + * + * This contains functions that are shared between two or more handlers. + * + * @package core + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class util { + /** + * Convert a connection string to an array of servers + * + * EG: Converts: "abc:123, xyz:789" to + * + * array( + * array('abc', '123'), + * array('xyz', '789'), + * ) + * + * @copyright 2013 Moodlerooms Inc. (http://www.moodlerooms.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Mark Nielsen + * + * @param string $str save_path value containing memcached connection string + * @return array + */ + public static function connection_string_to_memcache_servers($str) { + $servers = array(); + $parts = explode(',', $str); + foreach ($parts as $part) { + $part = trim($part); + $pos = strrpos($part, ':'); + if ($pos !== false) { + $host = substr($part, 0, $pos); + $port = substr($part, ($pos + 1)); + } else { + $host = $part; + $port = 11211; + } + $servers[] = array($host, $port); + } + return $servers; + } +}