2

I want to share a variable between different perl modules. So I created a perl module called MyCache.pm which saves the variable (in my case a hash variable):

package PerlModules::MyCache;
my %cache = ();
sub set {
  my ($key, $value) = @_;
  $cache{$key} = $value;
}
sub get {
  my ($key) = @_;
  return $cache{$key};
}

Now I have two handlers. The one handler will call the set method and the other one will call the get method to access the information.

package PerlModules::MyCacheSetter;
use Apache2::RequestRec();
use Apache2::RequestIO();
use Apache2::Const -compile => qw(OK);
use PerlModules::MyCache;
sub handler {
  my $r = shift;
  PerlModules::MyCache::set('test1', "true");
  PerlModules::MyCache::set('test2', "false");
  PerlModules::MyCache::set('test3', "true");
  return Apache2::Const::OK;
}

And here is the getter handler:

package PerlModules::MyCacheGetter;
use Apache2::RequestRec();
use Apache2::RequestIO();
use Apache2::Const -compile => qw(OK);
use PerlModules::MyCache;
sub handler {
  my $r = shift;
  $r->print(PerlModules::MyCache::get('test1'));
  $r->print(PerlModules::MyCache::get('test2'));
  $r->print(PerlModules::MyCache::get('test3'));
  return Apache2::Const::OK;
}

Now I've configured apache (via http.conf) to access these perl modules. I run the setter handler and then the getter, but there was no output.

In the error.log there are now some entries:

Use of uninitialized value in subroutine entry at ../MyCacheGetter.pm line 14.
Use of uninitialized value in subroutine entry at ../MyCacheGetter.pm line 15.
Use of uninitialized value in subroutine entry at ../MyCacheGetter.pm line 16.

This lines are the three calls of the get method. So what am I doing wrong? How can I fix the problem and share my cache variable between different handlers?

atticus3000
  • 409
  • 1
  • 4
  • 12
  • Are you running the setter handler and the getter handler in the same request? Maybe it would help if you show how you call the handlers. – mob Aug 02 '13 at 15:43
  • Hi, I'm using the following httpd.conf configuration to access the handlers. After this I'm calling via webbrowser the handlers (localhost/mcsetter): PerlModule PerlModules::MyCacheGetter SetHandler modperl PerlResponseHandler PerlModules::MyCacheGetter and the same for the setter. – atticus3000 Aug 02 '13 at 15:50
  • Are you making two seperate HTTP requests (1 for set, then another for get) ? If so, you're likely getting served by two different Apache child processes. What have you done to communicate the state of your cache across to other child processes? – David-SkyMesh Aug 03 '13 at 02:50
  • Yes. I need to make two seperate HTTP request. I've changed the configuration of apache in the apache2.conf file to: StartServers 1, MinSpareServers 1, MaxSpareServers 1, MaxClients 5. If I change the MaxClients to 1 it will work, but then I get sometimes an apache error that there are not enough Clients. – atticus3000 Aug 03 '13 at 05:36
  • @ManuelHinz When you make multiple requests, you're more than likely hitting different Apache children. That's why when you limited the number, you got the same child process and it "worked". However, you really don't want to limit the number of Apache children, you need to fix the architecture of your application. (continued...) – David-SkyMesh Aug 05 '13 at 00:59
  • @ManuelHinz (... continued) Apache children are re-used for multiple (usually a set number of) requests, and variable's contents are retained during the lifetime of an individual child process. However, child processes *DO* *NOT* *SHARE* variables (memory). You'll need to organise for some synchronisation of your "cache" variables across Apache children. – David-SkyMesh Aug 05 '13 at 01:00
  • @David-SkyMesh Thanks for your answer. Do you have an idea how I can share a variable between two different mod_perl handlers? – atticus3000 Aug 05 '13 at 15:43
  • @ManuelHinz A database (e.g: postgres), or Key-Value Store (e.g: redis) or interprocess comms (e.g: `IPC::ShareLite`). There are lots of different approaches. – David-SkyMesh Aug 06 '13 at 03:13
  • @ManuelHinz I've added a very basic example of how to use shared memory (interprocess comms) to share the cache across processes. – David-SkyMesh Aug 06 '13 at 04:15

1 Answers1

0

Your cache will only exist for the lifetime of a given Apache child process. If you want other processes to see it, you'll need to store it somewhere they can all get at it.

This is untested, but you can get the general idea: (Now tested). EDIT: OK, it seems like you can get some issues with Storable depending on what perl version and Storable version you're running. I've replaced Storable with Data::Serialize in my example. I've also added a line to the get/set methods so that either the -> or :: syntax can be used.

package PerlModules::MyCache;

use IPC::ShareLite qw/:lock/;
use Data::Serializer;
use 5.10.0;

my $key = 1234; # Your shared memory key (you set this!)

my $ipc = IPC::ShareLite->new(
    -key     => $key,
    -create  => 'yes',
    -destroy => 'no'
);

my $ser = Data::Serializer->new(
    serializer => 'Data::Dumper'
);

sub set {
    shift @_ if $_[0] eq __PACKAGE__;
    my ($key, $value) = @_;
    $ipc->lock(LOCK_EX);
    my $frozen; eval { $frozen = $ipc->fetch; };
    my $cache = defined($frozen) ? $ser->thaw($frozen) : {};
    $cache->{$key} = $value;
    $ipc->store($ser->freeze($cache));
    $ipc->unlock;
    return $value;
}

sub get {
    shift @_ if $_[0] eq __PACKAGE__;
    my ($key) = @_;
    my $frozen; eval { $frozen = $ipc->fetch; };
    my $cache = defined($frozen) ? $ser->thaw($frozen) : {};
    return $cache->{$key};
}

sub clear {
    shift @_ if $_[0] eq __PACKAGE__;
    $ipc->store($ser->freeze({}));
    return {};
}

1;

You might want to run PerlModules::MyCache->clear once before you test to ensure the correct structure of the cache storage.

David-SkyMesh
  • 5,041
  • 1
  • 31
  • 38