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