blob: 318c11afa003824d386321210fc7f34e3fa7b8c5 [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 {
136 $session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id);
137 $this->_fingerprint = md5($session_data);
138 return $session_data;
139 }
140
141 return FALSE;
142 }
143
144 public function write($session_id, $session_data)
145 {
146 if (isset($this->_memcached, $this->_lock_key))
147 {
148 $this->_memcached->replace($this->_lock_key, time(), 5);
149 if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
150 {
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300151 if ($this->_memcached->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration']))
Andrey Andreevc9eface2014-09-02 15:19:01 +0300152 {
153 $this->_fingerprint = $fingerprint;
154 return TRUE;
155 }
156
157 return FALSE;
158 }
159
Andrey Andreevdfb39be2014-10-06 01:50:14 +0300160 return $this->_memcached->touch($this->_key_prefix.$session_id, $this->_config['expiration']);
Andrey Andreevc9eface2014-09-02 15:19:01 +0300161 }
162
163 return FALSE;
164 }
165
166 // ------------------------------------------------------------------------
167
168 public function close()
169 {
170 if (isset($this->_memcached))
171 {
172 isset($this->_lock_key) && $this->_memcached->delete($this->_lock_key);
173 if ( ! $this->_memcached->quit())
174 {
175 return FALSE;
176 }
177
178 $this->_memcached = NULL;
179 return TRUE;
180 }
181
182 return FALSE;
183 }
184
185 // ------------------------------------------------------------------------
186
187 public function destroy($session_id)
188 {
189 if (isset($this->_memcached, $this->_lock_key))
190 {
191 $this->_memcached->delete($this->_key_prefix.$session_id);
192 return ($this->_cookie_destroy() && $this->close());
193 }
194
195 return $this->close();
196 }
197
198 // ------------------------------------------------------------------------
199
200 public function gc($maxlifetime)
201 {
202 return TRUE;
203 }
204
205 // ------------------------------------------------------------------------
206
207 protected function _get_lock($session_id)
208 {
209 if (isset($this->_lock_key))
210 {
211 return $this->_memcached->replace($this->_lock_key, time(), 5);
212 }
213
214 $lock_key = $this->_key_prefix.$session_id.':lock';
215 if ( ! ($ts = $this->_memcached->get($lock_key)))
216 {
217 if ( ! $this->_memcached->set($lock_key, TRUE, 5))
218 {
219 log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
220 return FALSE;
221 }
222
223 $this->_lock_key = $lock_key;
224 $this->_lock = TRUE;
225 return TRUE;
226 }
227
228 // Another process has the lock, we'll try to wait for it to free itself ...
229 $attempt = 0;
230 while ($attempt++ < 5)
231 {
232 usleep(((time() - $ts) * 1000000) - 20000);
233 if (($ts = $this->_memcached->get($lock_key)) < time())
234 {
235 continue;
236 }
237
238 if ( ! $this->_memcached->set($lock_key, time(), 5))
239 {
240 log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
241 return FALSE;
242 }
243
244 $this->_lock_key = $lock_key;
245 break;
246 }
247
248 if ($attempt === 5)
249 {
250 log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 5 attempts, aborting.');
251 return FALSE;
252 }
253
254 $this->_lock = TRUE;
255 return TRUE;
256 }
257
258 // ------------------------------------------------------------------------
259
260 protected function _release_lock()
261 {
262 if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
263 {
264 if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
265 {
266 log_message('error', 'Session: Error while trying to free lock for '.$this->_key_prefix.$session_id);
267 return FALSE;
268 }
269
270 $this->_lock_key = NULL;
271 $this->_lock = FALSE;
272 }
273
274 return TRUE;
275 }
276
277}
278
279/* End of file Session_memcached_driver.php */
280/* Location: ./system/libraries/Session/drivers/Session_memcached_driver.php */