b07f4debb7
At the moment we cache numerous pieces of information in module-level variables which then do not get updated in other threads/processes when they are changed by the user. This is a serious usability issue. Examples of this include the way we treat sysprefs (there is now a method to disable the syspref cache, but by default it is enabled), notices, frameworks, field mappings, and koha-conf.xml, at least. This patch sets the stage for eliminating this problem by making it possible to convert module-level cache variables into variables that are actually backed by whatever caching system may be configured. This is done through a special Koha::Cache::Object class which can be tied to the variables that are being used for caching and provided with a constructor method/closure to allow the cache to be reloaded when it expires. For example: my $cache = Koha::Cache->new(); my $data = 'whatever'; my $variable = Koha::Cache->create_scalar( { 'key' => 'whatever', 'timeout' => 2, 'constructor' => sub { return $data; }, } ); print "$$variable\n"; # Prints "whatever" The one change this necessitates for accessing the data is that the variable must be dereferenced an additional time before use (i.e. $$variable instead of $variable). There is no difference when the variable tied is a hash (created with Koha::Cache->create_hash). This is a small price to pay for Koha working in a multi-threaded, persistent environment. This change will also make caching easier in general. CHI was incompatible with the variable tying, so this patch also removes the dependency on CHI, using instead Cache::Memcached::Fast, Cache::FastMmap, and Cache::Memory, when they are available. To test: 1) Apply patch. 2) Run unit test t/Cache.t (after setting the MEMCACHED_SERVERS and CACHING_SYSTEM environment variables). As no changes were made to the tests already in that file, this passing demonstrates there are no regressions. 3) With memcached caching enabled (you must set the MEMCACHED_SERVERS and CACHING_SYSTEM environment variables) and DEBUG turned on (i.e. the DEBUG environment variable set to 1), try running a report via the web service ([intranet]/cgi-bin/koha/svc/report?id=1 and check your web server logs to confirm that there are messages like "get_from_cache for intranet:report:id:1" in them. 4) If the reports worked, sign off. NOTE: Technically you could test this without needing memcached by installing libcache-fastmmap-perl and setting CACHING_SYSTEM to 'fastmmap' instead of 'memcached'. You could also install libcache-perl and set CACHING_SYSTEM to 'memory' but there would be little point as the cached variables would go out of scope in between runs. Signed-off-by: Jonathan Druart <jonathan.druart@biblibre.com> Signed-off-by: Paul Poulain <paul.poulain@biblibre.com> Signed-off-by: Jared Camins-Esakov <jcamins@cpbibliography.com>
145 lines
4.2 KiB
Perl
145 lines
4.2 KiB
Perl
#!/usr/bin/perl
|
|
|
|
# Tests Koha::Cache and whichever type of cache is enabled (through Koha::Cache)
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Test::More tests => 29;
|
|
|
|
my $destructorcount = 0;
|
|
|
|
BEGIN {
|
|
use_ok('Koha::Cache');
|
|
use_ok('Koha::Cache::Object');
|
|
use_ok('C4::Context');
|
|
}
|
|
|
|
SKIP: {
|
|
my $cache = Koha::Cache->new();
|
|
|
|
skip "Cache not enabled", 13
|
|
unless ( Koha::Cache->is_cache_active() && defined $cache );
|
|
|
|
# test fetching an item that isnt in the cache
|
|
is( $cache->get_from_cache("not in here"),
|
|
undef, "fetching item NOT in cache" );
|
|
|
|
# test expiry time in cache
|
|
$cache->set_in_cache( "timeout", "I AM DATA", 1 ); # expiry time of 1 second
|
|
sleep 2;
|
|
is( $cache->get_from_cache("timeout"),
|
|
undef, "fetching expired item from cache" );
|
|
|
|
# test fetching a valid, non expired, item from cache
|
|
$cache->set_in_cache( "clear_me", "I AM MORE DATA", 1000 )
|
|
; # overly large expiry time, clear below
|
|
$cache->set_in_cache( "dont_clear_me", "I AM MORE DATA22", 1000 )
|
|
; # overly large expiry time, clear below
|
|
is(
|
|
$cache->get_from_cache("clear_me"),
|
|
"I AM MORE DATA",
|
|
"fetching valid item from cache"
|
|
);
|
|
|
|
# test clearing from cache
|
|
$cache->clear_from_cache("clear_me");
|
|
is( $cache->get_from_cache("clear_me"),
|
|
undef, "fetching cleared item from cache" );
|
|
is(
|
|
$cache->get_from_cache("dont_clear_me"),
|
|
"I AM MORE DATA22",
|
|
"fetching valid item from cache (after clearing another item)"
|
|
);
|
|
|
|
#test flushing from cache
|
|
$cache->set_in_cache( "flush_me", "testing 1 data" );
|
|
$cache->flush_all;
|
|
is( $cache->get_from_cache("flush_me"),
|
|
undef, "fetching flushed item from cache" );
|
|
is( $cache->get_from_cache("dont_clear_me"),
|
|
undef, "fetching flushed item from cache" );
|
|
|
|
my $constructorcount = 0;
|
|
my $myscalar = $cache->create_scalar(
|
|
{
|
|
'key' => 'myscalar',
|
|
'timeout' => 1,
|
|
'allowupdate' => 1,
|
|
'unset' => 1,
|
|
'constructor' => sub { return ++$constructorcount; },
|
|
'destructor' => sub { return ++$destructorcount; },
|
|
}
|
|
);
|
|
ok( defined($myscalar), 'Created tied scalar' );
|
|
is( $$myscalar, 1, 'Constructor called to first initialize' );
|
|
is( $$myscalar, 1, 'Data retrieved from cache' );
|
|
sleep 2;
|
|
is( $$myscalar, 2, 'Constructor called again when timeout reached' );
|
|
$$myscalar = 5;
|
|
is( $$myscalar, 5, 'Stored new value to cache' );
|
|
is( $constructorcount, 2, 'Constructor not called after storing value' );
|
|
undef $myscalar;
|
|
|
|
is( $cache->get_from_cache("myscalar"),
|
|
undef, 'Item removed from cache on destruction' );
|
|
|
|
my %hash = ( 'key' => 'value' );
|
|
|
|
my $myhash = $cache->create_hash(
|
|
{
|
|
'key' => 'myhash',
|
|
'timeout' => 1,
|
|
'allowupdate' => 1,
|
|
'unset' => 1,
|
|
'constructor' => sub { return { %hash }; },
|
|
}
|
|
);
|
|
|
|
ok(defined $myhash, 'Created tied hash');
|
|
|
|
is($myhash->{'key'}, 'value', 'Found expected value in hash');
|
|
ok(exists $myhash->{'key'}, 'Exists works');
|
|
$myhash->{'key2'} = 'surprise';
|
|
is($myhash->{'key2'}, 'surprise', 'Setting hash member worked');
|
|
$hash{'key2'} = 'nosurprise';
|
|
sleep 2;
|
|
is($myhash->{'key2'}, 'nosurprise', 'Cache change caught');
|
|
|
|
|
|
my $foundkeys = 0;
|
|
foreach my $key (keys %{$myhash}) {
|
|
$foundkeys++;
|
|
}
|
|
|
|
is($foundkeys, 2, 'Found expected 2 keys when iterating through hash');
|
|
|
|
isnt(scalar %{$myhash}, undef, 'scalar knows the hash is not empty');
|
|
|
|
$hash{'anotherkey'} = 'anothervalue';
|
|
|
|
sleep 2;
|
|
|
|
ok(exists $myhash->{'anotherkey'}, 'Cache reset properly');
|
|
|
|
delete $hash{'anotherkey'};
|
|
delete $myhash->{'anotherkey'};
|
|
|
|
ok(!exists $myhash->{'anotherkey'}, 'Key successfully deleted');
|
|
|
|
undef %hash;
|
|
%{$myhash} = ();
|
|
|
|
is(scalar %{$myhash}, 0, 'hash cleared');
|
|
|
|
$hash{'key'} = 'value';
|
|
is($myhash->{'key'}, 'value', 'retrieved value after clearing cache');
|
|
}
|
|
|
|
END {
|
|
SKIP: {
|
|
skip "Cache not enabled", 1
|
|
unless ( Koha::Cache->is_cache_active() );
|
|
is( $destructorcount, 1, 'Destructor run exactly once' );
|
|
}
|
|
}
|