use strict;
use warnings;
use Carp;
-use Clone qw( clone );
use Module::Load::Conditional qw(can_load);
use Koha::Cache::Object;
+use Sereal::Encoder;
+use Sereal::Decoder;
use base qw(Class::Accessor);
qw( cache memcached_cache fastmmap_cache memory_cache ));
our %L1_cache;
+our $L1_encoder = Sereal::Encoder->new;
+our $L1_decoder = Sereal::Decoder->new;
=head2 get_instance
- my $cache = Koha::Cache->get_instance();
+ my $cache = Koha::Caches->get_instance();
This gets a shared instance of the cache, set up in a very default way. This is
the recommended way to fetch a cache object. If possible, it'll be
=cut
-our $singleton_cache;
-sub get_instance {
- my ($class) = @_;
- $singleton_cache = $class->new() unless $singleton_cache;
- return $singleton_cache;
-}
-
=head2 new
Create a new Koha::Cache object. This is required for all cache-related functionality.
=cut
sub new {
- my ( $class, $self ) = @_;
+ my ( $class, $self, $subnamespace ) = @_;
$self->{'default_type'} =
$self->{cache_type}
|| $ENV{CACHING_SYSTEM}
$self->{'timeout'} ||= 0;
$self->{'namespace'} ||= $ENV{MEMCACHED_NAMESPACE} || 'koha';
-
- if ( can_load( modules => { 'Cache::Memcached::Fast' => undef } ) ) {
- _initialize_memcached($self);
- if ( $self->{'default_type'} eq 'memcached'
- && defined( $self->{'memcached_cache'} ) )
- {
- $self->{'cache'} = $self->{'memcached_cache'};
- }
+ $self->{namespace} .= ":$subnamespace:";
+
+ if ( $self->{'default_type'} eq 'memcached'
+ && can_load( modules => { 'Cache::Memcached::Fast' => undef } )
+ && _initialize_memcached($self)
+ && defined( $self->{'memcached_cache'} ) )
+ {
+ $self->{'cache'} = $self->{'memcached_cache'};
}
if ( $self->{'default_type'} eq 'fastmmap'
&& defined( $ENV{GATEWAY_INTERFACE} )
- && can_load( modules => { 'Cache::FastMmap' => undef } ) ) {
- _initialize_fastmmap($self);
- if ( defined( $self->{'fastmmap_cache'} ) )
- {
- $self->{'cache'} = $self->{'fastmmap_cache'};
- }
- }
-
- if ( can_load( modules => { 'Cache::Memory' => undef } ) ) {
- _initialize_memory($self);
- if ( $self->{'default_type'} eq 'memory'
- && defined( $self->{'memory_cache'} ) )
- {
- $self->{'cache'} = $self->{'memory_cache'};
- }
+ && can_load( modules => { 'Cache::FastMmap' => undef } )
+ && _initialize_fastmmap($self)
+ && defined( $self->{'fastmmap_cache'} ) )
+ {
+ $self->{'cache'} = $self->{'fastmmap_cache'};
}
- # Unless a default has already been picked, we go through in best-to-
- # least-best order, looking for something we can use. fastmmap_cache
- # is excluded because it doesn't support expiry in a useful way.
+ # Unless memcache or fastmmap has already been picked, use memory_cache
unless ( defined( $self->{'cache'} ) ) {
- foreach my $cachemember (qw(memcached_cache memory_cache )) {
- if ( defined( $self->{$cachemember} ) ) {
- $self->{'cache'} = $self->{$cachemember};
- last;
- }
+ if ( can_load( modules => { 'Cache::Memory' => undef } )
+ && _initialize_memory($self) )
+ {
+ $self->{'cache'} = $self->{'memory_cache'};
}
}
Expiry time of this cached entry in seconds.
-=item deepcopy
-
-If set, this will perform a deep copy of the item when it's retrieved. This
-means that it'll be safe if something later modifies the result of the
-function. Will be ignored in situations where the same behaviour comes from
-the caching layer anyway.
-
=item cache
The cache object to use if you want to provide your own. It should be an
$expiry //= $self->{timeout};
my $set_sub = $self->{ref($self->{$cache}) . "_set"};
- # Set in L1 cache
- $L1_cache{ $key } = $value;
+ my $flag = '-CF0'; # 0: scalar, 1: frozen data structure
+ if (ref($value)) {
+ # Set in L1 cache as a data structure, initially only in frozen form (for performance reasons)
+ $value = $L1_encoder->encode($value);
+ $L1_cache{$self->{namespace}}{$key}->{frozen} = $value;
+ $flag = '-CF1';
+ } else {
+ # Set in L1 cache as a scalar; exit if we are caching an undef
+ $L1_cache{$self->{namespace}}{$key} = $value;
+ return if !defined $value;
+ }
+ $value .= $flag;
# We consider an expiry of 0 to be inifinite
if ( $expiry ) {
return $set_sub
my $value = $cache->get_from_cache($key, [ $options ]);
-Retrieve the value stored under the specified key in the default cache.
+Retrieve the value stored under the specified key in the cache.
-The options can set an unsafe flag to avoid a deep copy.
-When this flag is set, you have to know what you are doing!
-If you are retrieving a structure and modify it, you will modify the contain
-of the cache!
+The possible options are:
+
+=over
+
+=item unsafe
+
+If set, this will avoid performing a deep copy of the item. This
+means that it won't be safe if something later modifies the result of the
+function. It should be used with caution, and could save processing time
+in some situations where is safe to use it. Make sure you know what you are doing!
+
+=item cache
+
+The cache object to use if you want to provide your own. It should be an
+instance of C<Cache::*> and follow the same interface as L<Cache::Memcache>.
+
+=back
=cut
return unless ( $self->{$cache} && ref( $self->{$cache} ) =~ m/^Cache::/ );
# Return L1 cache value if exists
- if ( exists $L1_cache{$key} ) {
- # No need to deep copy if it's a scalar
- # Or if we do not need to deep copy
- return $L1_cache{$key}
- if not ref $L1_cache{$key} or $unsafe;
- return clone $L1_cache{$key};
+ if ( exists $L1_cache{$self->{namespace}}{$key} ) {
+ if (ref($L1_cache{$self->{namespace}}{$key})) {
+ if ($unsafe) {
+ $L1_cache{$self->{namespace}}{$key}->{thawed} ||= $L1_decoder->decode($L1_cache{$self->{namespace}}{key}->{frozen});
+ return $L1_cache{$self->{namespace}}{$key}->{thawed};
+ } else {
+ return $L1_decoder->decode($L1_cache{$self->{namespace}}{$key}->{frozen});
+ }
+ } else {
+ # No need to thaw if it's a scalar
+ return $L1_cache{$self->{namespace}}{$key};
+ }
}
my $get_sub = $self->{ref($self->{$cache}) . "_get"};
- return $get_sub ? $get_sub->($key) : $self->{$cache}->get($key);
+ my $L2_value = $get_sub ? $get_sub->($key) : $self->{$cache}->get($key);
+
+ return if ref($L2_value);
+ return unless (defined($L2_value) && length($L2_value) >= 4);
+
+ my $flag = substr($L2_value, -4, 4, '');
+ if ($flag eq '-CF0') {
+ # it's a scalar
+ $L1_cache{$self->{namespace}}{$key} = $L2_value;
+ return $L2_value;
+ } elsif ($flag eq '-CF1') {
+ # it's a frozen data structure
+ my $thawed;
+ eval { $thawed = $L1_decoder->decode($L2_value); };
+ return if $@;
+ $L1_cache{$self->{namespace}}{$key}->{frozen} = $L2_value;
+ $L1_cache{$self->{namespace}}{$key}->{thawed} = $thawed if $unsafe;
+ return $thawed;
+ }
+
+ # Unknown value / data type returned from L2 cache
+ return;
}
=head2 clear_from_cache
return unless ( $self->{$cache} && ref( $self->{$cache} ) =~ m/^Cache::/ );
# Clear from L1 cache
- delete $L1_cache{$key};
+ delete $L1_cache{$self->{namespace}}{$key};
return $self->{$cache}->delete($key)
if ( ref( $self->{$cache} ) =~ m'^Cache::Memcached' );
sub flush_L1_cache {
my( $self ) = @_;
- %L1_cache = ();
+ delete $L1_cache{$self->{namespace}};
}
=head1 TIED INTERFACE