1717static const u32 INITIAL_CAPACITY = 65536 ; // 64K initial table size (matches upstream)
1818static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024 ;
1919static const u64 OVERFLOW_TRACE_ID = 0x7fffffffffffffffULL ; // Max 64-bit signed value
20+ // slot_base + local_slot must stay within a u32 so it never carries into
21+ // instance_id's bits of trace_id = (instance_id << 32) | (slot_base + slot).
22+ static const u64 SLOT_ID_RANGE = 0x100000000ull ; // 2^32
23+
24+ // Pure, allocation-free helpers for the expansion-overflow guard below;
25+ // exposed via the header so tests can exercise the 2^32 slot-id boundary
26+ // directly instead of needing billions of real put() calls to reach it.
27+ u64 CallTraceHashTable::nextGenerationCapacity (u32 capacity) {
28+ return (u64 )capacity * 2 ;
29+ }
30+
31+ bool CallTraceHashTable::wouldExceedSlotIdRange (u64 slot_base, u32 capacity) {
32+ u64 prospective_base = slot_base + capacity;
33+ return prospective_base + nextGenerationCapacity (capacity) > SLOT_ID_RANGE ;
34+ }
2035
2136// Define the sentinel value for CallTraceSample
2237CallTrace* const CallTraceSample::PREPARING = reinterpret_cast <CallTrace*>(1 );
@@ -26,7 +41,8 @@ class LongHashTable {
2641 LongHashTable *_prev;
2742 void *_padding0;
2843 u32 _capacity;
29- u32 _padding1[15 ];
44+ u32 _slot_base;
45+ u32 _padding1[14 ];
3046 volatile u32 _size;
3147 u32 _padding2[15 ];
3248
@@ -37,22 +53,25 @@ class LongHashTable {
3753 }
3854
3955public:
40- LongHashTable (LongHashTable *prev = nullptr , u32 capacity = 0 , bool should_clean = true )
41- : _prev(prev), _padding0(nullptr ), _capacity(capacity), _size(0 ) {
56+ LongHashTable (LongHashTable *prev = nullptr , u32 capacity = 0 ,
57+ u32 slot_base = 0 , bool should_clean = true )
58+ : _prev(prev), _padding0(nullptr ), _capacity(capacity),
59+ _slot_base (slot_base), _size(0 ) {
4260 memset (_padding1, 0 , sizeof (_padding1));
4361 memset (_padding2, 0 , sizeof (_padding2));
4462 if (should_clean) {
4563 clear ();
4664 }
4765 }
4866
49- static LongHashTable *allocate (LongHashTable *prev, u32 capacity, LinearAllocator* allocator) {
67+ static LongHashTable *allocate (LongHashTable *prev, u32 capacity,
68+ u32 slot_base, LinearAllocator* allocator) {
5069 void *memory = allocator->alloc (getSize (capacity));
5170 if (memory != nullptr ) {
5271 // Use placement new to invoke constructor in-place with parameters
5372 // LinearAllocator doesn't zero memory like OS::safeAlloc with anon mmap
5473 // so we need to explicitly clear the keys and values (should_clean = true)
55- LongHashTable *table = new (memory) LongHashTable (prev, capacity, true );
74+ LongHashTable *table = new (memory) LongHashTable (prev, capacity, slot_base, true );
5675 return table;
5776 }
5877 return nullptr ;
@@ -63,6 +82,8 @@ class LongHashTable {
6382
6483 u32 capacity () { return _capacity; }
6584
85+ u32 slotBase () { return _slot_base; }
86+
6687 u32 size () { return _size; }
6788
6889 u32 incSize () { return __sync_add_and_fetch (&_size, 1 ); }
@@ -89,10 +110,14 @@ CallTraceHashTable::CallTraceHashTable() : _instance_id(0), _parent_storage(null
89110 // Instance ID will be set externally via setInstanceId()
90111
91112 // Start with initial capacity, allowing expansion as needed
92- _table = LongHashTable::allocate (nullptr , INITIAL_CAPACITY , &_allocator);
113+ _table = LongHashTable::allocate (nullptr , INITIAL_CAPACITY , 0 , &_allocator);
93114 _overflow = 0 ;
94115}
95116
117+ void CallTraceHashTable::seedTableForTesting (u32 slot_base, u32 capacity) {
118+ _table = LongHashTable::allocate (nullptr , capacity, slot_base, &_allocator);
119+ }
120+
96121CallTraceHashTable::~CallTraceHashTable () {
97122 // LinearAllocator handles all memory cleanup automatically
98123 // No need to explicitly destroy tables since they're allocated from LinearAllocator
@@ -178,7 +203,7 @@ ChunkList CallTraceHashTable::clearTableOnly() {
178203 // RELEASE: pairs with ACQUIRE loads in collect() and put() to ensure the
179204 // freshly-initialised table is visible on weakly-ordered architectures (aarch64).
180205 __atomic_store_n (&_table,
181- LongHashTable::allocate (nullptr , INITIAL_CAPACITY , &_allocator),
206+ LongHashTable::allocate (nullptr , INITIAL_CAPACITY , 0 , &_allocator),
182207 __ATOMIC_RELEASE);
183208 _overflow = 0 ;
184209
@@ -280,8 +305,23 @@ void CallTraceHashTable::expandTableIfNeeded(LongHashTable* table, u32 size) {
280305 // EXPANSION LOGIC: Check if load ratio reached after incrementing size
281306 if (size >= (u32 ) (capacity * LOAD_RATIO ) &&
282307 table == __atomic_load_n (&_table, __ATOMIC_RELAXED)) { // quick check, if other thread already expanded the table
308+ if (wouldExceedSlotIdRange (table->slotBase (), capacity)) {
309+ // Expanding would push slot_base + local_slot past 2^32, carrying
310+ // into instance_id's bit range. Skip expansion; put() keeps working
311+ // on the current table at a higher load factor. This degraded state
312+ // is bounded, not permanent: the next processTraces() rotation resets
313+ // slot_base to 0 via clearTableOnly(), so normal JFR-flush cadence
314+ // recovers it.
315+ Counters::increment (CALLTRACE_STORAGE_EXPANSION_SKIPPED );
316+ return ;
317+ }
318+
319+ u64 prospective_base = (u64 )table->slotBase () + capacity;
320+ u64 prospective_capacity = nextGenerationCapacity (capacity);
321+
283322 // Allocate new table with double capacity using LinearAllocator
284- LongHashTable* new_table = LongHashTable::allocate (table, capacity * 2 , &_allocator);
323+ LongHashTable* new_table = LongHashTable::allocate (
324+ table, (u32 )prospective_capacity, (u32 )prospective_base, &_allocator);
285325 if (new_table != nullptr ) {
286326 // Atomic table swap - only one thread succeeds
287327 __atomic_compare_exchange_n (&_table, &table, new_table, false , __ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
@@ -384,11 +424,13 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
384424 }
385425
386426 if (trace == nullptr ) {
387- // Generate unique trace ID: upper 32 bits = instance_id, lower 32 bits = slot
427+ // Generate unique trace ID: upper 32 bits = instance_id, lower 32 bits =
428+ // slot_base + local slot (slot_base makes the low bits unique across
429+ // all LongHashTable generations of one active tenure)
388430 // ACQUIRE ordering synchronizes with RELEASE store in setInstanceId() to ensure
389431 // visibility of new instance_id on weakly-ordered architectures (aarch64, POWER)
390432 u64 instance_id = _instance_id.load (std::memory_order_acquire);
391- u64 trace_id = (instance_id << 32 ) | slot;
433+ u64 trace_id = (instance_id << 32 ) | (table-> slotBase () + slot) ;
392434 trace = storeCallTrace (num_frames, frames, truncated, trace_id);
393435 if (trace == nullptr ) {
394436 // Allocation failure - reset trace first, then clear key
0 commit comments