File Coverage

lib/Tie/FileLRUCache.pm
Criterion Covered Total %
statement 281 380 73.9
branch 80 112 71.4
condition 20 33 60.6
subroutine 30 32 93.7
pod 9 9 100.0
total 420 566 74.2


line stmt bran cond sub pod time code
1             package Tie::FileLRUCache;
2              
3 1     1   79168 use strict;
  1         2  
  1         24  
4 1     1   5 use warnings;
  1         1  
  1         24  
5              
6 1     1   436 use Class::ParmList qw (simple_parms parse_parms);
  1         1340  
  1         53  
7 1     1   432 use Digest::SHA1 qw(sha1_hex);
  1         653  
  1         56  
8 1     1   5 use Fcntl qw (:flock);
  1         2  
  1         111  
9 1     1   6 use File::Spec;
  1         2  
  1         22  
10 1     1   610 use Storable qw (nstore nfreeze retrieve);
  1         2727  
  1         56  
11 1     1   6 use Symbol qw (gensym);
  1         1  
  1         45  
12              
13 1     1   4 use vars qw ($VERSION);
  1         1  
  1         33  
14              
15             BEGIN {
16 1     1   3422 $VERSION = "1.06";
17             }
18              
19             ###########################################################################
20              
21             =head1 NAME
22              
23             Tie::FileLRUCache - A lightweight but robust filesystem based persistent LRU cache
24              
25             =head1 CHANGES
26              
27             1.06 2020.10.08 - Changed license to MIT License. Updated maintainer info.
28             Updated build files. Added GitHub repo meta to build.
29             Changed minimum supported version of Perl to 5.6.
30              
31             1.05 2005.09.13 - Changes to pod tests to make them more CPANTS friendly.
32             No functional changes.
33              
34             1.04 2005.09.13 - Removed use of lexical warnings pragma to fix compatibility
35             with Perl 5.005.
36              
37             Fixed minor typographical errors in documentation.
38              
39             1.03 2005.09.10 - Changed build test to handle difference in treatment of hashes
40             in scalar context between 5.6.x and 5.8.x versions of Perl that
41             caused a test failure under Perl 5.6.x.
42              
43             1.02 2005.09.08 - Added build tests. Major code cleanup. Improved platform portability.
44             Added and documented 'cache_dir', 'keep_last' and 'number_of_entries'
45             methods. Added Module::Build support.
46              
47              
48              
49             1.01 1999.12.09 - Added detainting in internal cache maintaining
50             functions to evade Taint's tainting
51             of filenames read via readdir().
52              
53             =head1 SYNOPSIS
54              
55             =head2 OBJECT INTERFACE
56              
57             use Tie::FileLRUCache;
58             my $cache = Tie::FileLRUCache->new({ -cache_dir => $directory, -keep_last => 100 });
59              
60             # Inserting value into LRU cache using '-key'
61             $cache->update({ -key => $key, -value => $value });
62              
63              
64             # Inserting value into LRU cache using '-cache_key'
65             my $cache_key = $cache->make_cache_key({ -key => $key });
66             $cache->update({ -cache_key => $cache_key, -value => $value });
67              
68              
69             # Checking LRU cache
70             my ($in_cache,$value) = $cache->check({ -key => $key });
71             if ($in_cache) {
72             return $value;
73             }
74             # Not in cache - do something else
75              
76              
77             # Checking LRU cache with speed up hack for objects, hashes, arrays etc used as keys
78             my $cache_key = $cache->make_cache_key({ -key => $something });
79             my ($in_cache,$value) = $cache->check({ -cache_key => $cache_key });
80             if ($in_cache) {
81             return $value;
82             }
83             # Not in cache - do something else
84              
85              
86             # Deleting a key and its value from the cache
87             $cache->delete({ -key => $key });
88              
89              
90             # Clearing LRU cache
91             $cache->clear;
92              
93             =head2 TIED INTERFACE
94              
95             use Tie::FileLRUCache;
96              
97             [$X =] tie %hash, 'Tie::FileLRUCache', $cache_dir, $keep_last_n;
98              
99             # Adding a key/value to the cache
100             $hash{$key} = $value;
101              
102             # Checking the cache
103             if (not exists $hash{$key}) {;
104             # No match
105             .
106             .
107             .
108              
109             } else {
110             my $value = $hash{$key};
111             .
112             .
113             .
114              
115             }
116              
117             # Removing a value from the cache;
118             delete $hash{$key};
119              
120             # Clearing the cache
121             %hash = ();
122              
123             Note: Iteration over the cache (each, keys, values) is _NOT_ supported.
124              
125             =cut
126              
127             =head1 DESCRIPTION
128              
129             Provides a lightweight persistent filesystem based LRU cache.
130              
131             It uses the 'last accessed' timestamp generated by the file system
132             to determine the 'oldest' cache entry and discards the oldest
133             cache entries when needed to stay under the -keep_last limit.
134              
135             If you store thing very fast (such that many entries receive the
136             same time stamp), it is essentially a coin toss which entry
137             within a single timestamped second gets purged from the cache
138             to make room for new ones.
139              
140             It is not designed to handle huge numbers of cached items. It is probably
141             unwise to set the 'keep_last' higher than around 100.
142              
143             =cut
144              
145             =head1 OBJECT METHODS
146              
147             =cut
148              
149             #######################################################################
150              
151             =over 4
152              
153             =item new({[ -cache_dir => $cache_directory] [, -keep_last => $keep_last_n ] });
154              
155             Creates and optionally initializes a Tie::FileLRUCache object:
156              
157             Example:
158              
159             my $cache = Tie::FileLRUCache->new({
160             -cache_dir => '/tmp/testing',
161             -keep_last => 100,
162             });
163              
164             The default cache size is 100 entries unless specified.
165              
166             =back
167              
168             =cut
169              
170             sub new {
171 13     13 1 2537 my $proto = shift;
172 13         17 my $package = __PACKAGE__;
173 13   66     47 my $class = ref ($proto) || $proto || $package;
174 13         22 my $self = bless {}, $class;
175              
176 13         66 my $parms = parse_parms({ -parms => \@_,
177             -legal => [-cache_dir, -keep_last],
178             -required => [],
179             -defaults => { -keep_last => 100,
180             -cache_dir => undef,
181             },
182             });
183 13 100       1231 if (not defined $parms) {
184 1         4 my $error_message = Class::ParmList->error;
185 1         7 require Carp;
186 1         160 Carp::croak ($package . "::new() - Parameter error '$error_message'\n");
187             }
188              
189             # Save settings
190 12         31 my ($cache_dir,$keep_last) = $parms->get(-cache_dir,-keep_last);
191 12         313 $self->cache_dir($cache_dir);
192 12         27 $self->keep_last($keep_last);
193              
194 12         40 $self;
195             }
196              
197             #######################################################################
198              
199             =over 4
200              
201             =item check({ -key => $key });
202              
203             Reads the cache for the key.
204              
205             Returns two values: $cache_hit (true if a hit was found, false if not)
206             $value (the cached value, undef if no hit)
207              
208             Examples:
209              
210             my ($cache_hit,$value) = $cache->check({ -key => $key });
211              
212             my ($cache_hit,$value) = $cache->check({ -cache_key => $cache_key });
213              
214             The '-key' form is used when you just want to use a raw key. It can use
215             blessed objects, hash refs, scalars, or array refs as keys. The more complex
216             structures take a speed penalty for computing a canonical form.
217             You can minimize this penalty by using the '-cache_key' form instead.
218              
219             The '-cache_key' form is used for performance reasons when using keys
220             such as complex blessed objects or hashes as a key. The -cache_key
221             is obtained with a call to 'make_cache_key'. It is legal to mix
222             -cache_key and -key based calls - they are cross-compatible.
223              
224             =back
225              
226             =cut
227              
228             sub check {
229 48     48 1 325 my $self = shift;
230 48         57 my $package = __PACKAGE__;
231              
232 48 100       82 if (not wantarray) {
233 1         3 require Carp;
234 1         54 Carp::croak ($package . "::check() - Called in a scalar context\n");
235             }
236              
237 47         231 my $parms = parse_parms({ -parms => \@_,
238             -legal => [-cache_key, -key],
239             -required => [],
240             -defaults => {},
241             });
242 46 100       4820 if (not defined $parms) {
243 1         3 my $error_message = Class::ParmList->error;
244 1         7 require Carp;
245 1         76 Carp::croak ($package . "::check() - $error_message\n");
246             }
247 45         136 my ($key,$cache_key) = $parms->get(-key,-cache_key);
248 45 100 100     1314 if (not (defined ($key) or defined ($cache_key))) {
249 1         4 require Carp;
250 1         55 Carp::croak ($package . "::check() - Called without either a -key or -cache_key\n");
251             }
252              
253 44         96 my $cache_dir = $self->cache_dir;
254 44 100       74 unless (defined $cache_dir) {
255 1         4 require Carp;
256 1         62 Carp::croak ($package . "::check - No cache directory set.\n");
257             }
258              
259             # Ok. Set our lock on the cache
260 43         87 $self->_lock_cache;
261              
262             # Generate the cache_key (done by making a cannonical
263             # network order Storable string out of the key) if we
264             # don't already have it
265 43 100       98 unless (defined $cache_key) {
266 5         18 $cache_key = $self->make_cache_key({ -key => $key });
267             }
268              
269             # Generate a unique cache file name by taking a SHA1 hash of $cache_key
270 43         287 my $cache_hash = lc(sha1_hex($cache_key));
271 43         136 $cache_hash =~ s/\s//gs;
272 43         249 my ($untainted_cache_hash) = $cache_hash =~ m/^([a-f0-9]+)$/s;
273 43         480 my $cache_file = File::Spec->catfile($cache_dir, "cl_$untainted_cache_hash");
274              
275             # Check if there is a cache entry for this key
276 43 100       768 unless (-e $cache_file) {
277 22         94 $self->_unlock_cache;
278 22         147 return (0,undef);
279             }
280              
281             # Yes. Get it. And update the last modified and last accessed dates.
282 21         49 my $entry;
283 21         32 eval {
284 21         67 $entry = retrieve($cache_file);
285 21         1653 my $now = time;
286 21         361 utime ($now, $now, $cache_file);
287             };
288 21 50       67 if ($@) {
289 0         0 my $error = $@;
290 0         0 $self->_unlock_cache;
291 0         0 require Carp;
292 0         0 Carp::croak($package . "::check - Error while retrieving cache entry file '$cache_file': $error\n");
293             }
294 21 50       65 unless (defined $entry) {
295 0         0 my $error = $!;
296 0         0 $self->_unlock_cache;
297 0         0 require Carp;
298 0         0 Carp::croak($package . "::update - Failed to retrieve cache entry file '$cache_file': $error\n");
299             }
300              
301             # Release the lock.
302 21         66 $self->_unlock_cache;
303              
304 21         45 my $cache_value = $entry->{'-value'};
305              
306             # Give them their cupie doll
307 21         165 return (1, $cache_value);
308             }
309              
310             #######################################################################
311              
312             =over 4
313              
314             =item make_cache_key({ -key => $key });
315              
316             Generates a cache key by canonicalizing a passed
317             key as a network ordered canonical Storable string.
318              
319             Example:
320              
321             my $cache_key = $cache->make_cache_key({ -key => $key });
322              
323             =back
324              
325             =cut
326              
327             sub make_cache_key {
328 11     11 1 25 my $self = shift;
329 11         12 my $package = __PACKAGE__;
330 11         45 my $parms = parse_parms({ -parms => \@_,
331             -legal => [],
332             -required => ['-key'],
333             -defaults => {},
334             });
335 11 100       1000 unless (defined $parms) {
336 1         5 my $error_message = Class::ParmList->error;
337 1         8 require Carp;
338 1         97 Carp::croak ($package . "::make_cache_key() - $error_message\n");
339             }
340 10         28 my ($key) = $parms->get(-key);
341              
342 10         176 my $temp = $Storable::canonical;
343 10         28 my $result = nfreeze(\$key);
344 10         369 $Storable::canonical = $temp;
345              
346 10 50       22 if (not $result) {
347 0         0 my $error = $!;
348 0         0 require Carp;
349 0         0 Carp::croak ($package . "::check() - Unable to serialize passed -key value: $error");
350             }
351 10         33 return $result;
352             }
353              
354             #######################################################################
355              
356             =over 4
357              
358             =item clear;
359              
360             Completely clears the cache of all cache entries.
361              
362             =back
363              
364             =cut
365              
366             sub clear {
367 4     4 1 22 my $self = shift;
368 4         8 my $package = __PACKAGE__;
369 4         7 my $cache_dir = $self->cache_dir;
370              
371 4 100       20 unless (defined $cache_dir) {
372 1         10 require Carp;
373 1         168 Carp::croak ($package . "::clear - No cache directory set.\n");
374             }
375 3 100       10 if ($cache_dir eq '') {
376 1         5 require Carp;
377 1         69 Carp::croak ($package . "::clear - Cannot use root directory as cache directory.\n");
378             }
379 2 50 33     38 if ((-e $cache_dir) and (not -d _)) {
380 0         0 require Carp;
381 0         0 Carp::croak ($package . "::clear - '$cache_dir' already exists and is not a directory.\n");
382             }
383              
384 2         9 $self->_lock_cache;
385              
386 2         8 my $cache_dir_fh = gensym;
387 2 50       71 if (not opendir ($cache_dir_fh, $cache_dir)) {
388 0         0 my $error = $!;
389 0         0 $self->_unlock_cache;
390 0         0 require Carp;
391 0         0 Carp::croak ($package . "::clear - Failed to open directory '$cache_dir' for reading: $error\n");
392             }
393              
394 2         55 my @raw_directory_list = readdir($cache_dir_fh);
395 2 50       30 unless (closedir ($cache_dir_fh)) {
396 0         0 my $error = $!;
397 0         0 $self->_unlock_cache;
398 0         0 require Carp;
399 0         0 Carp::croak ($package . "::clear - Failed to close directory '$cache_dir': $error\n");
400             }
401              
402             # Untaint the filenames, convert them to absolute file paths and unlink them.
403 2         35 my @raw_files_list = grep(/^(cacheline_[a-zA-Z0-9]{1,50}|cl_[a-zA-Z0-9]{1,50})$/s, @raw_directory_list);
404 2         5 my @file_list = ();
405 2         6 foreach my $item (@raw_files_list) {
406 10         69 my ($filename) = $item =~ m/^(.*)$/s;
407 10         94 my $file_path = File::Spec->catfile($cache_dir, $filename);
408 10 50       485 unless (unlink $file_path) {
409 0         0 my $error = $!;
410 0         0 $self->_unlock_cache;
411 0         0 require Carp;
412 0         0 Carp::croak($package . "::clear - Failed to unlink file '$file_path': $error");
413             }
414             }
415              
416 2         14 $self->_unlock_cache;
417              
418 2         13 return;
419             }
420              
421             #######################################################################
422              
423             =over 4
424              
425             =item update({ [-key => $key,] [-cache_key => $cache_key, ], -value => $value [, -keep_last => $keep_last_n ] });
426              
427             Updates the Least Recently Used (LRU) cache for the specified
428             key with the passed value. '-keep_last' is optional after the first access
429             to a dataset. It will use the I 'keep_last' used
430             if not specified.
431              
432             It is legal to use ordinary scalars, hash references, or array references
433             as keys as well as objects as -keys or -values. Basically, anything that
434             Storable can reproducibly serialize can be used.
435              
436             Examples:
437              
438             $cache->update({ -key => $key, -value => $value });
439              
440             $cache->update({ -key => $key, -value => $value, -keep_last => 100});
441              
442             my $cache_key = $cache->make_cache_key({ -key => $key });
443             $cache->update({ -cache_key => $cache_key, -value => $value });
444              
445             my $cache_key = $cache->make_cache_key({ -key => $key });
446             $cache->update({ -cache_key => $cache_key, -value => $value, -keep_last => 50 });
447              
448             -cache_key is assumed to be a simple scalar value for use as a key.
449              
450             -key can be pretty much anything Storable can successfully and reproducibly serialize.
451              
452             One or the other I be passed.
453              
454             =back
455              
456             =cut
457              
458             sub update {
459 21     21 1 6001426 my $self = shift;
460 21         64 my $package = __PACKAGE__;
461              
462 21         164 my $parms = parse_parms({ -parms => \@_,
463             -legal => ['-keep_last', '-key', '-cache_key'],
464             -required => ['-value'],
465             -defaults => {'-keep_last' => $self->keep_last},
466             });
467 21 100       3555 unless (defined $parms) {
468 1         3 my $error_message = Class::ParmList->error;
469 1         6 require Carp;
470 1         83 Carp::croak ($package . "::update() - $error_message\n");
471             }
472 20         95 my ($key,$cache_key,$value,$keep_last) = $parms->get('-key', '-cache_key', '-value', '-keep_last');
473 20 100 100     981 unless (defined ($key) or defined ($cache_key)) {
474 1         4 require Carp;
475 1         79 Carp::croak ($package . "::update() - Called without either a -key or -cache_key. At least one of them must be passed.\n");
476             }
477              
478 19         64 my ($cache_dir) = $self->cache_dir;
479 19 100       47 unless (defined $cache_dir) {
480 1         4 require Carp;
481 1         75 Carp::croak ($package . "::update - No cache directory set.\n");
482             }
483              
484             # Generate the cache_key (done by making a cannonical
485             # network order Storable string out of the key) if we
486             # don't already have one.
487 18 100       54 unless (defined $cache_key) {
488 2         7 $cache_key = $self->make_cache_key({ -key => $key });
489             }
490              
491             # Generate a unique cache file
492             # name by taking a SHA1 hash of
493             # $cache_key and dumping it as hex
494 18         268 my $cache_hash = lc(sha1_hex($cache_key));
495 18         99 $cache_hash =~ s/\s//gs;
496 18         151 my ($untainted_cache_hash) = $cache_hash =~ m/^([a-f0-9]+)$/s;
497 18         383 my $cache_file = File::Spec->catfile($cache_dir, "cl_$untainted_cache_hash");
498              
499             # Serialize the $value for storage
500 18         77 my $entry = { -value => $value };
501              
502             # Set our lock on the cache directory
503 18         86 $self->_lock_cache;
504              
505             ##########
506             # Store the cache entry.
507 18         29 my $result;
508 18         37 eval { $result = nstore($entry,$cache_file); };
  18         117  
509 18 50       5491 if ($@) {
510 0         0 my $error = $@;
511 0         0 $self->_unlock_cache;
512 0         0 require Carp;
513 0         0 Carp::croak($package . "::update - Error while saving cache entry file '$cache_file': $error");
514             }
515 18 50       55 unless (defined $result) {
516 0         0 my $error = $!;
517 0         0 $self->_unlock_cache;
518 0         0 require Carp;
519 0         0 Carp::croak($package . "::update - Error while saving cache entry file '$cache_file': $error\n");
520             }
521              
522             ########################################
523             # Check if we need to purge old entries
524 18         50 my $cache_dir_fh = gensym;
525 18 50       672 unless (opendir ($cache_dir_fh, $cache_dir)) {
526 0         0 my $error = $!;
527 0         0 $self->_unlock_cache;
528 0         0 require Carp;
529 0         0 Carp::croak ($package . "::update - Failed to open directory '$cache_dir' for reading: $error\n");
530             }
531 18         701 my @raw_file_list = grep(/^(cacheline_[a-fA-F0-9]{1,50}|cl_[a-fA-F0-9]{1,50})$/s,readdir($cache_dir_fh));
532 18 50       292 unless (closedir ($cache_dir_fh)) {
533 0         0 my $error = $!;
534 0         0 $self->_unlock_cache;
535 0         0 require Carp;
536 0         0 Carp::croak ($package . "::update - Failed to close directory '$cache_dir': $error\n");
537             }
538              
539             # Untainting the filenames and converting them to absolute file paths.
540 18         58 my @file_list = ();
541 18         45 foreach my $item (@raw_file_list) {
542 58         221 my ($filename) = $item =~ m/^(.*)$/s;
543 58         460 my $file_path = File::Spec->catfile($cache_dir, $filename);
544 58         150 push (@file_list,$file_path);
545             }
546 18         45 my $n_files = $#file_list + 1;
547              
548             # No problems. All done.
549 18 100       58 if ($n_files <= $keep_last) {
550 14         48 $self->_unlock_cache;
551 14         164 return;
552             }
553              
554             # Too many entries. Delete the excess entries (usually only one)
555 4         10 my %file_last_access = ();
556 4         10 foreach my $file (@file_list) {
557 24         271 my $last_accessed = (stat($file))[9];
558 24         91 $file_last_access{$file} = $last_accessed;
559             }
560 4         35 my @sorted_file_list = sort { $file_last_access{$b} <=> $file_last_access{$a} } @file_list;
  34         70  
561 4   66     30 while (($n_files > $keep_last) and ($n_files > 0)) {
562 4         6 $n_files--;
563 4         8 my $pruned_file = $sorted_file_list[$n_files];
564 4 50       266 unless (unlink $pruned_file) {
565 0         0 my $error = $!;
566 0         0 $self->_unlock_cache;
567 0         0 require Carp;
568 0         0 Carp::croak($package . "::update - Failed to unlink file '$pruned_file': $error");
569             }
570             }
571              
572             # Release our lock and return
573 4         85 $self->_unlock_cache;
574 4         58 return;
575             }
576              
577             #######################################################################
578              
579             =over 4
580              
581             =item delete({ -key => $key });
582              
583             Forces the deletion of a specific key from the cache.
584              
585             Example:
586              
587             $cache->delete({ -key => $key });
588              
589             =back
590              
591             =cut
592              
593             sub delete {
594 8     8 1 101 my $self = shift;
595 8         8 my $package = __PACKAGE__;
596              
597 8         31 my $parms = parse_parms({ -parms => \@_,
598             -legal => [-key, -cache_key],
599             -required => [],
600             -defaults => {},
601             });
602 7 100       647 if (not defined $parms) {
603 1         3 my $error_message = Class::ParmList->error;
604 1         6 require Carp;
605 1         76 Carp::croak ($package . "::delete() - $error_message\n");
606             }
607 6         19 my ($key,$cache_key) = $parms->get(-key, -cache_key);
608 6 100 100     146 if (not (defined ($key) or defined ($cache_key))) {
609 1         5 require Carp;
610 1         57 Carp::croak ($package . "::delete() - Called without either a -key or -cache_key\n");
611             }
612              
613 5         9 my $cache_dir = $self->cache_dir;
614 5 100       10 unless (defined $cache_dir) {
615 1         4 require Carp;
616 1         62 Carp::croak ($package . "::delete - No cache directory set.\n");
617             }
618 4 50       19 if ($cache_dir eq '') {
619 0         0 require Carp;
620 0         0 Carp::croak ($package . "::delete - Cannot use root directory as cache directory.\n");
621             }
622              
623             # Generate the cache_key (done by making a cannonical
624             # network order Storable string out of the key) if we
625             # don't already have it
626 4 100       14 if (not defined $cache_key) {
627 2         7 $cache_key = $self->make_cache_key({ -key => $key });
628             }
629              
630             # Generate a unique cache file
631             # name by taking a SHA1 hash of
632             # $cache_key
633 4         21 my $cache_hash = lc(sha1_hex($cache_key));
634 4         12 $cache_hash =~ s/\s//gs;
635 4         17 my ($untainted_cache_hash) = $cache_hash =~ m/^([a-f0-9]+)$/s;
636 4         42 my $cache_file = File::Spec->catfile($cache_dir, "cl_$untainted_cache_hash");
637              
638             # Ok. Set our lock on the cache directory
639 4         13 $self->_lock_cache;
640              
641             # If it is in the cache, remove it
642 4 50 33     218 if ((-e $cache_file) and (not unlink $cache_file)) {
643 0         0 my $error = $!;
644 0         0 $self->_unlock_cache;
645 0         0 require Carp;
646 0         0 Carp::croak($package . "::delete - Failed to unlink file '$cache_file': $error");
647             }
648              
649             # Release our lock and return
650 4         16 $self->_unlock_cache;
651             }
652              
653             #######################################################################
654              
655             =over 4
656              
657             =item cache_dir([$cache_directory_path]);
658              
659             Get/Set accessor for the cache directory path.
660              
661             Ex.
662              
663             my $cache_directory = $cache->cache_dir;
664              
665             $cache->cache_dir($cache_directory);
666              
667             =back
668              
669             =cut
670              
671 163     163 1 348 sub cache_dir { return shift->_property('cache_dir', @_); }
672              
673             #######################################################################
674              
675             =over 4
676              
677             =item keep_last([$keep_last_n]);
678              
679             Get/Set accessor for the keep last N setting.
680              
681             Ex.
682              
683             my $n_last = $cache->keep_last;
684              
685             $cache->keep_last(20);
686              
687             =back
688              
689             =cut
690              
691 37     37 1 137 sub keep_last { return shift->_property('keep_last', @_); }
692              
693              
694             #######################################################################
695              
696             =over 4
697              
698             =item number_of_entries;
699              
700             Returns the current number of entries in the cache.
701              
702             =back
703              
704             =cut
705              
706             sub number_of_entries {
707 2     2 1 2000426 my $self = shift;
708 2         10 my $package = __PACKAGE__;
709              
710 2         16 my $cache_dir_fh = gensym;
711 2         95 my $cache_dir = $self->cache_dir;
712 2 50       20 unless (defined $cache_dir) {
713 0         0 require Carp;
714 0         0 Carp::croak ($package . "::delete - No cache directory set.\n");
715             }
716 2 50       10 if ($cache_dir eq '') {
717 0         0 require Carp;
718 0         0 Carp::croak ($package . "::delete - Cannot use root directory as cache directory.\n");
719             }
720              
721 2 50       157 unless (opendir ($cache_dir_fh, $cache_dir)) {
722 0         0 my $error = $!;
723 0         0 require Carp;
724 0         0 Carp::croak ($package . "::update - Failed to open directory '$cache_dir' for reading: $error\n");
725             }
726 2         149 my @raw_file_list = grep(/^(cacheline_[a-fA-F0-9]{1,50}|cl_[a-fA-F0-9]{1,50})$/s,readdir($cache_dir_fh));
727 2 50       57 unless (closedir ($cache_dir_fh)) {
728 0         0 my $error = $!;
729 0         0 require Carp;
730 0         0 Carp::croak ($package . "::update - Failed to close directory '$cache_dir': $error\n");
731             }
732 2         8 my $n_entries = $#raw_file_list + 1;
733 2         23 return $n_entries;
734             }
735              
736             #######################################################################
737             # #
738             # PRIVATE METHODS #
739             # #
740             # Internals. Documented for maintainance reasons only. #
741             # Do not use these methods from outside this module. #
742             # #
743             #######################################################################
744              
745             #######################################################################
746             # _cache_lock_fh([$fh]);
747             #
748             # Get/Set accessor used to store a reference to the filehandle
749             # used for locking.
750              
751 134     134   286 sub _cache_lock_fh { return shift->_property('_cache_lock_fh', @_); }
752              
753             #######################################################################
754             # _lock_cache;
755             #
756             # Obtains a lock on the 'cache.lock' file for this LRU cache.
757             #
758             # Example:
759             # $self->_lock_cache;
760             #
761             # This will create the 'cache.lock' file if it does not already exist,
762             # creating any intermediate directories as needed.
763             #
764             # It also writes the current PID to the lock file.
765              
766             sub _lock_cache {
767 67     67   82 my $self = shift;
768 67         79 my $package = __PACKAGE__;
769              
770 67         122 my $cache_dir = $self->cache_dir;
771 67 50       135 if (not defined $cache_dir) {
772 0         0 require Carp;
773 0         0 Carp::croak ($package . "::_lock_cache - No cache directory set.\n");
774             }
775 67 50       135 if ($cache_dir eq '') {
776 0         0 require Carp;
777 0         0 Carp::croak ($package . "::_lock_cache - Cannot use root directory as cache directory.\n");
778             }
779 67 50 33     1870 if ((-e $cache_dir) and (not -d _)) {
780 0         0 require Carp;
781 0         0 Carp::croak ($package . "::_lock_cache - '$cache_dir' already exists and is not a directory.\n");
782             }
783 67 50       730 if (not -e $cache_dir) {
784 0         0 eval {
785 0         0 require File::Path;
786 0         0 File::Path::mkpath ($cache_dir);
787             };
788 0 0       0 if ($@) {
789 0         0 my $error = $@;
790 0         0 require Carp;
791 0         0 Carp::croak ($package . "::_lock_cache - unable to create directory '$cache_dir': $error");
792             }
793             }
794 67 50 33     747 if (not ((-e $cache_dir) and (-d _))) {
795 0         0 require Carp;
796 0         0 Carp::croak ($package . "::_lock_cache - Unable to create directory '$cache_dir'\n");
797             }
798 67         792 my $document_name = File::Spec->catfile($cache_dir,'.cache.lock');
799 67         265 my $cache_lock_fh = gensym;
800 67 50       3633 unless (open ($cache_lock_fh,">>$document_name")) {
801 0         0 my $error = $!;
802 0         0 require Carp;
803 0         0 Carp::croak ($package . "::_lock_cache - Unable to open '$document_name': $error\n");
804             }
805 67         254 my $lock_timeout = 100;
806 67         729 while (not flock($cache_lock_fh, LOCK_EX()|LOCK_NB())) {
807 0         0 $lock_timeout--;
808 0         0 select (undef,undef,undef,0.1);
809 0 0       0 if ($lock_timeout == 0) {
810 0         0 my $error = $!;
811 0         0 require Carp;
812 0         0 Carp::croak ($package . "::_lock_cache - Unable to get an exclusive lock on '$document_name': $error\n");
813             }
814             }
815 67         350 my $fh = select ($cache_lock_fh);
816 67         241 $|++;
817 67         161 select ($fh);
818 67 50       1750 unless (truncate ($cache_lock_fh, 0)) {
819 0         0 my $error = $!;
820 0         0 require Carp;
821 0         0 Carp::croak ($package . "::_lock_cache - Unable to truncate '$document_name': $error\n");
822             }
823 67         1997 print $cache_lock_fh "$$\n";
824 67         312 $self->_cache_lock_fh($cache_lock_fh);
825              
826 67         116 return;
827             }
828              
829             #######################################################################
830              
831             # _unlock_cache;
832             #
833             # Release a lock on the 'cache.lock' file for this LRU cache.
834             #
835             # Example:
836             # $self->_unlock_cache;
837              
838             sub _unlock_cache {
839 67     67   109 my $self = shift;
840 67         87 my $package = __PACKAGE__;
841              
842 67         107 my $cache_lock_fh = $self->_cache_lock_fh;
843 67 50       1876 unless (truncate ($cache_lock_fh,0)) {
844 0         0 my $error = $!;
845 0         0 require Carp;
846 0         0 Carp::croak ($package . "::_lock_cache - Unable to truncate cache.lock file: $error\n");
847             }
848 67 50       921 unless (close ($cache_lock_fh)) {
849 0         0 my $error = $!;
850 0         0 require Carp;
851 0         0 Carp::croak ($package . "::_unlock_cache - Error while closing cache.lock file: $error\n");
852             }
853 67         220 return;
854             }
855              
856             ####################################################################
857             # _property('property_name' => $property_value)
858             #
859             # get/set base accessor for property values
860              
861             sub _property {
862 334     334   430 my $self = shift;
863              
864 334         409 my $property = shift;
865              
866 334         400 my $package = __PACKAGE__;
867 334 100       652 if (0 == @_) {
    50          
868 229         382 my $output = $self->{$package}->{$property};
869 229         669 return $output;
870              
871             } elsif (1 == @_) {
872 105         119 my $input = shift;
873 105         210 $self->{$package}->{$property} = $input;
874 105         380 return;
875              
876             } else {
877 0         0 require Carp;
878 0         0 Carp::croak("Bad calling parameters to ${package}::${property}()\n");
879             }
880             }
881              
882             ####################################################################
883              
884             sub TIEHASH {
885 4     4   815 my $proto = shift;
886 4         5 my $package = __PACKAGE__;
887 4   33     18 my $class = ref ($proto) || $proto || $package;
888 4         8 my $self = bless {}, $class;
889              
890 4         7 my ($cache_dir,$keep_last) = @_;
891              
892 4 100       8 $keep_last = 100 unless (defined $keep_last);
893 4 100 66     22 unless (defined ($cache_dir) and ($cache_dir ne '')) {
894 1         12 require Carp;
895 1         174 Carp::croak($package . ": Missing required parameter (cache_dir)\n");
896             }
897 3         10 $self->cache_dir($cache_dir);
898 3         7 $self->keep_last($keep_last);
899 3         7 return $self;
900             }
901              
902             #######################################################################
903              
904             sub STORE {
905 9     9   6001338 my $self = shift;
906              
907 9         48 my ($key,$value) = @_;
908              
909 9 100       66 if (ref(\$key) eq 'SCALAR') {
910 8         85 $self->update({ -cache_key => $key, -value => $value });
911             } else {
912 1         4 $self->update({ -key => $key, -value => $value });
913             }
914             }
915              
916             #######################################################################
917              
918             sub FETCH {
919 7     7   53 my $self = shift;
920              
921 7         14 my ($key) = @_;
922              
923 7 100       21 if (ref(\$key) eq 'SCALAR') {
924 6         22 my ($cache_hit, $value) = $self->check({ -cache_key => $key });
925 6         49 return $value;
926              
927             } else {
928 1         4 my ($cache_hit,$value) = $self->check({ -key => $key });
929 1         7 return $value;
930             }
931              
932             }
933              
934             #######################################################################
935              
936             sub DELETE {
937 2     2   20 my $self = shift;
938              
939 2         3 my ($key) = @_;
940              
941 2 100       9 if (ref(\$key) eq 'SCALAR') {
942 1         6 $self->delete({ -cache_key => $key });
943             } else {
944 1         5 $self->delete({ -key => $key });
945             }
946             }
947              
948             #######################################################################
949              
950             sub CLEAR {
951 1     1   8 my $self = shift;
952              
953 1         6 $self->clear;
954             }
955              
956             #######################################################################
957              
958             sub EXISTS {
959 18     18   176 my $self = shift;
960              
961 18         33 my ($key) = @_;
962              
963 18 100       47 if (ref(\$key) eq 'SCALAR') {
964 16         56 my ($cache_hit,$value) = $self->check({ -cache_key => $key });
965 16         74 return $cache_hit;
966             } else {
967 2         8 my ($cache_hit,$value) = $self->check({ -key => $key });
968 2         11 return $cache_hit;
969             }
970             }
971              
972             #######################################################################
973              
974             #
975             # Iteration over the cache is not supported
976             #
977              
978 1     1   15 sub FIRSTKEY { undef; }
979              
980             #######################################################################
981              
982             #
983             # Iteration over the cache is not supported
984             #
985              
986 0     0     sub NEXTKEY { undef; }
987              
988             #######################################################################
989             #
990             # We return the number of cache entries in a scalar context
991             #
992              
993             sub SCALAR {
994 0     0     my $self = shift;
995              
996 0           return $self->number_of_entries;
997             }
998              
999             #######################################################################
1000             #######################################################################
1001              
1002             =head1 COPYRIGHT
1003              
1004             Copyright 1999, 2020 Jerilyn Franz and FreeRun Technologies, Inc. All Rights Reserved.
1005              
1006             =head1 VERSION
1007              
1008             1.06 released 2020.10.08
1009              
1010             =head1 LICENSE
1011              
1012             MIT License
1013              
1014             Copyright (c) 2020 Jerilyn Franz
1015              
1016             Permission is hereby granted, free of charge, to any person obtaining a copy
1017             of this software and associated documentation files (the "Software"), to deal
1018             in the Software without restriction, including without limitation the rights
1019             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1020             copies of the Software, and to permit persons to whom the Software is
1021             furnished to do so, subject to the following conditions:
1022              
1023             The above copyright notice and this permission notice shall be included in all
1024             copies or substantial portions of the Software.
1025              
1026             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1027             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1028             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1029             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1030             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1031             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1032             SOFTWARE.
1033              
1034             =head1 DISCLAIMER
1035              
1036             THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS
1037             OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE
1038             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
1039             PARTICULAR PURPOSE.
1040              
1041             Use of this software in any way or in any form, source or binary,
1042             is not allowed in any country which prohibits disclaimers of any
1043             implied warranties of merchantability or fitness for a particular
1044             purpose or any disclaimers of a similar nature.
1045              
1046             IN NO EVENT SHALL I BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
1047             SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
1048             USE OF THIS SOFTWARE AND ITS DOCUMENTATION (INCLUDING, BUT NOT
1049             LIMITED TO, LOST PROFITS) EVEN IF I HAVE BEEN ADVISED OF THE
1050             POSSIBILITY OF SUCH DAMAGE
1051              
1052             =head1 AUTHOR
1053              
1054             Jerilyn Franz
1055              
1056             =head1 TODO
1057              
1058             Nothing.
1059              
1060             =cut
1061              
1062             1;