File Coverage

blib/lib/XAO/DO/Cache/Memory.pm
Criterion Covered Total %
statement 94 132 71.2
branch 29 46 63.0
condition 18 23 78.2
subroutine 14 16 87.5
pod 10 10 100.0
total 165 227 72.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XAO::DO::Cache::Memory - memory storage back-end for XAO::Cache
4              
5             =head1 SYNOPSIS
6              
7             You should not use this object directly, it is a back-end for
8             XAO::Cache.
9              
10             =head1 DESCRIPTION
11              
12             Cache::Memory is the default implementation of XAO::Cache back-end. It
13             stores data in memory.
14              
15             =head1 METHODS
16              
17             =over
18              
19             =cut
20              
21             ###############################################################################
22             package XAO::DO::Cache::Memory;
23 2     2   16 use strict;
  2         3  
  2         73  
24 2     2   13 use XAO::Utils;
  2         3  
  2         208  
25 2     2   13 use XAO::Objects;
  2         19  
  2         74  
26 2     2   921 use Clone qw(clone);
  2         5379  
  2         148  
27 2     2   21 use feature qw(state);
  2         6  
  2         351  
28              
29 2     2   15 use base XAO::Objects->load(objname => 'Atom');
  2         4  
  2         12  
30              
31             our $VERSION=2.1;
32              
33             ###############################################################################
34              
35             =item calculate_size ($)
36              
37             Calculates size in bytes of the given reference.
38              
39             =cut
40              
41             sub calculate_size ($$) {
42 620     620 1 877 my $self=shift;
43 620         770 my $d=shift;
44              
45 620         884 state $have_devel_size;
46 620 100       1168 if(!defined $have_devel_size) {
47 1         178 eval 'require Devel::Size';
48 1 50       23 if($@) {
49 1         17 $have_devel_size=0;
50 1         15 eprint "Consider installing Devel::Size for size limited caches, it is faster and more accurate";
51             }
52             else {
53 0         0 $have_devel_size=1;
54 0         0 $Devel::Size::warn=0;
55             }
56             }
57              
58 620 50       982 if($have_devel_size) {
59 0         0 return Devel::Size::total_size($d);
60             }
61             else {
62 620         985 my $r=ref($d);
63 620         810 my $sz=0;
64 620         1168 while($r eq 'REF') {
65 0         0 $d=$$d;
66 0         0 $r=ref($d);
67 0         0 $sz+=4;
68             }
69 620 50       1366 if($r eq 'ARRAY') {
    50          
    50          
    0          
70 0         0 foreach my $dd (@$d) {
71 0         0 $sz+=$self->calculate_size($dd);
72             }
73             }
74             elsif($r eq 'HASH') {
75 0         0 foreach my $dk (keys %$d) {
76             # very rough estimate
77 0         0 $sz+=length($dk) * 4 + $self->calculate_size($d->{$dk});
78             }
79             }
80             elsif($r eq 'SCALAR') {
81 620         996 $sz=length($$d) * 4 + 4;
82             }
83             elsif($r eq '') {
84 0         0 $sz=length($d) * 4 + 4;
85             }
86             else {
87 0         0 $sz+=200;
88             }
89 620         1037 return $sz;
90             }
91             }
92              
93             ###############################################################################
94              
95             =item drop (@)
96              
97             Drops an element from the cache.
98              
99             =cut
100              
101             sub drop ($@) {
102 8     8 1 12 my $self=shift;
103              
104 8         21 my $key=$self->make_key($_[0]);
105 8         17 my $data=$self->{data};
106 8         13 my $ed=$data->{$key};
107              
108 8 50       17 return unless $ed;
109              
110 8 100       16 if($ed->{next}) {
111 4         10 $data->{$ed->{next}}->{previous}=$ed->{previous};
112             }
113             else {
114 4         8 $self->{least_recent}=$ed->{previous};
115             }
116              
117 8 100       24 if($ed->{previous}) {
118 7         16 $data->{$ed->{previous}}->{next}=$ed->{next};
119             }
120             else {
121 1         4 $self->{most_recent}=$ed->{next};
122             }
123              
124 8         37 delete $data->{$key};
125             }
126              
127             ###############################################################################
128              
129             =item drop_all ($)
130              
131             Drops all elements.
132              
133             =cut
134              
135             sub drop_all ($$$) {
136 10     10 1 28 my ($self,$key,$ed)=@_;
137              
138 10         20 $self->{'data'}={ };
139 10         30 $self->{'least_recent'}=$self->{'most_recent'}=undef;
140 10         37 $self->{'current_size'}=0;
141             }
142              
143             ###############################################################################
144              
145             =item get (\@)
146              
147             Retrieves an element from the cache. Does not check if it is expired or
148             not, that is done in exists() method and does not update access time.
149              
150             =cut
151              
152             sub get ($$) {
153 1511     1511 1 2157 my $self=shift;
154              
155 1511         2452 my $key=$self->make_key($_[0]);
156              
157             ### dprint "MEMORY: get(",$key,")";
158              
159 1511         2905 my $ed=$self->{'data'}->{$key};
160              
161 1511         2085 my $expire=$self->{'expire'};
162              
163 1511   100     3640 my $exists=($ed && (!$expire || $ed->{'access_time'} + $expire > time));
164              
165 1511 100       3268 return $exists ? $ed->{'element'} : undef;
166             }
167              
168             ###############################################################################
169              
170             =item make_key (\@)
171              
172             Makes a key from the given list of coordinates.
173              
174             =cut
175              
176             sub make_key ($$) {
177 2321     2321 1 2871 my $self=shift;
178 2321 100       2968 return join("\001",map { defined($_) ? $_ : '' } @{$_[0]});
  3588         9658  
  2321         3894  
179             }
180              
181             ###############################################################################
182              
183             =item put (\@\$)
184              
185             Add a new element to the cache; before adding it checks cache size and
186             throws out elements to make space for the new element. Order of removal
187             depends on when an element was accessed last.
188              
189             =cut
190              
191             sub put ($$$) {
192 802     802 1 1079 my $self=shift;
193              
194 802         1294 my $key=$self->make_key(shift);
195              
196             # We store a deep copy, not an actual data piece. It must be OK to
197             # modify the original data after it's cached.
198             #
199 802         3445 my $element=clone(shift);
200              
201 802         1315 my $data=$self->{data};
202 802         1136 my $size=$self->{size};
203 802 100       1601 my $nsz=$size ? $self->calculate_size($element) : 0;
204              
205 802         1221 my $lr=$self->{least_recent};
206 802         1154 my $expire=$self->{'expire'};
207 802         1075 my $now=time;
208 802         975 my $count=5;
209 802         1417 while(defined($lr)) {
210 1212         1716 my $lred=$data->{$lr};
211 1212 50       2087 last unless $count--;
212             last unless ($size && $self->{current_size}+$nsz>$size) ||
213 1212 100 100     4910 ($expire && $lred->{access_time}+$expire < $now);
      100        
      100        
214 421         787 $lr=$self->drop_oldest($lr,$lred);
215             }
216              
217             $data->{$key}={
218             size => $nsz,
219             element => $element,
220             access_time => time,
221             previous => undef,
222             next => $self->{most_recent},
223 802         3214 };
224              
225             ### dprint "MEMORY: put(",$key," => ",$element,") size=",$self->{'size'}," expire=",$self->{'expire'};
226              
227             $data->{$self->{most_recent}}->{previous}=$key
228 802 100       2133 if defined($self->{most_recent});
229              
230 802         1158 $self->{most_recent}=$key;
231 802 100       1465 $self->{least_recent}=$key unless defined($self->{least_recent});
232 802         1101 $self->{current_size}+=$nsz;
233              
234 802         1516 return undef;
235             }
236              
237             ###############################################################################
238              
239             =item setup (%)
240              
241             Sets expiration time and maximum cache size.
242              
243             =cut
244              
245             sub setup ($%) {
246 9     9 1 18 my $self=shift;
247 9         26 my $args=get_args(\@_);
248              
249 9   100     37 $self->{'expire'}=$args->{'expire'} || 0;
250 9   100     37 $self->{'size'}=($args->{'size'} || 0) * 1024;
251              
252 9         26 $self->drop_all();
253             }
254              
255             ###############################################################################
256              
257             =back
258              
259             =head1 PRIVATE METHODS
260              
261             =over
262              
263             ###############################################################################
264              
265             =item drop_oldest ($)
266              
267             Drops oldest element from the cache using supplied key and element.
268              
269             =cut
270              
271             sub drop_oldest ($$$) {
272 421     421 1 788 my ($self,$key,$ed)=@_;
273              
274             ### dprint "drop_oldest()";
275              
276             $self->{most_recent}=undef if defined($self->{most_recent}) &&
277 421 100 66     1263 $self->{most_recent} eq $key;
278              
279 421         622 my $previous=$ed->{previous};
280 421         579 $self->{least_recent}=$previous;
281              
282 421         621 $self->{current_size}-=$ed->{size};
283              
284 421         539 my $data=$self->{data};
285              
286 421 100       1199 $data->{$previous}->{next}=undef if defined($previous);
287              
288 421         832 delete $data->{$key};
289              
290             ### $self->print_chain();
291              
292 421         1306 return $previous;
293             }
294              
295             ###############################################################################
296              
297             =item print_chain ()
298              
299             Prints cache as a chain from the most recent to the least recent. The
300             order is most_recent->next->...->next->least_recent.
301              
302             =cut
303              
304             sub print_chain ($) {
305 0     0 1   my $self=shift;
306 0           my $data=$self->{data};
307              
308             dprint "CHAIN: mr=",$self->{most_recent},
309             " lr=",$self->{least_recent},
310             " csz=",$self->{current_size},
311 0           " size=",$self->{size},"\n";
312 0           my $id=$self->{most_recent};
313 0           my $c='';
314 0           while(defined($id)) {
315 0           my $ed=$data->{$id};
316 0 0         $c.="->" if $id ne $self->{most_recent};
317 0   0       $c.="[$id/$ed->{access_time}/".($ed->{previous}||'')."/".($ed->{next}||'')."]";
      0        
318 0           $id=$ed->{next};
319             }
320 0           print STDERR "$c\n";
321             }
322              
323             ###############################################################################
324              
325             =item touch ($)
326              
327             Private method that updates access time and moves an element to the most
328             recent position.
329              
330             =cut
331              
332             sub touch ($$$) {
333 0     0 1   my ($self,$key,$ed)=@_;
334              
335 0           $ed->{access_time}=time;
336              
337 0           my $previous=$ed->{previous};
338 0 0         if(defined $previous) {
339 0           my $next=$ed->{next};
340              
341 0           my $data=$self->{data};
342              
343 0           my $ped=$data->{$previous};
344 0           $ped->{next}=$next;
345              
346 0 0         $self->{least_recent}=$previous if $self->{least_recent} eq $key;
347              
348 0 0         if(defined($next)) {
349 0           my $ned=$data->{$next};
350 0           $ned->{previous}=$previous;
351             }
352              
353 0           $ed->{next}=$self->{most_recent};
354 0           $ed->{previous}=undef;
355              
356 0           $self->{most_recent}=$data->{$ed->{next}}->{previous}=$key;
357             }
358              
359             ### $self->print_chain;
360             }
361              
362             ###############################################################################
363             1;
364             __END__