. /** * Memcached based session handler. * * @package core * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\session; defined('MOODLE_INTERNAL') || die(); /** * Memcached based session handler. * * @package core * @copyright 2013 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class memcached extends handler { /** @var string $savepath save_path string */ protected $savepath; /** @var array $servers list of servers parsed from save_path */ protected $servers; /** @var string $prefix session key prefix */ protected $prefix; /** @var int $acquiretimeout how long to wait for session lock */ protected $acquiretimeout = 120; /** * @var int $lockexpire how long to wait before expiring the lock so that other requests * may continue execution, ignored if memcached <= 2.1.0. */ protected $lockexpire = 7200; /** * Create new instance of handler. */ public function __construct() { global $CFG; if (empty($CFG->session_memcached_save_path)) { $this->savepath = ''; } else { $this->savepath = $CFG->session_memcached_save_path; } if (empty($this->savepath)) { $this->servers = array(); } else { $this->servers = util::connection_string_to_memcache_servers($this->savepath); } if (empty($CFG->session_memcached_prefix)) { $this->prefix = ini_get('memcached.sess_prefix'); } else { $this->prefix = $CFG->session_memcached_prefix; } if (!empty($CFG->session_memcached_acquire_lock_timeout)) { $this->acquiretimeout = (int)$CFG->session_memcached_acquire_lock_timeout; } if (!empty($CFG->session_memcached_lock_expire)) { $this->lockexpire = (int)$CFG->session_memcached_lock_expire; } } /** * Start the session. * @return bool success */ public function start() { // NOTE: memcached <= 2.1.0 expires session locks automatically after max_execution_time, // this leads to major difference compared to other session drivers that timeout // and stop the second request execution instead. $default = ini_get('max_execution_time'); set_time_limit($this->acquiretimeout); $result = parent::start(); set_time_limit($default); return $result; } /** * Init session handler. */ public function init() { if (!extension_loaded('memcached')) { throw new exception('sessionhandlerproblem', 'error', '', null, 'memcached extension is not loaded'); } $version = phpversion('memcached'); if (!$version or version_compare($version, '2.0') < 0) { throw new exception('sessionhandlerproblem', 'error', '', null, 'memcached extension version must be at least 2.0'); } if (empty($this->savepath)) { throw new exception('sessionhandlerproblem', 'error', '', null, '$CFG->session_memcached_save_path must be specified in config.php'); } ini_set('session.save_handler', 'memcached'); ini_set('session.save_path', $this->savepath); ini_set('memcached.sess_prefix', $this->prefix); ini_set('memcached.sess_locking', '1'); // Locking is required! // Try to configure lock and expire timeouts - ignored if memcached <=2.1.0. ini_set('memcached.sess_lock_max_wait', $this->acquiretimeout); ini_set('memcached.sess_lock_expire', $this->lockexpire); } /** * Check for existing session with id $sid. * * Note: this verifies the storage backend only, not the actual session records. * * @param string $sid * @return bool true if session found. */ public function session_exists($sid) { if (!$this->servers) { return false; } $memcached = new \Memcached(); $memcached->addServers($this->servers); $value = $memcached->get($this->prefix.$sid); $memcached->quit(); return ($value !== false); } /** * Kill all active sessions, the core sessions table is * purged afterwards. */ public function kill_all_sessions() { global $DB; if (!$this->servers) { return; } $memcached = new \Memcached(); $memcached->addServers($this->servers); // Note: this can be significantly improved by fetching keys from memcached, // 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) { $memcached->delete($this->prefix.$record->sid); } $rs->close(); $memcached->quit(); } /** * Kill one session, the session record is removed afterwards. * @param string $sid */ public function kill_session($sid) { if (!$this->servers) { return; } $memcached = new \Memcached(); $memcached->addServers($this->servers); $memcached->delete($this->prefix.$sid); $memcached->quit(); } }