1313/**
1414 * Test for bug #5: Stripe contention diagnostics track the wrong stripe.
1515 *
16- * Bug: putInternal and removeInternal compute the stripe index for contention
16+ * Bug: putInternal and removeInternal computed the stripe index for contention
1717 * tracking as {@code hash & STRIPE_MASK}, but getStripeLock computes it as
18- * {@code (hash & tableMask) & STRIPE_MASK}. When the table is smaller than
19- * STRIPE_COUNT (e.g., default capacity 16 vs STRIPE_COUNT 32), these produce
20- * different values. For example, with hash=112, tableMask=15, STRIPE_MASK=31:
21- * - getStripeLock: (112 & 15) & 31 = 0 (actual lock acquired on stripe 0)
22- * - putInternal: 112 & 31 = 16 (contention tracked on stripe 16)
23- *
24- * This means per-stripe contention metrics are attributed to the wrong stripes.
18+ * {@code (spread(hash) & tableMask) & STRIPE_MASK}. When the table is smaller
19+ * than STRIPE_COUNT, these produce different values, so per-stripe metrics
20+ * were attributed to incorrect stripes.
2521 */
2622class MultiKeyMapStripeTrackingTest {
2723
24+ /** Mirrors MultiKeyMap.spread() */
25+ private static int spread (int h ) {
26+ return h ^ (h >>> 16 );
27+ }
28+
2829 @ Test
2930 void testPutTracksAcquisitionOnCorrectStripe () throws Exception {
3031 MultiKeyMap <String > map = new MultiKeyMap <>(16 );
@@ -48,36 +49,31 @@ void testPutTracksAcquisitionOnCorrectStripe() throws Exception {
4849 return ; // Can't trigger on this machine/config
4950 }
5051
51- // Find a key whose hash has bits set in (stripeMask & ~tableMask),
52- // causing the buggy and correct stripe computations to differ.
53- int diffBits = stripeMask & ~tableMask ;
54-
52+ // Find a key where the correct stripe (with spread) differs from
53+ // the old buggy stripe (hash & STRIPE_MASK without spread or tableMask)
5554 String testKey = null ;
5655 int testHash = 0 ;
5756 for (int i = 0 ; i < 10000 ; i ++) {
5857 String candidate = "key" + i ;
5958 int h = candidate .hashCode ();
60- if ((h & diffBits ) != 0 ) {
59+ int correctStripe = (spread (h ) & tableMask ) & stripeMask ;
60+ int buggyStripe = h & stripeMask ;
61+ if (correctStripe != buggyStripe ) {
6162 testKey = candidate ;
6263 testHash = h ;
6364 break ;
6465 }
6566 }
66- assertNotNull (testKey , "Should find a key with hash bits in the differing range " );
67+ assertNotNull (testKey , "Should find a key where correct and buggy stripes differ " );
6768
68- int correctStripe = (testHash & tableMask ) & stripeMask ;
69- int buggyStripe = testHash & stripeMask ;
70- assertNotEquals (correctStripe , buggyStripe ,
71- "Precondition: correct and buggy stripes must differ for this key" );
69+ int expectedStripe = (spread (testHash ) & tableMask ) & stripeMask ;
7270
7371 // Perform a put
7472 map .put (testKey , "value" );
7573
76- // Verify acquisition was tracked on the correct stripe (the one matching getStripeLock)
77- assertEquals (1 , acq [correctStripe ].get (),
78- "Acquisition should be tracked on stripe " + correctStripe + " (matching getStripeLock)" );
79- assertEquals (0 , acq [buggyStripe ].get (),
80- "Stripe " + buggyStripe + " should have no acquisitions (wrong stripe)" );
74+ // Verify acquisition was tracked on the correct stripe
75+ assertEquals (1 , acq [expectedStripe ].get (),
76+ "Acquisition should be tracked on stripe " + expectedStripe + " (matching getStripeIndex)" );
8177 }
8278
8379 @ Test
@@ -101,36 +97,32 @@ void testRemoveTracksAcquisitionOnCorrectStripe() throws Exception {
10197 return ;
10298 }
10399
104- int diffBits = stripeMask & ~tableMask ;
105-
106100 String testKey = null ;
107101 int testHash = 0 ;
108102 for (int i = 0 ; i < 10000 ; i ++) {
109103 String candidate = "key" + i ;
110104 int h = candidate .hashCode ();
111- if ((h & diffBits ) != 0 ) {
105+ int correctStripe = (spread (h ) & tableMask ) & stripeMask ;
106+ int buggyStripe = h & stripeMask ;
107+ if (correctStripe != buggyStripe ) {
112108 testKey = candidate ;
113109 testHash = h ;
114110 break ;
115111 }
116112 }
117113 assertNotNull (testKey );
118114
119- int correctStripe = (testHash & tableMask ) & stripeMask ;
120- int buggyStripe = testHash & stripeMask ;
121- assertNotEquals (correctStripe , buggyStripe );
115+ int expectedStripe = (spread (testHash ) & tableMask ) & stripeMask ;
122116
123117 // Put the key first (this also increments the correct stripe's counter)
124118 map .put (testKey , "value" );
125- int acqAfterPut = acq [correctStripe ].get ();
119+ int acqAfterPut = acq [expectedStripe ].get ();
126120
127121 // Now remove it - should also track on the correct stripe
128122 map .remove (testKey );
129123
130- assertEquals (acqAfterPut + 1 , acq [correctStripe ].get (),
131- "Remove acquisition should be tracked on stripe " + correctStripe );
132- assertEquals (0 , acq [buggyStripe ].get (),
133- "Stripe " + buggyStripe + " should have no acquisitions" );
124+ assertEquals (acqAfterPut + 1 , acq [expectedStripe ].get (),
125+ "Remove acquisition should be tracked on stripe " + expectedStripe );
134126 }
135127
136128 @ Test
@@ -155,8 +147,8 @@ void testNoAcquisitionsAboveTableSizeStripes() throws Exception {
155147 map .put ("item" + i , "val" + i );
156148 }
157149
158- // With table size 16 and stripe count 32, the correct stripe index
159- // is ( hash & 15) & 31 which can only produce values in [0, 15].
150+ // With table size 16 and stripe count 32, the stripe index is
151+ // (spread( hash) & 15) & 31 which can only produce values in [0, 15].
160152 // Stripes [16, 31] should never receive acquisitions.
161153 for (int s = tableSize ; s < acq .length ; s ++) {
162154 assertEquals (0 , acq [s ].get (),
0 commit comments