. /** * 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. * * Note: You may need to manually configure redundancy and fail-over * if you specify multiple servers. * * @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 { /** @var string $savepath save_path string */ protected $savepath; /** @var array $servers list of servers parsed from save_path */ protected $servers; /** @var int $acquiretimeout how long to wait for session lock */ protected $acquiretimeout = 120; /** * 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'); } // Check in case anybody mistakenly includes tcp://, which you // would do in the raw PHP config. We require the same format as // for memcached (without tcp://). Otherwse the code that splits it into // individual servers won't have worked properly. if (strpos($this->savepath, 'tcp://') !== false) { throw new exception('sessionhandlerproblem', 'error', '', null, '$CFG->session_memcache_save_path should not contain tcp://'); } ini_set('session.save_handler', 'memcache'); // The format of save_path is different for memcache (compared to memcached). // We are using the same format in config.php to avoid confusion. // It has to have tcp:// at the start of each entry. $memcacheformat = preg_replace('~(^|,\s*)~','$1tcp://', $this->savepath); ini_set('session.save_path', $memcacheformat); } /** * Check the backend contains data for this session id. * * Note: this is intended to be called from manager::session_exists() only. * * @param string $sid PHP session ID * @return bool true if session found. */ public function session_exists($sid) { $result = false; foreach ($this->get_memcaches() as $memcache) { if ($result === false) { $value = $memcache->get($sid); if ($value !== false) { $result = true; } } $memcache->close(); } return $result; } /** * Gets the Memcache objects, one for each server. * The connects must be closed manually after use. * * Note: the servers are not automatically synchronised * when accessed via Memcache class, it needs to be * done manually by accessing all configured servers. * * @return \Memcache[] Array of initialised memcache objects */ protected function get_memcaches() { $result = array(); foreach ($this->servers as $server) { $memcache = new \Memcache(); $memcache->addServer($server[0], $server[1]); $result[] = $memcache; } return $result; } /** * Kills all active sessions, the core sessions table is purged afterwards. */ public function kill_all_sessions() { global $DB; if (!$this->servers) { return; } $memcaches = $this->get_memcaches(); // 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) { foreach ($memcaches as $memcache) { $memcache->delete($record->sid); } } $rs->close(); foreach ($memcaches as $memcache) { $memcache->close(); } } /** * Kills one session, the session record is removed afterwards. * * @param string $sid PHP session ID */ public function kill_session($sid) { foreach ($this->get_memcaches() as $memcache) { $memcache->delete($sid); $memcache->close(); } } }