> > 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 =%<====

Reply via email to