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   20 use strict;
  2         4  
  2         76  
24 2     2   10 use XAO::Utils;
  2         4  
  2         215  
25 2     2   12 use XAO::Objects;
  2         4  
  2         65  
26 2     2   912 use Clone qw(clone);
  2         5357  
  2         167  
27 2     2   17 use feature qw(state);
  2         5  
  2         332  
28              
29 2     2   15 use base XAO::Objects->load(objname => 'Atom');
  2         4  
  2         20  
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 1012 my $self=shift;
43 620         795 my $d=shift;
44              
45 620         857 state $have_devel_size;
46 620 100       1386 if(!defined $have_devel_size) {
47 1         150 eval 'require Devel::Size';
48 1 50       9 if($@) {
49 1         12 $have_devel_size=0;
50 1         17 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       998 if($have_devel_size) {
59 0         0 return Devel::Size::total_size($d);
60             }
61             else {
62 620         1096 my $r=ref($d);
63 620         878 my $sz=0;
64 620         1323 while($r eq 'REF') {
65 0         0 $d=$$d;
66 0         0 $r=ref($d);
67 0         0 $sz+=4;
68             }
69 620 50       1457 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         1005 $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         1065 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 16 my $self=shift;
103              
104 8         16 my $key=$self->make_key($_[0]);
105 8         18 my $data=$self->{data};
106 8         18 my $ed=$data->{$key};
107              
108 8 50       17 return unless $ed;
109              
110 8 100       18 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       20 if($ed->{previous}) {
118 7         35 $data->{$ed->{previous}}->{next}=$ed->{next};
119             }
120             else {
121 1         3 $self->{most_recent}=$ed->{next};
122             }
123              
124 8         33 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 26 my ($self,$key,$ed)=@_;
137              
138 10         31 $self->{'data'}={ };
139 10         29 $self->{'least_recent'}=$self->{'most_recent'}=undef;
140 10         34 $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 2240 my $self=shift;
154              
155 1511         2538 my $key=$self->make_key($_[0]);
156              
157             ### dprint "MEMORY: get(",$key,")";
158              
159 1511         2920 my $ed=$self->{'data'}->{$key};
160              
161 1511         2288 my $expire=$self->{'expire'};
162              
163 1511   100     4001 my $exists=($ed && (!$expire || $ed->{'access_time'} + $expire > time));
164              
165 1511 100       3458 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 3097 my $self=shift;
178 2321 100       3004 return join("\001",map { defined($_) ? $_ : '' } @{$_[0]});
  3588         10882  
  2321         4009  
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 1170 my $self=shift;
193              
194 802         1325 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         4287 my $element=clone(shift);
200              
201 802         1435 my $data=$self->{data};
202 802         1126 my $size=$self->{size};
203 802 100       1667 my $nsz=$size ? $self->calculate_size($element) : 0;
204              
205 802         1331 my $lr=$self->{least_recent};
206 802         1111 my $expire=$self->{'expire'};
207 802         1120 my $now=time;
208 802         1055 my $count=5;
209 802         1531 while(defined($lr)) {
210 1212         1778 my $lred=$data->{$lr};
211 1212 50       2166 last unless $count--;
212             last unless ($size && $self->{current_size}+$nsz>$size) ||
213 1212 100 100     5284 ($expire && $lred->{access_time}+$expire < $now);
      100        
      100        
214 421         788 $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         3675 };
224              
225             ### dprint "MEMORY: put(",$key," => ",$element,") size=",$self->{'size'}," expire=",$self->{'expire'};
226              
227             $data->{$self->{most_recent}}->{previous}=$key
228 802 100       2259 if defined($self->{most_recent});
229              
230 802         1166 $self->{most_recent}=$key;
231 802 100       1532 $self->{least_recent}=$key unless defined($self->{least_recent});
232 802         1096 $self->{current_size}+=$nsz;
233              
234 802         1567 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 19 my $self=shift;
247 9         27 my $args=get_args(\@_);
248              
249 9   100     40 $self->{'expire'}=$args->{'expire'} || 0;
250 9   100     36 $self->{'size'}=($args->{'size'} || 0) * 1024;
251              
252 9         27 $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 851 my ($self,$key,$ed)=@_;
273              
274             ### dprint "drop_oldest()";
275              
276             $self->{most_recent}=undef if defined($self->{most_recent}) &&
277 421 100 66     1334 $self->{most_recent} eq $key;
278              
279 421         700 my $previous=$ed->{previous};
280 421         583 $self->{least_recent}=$previous;
281              
282 421         623 $self->{current_size}-=$ed->{size};
283              
284 421         537 my $data=$self->{data};
285              
286 421 100       1168 $data->{$previous}->{next}=undef if defined($previous);
287              
288 421         815 delete $data->{$key};
289              
290             ### $self->print_chain();
291              
292 421         1338 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__