5353#include "pcache_arena.h"
5454#include "pcache_htable.h"
5555
56+ /*
57+ * The key hash. Not the core's, which ADDS per-word mixes, so keys whose
58+ * 4-byte words sum alike collide on the full 32 bits - for sequential or
59+ * numeric keys (phone numbers, "user123456") it produced 65k distinct
60+ * values for 500,000 keys and put 44% of the entries into the overflow
61+ * table, whose single lock then serialised every miss. MurmurHash3
62+ * x86_32 (public domain, Austin Appleby): 0.2% overflow on the same keys,
63+ * the uniform expectation. Local to this node - never on the wire, never
64+ * in the DB - so it can change without a protocol bump.
65+ */
66+ unsigned int pcache_key_hash (const str * key )
67+ {
68+ const unsigned char * p = (const unsigned char * )key -> s ;
69+ unsigned int len = (unsigned int )key -> len , n = len >> 2 , i , k ;
70+ unsigned int h = 0x9747b28cU ; /* the seed */
71+
72+ for (i = 0 ; i < n ; i ++ , p += 4 ) {
73+ memcpy (& k , p , 4 );
74+ k *= 0xcc9e2d51U ; k = (k << 15 ) | (k >> 17 ); k *= 0x1b873593U ;
75+ h ^= k ; h = (h << 13 ) | (h >> 19 ); h = h * 5 + 0xe6546b64U ;
76+ }
77+ k = 0 ;
78+ switch (len & 3 ) {
79+ case 3 : k ^= (unsigned int )p [2 ] << 16 ; /* fall through */
80+ case 2 : k ^= (unsigned int )p [1 ] << 8 ; /* fall through */
81+ case 1 : k ^= p [0 ];
82+ k *= 0xcc9e2d51U ; k = (k << 15 ) | (k >> 17 ); k *= 0x1b873593U ;
83+ h ^= k ;
84+ }
85+ h ^= len ;
86+ h ^= h >> 16 ; h *= 0x85ebca6bU ; h ^= h >> 13 ; h *= 0xc2b2ae35U ; h ^= h >> 16 ;
87+ return h ;
88+ }
89+
5690/* The selftest deliberately drives two rejection paths (a non-numeric add
5791 * and an oversize store). Both log at L_ERR by design, which in a PASSING
5892 * selftest reads as a real fault. This flag downgrades exactly those two
@@ -305,7 +339,8 @@ static int ovf_fetch(pcache_htable_t *ht, const str *key, unsigned int hash,
305339 */
306340static int _pcache_ht_fetch_buf (pcache_htable_t * ht , const str * key ,
307341 char * dst , unsigned int dstlen , unsigned int * vlen_out ,
308- unsigned int now , unsigned int * exp_out , long long * ll_out )
342+ unsigned int now , unsigned int * exp_out , long long * ll_out ,
343+ unsigned char * fl_out )
309344{
310345 pcache_bucket_t * b ;
311346 uint64_t route ;
@@ -329,7 +364,7 @@ static int _pcache_ht_fetch_buf(pcache_htable_t *ht, const str *key,
329364 if (!ht || !key || (!dst && dstlen ))
330365 return -1 ;
331366
332- hash = core_hash (key , NULL , 0 );
367+ hash = pcache_key_hash (key );
333368 tag = tag_of (hash );
334369
335370again :
@@ -381,6 +416,8 @@ static int _pcache_ht_fetch_buf(pcache_htable_t *ht, const str *key,
381416 HT_ST (ht , hits );
382417 if (exp_out )
383418 * exp_out = exp ; /* absolute ticks, 0 = never */
419+ if (fl_out )
420+ * fl_out = fl ; /* record flags, e.g. F_PASSIVE */
384421 * vlen_out = vlen ;
385422
386423 /* the value did not fit: the length above tells the caller what it
@@ -411,7 +448,7 @@ static int _pcache_ht_fetch_buf(pcache_htable_t *ht, const str *key,
411448 * be reached from here - it is handled defensively all the same.
412449 */
413450static int _pcache_ht_fetch (pcache_htable_t * ht , const str * key , str * val ,
414- unsigned int now , unsigned int * exp_out )
451+ unsigned int now , unsigned int * exp_out , unsigned char * fl_out )
415452{
416453 unsigned int vlen = 0 ;
417454 long long ll = 0 ;
@@ -423,7 +460,7 @@ static int _pcache_ht_fetch(pcache_htable_t *ht, const str *key, str *val,
423460 return -1 ;
424461
425462 rc = _pcache_ht_fetch_buf (ht , key , scratch , PCACHE_CELL_MAX , & vlen ,
426- now , exp_out , & ll );
463+ now , exp_out , & ll , fl_out );
427464 if (rc < 0 )
428465 return rc == PCACHE_E_TOOSMALL ? -1 : rc ;
429466
@@ -449,15 +486,17 @@ static int _pcache_ht_fetch(pcache_htable_t *ht, const str *key, str *val,
449486
450487int pcache_ht_fetch (pcache_htable_t * ht , const str * key , str * val )
451488{
452- return _pcache_ht_fetch (ht , key , val , get_ticks (), NULL );
489+ return _pcache_ht_fetch (ht , key , val , get_ticks (), NULL , NULL );
453490}
454491
455492/* like pcache_ht_fetch, but also returns the record's absolute expiry
456493 * (0 = never) - the MI perf_get needs the TTL alongside the value */
457494int pcache_ht_fetch_ex (pcache_htable_t * ht , const str * key , str * val ,
458- unsigned int * expires )
495+ unsigned int * expires , unsigned char * rflags )
459496{
460- return _pcache_ht_fetch (ht , key , val , get_ticks (), expires );
497+ if (rflags )
498+ * rflags = 0 ;
499+ return _pcache_ht_fetch (ht , key , val , get_ticks (), expires , rflags );
461500}
462501
463502/* existence probe - see the contract in pcache_htable.h. Shares the whole
@@ -478,7 +517,7 @@ int pcache_ht_probe(pcache_htable_t *ht, const str *key, unsigned int *vlen,
478517 * is_counter = 0 ;
479518
480519 rc = _pcache_ht_fetch_buf (ht , key , NULL , 0 , & len , get_ticks (),
481- & exp , NULL );
520+ & exp , NULL , NULL );
482521 if (rc < 0 )
483522 return rc ; /* -2 = absent or expired */
484523 if (vlen )
@@ -511,7 +550,7 @@ int pcache_ht_fetch_buf(pcache_htable_t *ht, const str *key, char *buf,
511550 }
512551
513552 rc = _pcache_ht_fetch_buf (ht , key , buf , buflen , vlen , get_ticks (),
514- NULL , & ll );
553+ NULL , & ll , NULL );
515554
516555 if (rc == PCACHE_E_TOOSMALL ) {
517556 /* *vlen must never exceed the caller's buffer: {buf,*vlen} has to
@@ -564,6 +603,17 @@ static struct povf *ovf_find(pcache_htable_t *ht, const str *key,
564603
565604int pcache_ht_store (pcache_htable_t * ht , const str * key , const str * val ,
566605 unsigned int expires )
606+ {
607+ return pcache_ht_store_ex (ht , key , val , expires , 0 );
608+ }
609+
610+ /* @rflags is stamped on the stored record: 0 for a local consumer write
611+ * (the authoritative kind), PCACHE_F_PASSIVE for a value that arrived
612+ * through a cluster pull. The identical-bytes TTL-bump path keeps the
613+ * record's existing flags - re-pulling bytes the owner wrote must not
614+ * demote the owner's copy. */
615+ int pcache_ht_store_ex (pcache_htable_t * ht , const str * key , const str * val ,
616+ unsigned int expires , unsigned char rflags )
567617{
568618 pcache_bucket_t * b ;
569619 pcache_rec_t * nr , * old = NULL ;
@@ -580,14 +630,14 @@ int pcache_ht_store(pcache_htable_t *ht, const str *key, const str *val,
580630 return -1 ;
581631 }
582632
583- hash = core_hash (key , NULL , 0 );
633+ hash = pcache_key_hash (key );
584634 tag = tag_of (hash );
585635
586636 /* build the full replacement record before any lock (3.5b rule 3) */
587637 nr = pcache_cell_alloc (PCACHE_REC_SIZE (key -> len , val -> len ));
588638 if (!nr )
589639 return -2 ; /* arena full - write dropped */
590- nr -> rflags = 0 ;
640+ nr -> rflags = rflags ;
591641 nr -> klen = (unsigned short )key -> len ;
592642 nr -> vlen = (unsigned int )val -> len ;
593643 nr -> expires = expires ;
@@ -645,7 +695,7 @@ int pcache_ht_store(pcache_htable_t *ht, const str *key, const str *val,
645695 * from a previous life. Without it, storing an 8-byte
646696 * string over a native counter left PCACHE_F_INT set and
647697 * the read path re-interpreted the ASCII as an int64 */
648- old -> rflags = 0 ;
698+ old -> rflags = rflags ;
649699 old -> vlen = (unsigned int )val -> len ;
650700 memcpy (old -> data + key -> len , val -> s , val -> len );
651701 old -> expires = expires ;
@@ -744,7 +794,7 @@ int pcache_ht_add(pcache_htable_t *ht, const str *key, long long delta,
744794 if (key -> len > 0xFFFF )
745795 return -1 ;
746796
747- hash = core_hash (key , NULL , 0 );
797+ hash = pcache_key_hash (key );
748798 tag = tag_of (hash );
749799
750800 /* the counter record is pre-built outside any lock (3.5b); it either
@@ -910,7 +960,7 @@ int pcache_ht_touch(pcache_htable_t *ht, const str *key, unsigned int expires)
910960 unsigned char tag ;
911961 int i , rc = 0 ;
912962
913- hash = core_hash (key , NULL , 0 );
963+ hash = pcache_key_hash (key );
914964 tag = tag_of (hash );
915965
916966again :
@@ -965,7 +1015,7 @@ int pcache_ht_remove(pcache_htable_t *ht, const str *key)
9651015 unsigned char tag ;
9661016 int i ;
9671017
968- hash = core_hash (key , NULL , 0 );
1018+ hash = pcache_key_hash (key );
9691019 tag = tag_of (hash );
9701020
9711021again :
@@ -1657,7 +1707,7 @@ static int st_walk_cb(const str *key, const str *val, unsigned int exp,
16571707static pcache_rec_t * st_slot_of (pcache_htable_t * ht , const str * key )
16581708{
16591709 uint64_t route ;
1660- unsigned int hash = core_hash (( str * )key , NULL , 0 );
1710+ unsigned int hash = pcache_key_hash (( const str * )key );
16611711 pcache_bucket_t * b = bucket_at (ht , route_idx (ht , hash , & route ));
16621712 int i = find_slot (b , key , hash , tag_of (hash ));
16631713
@@ -1702,7 +1752,7 @@ int pcache_htable_selftest(void)
17021752 pkg_free (out .s );
17031753
17041754 /* versionless TTL bump: byte-identical value, version must hold */
1705- b = bucket_at (ht , route_idx (ht , core_hash (& k , NULL , 0 ), & route ));
1755+ b = bucket_at (ht , route_idx (ht , pcache_key_hash (& k ), & route ));
17061756 ver0 = b -> version ;
17071757 HCHK (pcache_ht_store (ht , & k , & v , get_ticks () + 100 ) == 0 ,
17081758 "bump store failed\n" );
@@ -1733,9 +1783,9 @@ int pcache_htable_selftest(void)
17331783 * wrapper here) */
17341784 v .s = "temp" ; v .len = 4 ;
17351785 HCHK (pcache_ht_store (ht , & k , & v , 500 ) == 0 , "expired store failed\n" );
1736- HCHK (_pcache_ht_fetch (ht , & k , & out , 1000 , NULL ) == -2 ,
1786+ HCHK (_pcache_ht_fetch (ht , & k , & out , 1000 , NULL , NULL ) == -2 ,
17371787 "expired key still hits\n" );
1738- rc = _pcache_ht_fetch (ht , & k , & out , 400 , NULL );
1788+ rc = _pcache_ht_fetch (ht , & k , & out , 400 , NULL , NULL );
17391789 HCHK (rc == 0 , "live key missed\n" );
17401790 pkg_free (out .s );
17411791 pcache_ht_remove (ht , & k );
0 commit comments