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   27 use strict;
  2         2  
  2         105  
24 2     2   8 use XAO::Utils;
  2         5  
  2         207  
25 2     2   16 use XAO::Objects;
  2         4  
  2         74  
26 2     2   1030 use Clone qw(clone);
  2         1363  
  2         140  
27 2     2   13 use feature qw(state);
  2         3  
  2         280  
28              
29 2     2   10 use base XAO::Objects->load(objname => 'Atom');
  2         5  
  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 639 my $self=shift;
43 620         648 my $d=shift;
44              
45 620         624 state $have_devel_size;
46 620 100       930 if(!defined $have_devel_size) {
47 1         124 eval 'require Devel::Size';
48 1 50       31 if($@) {
49 1         7 $have_devel_size=0;
50 1         12 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       782 if($have_devel_size) {
59 0         0 return Devel::Size::total_size($d);
60             }
61             else {
62 620         795 my $r=ref($d);
63 620         619 my $sz=0;
64 620         979 while($r eq 'REF') {
65 0         0 $d=$$d;
66 0         0 $r=ref($d);
67 0         0 $sz+=4;
68             }
69 620 50       1120 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         805 $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         828 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 8 my $self=shift;
103              
104 8         13 my $key=$self->make_key($_[0]);
105 8         9 my $data=$self->{data};
106 8         13 my $ed=$data->{$key};
107              
108 8 50       12 return unless $ed;
109              
110 8 100       10 if($ed->{next}) {
111 4         9 $data->{$ed->{next}}->{previous}=$ed->{previous};
112             }
113             else {
114 4         6 $self->{least_recent}=$ed->{previous};
115             }
116              
117 8 100       10 if($ed->{previous}) {
118 7         11 $data->{$ed->{previous}}->{next}=$ed->{next};
119             }
120             else {
121 1         3 $self->{most_recent}=$ed->{next};
122             }
123              
124 8         23 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 17 my ($self,$key,$ed)=@_;
137              
138 10         20 $self->{'data'}={ };
139 10         26 $self->{'least_recent'}=$self->{'most_recent'}=undef;
140 10         33 $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 1535 my $self=shift;
154              
155 1511         2040 my $key=$self->make_key($_[0]);
156              
157             ### dprint "MEMORY: get(",$key,")";
158              
159 1511         2078 my $ed=$self->{'data'}->{$key};
160              
161 1511         1609 my $expire=$self->{'expire'};
162              
163 1511   100     2638 my $exists=($ed && (!$expire || $ed->{'access_time'} + $expire > time));
164              
165 1511 100       2458 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 2231 my $self=shift;
178 2321 100       2248 return join("\001",map { defined($_) ? $_ : '' } @{$_[0]});
  3588         6827  
  2321         2817  
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 877 my $self=shift;
193              
194 802         979 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         2760 my $element=clone(shift);
200              
201 802         980 my $data=$self->{data};
202 802         840 my $size=$self->{size};
203 802 100       1278 my $nsz=$size ? $self->calculate_size($element) : 0;
204              
205 802         982 my $lr=$self->{least_recent};
206 802         837 my $expire=$self->{'expire'};
207 802         865 my $now=time;
208 802         774 my $count=5;
209 802         1133 while(defined($lr)) {
210 1212         1388 my $lred=$data->{$lr};
211 1212 50       1616 last unless $count--;
212             last unless ($size && $self->{current_size}+$nsz>$size) ||
213 1212 100 100     3799 ($expire && $lred->{access_time}+$expire < $now);
      100        
      100        
214 421         654 $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         2533 };
224              
225             ### dprint "MEMORY: put(",$key," => ",$element,") size=",$self->{'size'}," expire=",$self->{'expire'};
226              
227             $data->{$self->{most_recent}}->{previous}=$key
228 802 100       1770 if defined($self->{most_recent});
229              
230 802         904 $self->{most_recent}=$key;
231 802 100       1189 $self->{least_recent}=$key unless defined($self->{least_recent});
232 802         852 $self->{current_size}+=$nsz;
233              
234 802         1234 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 12 my $self=shift;
247 9         19 my $args=get_args(\@_);
248              
249 9   100     29 $self->{'expire'}=$args->{'expire'} || 0;
250 9   100     30 $self->{'size'}=($args->{'size'} || 0) * 1024;
251              
252 9         25 $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 603 my ($self,$key,$ed)=@_;
273              
274             ### dprint "drop_oldest()";
275              
276             $self->{most_recent}=undef if defined($self->{most_recent}) &&
277 421 100 66     1043 $self->{most_recent} eq $key;
278              
279 421         543 my $previous=$ed->{previous};
280 421         496 $self->{least_recent}=$previous;
281              
282 421         512 $self->{current_size}-=$ed->{size};
283              
284 421         488 my $data=$self->{data};
285              
286 421 100       804 $data->{$previous}->{next}=undef if defined($previous);
287              
288 421         564 delete $data->{$key};
289              
290             ### $self->print_chain();
291              
292 421         969 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__