Apache-Cache
view release on metacpan or search on metacpan
lib/Apache/Cache.pm view on Meta::CPAN
=item *
C<cachename> optional, string
The namespace associated with this cache.
Defaults to "Default" if not explicitly set.
=item *
C<default_lock_timeout> optional, integer
Number of second(s) to wait for locks used each time manipulating data in the shared memory.
Defaults to not waiting. This means a get() - for expample - on a temporary locked
key - certainely by another process - will return a FAILED status.
=back
Additionnaly, all Apache::SharedMem parameters are also customizable. See L<Apache::SharedMem>.
=cut
sub new
{
my $pkg = shift;
my $class = ref($pkg) || $pkg;
my $options =
{
namespace => (caller())[0],
cachename => 'Default',
default_expires_in => EXPIRES_NEVER,
max_keys => undef(),
max_size => undef(),
default_lock_timeout=> undef(),
};
croak("odd number of arguments for object construction")
if(@_ % 2);
my @del;
for(my $x = 0; $x < $#_; $x += 2)
{
if(exists($options->{lc($_[$x])}))
{
$options->{lc($_[$x])} = $_[($x + 1)];
# We split off this parameter from the main argument list.
# Remaining arguments will be send to Apache::SharedMem
splice(@_, $x, 2);
$x -= 2;
}
}
foreach my $name (qw(cachename namespace))
{
croak("$pkg object creation missing $name parameter.")
unless(defined($options->{$name}) && $options->{$name} ne '');
}
my $self = $class->SUPER::new(@_, namespace=>$options->{namespace});
return(undef()) unless(defined($self));
$self->{cache_options} = $options;
unless($self->SUPER::exists($options->{cachename}, $self->_lock_timeout))
{
return(undef()) if($self->SUPER::status eq FAILURE);
$self->_init_cache || return undef;
}
bless($self, $class);
return($self);
}
=pod
=head2 set (identifier => data, [timeout])
$cache->set(mykey=>'the data to cache', '15 minutes');
if($cache->status & FAILURE)
{
warn("can't save data to cache: $cache->error");
}
Store an item in the cache.
=over 4
=item *
C<identifier> required, string
A string uniquely identifying the data.
=item *
C<data> required, scalar or reference to any perl data type, except CODE and GLOB
The data to store in the cache.
=item *
C<timeout> optional, date
The data expiration time for objects place in the cache. Integers is interpreted in seconds, constant
EXPIRES_NOW make data expire imédiately and constant EXPIRES_NEVER make the data never expire. The
timeout can also be in a human readable format, see L<Time::ParseDate> for this format specification.
=back
On failure this method return C<undef()> and set status to FAILURE, see status() method below
status : FAILURE SUCCESS
=cut
sub set
{
my $self = shift;
my $key = defined($_[0]) && $_[0] ne '' ? shift : croak(defined($_[0]) ? 'Not enough arguments for set method' : 'Invalid argument "" for set method');
my $value = defined($_[0]) ? shift : croak('Not enough arguments for set method');
my $time = defined($_[0]) ? shift : $self->{cache_options}->{default_expires_in};
my $lock_timeout = $self->{cache_options}->{default_lock_timeout};
croak('Too many arguments for set method') if(@_);
$self->_unset_error;
$self->_debug;
if($key eq '_cache_metadata')
{
$self->_set_status(FAILURE);
$self->_set_error("$key: reserved key");
return(undef());
}
my $timeout;
if($time)
{
if($time =~ m/\D/)
{
$timeout = parsedate($time, TIMEFIRST=>1, PREFER_FUTURE=>1);
unless(defined $timeout)
{
$self->_set_error("error on timeout string decoding. time string requested: $time");
$self->_set_status(FAILURE);
return(undef());
}
}
elsif($time eq EXPIRES_NOW)
{
$timeout = EXPIRES_NOW;
}
else
{
$timeout = time() + $time;
}
}
else
{
$timeout = EXPIRES_NEVER;
}
$self->_debug('timeout is set for expires in ', ($timeout - time()), ' seconds');
if(defined $lock_timeout ? $self->lock(LOCK_EX, $lock_timeout) : $self->lock(LOCK_EX|LOCK_NB))
{
my $data = $self->_get_datas || return(undef());
$data->{$key} = $value;
$data->{'_cache_metadata'}->{'timestamps'}->{$key} = $timeout;
push(@{$data->{'_cache_metadata'}->{'queue'}}, $key);
$self->_check_keys($data);
$self->_check_size($data);
$self->SUPER::set($self->{cache_options}->{cachename}=>$data, NOWAIT);
my $rv = $self->status; # saving returned status
$self->unlock; # don't wait for Apache::SharedMem to auto unlock on destroy
return(undef()) if($rv eq FAILURE);
return($value);
}
else
{
$self->_set_error('can\'t get exclusive lock for "set" method');
$self->_set_status(FAILURE);
return(undef());
}
}
=pod
=head2 get (identifier)
my $value = $cache->get('Key');
if($cache->status & (EXPIRED | FAILURE)) # if status is EXPIRED or FAILURE
{
$value = 'fresh value';
}
Fetch the data specified. If data where never set, or if data have expired, this method return
C<undef> and status is set to EXPIRED.
=over 4
=item *
C<identifier> required, string
A string uniquely identifying the data.
=back
status : FAILURE SUCCESS EXPIRED
=cut
sub get
{
if(@_ != 2)
{
confess('Apache::Cache: Too many arguments for "get" method') if(@_ > 2);
confess('Apache::Cache: Not enough arguments for "get" method') if(@_ < 2);
}
my($self, $key) = @_;
my $data = $self->_get_datas || return(undef());
unless(exists $data->{$key})
{
$self->_set_status(EXPIRED);
return(undef());
}
my $value = $data->{$key};
my $timeout = $data->{_cache_metadata}->{timestamps}->{$key};
if(!defined $timeout || $timeout == EXPIRES_NOW || ($timeout != EXPIRES_NEVER && $timeout <= time()))
{
$self->_set_error("data was expired");
$self->delete($key); # if delete failed, error string will be its own but not status
$self->_set_status(EXPIRED);
return(undef());
}
else
{
$self->_set_status(SUCCESS);
return($value);
}
}
=pod
=head2 delete (identifier)
Delete the data associated with the identifier from the cache.
=over 4
=item *
C<identifier> required, string
A string uniquely identifying the data.
=back
status: SUCCESS FAILURE
=cut
sub delete
{
if(@_ != 2)
{
confess('Apache::Cache: Too many arguments for "delete" method') if(@_ > 2);
confess('Apache::Cache: Not enough arguments for "delete" method') if(@_ < 2);
}
my($self, $key) = @_;
my $lock_timeout = $self->{cache_options}->{default_lock_timeout};
my $rv = undef;
if(defined $lock_timeout ? $self->lock(LOCK_EX, $lock_timeout) : $self->lock(LOCK_EX|LOCK_NB))
{
my $data = $self->_get_datas || return(undef());
if(exists $data->{$key})
{
$rv = delete($data->{$key});
delete($data->{_cache_metadata}->{timestamps}->{$key});
$data->{_cache_metadata}->{queue} = \@{grep($_ ne $key, @{$data->{_cache_metadata}->{queue}})};
$self->SUPER::set($self->{cache_options}->{cachename}=>$data);
return(undef()) if($self->status & FAILURE);
}
$self->unlock;
}
return($rv);
}
=head2 clear
Remove all objects from the namespace associated with this cache instance.
status: SUCCESS FAILURE
=cut
sub clear
{
my $self = shift;
$self->_init_cache;
}
# inherited from Apache::SharedMem
=head2 status
Return the last called method status. This status should be used with bitmask operators
&, ^, ~ and | like this :
# is last method failed ?
if($object->status & FAILURE) {something to do on failure}
# is last method don't succed ?
if($object->status ^ SUCCESS) {something to do on failure}
# is last method failed or expired ?
if($object->status & (FAILURE | EXPIRED)) {something to do on expired or failure}
It's not recommended to use equality operator (== and !=) or (eq and ne), they may don't
work in future versions.
To import status' constants, you have to use the :status import tag, like below :
use Apache::Cache qw(:status);
=cut
sub _check_keys
{
my($self, $data) = @_;
my $max_keys = $self->{cache_options}->{max_keys};
return() unless(defined $max_keys && $max_keys);
my $metadata = $data->{_cache_metadata};
my $nkeys = @{$metadata->{queue}};
$self->_debug("cache have now $nkeys keys");
if($nkeys > $max_keys)
{
my $time = time();
my $nkeys_target = int($max_keys - ($max_keys/10));
$self->_debug("cache is full, max_key: $max_keys, current key counts: $nkeys, cleaning ", $nkeys - $nkeys_target, " keys");
# cheching for expired datas
for(my $i = $nkeys - 1; $i >= 0; $i--)
{
if($metadata->{timestamps}->{$metadata->{queue}->[$i]} > $time)
{
my $key = $metadata->{queue}->[$i];
$self->_debug("$key is out of date, discarding");
delete($data->{$key});
delete($metadata->{timestamps}->{$key});
@{$metadata->{queue}} = grep($_ ne $key, @{$metadata->{queue}});
last if(--$nkeys <= $nkeys_target);
}
}
if($nkeys > $nkeys_target)
{
# splice of delete candidates
my @key2del = splice(@{$metadata->{queue}}, 0, ($nkeys - $nkeys_target - 1));
$self->_debug('cleaning not timed out keys: ', join(', ', @key2del));
delete(@$data{@key2del});
delete(@{$metadata->{timestamps}}{@key2del});
}
}
}
sub _check_size
{
my($self, $data) = @_;
my $max_size = $self->{cache_options}->{max_keys};
return() unless(defined $max_size && $max_size);
}
sub _init_cache
{
my $self = shift;
my $cache_registry =
{
_cache_metadata =>
{
timestamps => {},
queue => [],
}
};
$self->SUPER::set($self->{cache_options}->{cachename}=>$cache_registry, $self->_lock_timeout);
return($self->SUPER::status eq FAILURE ? undef : 1);
}
sub _lock_timeout
{
my $self = shift;
my $lock_timeout = $self->{cache_options}->{default_lock_timeout};
return(defined $lock_timeout ? $lock_timeout : NOWAIT);
}
sub _get_datas
{
my $self = shift;
my $data = $self->SUPER::get($self->{cache_options}->{cachename}, $self->_lock_timeout);
if($self->status eq FAILURE)
{
$self->_set_error("can't get the cacheroot: ", $self->error);
return(undef());
}
croak("Apache::Cache: wrong data format.")
if(ref($data) ne 'HASH' || ! exists $data->{_cache_metadata});
return($data);
}
1;
=pod
=head1 EXPORTS
=head2 Default exports
None.
=head2 Available exports
Following constant is available for exports : EXPIRED SUCCESS FAILURE
EXPIRES_NOW EXPIRES_NEVER LOCK_EX LOCK_SH LOCK_UN.
=head2 Export tags defined
The tag ":all" will get all of the above exports.
Following tags are also available :
=over 4
=item
:status
Contents: SUCCESS FAILURE EXPIRED
This tag is really recommended to the importation all the time.
=item
:expires
Contents: EXPIRES_NOW EXPIRES_NEVER
=item
:lock
Contents: LOCK_EX LOCK_SH LOCK_UN LOCK_NB
=back
=head1 KNOW BUGS
Under mod_perl, with eavy load, this error may occured some time:
( run in 0.501 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )