> > One of the shiny golden nuggets I received from said slice was a
> > shared memory cache. It was simple, it was elegant, it was
> > perfect. It was also based on IPC::Shareable. GREAT idea. BAD
> > juju.
> Just use Cache::Cache. It's faster and easier.
Now, ya see...
Once upon a time, not many moons ago, the issue of Cache::Cache came up with
the SharedMemory Cache and the fact that it has NO locking semantics. When
I found this thread in searching for ways to implement my own locking scheme
to make up for this lack, I came upon YOUR comments that perhaps
Apache::Session::Lock::Semaphore could be used, without any of the rest of
the Apache::Session package.
That was a good enough lead for me.
So a went into the manpage, and I went into the module, and then I
mis-understood how the semaphore key was determined, and wasted a good hour
or two trying to patch it. Then I reverted to my BASICS: Data::Dumper is
your FRIEND. Print DEBUGGING messages. Duh, of course, except for some
reason I didn't think to worry about it, at first, in somebody else's
module. <sigh> So, see what I did wrong, undo the patches, and:
A:S:L:S makes the ASSUMPTION that the argument passed to its locking methods
is an Apache::Session object. Specifically, that it is a hashref of the
following (at least partial) structure:
{
data => {
_session_id => (something)
}
}
The _session_id is used as the seed for the locking semaphore. *IF* I
understood the requirements correctly, the _session_id has to be the same
FOR EVERY PROCESS in order for the locking to work as desired, for a given
shared data structure.
So my new caching code is at the end of this message.
***OH WOW!*** So, DURING the course of composing this message, I've
realized that the function expire_old_accounts() is now redundant!
Cache::Cache takes care of that, both with expires_in and max_size. I'm
leaving it in for reference, just to show how it's improved. :-)
***OH WOW! v1.1*** :-) I've also just now realized that the call to
bind_accounts() could actually go right inside lookup_account(), if:
1) lookup_account() is the only function using the cache, or
2) lookup_account() is ALWAYS THE FIRST function to access the cache, or
3) every OTHER function accessing the cache has the same call,
of the form "bind() unless defined $to_bind;"
I think for prudence I'll leave outside for now.
L8r,
Rob
====>%= snip =%<====
use Apache::Session::Lock::Semaphore ();
use Cache::SizeAwareSharedMemoryCache ();
# this is used in %cache_options, as well as for locking
use constant SIGNATURE => 'EXIT';
use constant MAX_ACCOUNTS => 300;
# use vars qw/%ACCOUNTS/;
use vars qw/$ACCOUNTS $locker/;
my %cache_options = ( namespace => SIGNATURE,
default_expires_in =>
max_size => MAX_ACCOUNTS );
sub handler {
# ... init code here. parse $account from the request, and then:
bind_accounts() unless defined($ACCOUNTS);
# verify (access the cache)
my $accountinfo = lookup_account($account)
or $r->log_reason("no such account: $account"), return
HTTP_NO_CONTENT;
# ... content here
}
# Bind the account variables to shared memory
sub bind_accounts {
warn "bind_accounts: Binding shared memory" if $debug;
$ACCOUNTS =
Cache::SizeAwareSharedMemoryCache->new( \%cache_options ) or
croak( "Couldn't instantiate SizeAwareSharedMemoryCache : $!" );
# Shut up Apache::Session::Lock::Semaphore
$ACCOUNTS->{data}->{_session_id} = join '', SIGNATURE, @INC;
$locker = Apache::Session::Lock::Semaphore->new();
# not quite ready to trust this yet. :-)
# We'll keep it separate for now.
#
#$ACCOUNTS->set('locker', $locker);
warn "bind_accounts: done" if $debug;
}
### DEPRECATED! Cache::Cache does this FOR us!
# bring the current session to the front and
# get rid of any that haven't been used recently
sub expire_old_accounts {
### DEPRECATED!
return;
my $id = shift;
warn "expire_old_accounts: entered\n" if $debug;
$locker->acquire_write_lock($ACCOUNTS);
#tied(%ACCOUNTS)->shlock;
my @accounts = grep( $id ne $_, @{$ACCOUNTS->get('QUEUE') || []} );
unshift @accounts, $id;
if (@accounts > MAX_ACCOUNTS) {
my $to_delete = pop @accounts;
$ACCOUNTS->remove($to_delete);
}
$ACCOUNTS->set('QUEUE', \@accounts);
$locker->release_write_lock($ACCOUNTS);
#tied(%ACCOUNTS)->shunlock;
warn "expire_old_accounts: done\n" if $debug;
}
sub lookup_account {
my $id = shift;
warn "lookup_account: begin" if $debug;
expire_old_accounts($id);
warn "lookup_account: Accessing \$ACCOUNTS{$id}" if $debug;
my $s = $ACCOUNTS->get($id);
if (defined $s) {
# SUCCESSFUL CACHE HIT
warn "lookup_account: Retrieved accountinfo from Cache (bypassing SQL)" if
$debug;
return $s;
}
## NOT IN CACHE... refreshing.
warn "lookup_account: preparing SQL" if $debug;
# ... do some SQL here. Assign results to $s
$locker->acquire_write_lock($ACCOUNTS);
# tied(%ACCOUNTS)->shlock;
warn "lookup_account: assigning \$s to shared mem" if $debug;
$ACCOUNTS->set($id, $s);
$locker->release_write_lock($ACCOUNTS);
# tied(%ACCOUNTS)->shunlock;
return $s;
}
====>%= snip =%<====