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   18 use strict;
  2         6  
  2         91  
24 2     2   14 use XAO::Utils;
  2         4  
  2         266  
25 2     2   16 use XAO::Objects;
  2         3  
  2         108  
26 2     2   1156 use Clone qw(clone);
  2         6625  
  2         143  
27 2     2   19 use feature qw(state);
  2         4  
  2         341  
28              
29 2     2   15 use base XAO::Objects->load(objname => 'Atom');
  2         4  
  2         18  
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 915 my $self=shift;
43 620         804 my $d=shift;
44              
45 620         787 state $have_devel_size;
46 620 100       1123 if(!defined $have_devel_size) {
47 1         185 eval 'require Devel::Size';
48 1 50       24 if($@) {
49 1         3 $have_devel_size=0;
50 1         11 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       949 if($have_devel_size) {
59 0         0 return Devel::Size::total_size($d);
60             }
61             else {
62 620         914 my $r=ref($d);
63 620         768 my $sz=0;
64 620         1326 while($r eq 'REF') {
65 0         0 $d=$$d;
66 0         0 $r=ref($d);
67 0         0 $sz+=4;
68             }
69 620 50       1322 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         1047 $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         1036 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 19 my $self=shift;
103              
104 8         16 my $key=$self->make_key($_[0]);
105 8         18 my $data=$self->{data};
106 8         21 my $ed=$data->{$key};
107              
108 8 50       17 return unless $ed;
109              
110 8 100       15 if($ed->{next}) {
111 4         9 $data->{$ed->{next}}->{previous}=$ed->{previous};
112             }
113             else {
114 4         7 $self->{least_recent}=$ed->{previous};
115             }
116              
117 8 100       25 if($ed->{previous}) {
118 7         17 $data->{$ed->{previous}}->{next}=$ed->{next};
119             }
120             else {
121 1         2 $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 25 my ($self,$key,$ed)=@_;
137              
138 10         22 $self->{'data'}={ };
139 10         31 $self->{'least_recent'}=$self->{'most_recent'}=undef;
140 10         39 $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 2103 my $self=shift;
154              
155 1511         2431 my $key=$self->make_key($_[0]);
156              
157             ### dprint "MEMORY: get(",$key,")";
158              
159 1511         2784 my $ed=$self->{'data'}->{$key};
160              
161 1511         2089 my $expire=$self->{'expire'};
162              
163 1511   100     3794 my $exists=($ed && (!$expire || $ed->{'access_time'} + $expire > time));
164              
165 1511 100       3159 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 2983 my $self=shift;
178 2321 100       3048 return join("\001",map { defined($_) ? $_ : '' } @{$_[0]});
  3588         10225  
  2321         3711  
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 1133 my $self=shift;
193              
194 802         1323 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         3711 my $element=clone(shift);
200              
201 802         1355 my $data=$self->{data};
202 802         1104 my $size=$self->{size};
203 802 100       1551 my $nsz=$size ? $self->calculate_size($element) : 0;
204              
205 802         1205 my $lr=$self->{least_recent};
206 802         1121 my $expire=$self->{'expire'};
207 802         1029 my $now=time;
208 802         1159 my $count=5;
209 802         1556 while(defined($lr)) {
210 1212         1883 my $lred=$data->{$lr};
211 1212 50       2077 last unless $count--;
212             last unless ($size && $self->{current_size}+$nsz>$size) ||
213 1212 100 100     5335 ($expire && $lred->{access_time}+$expire < $now);
      100        
      100        
214 421         817 $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         3324 };
224              
225             ### dprint "MEMORY: put(",$key," => ",$element,") size=",$self->{'size'}," expire=",$self->{'expire'};
226              
227             $data->{$self->{most_recent}}->{previous}=$key
228 802 100       2131 if defined($self->{most_recent});
229              
230 802         1166 $self->{most_recent}=$key;
231 802 100       1430 $self->{least_recent}=$key unless defined($self->{least_recent});
232 802         1081 $self->{current_size}+=$nsz;
233              
234 802         1521 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 22 my $self=shift;
247 9         29 my $args=get_args(\@_);
248              
249 9   100     40 $self->{'expire'}=$args->{'expire'} || 0;
250 9   100     38 $self->{'size'}=($args->{'size'} || 0) * 1024;
251              
252 9         24 $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 859 my ($self,$key,$ed)=@_;
273              
274             ### dprint "drop_oldest()";
275              
276             $self->{most_recent}=undef if defined($self->{most_recent}) &&
277 421 100 66     1294 $self->{most_recent} eq $key;
278              
279 421         595 my $previous=$ed->{previous};
280 421         597 $self->{least_recent}=$previous;
281              
282 421         664 $self->{current_size}-=$ed->{size};
283              
284 421         540 my $data=$self->{data};
285              
286 421 100       1124 $data->{$previous}->{next}=undef if defined($previous);
287              
288 421         814 delete $data->{$key};
289              
290             ### $self->print_chain();
291              
292 421         1273 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__