blob: 8905e8d6f766d8fcd634a2b7789bf7e0b7939dce [file] [log] [blame]
Andrey Andreevc9eface2014-09-02 15:19:01 +03001<?php
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.2.4 or newer
6 *
7 * NOTICE OF LICENSE
8 *
9 * Licensed under the Open Software License version 3.0
10 *
11 * This source file is subject to the Open Software License (OSL 3.0) that is
12 * bundled with this package in the files license.txt / license.rst. It is
13 * also available through the world wide web at this URL:
14 * http://opensource.org/licenses/OSL-3.0
15 * If you did not receive a copy of the license and are unable to obtain it
16 * through the world wide web, please send an email to
17 * licensing@ellislab.com so we can send you a copy immediately.
18 *
19 * @package CodeIgniter
20 * @author Andrey Andreev
21 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
22 * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
23 * @link http://codeigniter.com
24 * @since Version 3.0
25 * @filesource
26 */
27defined('BASEPATH') OR exit('No direct script access allowed');
28
29/**
30 * CodeIgniter Session Memcached Driver
31 *
32 * @package CodeIgniter
33 * @subpackage Libraries
34 * @category Sessions
35 * @author Andrey Andreev
36 * @link http://codeigniter.com/user_guide/libraries/sessions.html
37 */
38class CI_Session_memcached_driver extends CI_Session_driver implements SessionHandlerInterface {
39
40 /**
Andrey Andreevc9eface2014-09-02 15:19:01 +030041 * Memcached instance
42 *
43 * @var Memcached
44 */
45 protected $_memcached;
46
47 /**
48 * Key prefix
49 *
50 * @var string
51 */
52 protected $_key_prefix = 'ci_session:';
53
54 /**
55 * Lock key
56 *
57 * @var string
58 */
59 protected $_lock_key;
60
61 // ------------------------------------------------------------------------
62
63 /**
64 * Class constructor
65 *
66 * @param array $params Configuration parameters
67 * @return void
68 */
69 public function __construct(&$params)
70 {
71 parent::__construct($params);
72
Andrey Andreevdfb39be2014-10-06 01:50:14 +030073 if (empty($this->_config['save_path']))
Andrey Andreevc9eface2014-09-02 15:19:01 +030074 {
75 log_message('error', 'Session: No Memcached save path configured.');
76 }
77
Andrey Andreevdfb39be2014-10-06 01:50:14 +030078 if ($this->_config['match_ip'] === TRUE)
Andrey Andreevc9eface2014-09-02 15:19:01 +030079 {
80 $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':';
81 }
82 }
83
84 // ------------------------------------------------------------------------
85
86 public function open($save_path, $name)
87 {
88 $this->_memcached = new Memcached();
89 $server_list = array();
90 foreach ($this->_memcached->getServerList() as $server)
91 {
92 $server_list[] = $server['host'].':'.$server['port'];
93 }
94
Andrey Andreevdfb39be2014-10-06 01:50:14 +030095 if ( ! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->_config['save_path'], $matches, PREG_SET_ORDER))
Andrey Andreevc9eface2014-09-02 15:19:01 +030096 {
97 $this->_memcached = NULL;
Andrey Andreevdfb39be2014-10-06 01:50:14 +030098 log_message('error', 'Session: Invalid Memcached save path format: '.$this->_config['save_path']);
Andrey Andreevc9eface2014-09-02 15:19:01 +030099 return FALSE;
100 }
101
102 foreach ($matches as $match)
103 {
104 // If Memcached already has this server (or if the port is invalid), skip it
105 if (in_array($match[1].':'.$match[2], $server_list, TRUE))
106 {
107 log_message('debug', 'Session: Memcached server pool already has '.$match[1].':'.$match[2]);
108 continue;
109 }
110
111 if ( ! $this->_memcached->addServer($match[1], $match[2], isset($match[3]) ? $match[3] : 0))
112 {
113 log_message('error', 'Could not add '.$match[1].':'.$match[2].' to Memcached server pool.');
114 }
115 else
116 {
117 $server_list[] = $server['host'].':'.$server['port'];
118 }
119 }
120
121 if (empty($server_list))
122 {
123 log_message('error', 'Session: Memcached server pool is empty.');
124 return FALSE;
125 }
126
127 return TRUE;
128 }
129
130 // ------------------------------------------------------------------------
131
132 public function read($session_id)
133 {
134 if (isset($this->_memcached) && $this->_get_lock($session_id))
135 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200136 // Needed by write() to detect session_regenerate_id() calls
137 $this->_session_id = $session_id;
138
Andrey Andreevc9eface2014-09-02 15:19:01 +0300139 $session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id);
140 $this->_fingerprint = md5($session_data);
141 return $session_data;
142 }
143
144 return FALSE;
145 }
146
147 public function write($session_id, $session_data)
148 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200149 if ( ! isset($this->_memcached))
150 {
151 return FALSE;
152 }
153 // Was the ID regenerated?
154 elseif ($session_id !== $this->_session_id)
155 {
156 if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
157 {
158 return FALSE;
159 }
160
161 $this->_fingerprint = md5('');
162 $this->_session_id = $session_id;
163 }
164
165 if (isset($this->_lock_key))
Andrey Andreevc9eface2014-09-02 15:19:01 +0300166 {
167 $this->_memcached->replace($this->_lock_key, time(), 5);
168 if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
169 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300170 if ($this->_memcached->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration']))
Andrey Andreevc9eface2014-09-02 15:19:01 +0300171 {
172 $this->_fingerprint = $fingerprint;
173 return TRUE;
174 }
175
176 return FALSE;
177 }
178
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300179 return $this->_memcached->touch($this->_key_prefix.$session_id, $this->_config['expiration']);
Andrey Andreevc9eface2014-09-02 15:19:01 +0300180 }
181
182 return FALSE;
183 }
184
185 // ------------------------------------------------------------------------
186
187 public function close()
188 {
189 if (isset($this->_memcached))
190 {
191 isset($this->_lock_key) && $this->_memcached->delete($this->_lock_key);
192 if ( ! $this->_memcached->quit())
193 {
194 return FALSE;
195 }
196
197 $this->_memcached = NULL;
198 return TRUE;
199 }
200
201 return FALSE;
202 }
203
204 // ------------------------------------------------------------------------
205
206 public function destroy($session_id)
207 {
208 if (isset($this->_memcached, $this->_lock_key))
209 {
210 $this->_memcached->delete($this->_key_prefix.$session_id);
Andrey Andreev7474a672014-10-31 23:35:32 +0200211 return $this->_cookie_destroy();
Andrey Andreevc9eface2014-09-02 15:19:01 +0300212 }
213
Andrey Andreev7474a672014-10-31 23:35:32 +0200214 return FALSE;
Andrey Andreevc9eface2014-09-02 15:19:01 +0300215 }
216
217 // ------------------------------------------------------------------------
218
219 public function gc($maxlifetime)
220 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200221 // Not necessary, Memcached takes care of that.
Andrey Andreevc9eface2014-09-02 15:19:01 +0300222 return TRUE;
223 }
224
225 // ------------------------------------------------------------------------
226
227 protected function _get_lock($session_id)
228 {
229 if (isset($this->_lock_key))
230 {
231 return $this->_memcached->replace($this->_lock_key, time(), 5);
232 }
233
234 $lock_key = $this->_key_prefix.$session_id.':lock';
235 if ( ! ($ts = $this->_memcached->get($lock_key)))
236 {
237 if ( ! $this->_memcached->set($lock_key, TRUE, 5))
238 {
239 log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
240 return FALSE;
241 }
242
243 $this->_lock_key = $lock_key;
244 $this->_lock = TRUE;
245 return TRUE;
246 }
247
248 // Another process has the lock, we'll try to wait for it to free itself ...
249 $attempt = 0;
250 while ($attempt++ < 5)
251 {
252 usleep(((time() - $ts) * 1000000) - 20000);
253 if (($ts = $this->_memcached->get($lock_key)) < time())
254 {
255 continue;
256 }
257
258 if ( ! $this->_memcached->set($lock_key, time(), 5))
259 {
260 log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
261 return FALSE;
262 }
263
264 $this->_lock_key = $lock_key;
265 break;
266 }
267
268 if ($attempt === 5)
269 {
270 log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 5 attempts, aborting.');
271 return FALSE;
272 }
273
274 $this->_lock = TRUE;
275 return TRUE;
276 }
277
278 // ------------------------------------------------------------------------
279
280 protected function _release_lock()
281 {
282 if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
283 {
284 if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
285 {
286 log_message('error', 'Session: Error while trying to free lock for '.$this->_key_prefix.$session_id);
287 return FALSE;
288 }
289
290 $this->_lock_key = NULL;
291 $this->_lock = FALSE;
292 }
293
294 return TRUE;
295 }
296
297}
298
299/* End of file Session_memcached_driver.php */
300/* Location: ./system/libraries/Session/drivers/Session_memcached_driver.php */