1<?php 2// +----------------------------------------------------------------------+ 3// | PEAR :: Cache | 4// +----------------------------------------------------------------------+ 5// | Copyright (c) 1997-2003 The PHP Group | 6// +----------------------------------------------------------------------+ 7// | This source file is subject to version 2.0 of the PHP license, | 8// | that is bundled with this package in the file LICENSE, and is | 9// | available at through the world-wide-web at | 10// | http://www.php.net/license/2_02.txt. | 11// | If you did not receive a copy of the PHP license and are unable to | 12// | obtain it through the world-wide-web, please send a note to | 13// | license@php.net so we can mail you a copy immediately. | 14// +----------------------------------------------------------------------+ 15// | Authors: Ulf Wendel <ulf.wendel@phpdoc.de> | 16// | Sebastian Bergmann <sb@sebastian-bergmann.de> | 17// +----------------------------------------------------------------------+ 18// 19// $Id: shm.php 186977 2005-05-25 10:00:41Z dufuz $ 20 21require_once 'Cache/Container.php'; 22 23/** 24* Stores cache data into shared memory. 25* 26* Well, this is not a very efficient implementation. Indeed it's much 27* slower than the file container as far as my tests showed. Files are 28* cached by most operating systems and it will be hard to write a faster 29* caching algorithm using PHP. 30* 31* @author Ulf Wendel <ulf.wendel@phpdoc.de> 32* @version $Id: shm.php 186977 2005-05-25 10:00:41Z dufuz $ 33* @package Cache 34*/ 35class Cache_Container_shm extends Cache_Container 36{ 37 /** 38 * Key of the semaphore used to sync the SHM access 39 * 40 * @var int 41 */ 42 var $sem_key = null; 43 44 /** 45 * Permissions of the semaphore used to sync the SHM access 46 * 47 * @var int 48 */ 49 var $sem_perm = 0644; 50 51 /** 52 * Semaphore handler 53 * 54 * @var resource 55 */ 56 var $sem_id = null; 57 58 /** 59 * Key of the shared memory block used to store cache data 60 * 61 * @var int 62 */ 63 var $shm_key = null; 64 65 /** 66 * Size of the shared memory block used 67 * 68 * Note: the container does only use _one_ shm block no more! 69 * 70 * @var int 71 */ 72 var $shm_size = 131072; 73 74 /** 75 * Permissions of the shared memory block 76 * 77 * @var int 78 */ 79 var $shm_perm = 0644; 80 81 /** 82 * Shared memory handler 83 * 84 * @var resource 85 */ 86 var $shm_id = null; 87 88 /** 89 * Hash of cache entries 90 * 91 * Used by the garbage collection to find old entries. 92 * 93 * @var array 94 */ 95 var $entries = array(); 96 97 /** 98 * Number of bytes consumed by the cache 99 * 100 * @var int 101 */ 102 var $total_size = 0; 103 104 /** 105 * Creates a shared memory container 106 * 107 * @param array shm_key, sem_key, shm_size, sem_perm, shm_perm 108 */ 109 function Cache_Container_shm($options = '') 110 { 111 if (is_array($options)) { 112 $this->setOptions($options, array_merge($this->allowed_options, 113 array('shm_key', 'sem_key', 114 'shm_size', 'sem_perm', 115 'shm_perm' 116 ) 117 ) 118 ); 119 } 120 // Cache::Container high- and lowwater defaults should be overridden if 121 // not already done by the user 122 if (!isset($options['highwater'])) { 123 $this->highwater = round(0.75 * 131072); 124 } 125 if (!isset($options['lowwater'])) { 126 $this->lowwater = round(0.5 * 131072); 127 } 128 if (!isset($options['shm_size'])) { 129 $this->shm_size = 131072; 130 } 131 //get SHM and Semaphore handles 132 if (!($this->shm_id = shmop_open($this->shm_key, 'c', $this->shm_perm, $this->shm_size))) { 133 new Cache_Error("Can't open SHM segment '{$this->shm_key}', size '{$this->shm_size}'.", 134 __FILE__, 135 __LINE__ 136 ); 137 } 138 if (!($this->sem_id = sem_get($this->sem_key, 1, $this->sem_perm))) { 139 new Cache_Error("Can't get semaphore '{$this->sem_key}' using perms '{$this->sem_perm}'.", 140 __FILE__, 141 __LINE__ 142 ); 143 } 144 } // end constructor 145 146 function fetch($id, $group) 147 { 148 sem_acquire($this->sem_id); 149 150 $cachedata = shmop_read($this->shm_id, 0, $this->shm_size); 151 152 sem_release($this->sem_id); 153 154 $cachedata = $this->decode($cachedata); 155 156 if (!isset($cachedata[$group][$id])) { 157 return array(null, null, null); 158 } else { 159 $cachedata = $cachedata[$group][$id]; 160 } 161 return array($cachedata['expire'], 162 $cachedata['cachedata'], 163 $cachedata['userdata'] 164 ); 165 } // end func fetch 166 167 function save($id, $data, $expire, $group, $userdata) 168 { 169 $this->flushPreload($id, $group); 170 171 sem_acquire($this->sem_id); 172 173 $cachedata = $this->decode(shmop_read($this->shm_id, 0, $this->shm_size)); 174 $cachedata[$group][$id] = array('expire' => $this->getExpiresAbsolute($expire), 175 'cachedata' => $data, 176 'userdata' => $userdata, 177 'changed' => time() 178 ); 179 180 if (strlen($newdata = $this->encode($cachedata)) > $this->shm_size) { 181 $cachedata = $this->garbageCollection(time(), $cachedata); 182 } 183 shmop_write($this->shm_id, $newdata, 0); 184 185 sem_release($this->sem_id); 186 187 return true; 188 } // end func save 189 190 function remove($id, $group) 191 { 192 $this->flushPreload($id, $group); 193 194 sem_acquire($this->sem_id); 195 196 $cachedata = $this->decode(shmop_read($this->shm_id, 0, $this->shm_size)); 197 unset($cachedata[$group][$id]); 198 shmop_write($this->shm_id, $this->encode($cachedata), 0); 199 200 sem_release($this->sem_id); 201 } // end func remove 202 203 function flush($group = '') 204 { 205 $this->flushPreload(); 206 207 sem_acquire($this->sem_id); 208 209 shmop_write($this->shm_id, $this->encode(array()), 0); 210 211 sem_release($this->sem_id); 212 } // end func flush 213 214 function idExists($id, $group) 215 { 216 sem_acquire($this->sem_id); 217 218 $cachedata = shm_read($this->shm_id, 0, $this->shm_size); 219 220 sem_release($this->sem_id); 221 222 $cachedata = $this->decode($cachedata); 223 224 return isset($cachedata[$group][$id]); 225 } // end func isExists 226 227 function garbageCollection($maxlifetime, $cachedata = array()) 228 { 229 if ($lock = empty($cachedata)) { 230 sem_acquire($this->sem_id); 231 $cachedata = $this->decode(shmop_read($this->shm_id, 0, $this->shm_size)); 232 } 233 234 $this->doGarbageCollection($maxlifetime, $cachedata); 235 if ($this->total_size > $this->highwater) { 236 krsort($this->entries); 237 reset($this->entries); 238 239 while ($this->total_size > $this->lowwater && list($size, $entries) = each($this->entries)) { 240 reset($entries); 241 242 while (list($k, $entry) = each($entries)) { 243 unset($cachedata[$entry['group']][$entry['id']]); 244 $this->total_size -= $size; 245 } 246 } 247 } 248 249 if ($lock) { 250 sem_release($this->sem_id); 251 } 252 $this->entries = array(); 253 $this->total_size = 0; 254 255 return $cachedata; 256 } // end func garbageCollection 257 258 function doGarbageCollection($maxlifetime, &$cachedata) 259 { 260 $changed = time() - $maxlifetime; 261 $removed = 0; 262 263 reset($cachedata); 264 265 while (list($group, $groupdata) = each($cachedata)) { 266 reset($groupdata); 267 268 while (list($id, $data) = each($groupdata)) { 269 if ($data['expire'] < time() || $data['changed'] < $changed) { 270 unset($cachedata[$group][$id]); 271 } 272 } 273 274 // ugly but simple to implement :/ 275 $size = strlen($this->encode($data)); 276 $this->entries[$size][] = array( 277 'group' => $group, 278 'id' => $id 279 ); 280 281 $this->total_size += $size; 282 } 283 284 return $removed; 285 } // end func doGarbageCollection 286} // end class Cache_Container_shm 287?> 288