LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_clock.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 80 86 93.0 %
Date: 2026-09-17 04:28:31 Functions: 4 4 100.0 %

          Line data    Source code
       1             : #include "fd_progcache_user.h"
       2             : #include "fd_progcache_clock.h"
       3             : #include "fd_progcache_reclaim.h"
       4             : #include "../../util/racesan/fd_racesan_target.h"
       5             : 
       6             : /* evict_inner is the eviction sweep on one size class, the shared body of
       7             :    fd_prog_evict and fd_prog_preevict.  metrics==NULL is the admin variant:
       8             :    identical scan and claim, no counters. */
       9             : 
      10             : static fd_progcache_rec_t *
      11             : evict_inner( fd_progcache_join_t *    join,
      12             :              fd_progcache_metrics_t * metrics,
      13     1347784 :              ulong                    class_idx ) {
      14     1347784 :   fd_progcache_shmem_t * shmem = join->shmem;
      15     1347784 :   fd_progcache_rec_t *   recs  = join->rec.ele;
      16             : 
      17     1347784 :   ulong base      = shmem->cache.rec_base [ class_idx ];
      18     1347784 :   ulong class_max = shmem->cache.class_max[ class_idx ];
      19     1347784 :   FD_TEST( class_max );
      20             : 
      21             :   /* Do at most 2x class_max (number of slots in class) iterations.
      22             :      If we can't evict, return NULL and we'll try to spill.  Every skip is a
      23             :      continue; the one way out with a slot is the handover at the bottom. */
      24     1347784 :   fd_progcache_rec_t * claim    = NULL;
      25     1347784 :   ulong                iter_rem = 2UL*class_max;
      26    48217953 :   while( iter_rem ) {
      27    47782077 :     iter_rem--;
      28             : 
      29             :     /* Get a candidate */
      30    47782077 :     ulong ticket = __atomic_fetch_add( &shmem->cache.clock_hand[ class_idx ].val, 1UL, __ATOMIC_RELAXED );
      31    47782077 :     fd_progcache_rec_t * rec = recs + base + ( ticket % class_max );
      32             : 
      33    47782077 :     uchar st = __atomic_load_n( &rec->state, __ATOMIC_RELAXED );
      34    47782077 :     fd_racesan_hook( "prog_clock_evict:post_load_bits" );
      35             : 
      36             :     /* Candidate is free or in flight: skip. */
      37    47782077 :     if( !( st & FD_PROGCACHE_REC_LIVE ) ) continue;
      38             : 
      39    45190979 :     if( FD_UNLIKELY( !( st & FD_PROGCACHE_REC_MAPPED ) ) ) {
      40             : 
      41             :       /* Candidate is a zombie, already unmapped, so we can try to
      42             :          write-lock it directly (skip if still read-locked / in use). */
      43             : 
      44           6 :       if( FD_UNLIKELY( atomic_load_explicit( &rec->txn_idx, memory_order_relaxed )!=UINT_MAX ) ) continue;
      45           6 :       if( FD_UNLIKELY( !fd_rwlock_trywrite( &rec->lock ) ) ) continue;
      46             :       /* The write lock freezes the record, so the re-check under it decides. */
      47           6 :       uchar cur = __atomic_load_n( &rec->state, __ATOMIC_RELAXED );
      48           6 :       if( FD_UNLIKELY( ( ( cur & ( FD_PROGCACHE_REC_LIVE|FD_PROGCACHE_REC_MAPPED ) )!=FD_PROGCACHE_REC_LIVE ) |
      49           6 :                        ( atomic_load_explicit( &rec->txn_idx, memory_order_relaxed )!=UINT_MAX ) ) ) {
      50           0 :         fd_rwlock_unwrite( &rec->lock );
      51           0 :         continue;
      52           0 :       }
      53             :       /* Write locked by the trywrite; fall through to fd_progcache_rec_reinit */
      54             : 
      55    45190973 :     } else {
      56             : 
      57             :       /* Candidate is a live cache record: run CLOCK */
      58    45190973 :       fd_progcache_rec_key_t pair = rec->pair;
      59             : 
      60             :       /* If there's any read-lock on the record, it'll fail later, so might as well
      61             :          skipping it now (this is just an optimization) */
      62    45190973 :       if( FD_UNLIKELY( atomic_load_explicit( &rec->lock.value, memory_order_relaxed )!=0 ) ) continue;
      63             : 
      64    34708876 :       if( st & FD_PROGCACHE_REC_VISITED ) {
      65             :         /* clear the VISITED bit, i.e. CLOCK second chance */
      66      912350 :         __atomic_fetch_and( &rec->state, (uchar)~FD_PROGCACHE_REC_VISITED, __ATOMIC_RELAXED );
      67      912350 :         continue;
      68      912350 :       }
      69             : 
      70             :       /* An attached record is on its fork's list: claim it under the fork lock,
      71             :          or skip it when eviction is rooted-only. */
      72    33796526 :       uint owner_idx = atomic_load_explicit( &rec->txn_idx, memory_order_relaxed );
      73    33796526 :       if( FD_UNLIKELY( owner_idx!=UINT_MAX ) ) {
      74    33796424 : #if FD_PROGCACHE_EVICT_UNROOTED
      75             :         /* Lock order matches fd_progcache_push: txn.rwlock -> txn->lock -> chain -> rec.lock. */
      76    33796424 :         if( FD_UNLIKELY( (ulong)owner_idx>=shmem->txn.max ) ) continue;
      77    33796424 :         fd_racesan_hook( "prog_clock_evict:pre_fork_lock" );
      78    33796424 :         fd_rwlock_read( &shmem->txn.rwlock );
      79             :         /* Recycling a txn slot needs the write lock, so a matching re-read here
      80             :            proves the slot still owns rec. */
      81    33796424 :         if( FD_UNLIKELY( atomic_load_explicit( &rec->txn_idx, memory_order_relaxed )!=owner_idx ) ) {
      82           0 :           fd_rwlock_unread( &shmem->txn.rwlock );
      83           0 :           continue;
      84           0 :         }
      85    33796424 :         fd_progcache_txn_t * owner = &join->txn.pool[ owner_idx ];
      86    33796424 :         if( FD_UNLIKELY( !fd_rwlock_trywrite( &owner->lock ) ) ) {
      87    32884599 :           fd_rwlock_unread( &shmem->txn.rwlock );
      88    32884599 :           continue;
      89    32884599 :         }
      90      911825 :         fd_racesan_hook( "prog_clock_evict:pre_delete" );
      91      911825 :         long res = fd_prog_delete_rec_claim_txn( join, rec, &pair, owner );
      92      911825 :         fd_racesan_hook( "prog_clock_evict:post_delete" );
      93      911825 :         fd_rwlock_unwrite( &owner->lock );
      94      911825 :         fd_rwlock_unread( &shmem->txn.rwlock );
      95      911825 :         if( FD_UNLIKELY( res<0L ) ) continue;
      96             : #else
      97             :         continue;
      98             : #endif
      99      911825 :       } else {
     100         102 :         fd_racesan_hook( "prog_clock_evict:pre_delete" );
     101         102 :         long res = fd_prog_delete_rec_claim( join, rec, &pair );
     102         102 :         fd_racesan_hook( "prog_clock_evict:post_delete" );
     103         102 :         if( FD_UNLIKELY( res<0L ) ) continue;
     104         102 :       }
     105             :       /* Write locked by the claim; fall through to fd_progcache_rec_reinit */
     106    33796526 :     }
     107             : 
     108      911908 :     if( FD_LIKELY( metrics ) ) {
     109      911893 :       metrics->evict_cnt++;
     110      911893 :       metrics->evict_tot_sz += rec->rodata_sz;
     111      911893 :       metrics->evict_per_class[ class_idx ]++;
     112      911893 :     }
     113             : 
     114             :     /* Unmapped and write locked, so nobody else can take the slot */
     115      911908 :     claim = fd_progcache_rec_reinit( join, rec );
     116      911908 :     break;
     117    45190979 :   }
     118             : 
     119     1347784 :   return claim;
     120     1347784 : }
     121             : 
     122             : fd_progcache_rec_t *
     123             : fd_prog_evict( fd_progcache_t * cache,
     124     1347769 :                ulong            sz ) {
     125     1347769 :   ulong class_idx = fd_progcache_cache_class( sz );
     126     1347769 :   FD_TEST( class_idx<FD_PROGCACHE_CACHE_CLASS_CNT );
     127     1347769 :   return evict_inner( cache->join, cache->metrics, class_idx );
     128     1347769 : }
     129             : 
     130             : ulong
     131             : fd_prog_preevict( fd_progcache_join_t *    join,
     132             :                   fd_progcache_metrics_t * metrics,
     133             :                   ulong                    class_idx,
     134          69 :                   ulong                    free_target ) {
     135          69 :   FD_TEST( class_idx<FD_PROGCACHE_CACHE_CLASS_CNT );
     136          69 :   if( FD_LIKELY( fd_progcache_class_free_cnt( join->shmem, class_idx )>=free_target ) ) return 0UL;
     137          15 :   fd_progcache_rec_t * rec = evict_inner( join, metrics, class_idx );
     138          15 :   if( FD_UNLIKELY( !rec ) ) return 0UL;
     139          15 :   fd_progcache_rec_abandon( join, rec );
     140          15 :   return 1UL;
     141          15 : }
     142             : 
     143             : void
     144             : fd_progcache_housekeeping( fd_progcache_join_t *    join,
     145          54 :                            fd_progcache_metrics_t * metrics ) {
     146          54 :   ulong ticket = __atomic_fetch_add( &join->shmem->cache.housekeep_hand.val, 1UL, __ATOMIC_RELAXED );
     147          54 :   fd_prog_preevict( join, metrics, ticket % FD_PROGCACHE_CACHE_CLASS_CNT, 2UL );
     148          54 : }

Generated by: LCOV version 1.14