Cache
view release on metacpan or search on metacpan
lib/Cache/File/Entry.pm view on Meta::CPAN
# replace existing data
rename($filename, $self->{path});
# fix permissions of tempfile
my $mode = 0666 & ~($self->{cache}->cache_umask());
chmod $mode, $self->{path};
# invalidate any active handle locks
unlink($self->{path} . $Cache::File::LOCK_EXT);
delete $PROCESS_LOCKS{$self->{path}};
$self->_set_expiry($expiry) if $expiry or $exists;
$cache->update_last_use($key, $time) if $exists;
$cache->change_size($self->size() - $orig_size);
# ensure pending expiries are removed
$cache->purge();
$cache->unlock();
}
sub _get {
my Cache::File::Entry $self = shift;
my $cache = $self->{cache};
my $key = $self->{key};
my $exists;
my $time = time();
$cache->lock();
if ($exists = $self->exists()) {
# update last used
$cache->update_last_use($key, $time);
# lock entry for reading
$self->_lock(LOCK_SH);
}
$cache->unlock();
return undef unless $exists;
File::NFSLock::uncache($self->{path})
if $cache->cache_lock_level() == Cache::File::LOCK_NFS();
my $fh = Symbol::gensym();
my $data;
my $oldmask = umask $self->{cache}->cache_umask();
if (open($fh, $self->{path})) {
binmode $fh;
# slurp mode
local $/;
$data = <$fh>;
close($fh);
}
umask $oldmask;
# shared locks can be unlocked without holding cache lock
$self->_unlock();
return $data;
}
sub size {
my Cache::File::Entry $self = shift;
return -s $self->{path};
}
sub remove {
my Cache::File::Entry $self = shift;
my $cache = $self->{cache};
my $key = $self->{key};
$cache->lock();
unless (-r $self->{path}) {
$cache->unlock();
return;
}
my $index = $cache->get_index();
my $index_entries = $cache->get_index_entries($key)
or warnings::warnif('Cache', "missing index entry for $key");
delete $$index{$key};
if ($$index_entries{age}) {
my $ageheap = $cache->get_age_heap();
$ageheap->delete($$index_entries{age}, $key);
}
if ($$index_entries{lastuse}) {
my $useheap = $cache->get_use_heap();
$useheap->delete($$index_entries{lastuse}, $key);
}
if ($$index_entries{expiry}) {
my $expheap = $cache->get_exp_heap();
$expheap->delete($$index_entries{expiry}, $key)
}
my $size = 0;
if ($self->_trylock(LOCK_SH)) {
$size = (-s $self->{path});
$cache->change_size(-$size);
$self->_unlock();
}
$cache->change_count(-1);
unlink($self->{path});
# obliterate any entry lockfile
unlink($self->{path} . $Cache::File::LOCK_EXT);
delete $PROCESS_LOCKS{$self->{path}};
$cache->unlock();
return $size;
}
lib/Cache/File/Entry.pm view on Meta::CPAN
$cache->update_last_use($key, $time) if $exists;
my $orig_size = $writing? ($exists? $self->size() : 0) : undef;
# open handle - entry lock will be held as self persists in the closure
my $oldmask = umask $cache->cache_umask();
my $handle = Cache::File::Handle->new($self->{path}, $mode, undef,
sub { $self->_handle_closed(shift, $orig_size); } );
umask $oldmask;
$handle or warnings::warnif('io', 'Failed to open '.$self->{path}.": $!");
$cache->unlock();
return $handle;
}
sub validity {
my Cache::File::Entry $self = shift;
my $cache = $self->{cache};
$cache->lock();
my $index_entries = $cache->get_index_entries($self->{key});
$cache->unlock();
return $index_entries? $$index_entries{validity} : undef;
}
sub set_validity {
my Cache::File::Entry $self = shift;
my ($data) = @_;
my $key = $self->{key};
my $cache = $self->{cache};
$cache->lock();
my $index_entries = $cache->get_index_entries($key);
unless ($index_entries) {
$self->set('');
$index_entries = $cache->get_index_entries($key);
}
$$index_entries{validity} = $data;
$cache->set_index_entries($key, $index_entries);
$cache->unlock();
}
# UTILITY METHODS
sub _handle_closed {
my Cache::File::Entry $self = shift;
my ($handle, $orig_size) = @_;
unless (defined $orig_size) {
# shared locks can be unlocked without holding cache lock
$self->_unlock();
return;
}
my $cache = $self->{cache};
$cache->lock();
# check if file still exists and our lock is still valid. this order is
# used to prevent a race between checking lock and getting size
my $new_size = $self->size();
(defined $new_size and $self->_check_lock()) or $new_size = 0;
# release entry lock
$self->_unlock();
# update sizes
if (defined $orig_size and $orig_size != $new_size) {
$cache->change_size($new_size - $orig_size);
}
$cache->unlock();
}
sub _make_path {
my Cache::File::Entry $self = shift;
unless (-d $self->{dir}) {
my $oldmask = umask $self->{cache}->cache_umask();
eval { mkpath($self->{dir}); };
if ($@) {
warnings::warnif('io',
'Failed to create path '.$self->{dir}.": $@");
return 0;
}
umask $oldmask;
}
return 1;
}
sub _lock {
my Cache::File::Entry $self = shift;
my ($type, $tryonly) = @_;
$type ||= LOCK_EX;
# entry already has the lock?
$self->{lockdetails} and die "entry already holding a lock";
my $path = $self->{path};
my $lock_details = $PROCESS_LOCKS{$path};
if ($lock_details) {
if ($$lock_details{type} != $type) {
$tryonly and return 0;
croak "process already holding entry lock of different type";
}
$$lock_details{count}++;
lib/Cache/File/Entry.pm view on Meta::CPAN
$lock_details = $PROCESS_LOCKS{$path} = {};
# no need for any locking with LOCK_NONE
if ($self->{cache}->cache_lock_level() != Cache::File::LOCK_NONE()) {
local $File::NFSLock::LOCK_EXTENSION = $Cache::File::LOCK_EXT;
my $oldmask = umask $self->{cache}->cache_umask();
my $lock = File::NFSLock->new({
file => $path,
lock_type => $type | ($tryonly? LOCK_NB : 0),
stale_lock_timeout => $Cache::File::STALE_LOCK_TIMEOUT,
});
unless ($lock) {
umask $oldmask;
$tryonly and return 0;
die "Failed to obtain lock on lockfile on '$path': ".
$File::NFSLock::errstr."\n";
}
# count the number of hard links to the lockfile and open it
# if we can't reopen the lockfile then it has already been removed...
# we do the stat on the file rather than the filehandle, as otherwise
# there would be a race between opening the file and getting the link
# count (such that we could end up with a link count that is already 0).
my $fh = Symbol::gensym;
my $linkcount;
my $lockfile = $path . $Cache::File::LOCK_EXT;
if (($linkcount = (stat $lockfile)[3]) and open($fh, $lockfile)) {
$$lock_details{lock} = $lock;
$$lock_details{lockfh} = $fh;
$$lock_details{linkcount} = $linkcount;
}
else {
# lock failed - remove lock details
delete $PROCESS_LOCKS{$path};
}
umask $oldmask;
}
# lock obtained
$$lock_details{type} = $type;
$$lock_details{count} = 1;
# use lock details reference as an internal lock check
$self->{lockdetails} = $lock_details;
return 1;
}
sub _trylock {
my Cache::File::Entry $self = shift;
my ($type) = @_;
return $self->_lock($type, 1);
}
sub _unlock {
my Cache::File::Entry $self = shift;
$self->{lockdetails} or die 'not locked';
# is our lock still valid?
$self->_check_lock() or return;
$self->{lockdetails} = undef;
my $lock_details = $PROCESS_LOCKS{$self->{path}};
--$$lock_details{count} == 0
or return;
if ($self->{cache}->cache_lock_level() != Cache::File::LOCK_NONE()) {
$$lock_details{lock}->unlock;
}
delete $PROCESS_LOCKS{$self->{path}};
}
# check that we still hold our lock
sub _check_lock {
my Cache::File::Entry $self = shift;
$self->{lockdetails} or return 0;
my $lock_details = $PROCESS_LOCKS{$self->{path}}
or return 0;
# check lock details reference still matches global
$self->{lockdetails} == $lock_details
or return 0;
if ($self->{cache}->cache_lock_level() != Cache::File::LOCK_NONE()) {
# check filehandle is still connected to filesystem
my $lockfh = $$lock_details{lockfh};
if (((stat $lockfh)[3] || 0) < $$lock_details{linkcount}) {
# lock is gone
delete $PROCESS_LOCKS{$self->{path}};
return 0;
}
}
return 1;
}
1;
__END__
=head1 SEE ALSO
Cache::Entry, Cache::File
=head1 AUTHOR
Chris Leishman <chris@leishman.org>
Based on work by DeWitt Clinton <dewitt@unto.net>
=head1 COPYRIGHT
Copyright (C) 2003-2006 Chris Leishman. All Rights Reserved.
This module is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
either expressed or implied. This program is free software; you can
( run in 0.876 second using v1.01-cache-2.11-cpan-39bf76dae61 )