Bug 17189: Use delete to flush a L1 cache namespace
[koha-ffzg.git] / Koha / Cache.pm
index f389395..ea2ebcb 100644 (file)
@@ -38,9 +38,10 @@ The first, traditional OO interface provides the following functions:
 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);
 
@@ -48,10 +49,12 @@ __PACKAGE__->mk_ro_accessors(
     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
@@ -59,13 +62,6 @@ persistent across multiple instances.
 
 =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.
@@ -73,7 +69,7 @@ Create a new Koha::Cache object. This is required for all cache-related function
 =cut
 
 sub new {
-    my ( $class, $self ) = @_;
+    my ( $class, $self, $subnamespace ) = @_;
     $self->{'default_type'} =
          $self->{cache_type}
       || $ENV{CACHING_SYSTEM}
@@ -83,44 +79,31 @@ sub new {
 
     $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'};
         }
     }
 
@@ -242,13 +225,6 @@ The possible options are:
 
 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
@@ -281,9 +257,19 @@ sub set_in_cache {
     $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
@@ -299,30 +285,77 @@ sub set_in_cache {
 
 =head2 get_from_cache
 
-    my $value = $cache->get_from_cache($key);
+    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 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
 
 sub get_from_cache {
-    my ( $self, $key, $cache ) = @_;
+    my ( $self, $key, $options ) = @_;
+    my $cache  = $options->{cache}  || 'cache';
+    my $unsafe = $options->{unsafe} || 0;
     $key =~ s/[\x00-\x20]/_/g;
-    $cache ||= 'cache';
     croak "No key" unless $key;
     $ENV{DEBUG} && carp "get_from_cache for $key";
     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:
-        return $L1_cache{$key}
-            unless ref $L1_cache{$key};
-        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
@@ -341,7 +374,7 @@ sub 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' );
@@ -370,7 +403,7 @@ sub flush_all {
 
 sub flush_L1_cache {
     my( $self ) = @_;
-    %L1_cache = ();
+    delete $L1_cache{$self->{namespace}};
 }
 
 =head1 TIED INTERFACE