File Coverage

blib/lib/IPC/Cache.pm
Criterion Covered Total %
statement 154 156 98.7
branch 30 48 62.5
condition 10 24 41.6
subroutine 22 22 100.0
pod 7 7 100.0
total 223 257 86.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             package IPC::Cache;
4              
5 2     2   1399 use strict;
  2         4  
  2         73  
6 2     2   22 use Carp;
  2         4  
  2         593  
7 2     2   2394 use IPC::ShareLite;
  2         13940  
  2         130  
8 2     2   2425 use Storable qw(freeze thaw dclone);
  2         8478  
  2         199  
9 2     2   20 use vars qw($VERSION);
  2         3  
  2         87  
10 2     2   9 use Fcntl ':flock';
  2         5  
  2         4099  
11              
12             $VERSION = '0.02';
13              
14             my $sEXPIRES_NOW = 0;
15             my $sEXPIRES_NEVER = -1;
16             my $sSUCCESS = 1;
17             my $sTRUE = 1;
18             my $sFALSE = 0;
19              
20              
21             # IPC::ShareLite converts a four character text string to the shared memory key
22              
23             my $sDEFAULT_CACHE_KEY = "IPCC";
24              
25              
26             # if a namespace is not specified, use this as a default
27              
28             my $sDEFAULT_NAMESPACE = "_default";
29              
30              
31              
32             # create a new Cache object that can be used to persist
33             # data across processes
34              
35             sub new
36             {
37 4     4 1 8802 my ($proto, $options) = @_;
38 4   33     58 my $class = ref($proto) || $proto;
39 4         12 my $self = {};
40 4         22 bless ($self, $class);
41              
42              
43             # this instance will use the namespace specified or the default
44              
45 4   66     38 my $namespace = $options->{namespace} || $sDEFAULT_NAMESPACE;
46              
47 4         32 $self->{_namespace} = $namespace;
48              
49              
50             # remember the expiration delta to be used for all objects if specified
51              
52 4   33     37 $self->{_expires_in} = $options->{expires_in} || $sEXPIRES_NEVER;
53              
54             # create a new share associated with the cache key
55              
56 4   33     18 my $cache_key = $options->{cache_key} || $sDEFAULT_CACHE_KEY;
57              
58 4 50       75 my $share = new IPC::ShareLite( -key => $cache_key, -create => 1 ) or
59             croak("Couldn't create new IPC::ShareLite");
60              
61             # store the share for this instance
62              
63 4         2007 $self->{_share} = $share;
64              
65            
66             # atomically initialize the segment as frozen data
67              
68 4         25 $self->_lock();
69              
70 4         15 my $frozen_data = $self->_get_frozen_data();
71              
72 4 100       57 if (not $frozen_data) {
73 1         3 my %data;
74 1         8 $frozen_data = freeze(\%data);
75 1         119 $self->_set_frozen_data($frozen_data);
76             }
77              
78 4         17 $self->_unlock();
79              
80              
81 4         2615 return $self;
82             }
83              
84              
85             # store an object in the cache associated with the identifier
86              
87             sub set
88             {
89 3     3 1 121 my ($self, $identifier, $object, $expires_in) = @_;
90              
91 3 50       12 $identifier or
92             croak("identifier required");
93              
94 3 50       10 my $namespace = $self->{_namespace} or
95             croak("namespace required");
96              
97              
98             # expiration time is based on a delta from the current time
99             # if expires_in is defined, the object will expire in that number of seconds from now
100             # else if expires_in is undefined, it will expire based on the global _expires_in
101            
102 3         4 my $expires_at;
103              
104 3 100       14 if (defined $expires_in) {
    50          
105 2         7 $expires_at = time() + $expires_in;
106             } elsif ($self->{_expires_in} ne $sEXPIRES_NEVER) {
107 0         0 $expires_at = time() + $self->{_expires_in};
108             } else {
109 1         210 $expires_at = $sEXPIRES_NEVER;
110             }
111              
112             # atomically add the new object to the cache in this instance's namespace
113              
114 3         10 $self->_lock();
115              
116 3         7 my $frozen_data = $self->_get_frozen_data();
117              
118 3         28 my %data = %{ thaw($frozen_data) };
  3         8  
119              
120 3         145 $data{$namespace}->{$identifier} = { object => $object, expires_at => $expires_at };
121              
122 3         14 $frozen_data = freeze(\%data);
123              
124 3         129 $self->_set_frozen_data($frozen_data);
125              
126 3         6 $self->_unlock();
127              
128 3         14 return $sSUCCESS;
129             }
130              
131              
132              
133             # retrieve an object from the cache associated with the identifier
134              
135             sub get
136             {
137 6     6 1 3000288 my ($self, $identifier) = @_;
138              
139 6 50       22 $identifier or
140             croak("identifier required");
141            
142 6 50       24 my $namespace = $self->{_namespace} or
143             croak("namespace required");
144              
145             # atomically (necessary for read-only?) check the cache for the specified object
146              
147 6         10 my $cloned_object = undef;
148              
149 6         34 $self->_lock();
150              
151 6         15 my $frozen_data = $self->_get_frozen_data();
152            
153 6         61 my %data = %{ thaw($frozen_data) };
  6         25  
154              
155 6 100       175 if (exists $data{$namespace}->{$identifier}) {
156              
157 5         14 my $object = $data{$namespace}->{$identifier}->{object};
158            
159 5         11 my $expires_at = $data{$namespace}->{$identifier}->{expires_at};
160            
161             # if the object has expired, remove it from the cache
162            
163 5 100       18 if (_s_should_expire($expires_at)) {
164 2         12 delete $data{$namespace}->{$identifier};
165             } else {
166             # if the object is a reference, clone it before returning it (may be unnecessary?)
167 3 50       8 $cloned_object = (ref $object) ? dclone($object) : $object;
168             }
169             }
170            
171 6         18 $self->_unlock();
172              
173 6         32 return $cloned_object;
174             }
175              
176              
177             # clear all objects in this instance's namespace
178              
179             sub clear
180             {
181 1     1 1 56 my ($self) = @_;
182              
183 1         5 my $namespace = $self->{_namespace};
184              
185             # atomically iterate over all of the key in this instance's namespace and delete them
186              
187 1         4 $self->_lock();
188              
189 1         9 my $frozen_data = $self->_get_frozen_data();
190            
191 1         8 my %data = %{ thaw($frozen_data) };
  1         9  
192              
193 1         45 foreach my $identifier (keys %{$data{$namespace}}) {
  1         11  
194 1         21 delete $data{$namespace}->{$identifier};
195             }
196              
197 1         17 $frozen_data = freeze(\%data);
198              
199 1         95 $self->_set_frozen_data($frozen_data);
200              
201 1         6 $self->_unlock();
202              
203 1         5 return $sSUCCESS;
204             }
205              
206              
207              
208             # iterate over all the objects in this instance's namespace and delete those that have expired
209              
210             sub purge
211             {
212 1     1 1 35 my ($self) = @_;
213              
214 1         4 my $namespace = $self->{_namespace};
215              
216 1         3 my $time = time();
217              
218             # atomically iterate over all of the keys in this instance's namespace and delete those that have expired
219              
220 1         4 $self->_lock();
221              
222 1         3 my $frozen_data = $self->_get_frozen_data();
223            
224 1         11 my %data = %{ thaw($frozen_data) };
  1         4  
225              
226 1         20 my $namespace_ref = $data{$namespace};
227              
228 1         15 _s_purge_namespace($namespace_ref, $time);
229              
230 1         6 $frozen_data = freeze(\%data);
231            
232 1         55 $self->_set_frozen_data($frozen_data);
233              
234 1         4 $self->_unlock();
235              
236 1         4 return $sSUCCESS;
237             }
238              
239              
240              
241             # purge expired objects from all namespaces associated with this cache key
242              
243             sub _purge_all
244             {
245 1     1   3 my ($self) = @_;
246              
247 1         2 my $time = time();
248              
249             # atomically iterate over all of the keys in all of this instance's namespaces and delete those that have expired
250              
251 1         3 $self->_lock();
252              
253 1         2 my $frozen_data = $self->_get_frozen_data();
254              
255 1         10 my %data = %{ thaw($frozen_data) };
  1         3  
256            
257 1         25 foreach my $namespace (keys %data) {
258              
259 1         2 my $namespace_ref = $data{$namespace};
260              
261 1         3 _s_purge_namespace($namespace_ref, $time);
262              
263             }
264              
265 1         12 $frozen_data = freeze(\%data);
266              
267 1         82 $self->_set_frozen_data($frozen_data);
268              
269 1         2 $self->_unlock();
270              
271 1         3 return $sSUCCESS;
272             }
273              
274              
275             # iterate over all the objects in the specified namespace and delete those that have expired
276              
277             sub _s_purge_namespace
278             {
279 2     2   4 my ($namespace_ref, $time) = @_;
280              
281 2         3 foreach my $identifier (keys %{$namespace_ref}) {
  2         12  
282            
283 1         3 my $expires_at = $namespace_ref->{$identifier}->{expires_at};
284              
285             # if the object has expired, remove it from the cache
286              
287 1 50       8 if (_s_should_expire($expires_at, $time)) {
288 1         6 delete $namespace_ref->{$identifier};
289             }
290             }
291            
292 2         5 return $sSUCCESS;
293             }
294              
295              
296             # determine whether an object should expire
297              
298             sub _s_should_expire
299             {
300 6     6   14 my ($expires_at, $time) = @_;
301              
302             # time is optional
303              
304 6   66     88 $time = $time || time();
305              
306 6 50       102 if ($expires_at == $sEXPIRES_NOW) {
    100          
    100          
307 0         0 return $sTRUE;
308             } elsif ($expires_at == $sEXPIRES_NEVER) {
309 2         7 return $sFALSE;
310             } elsif ($time >= $expires_at) {
311 3         11 return $sTRUE;
312             } else {
313 1         4 return $sFALSE;
314             }
315             }
316              
317              
318             # use this cache instance's frozen data to get an estimate of the memory consumption
319              
320             sub _size
321             {
322 1     1   3 my ($self) = @_;
323              
324 1         4 $self->_lock();
325              
326 1         8 my $frozen_data = $self->_get_frozen_data();
327              
328 1         12 my $size = length $frozen_data;
329              
330 1         3 $self->_unlock();
331              
332 1         46 return $size;
333             }
334              
335             # set the frozen data in the share
336              
337             sub _set_frozen_data
338             {
339 7     7   15 my ($self, $frozen_data) = @_;
340              
341 7 50       21 my $share = $self->{_share} or
342             croak("Couldn't get share");
343              
344 7         24 $share->store($frozen_data);
345              
346 7         54 return $sSUCCESS;
347             }
348              
349              
350             # get the frozen data from the share
351              
352             sub _get_frozen_data
353             {
354 17     17   26 my ($self) = @_;
355              
356 17 50       45 my $share = $self->{_share} or
357             croak("Couldn't get share");
358              
359 17         51 return $share->fetch();
360             }
361              
362              
363             # lock on the shared memory
364              
365             sub _lock
366             {
367 17     17   25 my ($self) = @_;
368              
369 17 50       50 my $share = $self->{_share} or
370             croak("Couldn't get share");
371              
372 17 50       63 $share->lock(LOCK_EX) or
373             croak("Couldn't lock");
374              
375 17         233 return $sSUCCESS;
376             }
377              
378              
379             # unlock on the shared memory
380              
381             sub _unlock
382             {
383 17     17   30 my ($self) = @_;
384              
385 17 50       47 my $share = $self->{_share} or
386             croak("Couldn't get share");
387              
388 17 50       52 $share->unlock() or
389             croak("Couldn't unlock");
390              
391 17         180 return $sSUCCESS;
392             }
393              
394              
395              
396             # clear all objects in all namespaces and release the shared memory
397              
398             sub CLEAR
399             {
400 1     1   105 my ($cache_key) = @_;
401              
402 1   33     7 $cache_key = $cache_key || $sDEFAULT_CACHE_KEY;
403              
404 1 50       7 my $tmp_share = new IPC::ShareLite( -key => $cache_key, -create => 1, -destroy => 1 ) or
405             croak("Couldn't create new IPC::ShareLite");
406              
407 1         170 return $sSUCCESS;
408             }
409              
410              
411              
412             # purge all objects in all namespaces that have expired
413              
414             sub PURGE
415             {
416 1     1 1 18 my ($cache_key) = @_;
417              
418 1   33     4 $cache_key = $cache_key || $sDEFAULT_CACHE_KEY;
419              
420             # note that this will not destroy the shared memory segment when it finishes
421              
422 1 50       26 my $tmp_cache = new IPC::Cache( { cache_key => $cache_key } ) or
423             croak("Couldn't instantiate new cache");
424              
425 1         7 $tmp_cache->_purge_all();
426              
427 1         6 return $sSUCCESS;
428             }
429              
430              
431             # get an estimate of the total memory consumption of the cache
432              
433             sub SIZE
434             {
435 1     1 1 698990 my ($cache_key) = @_;
436              
437 1   33     24 $cache_key = $cache_key || $sDEFAULT_CACHE_KEY;
438              
439 1 50       31 my $tmp_cache = new IPC::Cache( { cache_key => $cache_key } ) or
440             croak("Couldn't instantiate new cache");
441              
442 1         12 return $tmp_cache->_size();
443             }
444              
445              
446             1;
447              
448              
449             __END__