CHI
view release on metacpan or search on metacpan
lib/CHI/Driver.pm view on Meta::CPAN
predicate => 1,
},
storage => {
is => 'ro',
},
);
push @common_params, keys %attr;
for my $attr ( keys %attr ) {
has $attr => %{ $attr{$attr} };
}
}
sub _build_has_subcaches { undef }
# These methods must be implemented by subclass
foreach my $method (qw(fetch store remove get_keys get_namespaces)) {
no strict 'refs';
*{$method} = sub { die "method '$method' must be implemented by subclass" };
}
# Given a hash of params, return the subset that are not in CHI's common parameters.
#
push @common_params, qw(
discard_policy
discard_timeout
l1_cache
max_size
max_size_reduction_factor
mirror_cache
parent_cache
subcache_type
subcaches
);
my %common_params = map { ( $_, 1 ) } @common_params;
sub non_common_constructor_params {
my ( $class, $params ) = @_;
return {
map { ( $_, $params->{$_} ) }
grep { !$common_params{$_} } keys(%$params)
};
}
sub declare_unsupported_methods {
my ( $class, @methods ) = @_;
foreach my $method (@methods) {
no strict 'refs';
*{"$class\::$method"} =
sub { croak "method '$method' not supported by '$class'" };
}
}
sub cache_object_class { 'CHI::CacheObject' }
# To override time() for testing - must be writable in a dynamically scoped way from tests
our $Test_Time; ## no critic (ProhibitPackageVars)
our $Build_Depth = 0; ## no critic (ProhibitPackageVars)
sub valid_get_options { qw(expire_if busy_lock) }
sub valid_set_options { qw(expires_at expires_in expires_variance) }
sub BUILD {
my ( $self, $params ) = @_;
# Ward off infinite build recursion, e.g. from circular subcache configuration.
#
local $Build_Depth = $Build_Depth + 1;
die "$Build_Depth levels of CHI cache creation; infinite recursion?"
if ( $Build_Depth > $self->max_build_depth );
# Save off constructor params. Used to create metacache, for
# example. Hopefully this will not cause circular references...
#
$self->{constructor_params} = {%$params};
foreach my $param (qw(l1_cache mirror_cache parent_cache)) {
delete( $self->{constructor_params}->{$param} );
}
# If stats enabled, add ns_stats slot for keeping track of stats
#
my $stats = $self->chi_root_class->stats;
if ( $stats->enabled ) {
$self->{ns_stats} = $stats->stats_for_driver($self);
}
# Call BUILD_roles on any of the roles that need initialization.
#
$self->BUILD_roles($params);
}
sub BUILD_roles {
# Will be modified by roles that need it
}
sub _build_short_driver_name {
my ($self) = @_;
( my $name = $self->driver_class ) =~ s/^CHI::Driver:://;
return $name;
}
sub _build_label {
my ($self) = @_;
return $self->short_driver_name;
}
sub _build_metacache {
my $self = shift;
return CHI::Driver::Metacache->new( owner_cache => $self );
}
sub get {
my ( $self, $key, %params ) = @_;
croak "must specify key" unless defined($key);
my $ns_stats = $self->{ns_stats};
my $log_is_debug = $log->is_debug;
my $measure_time = defined($ns_stats) || $log_is_debug;
my ( $start_time, $elapsed_time );
# Fetch cache object
#
$start_time = gettimeofday() if $measure_time;
my $obj = eval { $params{obj} || $self->get_object($key) };
$elapsed_time = ( gettimeofday() - $start_time ) * 1000 if $measure_time;
if ( my $error = $@ ) {
$ns_stats->{'get_errors'}++ if defined($ns_stats);
$self->_handle_get_error( $error, $key );
return undef;
}
if ( !defined $obj ) {
$self->_record_get_stats( 'absent_misses', $elapsed_time )
if defined($ns_stats);
$self->_log_get_result( $log, "MISS (not in cache)",
$key, $elapsed_time )
if $log_is_debug;
return undef;
}
if ( defined( my $obj_ref = $params{obj_ref} ) ) {
$$obj_ref = $obj;
}
# Check if expired
#
my $is_expired = $obj->is_expired()
|| ( defined( $params{expire_if} )
&& $params{expire_if}->( $obj, $self ) );
if ($is_expired) {
$self->_record_get_stats( 'expired_misses', $elapsed_time )
if defined($ns_stats);
$self->_log_get_result( $log, "MISS (expired)", $key, $elapsed_time )
if $log_is_debug;
# If busy_lock value provided, set a new "temporary" expiration time that many
# seconds forward before returning undef
#
if ( defined( my $busy_lock = $params{busy_lock} ) ) {
my $time = $Test_Time || time();
my $busy_lock_time = $time + parse_duration($busy_lock);
$obj->set_early_expires_at($busy_lock_time);
$obj->set_expires_at($busy_lock_time);
$self->set_object( $key, $obj );
}
return undef;
}
$self->_record_get_stats( 'hits', $elapsed_time ) if defined($ns_stats);
$self->_log_get_result( $log, "HIT", $key, $elapsed_time ) if $log_is_debug;
return $obj->value;
}
sub _record_get_stats {
my ( $self, $stat, $elapsed_time ) = @_;
$self->{ns_stats}->{$stat}++;
$self->{ns_stats}->{'get_time_ms'} += $elapsed_time;
}
sub unpack_from_data {
my ( $self, $key, $data ) = @_;
return $self->cache_object_class->unpack_from_data( $key, $data,
$self->serializer );
}
sub get_object {
my ( $self, $key ) = @_;
croak "must specify key" unless defined($key);
$key = $self->transform_key($key);
my $data = $self->fetch($key) or return undef;
my $obj = $self->unpack_from_data( $key, $data );
return $obj;
}
sub get_expires_at {
my ( $self, $key ) = @_;
croak "must specify key" unless defined($key);
if ( my $obj = $self->get_object($key) ) {
return $obj->expires_at;
}
else {
return;
}
}
sub exists_and_is_expired {
my ( $self, $key ) = @_;
croak "must specify key" unless defined($key);
if ( my $obj = $self->get_object($key) ) {
return $obj->is_expired;
}
else {
return;
}
}
sub is_valid {
lib/CHI/Driver.pm view on Meta::CPAN
sub set_object {
my ( $self, $key, $obj ) = @_;
my $data = $obj->pack_to_data();
my $expires_on_backend = $self->expires_on_backend;
my @expires_in = (
$expires_on_backend && $obj->expires_at < CHI_Max_Time
? ( ( $obj->expires_at - time ) * $expires_on_backend )
: ()
);
eval { $self->store( $key, $data, @expires_in ) };
if ( my $error = $@ ) {
$self->{ns_stats}->{'set_errors'}++ if defined( $self->{ns_stats} );
$self->_handle_set_error( $error, $obj );
return 0;
}
return 1;
}
sub get_keys_iterator {
my ($self) = @_;
my @keys = $self->get_keys();
return sub { shift(@keys) };
}
sub clear {
my $self = shift;
die "clear takes no arguments" if @_;
$self->remove_multi( [ $self->get_keys() ] );
}
sub expire {
my ( $self, $key ) = @_;
croak "must specify key" unless defined($key);
my $time = $Test_Time || time();
if ( defined( my $obj = $self->get_object($key) ) ) {
my $expires_at = $time - 1;
$obj->set_early_expires_at($expires_at);
$obj->set_expires_at($expires_at);
$self->set_object( $key, $obj );
}
}
sub compute {
my $self = shift;
my $key = shift;
my $wantarray = wantarray();
# Allow these in either order for backward compatibility
my ( $code, $options ) =
( ref( $_[0] ) eq 'CODE' ) ? ( $_[0], $_[1] ) : ( $_[1], $_[0] );
croak "must specify key and code" unless defined($key) && defined($code);
my %get_options =
( ref($options) eq 'HASH' )
? slice_grep { /(?:expire_if|busy_lock)/ } $options
: ();
my $set_options =
( ref($options) eq 'HASH' )
? { slice_grep { !/(?:expire_if|busy_lock)/ } $options }
: $options;
my $value = $self->get( $key, %get_options );
if ( !defined $value ) {
my ( $start_time, $elapsed_time );
my $ns_stats = $self->{ns_stats};
$start_time = gettimeofday if defined($ns_stats);
$value = $wantarray ? [ $code->() ] : $code->();
$elapsed_time = ( gettimeofday() - $start_time ) * 1000
if defined($ns_stats);
$self->set( $key, $value, $set_options );
if ( defined($ns_stats) ) {
$ns_stats->{'computes'}++;
$ns_stats->{'compute_time_ms'} += $elapsed_time;
}
}
return $wantarray ? @$value : $value;
}
sub purge {
my ($self) = @_;
foreach my $key ( $self->get_keys() ) {
if ( my $obj = $self->get_object($key) ) {
if ( $obj->is_expired() ) {
$self->remove($key);
}
}
}
}
sub dump_as_hash {
my ($self) = @_;
my %hash;
foreach my $key ( $self->get_keys() ) {
if ( defined( my $value = $self->get($key) ) ) {
$hash{$key} = $value;
}
}
return \%hash;
}
sub is_empty {
my ($self) = @_;
return !$self->get_keys();
}
#
# (SEMI-) ATOMIC OPERATIONS
#
sub add {
my $self = shift;
my $key = shift;
if ( !$self->is_valid($key) ) {
$self->set( $key, @_ );
}
( run in 1.286 second using v1.01-cache-2.11-cpan-0d23b851a93 )