_config['save_path'])) { log_message('error', 'Session: No Redis save path configured.'); } elseif (preg_match('#(?:tcp://)?([^:?]+)(?:\:(\d+))?(\?.+)?#', $this->_config['save_path'], $matches)) { isset($matches[3]) OR $matches[3] = ''; // Just to avoid undefined index notices below $this->_config['save_path'] = array( 'host' => $matches[1], 'port' => empty($matches[2]) ? NULL : $matches[2], 'password' => preg_match('#auth=([^\s&]+)#', $matches[3], $match) ? $match[1] : NULL, 'database' => preg_match('#database=(\d+)#', $matches[3], $match) ? (int) $match[1] : NULL, 'timeout' => preg_match('#timeout=(\d+\.\d+)#', $matches[3], $match) ? (float) $match[1] : NULL ); preg_match('#prefix=([^\s&]+)#', $matches[3], $match) && $this->_key_prefix = $match[1]; } else { log_message('error', 'Session: Invalid Redis save path format: '.$this->_config['save_path']); } if ($this->_config['match_ip'] === TRUE) { $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':'; } } // ------------------------------------------------------------------------ public function open($save_path, $name) { if (empty($this->_config['save_path'])) { return FALSE; } $redis = new Redis(); if ( ! $redis->connect($this->_config['save_path']['host'], $this->_config['save_path']['port'], $this->_config['save_path']['timeout'])) { log_message('error', 'Session: Unable to connect to Redis with the configured settings.'); } elseif (isset($this->_config['save_path']['password']) && ! $redis->auth($this->_config['save_path']['password'])) { log_message('error', 'Session: Unable to authenticate to Redis instance.'); } elseif (isset($this->_config['save_path']['database']) && ! $redis->select($this->_config['save_path']['database'])) { log_message('error', 'Session: Unable to select Redis database with index '.$this->_config['save_path']['database']); } else { $this->_redis = $redis; return TRUE; } return FALSE; } // ------------------------------------------------------------------------ public function read($session_id) { if (isset($this->_redis) && $this->_get_lock($session_id)) { $session_data = (string) $this->_redis->get($this->_key_prefix.$session_id); $this->_fingerprint = md5($session_data); return $session_data; } return FALSE; } public function write($session_id, $session_data) { if (isset($this->_redis, $this->_lock_key)) { $this->_redis->setTimeout($this->_lock_key, 5); if ($this->_fingerprint !== ($fingerprint = md5($session_data))) { if ($this->_redis->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration'])) { $this->_fingerprint = $fingerprint; return TRUE; } return FALSE; } return $this->_redis->setTimeout($this->_key_prefix.$session_id, $this->_config['expiration']); } return FALSE; } // ------------------------------------------------------------------------ public function close() { if (isset($this->_redis)) { try { if ($this->_redis->ping() === '+PONG') { isset($this->_lock_key) && $this->_redis->delete($this->_lock_key); if ( ! $this->_redis->close()) { return FALSE; } } } catch (RedisException $e) { log_message('error', 'Session: Got RedisException on close(): '.$e->getMessage()); } $this->_redis = NULL; return TRUE; } return FALSE; } // ------------------------------------------------------------------------ public function destroy($session_id) { if (isset($this->_redis, $this->_lock_key)) { if ($this->_redis->delete($this->_key_prefix.$session_id) !== 1) { log_message('debug', 'Session: Redis::delete() expected to return 1, got '.var_export($result, TRUE).' instead.'); } return ($this->_cookie_destroy() && $this->close()); } return $this->close(); } // ------------------------------------------------------------------------ public function gc($maxlifetime) { // TODO: keys()/getKeys() is said to be performance-intensive, // although it supports patterns (*, [charlist] at the very least). // scan() seems to be recommended, but requires redis 2.8 // Not sure if we need any of these though, as we set keys with expire times return TRUE; } // ------------------------------------------------------------------------ protected function _get_lock($session_id) { if (isset($this->_lock_key)) { return $this->_redis->setTimeout($this->_lock_key, 5); } $lock_key = $this->_key_prefix.$session_id.':lock'; if (($ttl = $this->_redis->ttl($lock_key)) < 1) { if ( ! $this->_redis->setex($lock_key, 5, time())) { log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); return FALSE; } $this->_lock_key = $lock_key; if ($ttl === -1) { log_message('debug', 'Session: Lock for '.$this->_key_prefix.$session_id.' had no TTL, overriding.'); } $this->_lock = TRUE; return TRUE; } // Another process has the lock, we'll try to wait for it to free itself ... $attempt = 0; while ($attempt++ < 5) { usleep(($ttl * 1000000) - 20000); if (($ttl = $this->_redis->ttl($lock_key)) > 0) { continue; } if ( ! $this->_redis->setex($lock_key, 5, time())) { log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); return FALSE; } $this->_lock_key = $lock_key; break; } if ($attempt === 5) { log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 5 attempts, aborting.'); return FALSE; } $this->_lock = TRUE; return TRUE; } // ------------------------------------------------------------------------ protected function _release_lock() { if (isset($this->_redis, $this->_lock_key) && $this->_lock) { if ( ! $this->_redis->delete($this->_lock_key)) { log_message('error', 'Session: Error while trying to free lock for '.$this->_key_prefix.$session_id); return FALSE; } $this->_lock_key = NULL; $this->_lock = FALSE; } return TRUE; } } /* End of file Session_redis_driver.php */ /* Location: ./system/libraries/Session/drivers/Session_redis_driver.php */