blob: 3da7ddd6d59ca89488417f7b6cca889b5ee755aa [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 *
Andrey Andreev46f2f262014-11-11 14:37:51 +02007 * This content is released under the MIT License (MIT)
Andrey Andreevc9eface2014-09-02 15:19:01 +03008 *
Andrey Andreev46f2f262014-11-11 14:37:51 +02009 * Copyright (c) 2014, British Columbia Institute of Technology
Andrey Andreevc9eface2014-09-02 15:19:01 +030010 *
Andrey Andreev46f2f262014-11-11 14:37:51 +020011 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
Andrey Andreevc9eface2014-09-02 15:19:01 +030017 *
Andrey Andreev46f2f262014-11-11 14:37:51 +020018 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 * THE SOFTWARE.
28 *
29 * @package CodeIgniter
30 * @author EllisLab Dev Team
Andrey Andreevc9eface2014-09-02 15:19:01 +030031 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
Andrey Andreev46f2f262014-11-11 14:37:51 +020032 * @copyright Copyright (c) 2014, British Columbia Institute of Technology (http://bcit.ca/)
33 * @license http://opensource.org/licenses/MIT MIT License
34 * @link http://codeigniter.com
35 * @since Version 3.0.0
Andrey Andreevc9eface2014-09-02 15:19:01 +030036 * @filesource
37 */
38defined('BASEPATH') OR exit('No direct script access allowed');
39
40/**
41 * CodeIgniter Session Memcached Driver
42 *
Andrey Andreev46f2f262014-11-11 14:37:51 +020043 * @package CodeIgniter
Andrey Andreevc9eface2014-09-02 15:19:01 +030044 * @subpackage Libraries
45 * @category Sessions
Andrey Andreev46f2f262014-11-11 14:37:51 +020046 * @author Andrey Andreev
47 * @link http://codeigniter.com/user_guide/libraries/sessions.html
Andrey Andreevc9eface2014-09-02 15:19:01 +030048 */
49class CI_Session_memcached_driver extends CI_Session_driver implements SessionHandlerInterface {
50
51 /**
Andrey Andreevc9eface2014-09-02 15:19:01 +030052 * Memcached instance
53 *
54 * @var Memcached
55 */
56 protected $_memcached;
57
58 /**
59 * Key prefix
60 *
61 * @var string
62 */
63 protected $_key_prefix = 'ci_session:';
64
65 /**
66 * Lock key
67 *
68 * @var string
69 */
70 protected $_lock_key;
71
72 // ------------------------------------------------------------------------
73
74 /**
75 * Class constructor
76 *
77 * @param array $params Configuration parameters
78 * @return void
79 */
80 public function __construct(&$params)
81 {
82 parent::__construct($params);
83
Andrey Andreevdfb39be2014-10-06 01:50:14 +030084 if (empty($this->_config['save_path']))
Andrey Andreevc9eface2014-09-02 15:19:01 +030085 {
86 log_message('error', 'Session: No Memcached save path configured.');
87 }
88
Andrey Andreevdfb39be2014-10-06 01:50:14 +030089 if ($this->_config['match_ip'] === TRUE)
Andrey Andreevc9eface2014-09-02 15:19:01 +030090 {
91 $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':';
92 }
93 }
94
95 // ------------------------------------------------------------------------
96
97 public function open($save_path, $name)
98 {
99 $this->_memcached = new Memcached();
Andrey Andreev4f502562014-11-10 19:18:33 +0200100 $this->_memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, TRUE); // required for touch() usage
Andrey Andreevc9eface2014-09-02 15:19:01 +0300101 $server_list = array();
102 foreach ($this->_memcached->getServerList() as $server)
103 {
104 $server_list[] = $server['host'].':'.$server['port'];
105 }
106
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300107 if ( ! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->_config['save_path'], $matches, PREG_SET_ORDER))
Andrey Andreevc9eface2014-09-02 15:19:01 +0300108 {
109 $this->_memcached = NULL;
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300110 log_message('error', 'Session: Invalid Memcached save path format: '.$this->_config['save_path']);
Andrey Andreevc9eface2014-09-02 15:19:01 +0300111 return FALSE;
112 }
113
114 foreach ($matches as $match)
115 {
116 // If Memcached already has this server (or if the port is invalid), skip it
117 if (in_array($match[1].':'.$match[2], $server_list, TRUE))
118 {
119 log_message('debug', 'Session: Memcached server pool already has '.$match[1].':'.$match[2]);
120 continue;
121 }
122
123 if ( ! $this->_memcached->addServer($match[1], $match[2], isset($match[3]) ? $match[3] : 0))
124 {
125 log_message('error', 'Could not add '.$match[1].':'.$match[2].' to Memcached server pool.');
126 }
127 else
128 {
Andrey Andreeva8f29f92014-11-10 18:55:55 +0200129 $server_list[] = $match[1].':'.$match[2];
Andrey Andreevc9eface2014-09-02 15:19:01 +0300130 }
131 }
132
133 if (empty($server_list))
134 {
135 log_message('error', 'Session: Memcached server pool is empty.');
136 return FALSE;
137 }
138
139 return TRUE;
140 }
141
142 // ------------------------------------------------------------------------
143
144 public function read($session_id)
145 {
146 if (isset($this->_memcached) && $this->_get_lock($session_id))
147 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200148 // Needed by write() to detect session_regenerate_id() calls
149 $this->_session_id = $session_id;
150
Andrey Andreevc9eface2014-09-02 15:19:01 +0300151 $session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id);
152 $this->_fingerprint = md5($session_data);
153 return $session_data;
154 }
155
156 return FALSE;
157 }
158
159 public function write($session_id, $session_data)
160 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200161 if ( ! isset($this->_memcached))
162 {
163 return FALSE;
164 }
165 // Was the ID regenerated?
166 elseif ($session_id !== $this->_session_id)
167 {
168 if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
169 {
170 return FALSE;
171 }
172
173 $this->_fingerprint = md5('');
174 $this->_session_id = $session_id;
175 }
176
177 if (isset($this->_lock_key))
Andrey Andreevc9eface2014-09-02 15:19:01 +0300178 {
179 $this->_memcached->replace($this->_lock_key, time(), 5);
180 if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
181 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300182 if ($this->_memcached->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration']))
Andrey Andreevc9eface2014-09-02 15:19:01 +0300183 {
184 $this->_fingerprint = $fingerprint;
185 return TRUE;
186 }
187
188 return FALSE;
189 }
190
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300191 return $this->_memcached->touch($this->_key_prefix.$session_id, $this->_config['expiration']);
Andrey Andreevc9eface2014-09-02 15:19:01 +0300192 }
193
194 return FALSE;
195 }
196
197 // ------------------------------------------------------------------------
198
199 public function close()
200 {
201 if (isset($this->_memcached))
202 {
203 isset($this->_lock_key) && $this->_memcached->delete($this->_lock_key);
204 if ( ! $this->_memcached->quit())
205 {
206 return FALSE;
207 }
208
209 $this->_memcached = NULL;
210 return TRUE;
211 }
212
213 return FALSE;
214 }
215
216 // ------------------------------------------------------------------------
217
218 public function destroy($session_id)
219 {
220 if (isset($this->_memcached, $this->_lock_key))
221 {
222 $this->_memcached->delete($this->_key_prefix.$session_id);
Andrey Andreev7474a672014-10-31 23:35:32 +0200223 return $this->_cookie_destroy();
Andrey Andreevc9eface2014-09-02 15:19:01 +0300224 }
225
Andrey Andreev7474a672014-10-31 23:35:32 +0200226 return FALSE;
Andrey Andreevc9eface2014-09-02 15:19:01 +0300227 }
228
229 // ------------------------------------------------------------------------
230
231 public function gc($maxlifetime)
232 {
Andrey Andreev7474a672014-10-31 23:35:32 +0200233 // Not necessary, Memcached takes care of that.
Andrey Andreevc9eface2014-09-02 15:19:01 +0300234 return TRUE;
235 }
236
237 // ------------------------------------------------------------------------
238
239 protected function _get_lock($session_id)
240 {
241 if (isset($this->_lock_key))
242 {
243 return $this->_memcached->replace($this->_lock_key, time(), 5);
244 }
245
246 $lock_key = $this->_key_prefix.$session_id.':lock';
247 if ( ! ($ts = $this->_memcached->get($lock_key)))
248 {
249 if ( ! $this->_memcached->set($lock_key, TRUE, 5))
250 {
251 log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
252 return FALSE;
253 }
254
255 $this->_lock_key = $lock_key;
256 $this->_lock = TRUE;
257 return TRUE;
258 }
259
260 // Another process has the lock, we'll try to wait for it to free itself ...
261 $attempt = 0;
262 while ($attempt++ < 5)
263 {
264 usleep(((time() - $ts) * 1000000) - 20000);
265 if (($ts = $this->_memcached->get($lock_key)) < time())
266 {
267 continue;
268 }
269
270 if ( ! $this->_memcached->set($lock_key, time(), 5))
271 {
272 log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
273 return FALSE;
274 }
275
276 $this->_lock_key = $lock_key;
277 break;
278 }
279
280 if ($attempt === 5)
281 {
282 log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 5 attempts, aborting.');
283 return FALSE;
284 }
285
286 $this->_lock = TRUE;
287 return TRUE;
288 }
289
290 // ------------------------------------------------------------------------
291
292 protected function _release_lock()
293 {
294 if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
295 {
296 if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
297 {
298 log_message('error', 'Session: Error while trying to free lock for '.$this->_key_prefix.$session_id);
299 return FALSE;
300 }
301
302 $this->_lock_key = NULL;
303 $this->_lock = FALSE;
304 }
305
306 return TRUE;
307 }
308
309}
310
311/* End of file Session_memcached_driver.php */
312/* Location: ./system/libraries/Session/drivers/Session_memcached_driver.php */