diff --git a/FastMmap.xs b/FastMmap.xs index df8877c..b69f887 100644 --- a/FastMmap.xs +++ b/FastMmap.xs @@ -156,15 +156,17 @@ fc_is_locked(obj) void -fc_read(obj, hash_slot, key) +fc_read(obj, hash_slot, key, no_val) SV * obj; unsigned long hash_slot; SV * key; + unsigned long no_val; INIT: int key_len, val_len, found; void * key_ptr, * val_ptr; MU64 expire_on = 0; MU64 flags = 0; + MU64 version = 0; STRLEN pl_key_len; SV * val; @@ -177,15 +179,15 @@ fc_read(obj, hash_slot, key) key_len = (int)pl_key_len; /* Get value data pointer */ - found = mmc_read(cache, (MU64)hash_slot, key_ptr, key_len, &val_ptr, &val_len, &expire_on, &flags); + found = mmc_read(cache, (MU64)hash_slot, key_ptr, key_len, &val_ptr, &val_len, &expire_on, &version, &flags); /* If not found, use undef */ if (found == -1) { val = &PL_sv_undef; } else { - /* Cached an undef value? */ - if (flags & FC_UNDEF) { + /* Cached an undef value or don't want the actual value? */ + if (flags & FC_UNDEF || no_val) { val = &PL_sv_undef; } else { @@ -206,15 +208,17 @@ fc_read(obj, hash_slot, key) XPUSHs(sv_2mortal(newSViv((IV)flags))); XPUSHs(sv_2mortal(newSViv((IV)!found))); XPUSHs(sv_2mortal(newSViv((IV)expire_on))); + XPUSHs(sv_2mortal(newSViv((IV)version))); int -fc_write(obj, hash_slot, key, val, expire_on, in_flags) +fc_write(obj, hash_slot, key, val, expire_on, version, in_flags) SV * obj; unsigned long hash_slot; SV * key; SV * val; unsigned long expire_on; + unsigned long version; unsigned long in_flags; INIT: int key_len, val_len; @@ -252,7 +256,7 @@ fc_write(obj, hash_slot, key, val, expire_on, in_flags) } /* Write value to cache */ - RETVAL = mmc_write(cache, (MU64)hash_slot, key_ptr, key_len, val_ptr, val_len, (MU64)expire_on, (MU64)in_flags); + RETVAL = mmc_write(cache, (MU64)hash_slot, key_ptr, key_len, val_ptr, val_len, (MU64)expire_on, (MU64)version, (MU64)in_flags); OUTPUT: RETVAL @@ -263,7 +267,7 @@ fc_delete(obj, hash_slot, key) unsigned long hash_slot; SV * key; INIT: - MU64 out_flags; + MU64 flags, version; int key_len, did_delete; void * key_ptr; STRLEN pl_key_len; @@ -277,10 +281,11 @@ fc_delete(obj, hash_slot, key) key_len = (int)pl_key_len; /* Write value to cache */ - did_delete = mmc_delete(cache, (MU64)hash_slot, key_ptr, key_len, &out_flags); + did_delete = mmc_delete(cache, (MU64)hash_slot, key_ptr, key_len, &flags, &version); XPUSHs(sv_2mortal(newSViv((IV)did_delete))); - XPUSHs(sv_2mortal(newSViv((IV)out_flags))); + XPUSHs(sv_2mortal(newSViv((IV)flags))); + XPUSHs(sv_2mortal(newSViv((IV)version))); void @@ -321,7 +326,7 @@ fc_expunge(obj, mode, wb, len) void * key_ptr, * val_ptr; int key_len, val_len; - MU64 last_access, expire_on, flags; + MU64 last_access, expire_on, version, flags; FC_ENTRY @@ -336,7 +341,7 @@ fc_expunge(obj, mode, wb, len) for (item = 0; item < num_expunge; item++) { mmc_get_details(cache, to_expunge[item], &key_ptr, &key_len, &val_ptr, &val_len, - &last_access, &expire_on, &flags); + &last_access, &expire_on, &version, &flags); { HV * ih = (HV *)sv_2mortal((SV *)newHV()); @@ -365,6 +370,7 @@ fc_expunge(obj, mode, wb, len) hv_store(ih, "value", 5, val, 0); hv_store(ih, "last_access", 11, newSViv((IV)last_access), 0); hv_store(ih, "expire_on", 9, newSViv((IV)expire_on), 0); + hv_store(ih, "version", 7, newSViv((IV)version), 0); hv_store(ih, "flags", 5, newSViv((IV)flags), 0); /* Create reference to hash */ @@ -388,7 +394,7 @@ fc_get_keys(obj, mode) MU64 * entry_ptr; void * key_ptr, * val_ptr; int key_len, val_len; - MU64 last_access, expire_on, flags; + MU64 last_access, expire_on, version, flags; FC_ENTRY @@ -401,7 +407,7 @@ fc_get_keys(obj, mode) SV * key; mmc_get_details(cache, entry_ptr, &key_ptr, &key_len, &val_ptr, &val_len, - &last_access, &expire_on, &flags); + &last_access, &expire_on, &version, &flags); /* Create key SV, and set UTF8'ness if needed */ key = newSVpvn((const char *)key_ptr, key_len); @@ -422,6 +428,7 @@ fc_get_keys(obj, mode) hv_store(ih, "key", 3, key, 0); hv_store(ih, "last_access", 11, newSViv((IV)last_access), 0); hv_store(ih, "expire_on", 9, newSViv((IV)expire_on), 0); + hv_store(ih, "version", 7, newSViv((IV)version), 0); hv_store(ih, "flags", 5, newSViv((IV)flags), 0); /* Add value to hash-ref if mode 2 */ @@ -458,7 +465,7 @@ fc_get(obj, key) INIT: int key_len, val_len, found; void * key_ptr, * val_ptr; - MU64 hash_page, hash_slot, expire_on, flags; + MU64 hash_page, hash_slot, expire_on, version, flags; STRLEN pl_key_len; SV * val; @@ -477,7 +484,7 @@ fc_get(obj, key) mmc_lock(cache, hash_page); /* Get value data pointer */ - found = mmc_read(cache, hash_slot, key_ptr, key_len, &val_ptr, &val_len, &expire_on, &flags); + found = mmc_read(cache, hash_slot, key_ptr, key_len, &val_ptr, &val_len, &expire_on, &version, &flags); /* If not found, use undef */ if (found == -1) { @@ -495,10 +502,11 @@ fc_get(obj, key) void -fc_set(obj, key, val) +fc_set(obj, key, val, version) SV * obj; SV * key; SV * val; + unsigned long version; INIT: int key_len, val_len; void * key_ptr, * val_ptr; @@ -524,7 +532,7 @@ fc_set(obj, key, val) mmc_lock(cache, hash_page); /* Get value data pointer */ - mmc_write(cache, hash_slot, key_ptr, key_len, val_ptr, val_len, -1, flags); + mmc_write(cache, hash_slot, key_ptr, key_len, val_ptr, val_len, -1, version, flags); mmc_unlock(cache); diff --git a/lib/Cache/FastMmap.pm b/lib/Cache/FastMmap.pm index 088a67a..d79e9c2 100644 --- a/lib/Cache/FastMmap.pm +++ b/lib/Cache/FastMmap.pm @@ -787,7 +787,7 @@ sub get { # Hash value, lock page, read result my ($HashPage, $HashSlot) = fc_hash($Cache, $_[1]); my $Unlock = $Self->_lock_page($HashPage); - my ($Val, $Flags, $Found, $ExpireOn) = fc_read($Cache, $HashSlot, $_[1]); + my ($Val, $Flags, $Found, $ExpireOn, $Version) = fc_read($Cache, $HashSlot, $_[1], 0); # Value not found, check underlying data store if (!$Found && (my $read_cb = $Self->{read_cb})) { @@ -795,7 +795,7 @@ sub get { # Callback to read from underlying data store # (unlock page first if we allow recursive calls $Unlock = undef if $Self->{allow_recursive}; - $Val = eval { $read_cb->($Self->{context}, $_[1]); }; + ($Val, my $Opts) = eval { $read_cb->($Self->{context}, $_[1]); }; my $Err = $@; $Unlock = $Self->_lock_page($HashPage) if $Self->{allow_recursive}; @@ -805,9 +805,6 @@ sub get { # If we found it, or want to cache not-found, store back into our cache if (defined $Val || $Self->{cache_not_found}) { - # Are we doing writeback's? If so, need to mark as dirty in cache - my $write_back = $Self->{write_back}; - $Val = $Self->{serialize}(\$Val) if $Self->{serialize}; $Val = $Self->{compress}($Val) if $Self->{compress}; @@ -816,7 +813,8 @@ sub get { my $KVLen = length($_[1]) + (defined($Val) ? length($Val) : 0); $Self->_expunge_page(2, 1, $KVLen); - fc_write($Cache, $HashSlot, $_[1], $Val, -1, 0); + my $version = $Opts && defined $Opts->{version} ? int $Opts->{version} : 0; + fc_write($Cache, $HashSlot, $_[1], $Val, -1, $version, 0); } } @@ -830,7 +828,9 @@ sub get { $Val = ${$Self->{deserialize}($Val)} if defined($Val) && $Self->{deserialize}; # If explicitly asked to skip unlocking, we return the reference to the unlocker - return ($Val, $Unlock, { $Found ? (expire_on => $ExpireOn) : () }) if $SkipUnlock; + if ($SkipUnlock) { + return ($Val, $Unlock, { $Found ? (expire_on => $ExpireOn, version => $Version) : () }); + } return $Val; } @@ -870,6 +870,7 @@ sub set { defined $Opts->{expire_on} ? $Opts->{expire_on} : (defined $Opts->{expire_time} ? parse_expire_time($Opts->{expire_time}, _time()): -1) ) : -1; + my $version = defined $Opts->{version} ? int $Opts->{version} : 0; # Hash value, lock page my ($HashPage, $HashSlot) = fc_hash($Cache, $_[1]); @@ -893,7 +894,7 @@ sub set { $Self->_expunge_page(2, 1, $KVLen); # Now store into cache - my $DidStore = fc_write($Cache, $HashSlot, $_[1], $Val, $expire_on, $write_back ? FC_ISDIRTY : 0); + my $DidStore = fc_write($Cache, $HashSlot, $_[1], $Val, $expire_on, $version, $write_back ? FC_ISDIRTY : 0); # Unlock page $Unlock = undef; @@ -907,6 +908,25 @@ sub set { return $DidStore; } +=item I + +Search cache for given Key. Returns undef if not found. Does not +check any I, only checks for in the actual cache right now + +=cut +sub exists { + my ($Self, $Cache) = ($_[0], $_[0]->{Cache}); + + # Hash value, lock page, read result + my ($HashPage, $HashSlot) = fc_hash($Cache, $_[1]); + my $Unlock = $Self->_lock_page($HashPage); + my (undef, $Flags, $Found, $ExpireOn, $Version) = fc_read($Cache, $HashSlot, $_[1], 1); + + $Unlock = undef; + + return $Found ? { expire_on => $ExpireOn, version => $Version } : undef; +} + =item I Atomically retrieve and set the value of a Key. @@ -1070,7 +1090,7 @@ sub expire { # Hash value, lock page, read result my ($HashPage, $HashSlot) = fc_hash($Cache, $_[1]); my $Unlock = $Self->_lock_page($HashPage); - my ($Val, $Flags, $Found) = fc_read($Cache, $HashSlot, $_[1]); + my ($Val, $Flags, $Found, $Version) = fc_read($Cache, $HashSlot, $_[1], 0); # If we found it, remove it if ($Found) { @@ -1250,7 +1270,7 @@ sub multi_get { # Hash key to get slot in this page and read my $FinalKey = "$_[1]-$_"; (undef, $HashSlot) = fc_hash($Cache, $FinalKey); - my ($Val, $Flags, $Found, $ExpireOn) = fc_read($Cache, $HashSlot, $FinalKey); + my ($Val, $Flags, $Found, $ExpireOn) = fc_read($Cache, $HashSlot, $FinalKey, 0); next unless $Found; # If not using raw values, use thaw() to turn data back into object @@ -1302,7 +1322,7 @@ sub multi_set { # Now hash key and store into page (undef, $HashSlot) = fc_hash($Cache, $FinalKey); - my $DidStore = fc_write($Cache, $HashSlot, $FinalKey, $Val, $expire_on, 0); + my $DidStore = fc_write($Cache, $HashSlot, $FinalKey, $Val, $expire_on, 0, 0); } # Unlock page diff --git a/mmap_cache.c b/mmap_cache.c index 458af62..9137d00 100644 --- a/mmap_cache.c +++ b/mmap_cache.c @@ -406,7 +406,7 @@ int mmc_hash( * cache_mmap * cache, MU64 hash_slot, * void *key_ptr, int key_len, * void **val_ptr, int *val_len, - * MU64 *expire_on, MU64 *flags + * MU64 *expire_on_p, MU64 * version_p, MU64 *flags_p * ) * * Read key from current page @@ -416,7 +416,7 @@ int mmc_read( mmap_cache *cache, MU64 hash_slot, void *key_ptr, int key_len, void **val_ptr, int *val_len, - MU64 *expire_on_p, MU64 *flags_p + MU64 *expire_on_p, MU64 *version_p, MU64 *flags_p ) { MU64 * slot_ptr; @@ -458,8 +458,9 @@ int mmc_read( S_LastAccess(base_det) = now; /* Copy values to pointers */ - *flags_p = S_Flags(base_det); *expire_on_p = expire_on; + *version_p = S_Version(base_det); + *flags_p = S_Flags(base_det); *val_len = S_ValLen(base_det); *val_ptr = S_ValPtr(base_det); @@ -478,7 +479,7 @@ int mmc_read( * cache_mmap * cache, MU64 hash_slot, * void *key_ptr, int key_len, * void *val_ptr, int val_len, - * MU64 expire_on, MU64 flags + * MU64 expire_on, MU64 version, MU64 flags * ) * * Write key to current page @@ -488,7 +489,7 @@ int mmc_write( mmap_cache *cache, MU64 hash_slot, void *key_ptr, int key_len, void *val_ptr, int val_len, - MU64 expire_on, MU64 flags + MU64 expire_on, MU64 version, MU64 flags ) { int did_store = 0; MU64 kvlen = KV_SlotLen(key_len, val_len); @@ -525,6 +526,7 @@ int mmc_write( S_LastAccess(base_det) = now; S_ExpireOn(base_det) = expire_on; S_SlotHash(base_det) = hash_slot; + S_Version(base_det) = version; S_Flags(base_det) = flags; S_KeyLen(base_det) = (MU64)key_len; S_ValLen(base_det) = (MU64)val_len; @@ -565,7 +567,7 @@ int mmc_write( int mmc_delete( mmap_cache *cache, MU64 hash_slot, void *key_ptr, int key_len, - MU64 * flags + MU64 * flags_p, MU64 * version_p ) { /* Search slots for key */ MU64 * slot_ptr = _mmc_find_slot(cache, hash_slot, key_ptr, key_len, 2); @@ -581,7 +583,8 @@ int mmc_delete( /* Store flags in output pointer */ MU64 * base_det = S_Ptr(cache->p_base, *slot_ptr); - *flags = S_Flags(base_det); + *flags_p = S_Flags(base_det); + *version_p = S_Version(base_det); _mmc_delete_slot(cache, slot_ptr); return 1; @@ -985,7 +988,7 @@ void mmc_get_details( MU64 * base_det, void ** key_ptr, int * key_len, void ** val_ptr, int * val_len, - MU64 * last_access, MU64 * expire_on, MU64 * flags + MU64 * last_access_p, MU64 * expire_on_p, MU64 * version_p, MU64 * flags_p ) { cache = cache; @@ -995,9 +998,10 @@ void mmc_get_details( *val_ptr = S_ValPtr(base_det); *val_len = S_ValLen(base_det); - *last_access = S_LastAccess(base_det); - *expire_on = S_ExpireOn(base_det); - *flags = S_Flags(base_det); + *last_access_p = S_LastAccess(base_det); + *expire_on_p = S_ExpireOn(base_det); + *version_p = S_Version(base_det); + *flags_p = S_Flags(base_det); } diff --git a/mmap_cache.h b/mmap_cache.h index eb649e7..9e5d485 100644 --- a/mmap_cache.h +++ b/mmap_cache.h @@ -32,7 +32,7 @@ * // Lock page * mmc_lock(cache, hash_page); * // Get pointer to value data - * mmc_read(cache, hash_slot, (void *)key_ptr, (int)key_len, (void **)&val_ptr, (int *)val_len, &expire_time, &flags); + * mmc_read(cache, hash_slot, (void *)key_ptr, (int)key_len, (void **)&val_ptr, (int *)val_len, &expire_time, &version, &flags); * // Unlock page * mmc_unlock(cache); * @@ -43,7 +43,7 @@ * // Lock page * mmc_lock(cache, hash_page); * // Get pointer to value data - * mmc_write(cache, hash_slot, (void *)key_ptr, (int)key_len, (void *)val_ptr, (int)val_len, expire_time, flags); + * mmc_write(cache, hash_slot, (void *)key_ptr, (int)key_len, (void *)val_ptr, (int)val_len, expire_time, version, flags); * // Unlock page * mmc_unlock(cache); * @@ -129,6 +129,9 @@ * - HashValue (8 bytes) - Value key was hashed to, so we don't have to * rehash on a re-organisation of the hash table * + * - Version (8 bytes) - Opaque version of this object, useful when doing + * a conditional store + * * - Flags (8 bytes) - Various flags * * - KeyLen (8 bytes) - Length of key @@ -207,9 +210,9 @@ int mmc_unlock(mmap_cache *); int mmc_is_locked(mmap_cache *); /* Functions for getting/setting/deleting values in current page */ -int mmc_read(mmap_cache *, MU64, void *, int, void **, int *, MU64 *, MU64 *); -int mmc_write(mmap_cache *, MU64, void *, int, void *, int, MU64, MU64); -int mmc_delete(mmap_cache *, MU64, void *, int, MU64 *); +int mmc_read(mmap_cache *, MU64, void *, int, void **, int *, MU64 *, MU64 *, MU64 *); +int mmc_write(mmap_cache *, MU64, void *, int, void *, int, MU64, MU64, MU64); +int mmc_delete(mmap_cache *, MU64, void *, int, MU64 *, MU64 *); /* Functions of expunging values in current page */ int mmc_calc_expunge(mmap_cache *, int, int, MU64 *, MU64 ***); @@ -221,7 +224,7 @@ MU64 * mmc_iterate_next(mmap_cache_it *); void mmc_iterate_close(mmap_cache_it *); /* Retrieve details of a cache page/entry */ -void mmc_get_details(mmap_cache *, MU64 *, void **, int *, void **, int *, MU64 *, MU64 *, MU64 *); +void mmc_get_details(mmap_cache *, MU64 *, void **, int *, void **, int *, MU64 *, MU64 *, MU64 *, MU64 *); void mmc_get_page_details(mmap_cache * cache, MU64 * nreads, MU64 * nreadhits); void mmc_reset_page_details(mmap_cache * cache); diff --git a/mmap_cache_internals.h b/mmap_cache_internals.h index 02631ab..061512b 100644 --- a/mmap_cache_internals.h +++ b/mmap_cache_internals.h @@ -97,10 +97,11 @@ struct mmap_cache_it { #define S_LastAccess(s) (*(s+0)) #define S_ExpireOn(s) (*(s+1)) #define S_SlotHash(s) (*(s+2)) -#define S_Flags(s) (*(s+3)) -#define S_KeyLen(s) (*(s+4)) -#define S_ValLen(s) (*(s+5)) -#define SLOT_HEADER_COUNT 6 +#define S_Version(s) (*(s+3)) +#define S_Flags(s) (*(s+4)) +#define S_KeyLen(s) (*(s+5)) +#define S_ValLen(s) (*(s+6)) +#define SLOT_HEADER_COUNT 7 #define S_KeyPtr(s) ((void *)(s+SLOT_HEADER_COUNT)) #define S_ValPtr(s) (PTR_ADD((void *)(s+SLOT_HEADER_COUNT), S_KeyLen(s))) diff --git a/t/24.t b/t/24.t new file mode 100644 index 0000000..eb23179 --- /dev/null +++ b/t/24.t @@ -0,0 +1,109 @@ + +######################### + +use Test::More tests => 27; +use Test::Deep; +BEGIN { use_ok('Cache::FastMmap') }; +use Data::Dumper; +use strict; + +######################### + +# Test version numbers + +my $FC = Cache::FastMmap->new( + init_file => 1, + num_pages => 1, + page_size => 65536, + expire_time => 3, +); +ok( defined $FC ); + +my $only_newer_cb = sub ($k, $v, $o) { +}; + +ok( $FC->set('foo', '123abc', { version => 3 }), 'store item 1, version 3'); + +ok( $FC->set('baz', '789ghi'), 'store item 3'); +is( $FC->get('foo'), '123abc', "get item 1"); +is( $FC->get('bar'), '456def', "get item 2"); +is( $FC->get('baz'), '789ghi', "get item 3"); + +$now = $epoch+1; +Cache::FastMmap::_set_time_override($now); + +sub cb { return ( (defined $_[1] ? $_[1] : 'boo') . 'a', { expire_on => $_[2]->{expire_on} }); }; +sub cb2 { return ($_[1] . 'a'); }; +is( $FC->get_and_set('foo', \&cb), '123abca', "get_and_set item 1 after sleep 1"); +is( $FC->get_and_set('bar', \&cb), '456defa', "get_and_set item 2 after sleep 1"); +is( $FC->get_and_set('baz', \&cb2), '789ghia', "get_and_set item 3 after sleep 1"); +is( $FC->get_and_set('gah', \&cb), 'booa', "get_and_set item 4 after sleep 1"); + +my @e = $FC->get_keys(2); +cmp_deeply( + \@e, + bag( + superhashof({ key => 'foo', value => '123abca', last_access => num($now, 1), expire_on => num($now+1, 1) }), + superhashof({ key => 'bar', value => '456defa', last_access => num($now, 1), expire_on => num($now+2, 1) }), + superhashof({ key => 'baz', value => '789ghia', last_access => num($now, 1), expire_on => num($now+3, 1) }), + superhashof({ key => 'gah', value => 'booa', last_access => num($now, 1), expire_on => num($now+3, 1) }), + ), + "got expected keys" +) || diag explain [ $now, \@e ]; + +$now = $epoch+2; +Cache::FastMmap::_set_time_override($now); + +is( $FC->get('foo'), undef, "get item 1 after sleep 2"); +is( $FC->get('bar'), '456defa', "get item 2 after sleep 2"); +is( $FC->get('baz'), '789ghia', "get item 3 after sleep 2"); + +is( $FC->get_and_set('bar', \&cb), '456defaa', "get_and_set item 2 after sleep 2"); + +@e = $FC->get_keys(2); +cmp_deeply( + \@e, + bag( + superhashof({ key => 'bar', value => '456defaa', last_access => num($now, 1), expire_on => num($now+1, 1) }), + superhashof({ key => 'baz', value => '789ghia', last_access => num($now, 1), expire_on => num($now+2, 1) }), + superhashof({ key => 'gah', value => 'booa', last_access => num($now-1, 1), expire_on => num($now+2, 1) }), + ), + "got expected keys" +) || diag explain [ $now, \@e ]; + +$now = $epoch+3; +Cache::FastMmap::_set_time_override($now); + +is( $FC->get('foo'), undef, "get item 1 after sleep 3"); +is( $FC->get('bar'), undef, "get item 2 after sleep 3"); +is( $FC->get('baz'), '789ghia', "get item 3 after sleep 3"); + +@e = $FC->get_keys(2); +cmp_deeply( + \@e, + bag( + superhashof({ key => 'baz', value => '789ghia', last_access => num($now, 1), expire_on => num($now+1, 1) }), + superhashof({ key => 'gah', value => 'booa', last_access => num($now-2, 1), expire_on => num($now+1, 1) }), + ), + "got expected keys" +) || diag explain [ $now, \@e ]; + +$now = $epoch+4; +Cache::FastMmap::_set_time_override($now); + +is( $FC->get('foo'), undef, "get item 1 after sleep 4"); +is( $FC->get('bar'), undef, "get item 2 after sleep 4"); +is( $FC->get('baz'), undef, "get item 3 after sleep 4"); + +@e = $FC->get_keys(2); +cmp_deeply( + \@e, + bag(), + "got expected keys (empty)" +) || diag explain [ $now, \@e ]; + +$FC->empty(1); + +ok( eq_hash(\%BackingStore, { foo => '123abca', bar => '456defaa', baz => '789ghia', gah => 'booa' }), "items match expire 2"); + +