LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_clock.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 86 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 4 0.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           0 :              ulong                    class_idx ) {
      14           0 :   fd_progcache_shmem_t * shmem = join->shmem;
      15           0 :   fd_progcache_rec_t *   recs  = join->rec.ele;
      16             : 
      17           0 :   ulong base      = shmem->cache.rec_base [ class_idx ];
      18           0 :   ulong class_max = shmem->cache.class_max[ class_idx ];
      19           0 :   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           0 :   fd_progcache_rec_t * claim    = NULL;
      25           0 :   ulong                iter_rem = 2UL*class_max;
      26           0 :   while( iter_rem ) {
      27           0 :     iter_rem--;
      28             : 
      29             :     /* Get a candidate */
      30           0 :     ulong ticket = __atomic_fetch_add( &shmem->cache.clock_hand[ class_idx ].val, 1UL, __ATOMIC_RELAXED );
      31           0 :     fd_progcache_rec_t * rec = recs + base + ( ticket % class_max );
      32             : 
      33           0 :     uchar st = __atomic_load_n( &rec->state, __ATOMIC_RELAXED );
      34           0 :     fd_racesan_hook( "prog_clock_evict:post_load_bits" );
      35             : 
      36             :     /* Candidate is free or in flight: skip. */
      37           0 :     if( !( st & FD_PROGCACHE_REC_LIVE ) ) continue;
      38             : 
      39           0 :     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           0 :       if( FD_UNLIKELY( atomic_load_explicit( &rec->txn_idx, memory_order_relaxed )!=UINT_MAX ) ) continue;
      45           0 :       if( FD_UNLIKELY( !fd_rwlock_trywrite( &rec->lock ) ) ) continue;
      46             :       /* The write lock freezes the record, so the re-check under it decides. */
      47           0 :       uchar cur = __atomic_load_n( &rec->state, __ATOMIC_RELAXED );
      48           0 :       if( FD_UNLIKELY( ( ( cur & ( FD_PROGCACHE_REC_LIVE|FD_PROGCACHE_REC_MAPPED ) )!=FD_PROGCACHE_REC_LIVE ) |
      49           0 :                        ( 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           0 :     } else {
      56             : 
      57             :       /* Candidate is a live cache record: run CLOCK */
      58           0 :       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           0 :       if( FD_UNLIKELY( atomic_load_explicit( &rec->lock.value, memory_order_relaxed )!=0 ) ) continue;
      63             : 
      64           0 :       if( st & FD_PROGCACHE_REC_VISITED ) {
      65             :         /* clear the VISITED bit, i.e. CLOCK second chance */
      66           0 :         __atomic_fetch_and( &rec->state, (uchar)~FD_PROGCACHE_REC_VISITED, __ATOMIC_RELAXED );
      67           0 :         continue;
      68           0 :       }
      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           0 :       uint owner_idx = atomic_load_explicit( &rec->txn_idx, memory_order_relaxed );
      73           0 :       if( FD_UNLIKELY( owner_idx!=UINT_MAX ) ) {
      74           0 : #if FD_PROGCACHE_EVICT_UNROOTED
      75             :         /* Lock order matches fd_progcache_push: txn.rwlock -> txn->lock -> chain -> rec.lock. */
      76           0 :         if( FD_UNLIKELY( (ulong)owner_idx>=shmem->txn.max ) ) continue;
      77           0 :         fd_racesan_hook( "prog_clock_evict:pre_fork_lock" );
      78           0 :         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           0 :         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           0 :         fd_progcache_txn_t * owner = &join->txn.pool[ owner_idx ];
      86           0 :         if( FD_UNLIKELY( !fd_rwlock_trywrite( &owner->lock ) ) ) {
      87           0 :           fd_rwlock_unread( &shmem->txn.rwlock );
      88           0 :           continue;
      89           0 :         }
      90           0 :         fd_racesan_hook( "prog_clock_evict:pre_delete" );
      91           0 :         long res = fd_prog_delete_rec_claim_txn( join, rec, &pair, owner );
      92           0 :         fd_racesan_hook( "prog_clock_evict:post_delete" );
      93           0 :         fd_rwlock_unwrite( &owner->lock );
      94           0 :         fd_rwlock_unread( &shmem->txn.rwlock );
      95           0 :         if( FD_UNLIKELY( res<0L ) ) continue;
      96             : #else
      97             :         continue;
      98             : #endif
      99           0 :       } else {
     100           0 :         fd_racesan_hook( "prog_clock_evict:pre_delete" );
     101           0 :         long res = fd_prog_delete_rec_claim( join, rec, &pair );
     102           0 :         fd_racesan_hook( "prog_clock_evict:post_delete" );
     103           0 :         if( FD_UNLIKELY( res<0L ) ) continue;
     104           0 :       }
     105             :       /* Write locked by the claim; fall through to fd_progcache_rec_reinit */
     106           0 :     }
     107             : 
     108           0 :     if( FD_LIKELY( metrics ) ) {
     109           0 :       metrics->evict_cnt++;
     110           0 :       metrics->evict_tot_sz += rec->rodata_sz;
     111           0 :       metrics->evict_per_class[ class_idx ]++;
     112           0 :     }
     113             : 
     114             :     /* Unmapped and write locked, so nobody else can take the slot */
     115           0 :     claim = fd_progcache_rec_reinit( join, rec );
     116           0 :     break;
     117           0 :   }
     118             : 
     119           0 :   return claim;
     120           0 : }
     121             : 
     122             : fd_progcache_rec_t *
     123             : fd_prog_evict( fd_progcache_t * cache,
     124           0 :                ulong            sz ) {
     125           0 :   ulong class_idx = fd_progcache_cache_class( sz );
     126           0 :   FD_TEST( class_idx<FD_PROGCACHE_CACHE_CLASS_CNT );
     127           0 :   return evict_inner( cache->join, cache->metrics, class_idx );
     128           0 : }
     129             : 
     130             : ulong
     131             : fd_prog_preevict( fd_progcache_join_t *    join,
     132             :                   fd_progcache_metrics_t * metrics,
     133             :                   ulong                    class_idx,
     134           0 :                   ulong                    free_target ) {
     135           0 :   FD_TEST( class_idx<FD_PROGCACHE_CACHE_CLASS_CNT );
     136           0 :   if( FD_LIKELY( fd_progcache_class_free_cnt( join->shmem, class_idx )>=free_target ) ) return 0UL;
     137           0 :   fd_progcache_rec_t * rec = evict_inner( join, metrics, class_idx );
     138           0 :   if( FD_UNLIKELY( !rec ) ) return 0UL;
     139           0 :   fd_progcache_rec_abandon( join, rec );
     140           0 :   return 1UL;
     141           0 : }
     142             : 
     143             : ulong
     144             : fd_progcache_housekeeping( fd_progcache_join_t *    join,
     145           0 :                            fd_progcache_metrics_t * metrics ) {
     146           0 :   ulong ticket = __atomic_fetch_add( &join->shmem->cache.housekeep_hand.val, 1UL, __ATOMIC_RELAXED );
     147           0 :   return fd_prog_preevict( join, metrics, ticket % FD_PROGCACHE_CACHE_CLASS_CNT, 2UL );
     148           0 : }

Generated by: LCOV version 1.14