@@ -238,7 +238,7 @@ void TileVisibility<CAPACITY>::getTileVisibilityBitmap(uint64_t ts, uint64_t* ou
238238}
239239
240240template <size_t CAPACITY>
241- void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
241+ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts, uint64_t * gcSnapshotBitmap ) {
242242 // Drain the pending retirement slot left by deleteTileRecord's empty-chain path.
243243 VersionedData<CAPACITY>* pending = pendingRetire.exchange (nullptr , std::memory_order_acquire);
244244 if (pending) {
@@ -248,7 +248,12 @@ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
248248
249249 // Load old version
250250 VersionedData<CAPACITY>* oldVer = currentVersion.load (std::memory_order_acquire);
251- if (ts <= oldVer->baseTimestamp ) return ;
251+
252+ // Early return A: safeGcTs <= baseTimestamp, nothing to compact
253+ if (ts <= oldVer->baseTimestamp ) {
254+ std::memcpy (gcSnapshotBitmap, oldVer->baseBitmap , NUM_WORDS * sizeof (uint64_t ));
255+ return ;
256+ }
252257
253258 // Find the last block that should be compacted
254259 DeleteIndexBlock *blk = oldVer->head ;
@@ -275,7 +280,22 @@ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
275280 blk = blk->next .load (std::memory_order_acquire);
276281 }
277282
278- if (!lastFullBlk) return ;
283+ // Early return B: no compactable block
284+ if (!lastFullBlk) {
285+ std::memcpy (gcSnapshotBitmap, oldVer->baseBitmap , NUM_WORDS * sizeof (uint64_t ));
286+ if (oldVer->head ) {
287+ auto * tailSnap = tail.load (std::memory_order_acquire);
288+ size_t tailUsedSnap = tailUsed.load (std::memory_order_acquire);
289+ size_t cnt = (oldVer->head == tailSnap) ? tailUsedSnap : DeleteIndexBlock::BLOCK_CAPACITY;
290+ for (size_t i = 0 ; i < cnt; i++) {
291+ uint64_t item = oldVer->head ->items [i];
292+ if (item == 0 ) break ;
293+ if (extractTimestamp (item) <= ts) SET_BITMAP_BIT (gcSnapshotBitmap, extractRowId (item));
294+ else break ;
295+ }
296+ }
297+ return ;
298+ }
279299
280300 // Create new version with Copy-on-Write
281301 // Manually compute the new base bitmap from oldVer
@@ -304,8 +324,22 @@ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
304324 blk = blk->next .load (std::memory_order_acquire);
305325 }
306326
307- // Get new head and break the chain to avoid double-free
327+ // Compact path: build gcSnapshotBitmap by scanning the boundary block
308328 DeleteIndexBlock* newHead = lastFullBlk->next .load (std::memory_order_acquire);
329+ std::memcpy (gcSnapshotBitmap, newBaseBitmap, NUM_WORDS * sizeof (uint64_t ));
330+ if (newHead) {
331+ auto * tailSnap = tail.load (std::memory_order_acquire);
332+ size_t tailUsedSnap = tailUsed.load (std::memory_order_acquire);
333+ size_t cnt = (newHead == tailSnap) ? tailUsedSnap : DeleteIndexBlock::BLOCK_CAPACITY;
334+ for (size_t i = 0 ; i < cnt; i++) {
335+ uint64_t item = newHead->items [i];
336+ if (item == 0 ) break ;
337+ if (extractTimestamp (item) <= ts) SET_BITMAP_BIT (gcSnapshotBitmap, extractRowId (item));
338+ else break ;
339+ }
340+ }
341+
342+ // Break the chain to avoid double-free
309343 lastFullBlk->next .store (nullptr , std::memory_order_release);
310344
311345 // Create new version with new head - this is the atomic COW update
0 commit comments