File Coverage

blib/lib/Perlbal/Cache.pm
Criterion Covered Total %
statement 53 143 37.0
branch 7 62 11.2
condition 2 36 5.5
subroutine 11 18 61.1
pod 0 10 0.0
total 73 269 27.1


line stmt bran cond sub pod time code
1             # (This is a copy of Cache::SimpleLRU.)
2             # License to use and redistribute this under the same terms as Perl itself.
3              
4             package Perlbal::Cache;
5              
6 22     22   147 use strict;
  22         55  
  22         1099  
7 22     22   146 use fields qw(items size tail head maxsize);
  22         53  
  22         268  
8              
9 22     22   2551 use vars qw($VERSION);
  22         58  
  22         1328  
10 22     22   143 use constant PREVREF => 0; # ptr left, to newer item
  22         53  
  22         2751  
11 22     22   121 use constant VALUE => 1;
  22         47  
  22         1142  
12 22     22   171 use constant NEXTREF => 2; # ptr right, to older item
  22         46  
  22         1202  
13 22     22   133 use constant KEY => 3; # copy of key for unlinking from namespace on fallout
  22         44  
  22         46791  
14              
15             $VERSION = '1.0';
16              
17             sub new {
18 1     1 0 4 my $class = shift;
19 1         3 my $self = fields::new($class);
20 1 50       54 my $args = @_ == 1 ? $_[0] : { @_ };
21              
22 1         5 $self->{head} = undef,
23             $self->{tail} = undef,
24             $self->{items} = {}; # key -> arrayref, indexed by constants above
25 1         2 $self->{size} = 0;
26 1         3 $self->{maxsize} = $args->{maxsize}+0;
27 1         4 return $self;
28             }
29              
30             # need to DESTROY to cleanup doubly-linked list (circular refs)
31             sub DESTROY {
32 0     0   0 my $self = shift;
33 0         0 $self->set_maxsize(0);
34 0         0 $self->validate_list;
35             }
36              
37             # calls $code->($val) for each value in cache. $code must return true
38             # to continue walking. foreach returns true if you hit the end.
39             sub foreach {
40 0     0 0 0 my Perlbal::Cache $self = shift;
41 0         0 my $code = shift;
42 0         0 my $iter = $self->{head};
43 0         0 while ($iter) {
44 0         0 my $val = $iter->[VALUE];
45 0         0 $iter = $iter->[NEXTREF];
46 0 0       0 last unless $code->($val);
47             }
48 0 0       0 return $iter ? 0 : 1;
49             }
50              
51             sub size {
52 0     0 0 0 my Perlbal::Cache $self = shift;
53 0         0 return $self->{size};
54             }
55              
56             sub maxsize {
57 0     0 0 0 my Perlbal::Cache $self = shift;
58 0         0 return $self->{maxsize};
59             }
60              
61             sub set_maxsize {
62 1     1 0 2 my Perlbal::Cache $self = shift;
63 1         1 my $maxsize = shift;
64 1         3 $self->{maxsize} = $maxsize;
65 1         6 $self->drop_tail while
66             $self->{size} > $self->{maxsize};
67             }
68              
69             # For debugging only
70             sub validate_list {
71 0     0 0 0 my Perlbal::Cache $self = shift;
72              
73 0 0 0     0 die "no tail pointer\n" if $self->{size} && ! $self->{tail};
74 0 0 0     0 die "no head pointer\n" if $self->{size} && ! $self->{head};
75 0 0 0     0 die "unwanted tail pointer\n" if ! $self->{size} && $self->{tail};
76 0 0 0     0 die "unwanted head pointer\n" if ! $self->{size} && $self->{head};
77              
78 0         0 my $iter = $self->{head};
79 0         0 my $last = undef;
80 0         0 my $count = 1;
81 0         0 while ($count <= $self->{size}) {
82 0 0       0 if (! defined $iter) {
83 0         0 die "undefined iterator on element \#$count (trying to get to size $self->{size})\n";
84             }
85 0         0 my $key = $iter->[KEY];
86 0 0       0 my $it_via_hash = $self->{items}->{$key} or
87             die "item '$key' found in list, but not in hash\n";
88              
89 0 0       0 unless ($it_via_hash == $iter) {
90 0         0 die "Hash value of '$key' maps to different node than we found.\n";
91             }
92              
93 0 0 0     0 if ($count == 1 && $iter->[PREVREF]) {
94 0         0 die "Head element shouldn't have previous pointer!\n";
95             }
96 0 0 0     0 if ($count == $self->{size} && $iter->[NEXTREF]) {
97 0         0 die "Last element shouldn't have next pointer!\n";
98             }
99 0 0 0     0 if ($iter->[NEXTREF] && $iter->[NEXTREF]->[PREVREF] != $iter) {
100 0         0 die "next's previous should be us.\n";
101             }
102 0 0 0     0 if ($last && $iter->[PREVREF] != $last) {
103 0         0 die "defined \$last but its previous isn't us.\n";
104             }
105 0 0 0     0 if ($last && $last->[NEXTREF] != $iter) {
106 0         0 die "defined \$last but our next isn't it\n";
107             }
108 0 0 0     0 if (!$last && $iter->[PREVREF]) {
109 0         0 die "uh, we have a nextref but shouldn't\n";
110             }
111              
112 0         0 $last = $iter;
113 0         0 $iter = $iter->[NEXTREF];
114 0         0 $count++;
115             }
116 0         0 return 1;
117             }
118              
119             sub drop_tail {
120 0     0 0 0 my Perlbal::Cache $self = shift;
121 0 0       0 die "no tail (size)" unless $self->{size};
122              
123             ## who's going to die?
124 0 0       0 my $to_die = $self->{tail} or die "no tail (key)";
125              
126             ## set the tail to the item before the one dying.
127 0         0 $self->{tail} = $self->{tail}->[PREVREF];
128              
129             ## adjust the forward pointer on the tail to be undef
130 0 0       0 if (defined $self->{tail}) {
131 0         0 $self->{tail}->[NEXTREF] = undef;
132             }
133              
134             ## kill the item
135 0         0 delete $self->{items}->{$to_die->[KEY]};
136              
137             ## shrink the overall size
138 0         0 $self->{size}--;
139              
140 0 0       0 if (!$self->{size}) {
141 0         0 $self->{head} = undef;
142             }
143             }
144              
145             sub get {
146 32     32 0 65 my Perlbal::Cache $self = shift;
147 32         68 my ($key) = @_;
148              
149 32 100       385 my $item = $self->{items}{$key} or
150             return undef;
151              
152             # promote this to the head
153 9 50       35 unless ($self->{head} == $item) {
154 0 0       0 if ($self->{tail} == $item) {
155 0         0 $self->{tail} = $item->[PREVREF];
156             }
157              
158             # remove this element from the linked list.
159 0         0 my $next = $item->[NEXTREF];
160 0         0 my $prev = $item->[PREVREF];
161 0 0       0 if ($next) { $next->[PREVREF] = $prev; }
  0         0  
162 0 0       0 if ($prev) { $prev->[NEXTREF] = $next; }
  0         0  
163              
164             # make current head point backwards to this item
165 0         0 $self->{head}->[PREVREF] = $item;
166              
167             # make this item point forwards to current head, and backwards nowhere
168 0         0 $item->[NEXTREF] = $self->{head};
169 0         0 $item->[PREVREF] = undef;
170              
171             # make this the new head
172 0         0 $self->{head} = $item;
173             }
174              
175 9         46 return $item->[VALUE];
176             }
177              
178             sub remove {
179 0     0 0 0 my Perlbal::Cache $self = shift;
180 0         0 my ($key) = @_;
181              
182 0 0       0 my $item = $self->{items}{$key} or
183             return 0;
184 0         0 delete $self->{items}{$key};
185 0         0 $self->{size}--;
186              
187 0 0       0 if (!$self->{size}) {
188 0         0 $self->{head} = undef;
189 0         0 $self->{tail} = undef;
190 0         0 return 1;
191             }
192              
193 0 0       0 if ($self->{head} == $item) {
194 0         0 $self->{head} = $item->[NEXTREF];
195 0         0 $self->{head}->[PREVREF] = undef;
196 0         0 return 1;
197             }
198 0 0       0 if ($self->{tail} == $item) {
199 0         0 $self->{tail} = $item->[PREVREF];
200 0         0 $self->{tail}->[NEXTREF] = undef;
201 0         0 return 1;
202             }
203              
204             # remove from middle
205 0         0 $item->[PREVREF]->[NEXTREF] = $item->[NEXTREF];
206 0         0 $item->[NEXTREF]->[PREVREF] = $item->[PREVREF];
207 0         0 return 1;
208              
209             }
210              
211             sub set {
212 2     2 0 5 my Perlbal::Cache $self = shift;
213 2         5 my ($key, $value) = @_;
214              
215 2   33     23 $self->drop_tail while
      33        
216             $self->{maxsize} &&
217             $self->{size} >= $self->{maxsize} &&
218             ! exists $self->{items}->{$key};
219              
220 2 100       7 if (exists $self->{items}->{$key}) {
221             # update the value
222 1         3 my $it = $self->{items}->{$key};
223 1         4 $it->[VALUE] = $value;
224             } else {
225             # stick it at the end, for now
226 1         5 my $it = $self->{items}->{$key} = [];
227 1         2 $it->[PREVREF] = undef;
228 1         3 $it->[NEXTREF] = undef;
229 1         2 $it->[KEY] = $key;
230 1         3 $it->[VALUE] = $value;
231 1 50       3 if ($self->{size}) {
232 0         0 $self->{tail}->[NEXTREF] = $it;
233 0         0 $it->[PREVREF] = $self->{tail};
234             } else {
235 1         4 $self->{head} = $it;
236             }
237 1         2 $self->{tail} = $it;
238 1         2 $self->{size}++;
239             }
240              
241             # this will promote it to the top:
242 2         9 $self->get($key);
243             }
244              
245             1;