LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_user.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 283 419 67.5 %
Date: 2026-03-31 06:22:16 Functions: 13 15 86.7 %

          Line data    Source code
       1             : #include "fd_prog_load.h"
       2             : #include "fd_progcache_user.h"
       3             : #include "fd_progcache_reclaim.h"
       4             : #include "../../util/racesan/fd_racesan_target.h"
       5             : 
       6             : FD_TL fd_progcache_metrics_t fd_progcache_metrics_default;
       7             : 
       8             : fd_progcache_t *
       9             : fd_progcache_join( fd_progcache_t *       cache,
      10             :                    fd_progcache_shmem_t * shmem,
      11             :                    uchar *                scratch,
      12         180 :                    ulong                  scratch_sz ) {
      13         180 :   if( FD_UNLIKELY( !cache ) ) {
      14           0 :     FD_LOG_WARNING(( "NULL cache" ));
      15           0 :     return NULL;
      16           0 :   }
      17         180 :   if( FD_LIKELY( scratch_sz ) ) {
      18         180 :     if( FD_UNLIKELY( !scratch ) ) {
      19           3 :       FD_LOG_WARNING(( "NULL scratch" ));
      20           3 :       return NULL;
      21           3 :     }
      22         177 :     if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)scratch, FD_PROGCACHE_SCRATCH_ALIGN ) ) ) {
      23           3 :       FD_LOG_WARNING(( "misaligned scratch" ));
      24           3 :       return NULL;
      25           3 :     }
      26         177 :   }
      27         174 :   memset( cache, 0, sizeof(fd_progcache_t) );
      28         174 :   if( FD_UNLIKELY( !fd_progcache_shmem_join( cache->join, shmem ) ) ) return NULL;
      29             : 
      30         174 :   cache->metrics    = &fd_progcache_metrics_default;
      31         174 :   cache->scratch    = scratch;
      32         174 :   cache->scratch_sz = scratch_sz;
      33             : 
      34         174 :   return cache;
      35         174 : }
      36             : 
      37             : void *
      38             : fd_progcache_leave( fd_progcache_t *        cache,
      39         171 :                     fd_progcache_shmem_t ** opt_shmem ) {
      40         171 :   if( FD_UNLIKELY( !cache ) ) {
      41           0 :     FD_LOG_WARNING(( "NULL cache" ));
      42           0 :     return NULL;
      43           0 :   }
      44             : 
      45         171 :   while( cache->join->rec.reclaim_head!=UINT_MAX ) {
      46           0 :     fd_prog_reclaim_work( cache->join );
      47           0 :     FD_SPIN_PAUSE();
      48           0 :   }
      49             : 
      50         171 :   if( FD_UNLIKELY( !fd_progcache_shmem_leave( cache->join, opt_shmem ) ) ) return NULL;
      51         171 :   cache->scratch    = NULL;
      52         171 :   cache->scratch_sz = 0UL;
      53         171 :   return cache;
      54         171 : }
      55             : 
      56             : /* fd_progcache_load_fork pivots the progcache object to the selected
      57             :    fork (identified by tip XID).
      58             : 
      59             :    Populates cache->fork, which is a array-backed list of XIDs sorted
      60             :    newest to oldest.  Cache lookups only respect records with an XID
      61             :    present in that list.
      62             : 
      63             :    For any given xid, the epoch_slot0 is assumed to stay constant. */
      64             : 
      65             : static void
      66             : fd_progcache_load_fork_slow( fd_progcache_t * cache,
      67         105 :                              fd_xid_t const * xid ) {
      68         105 :   fd_accdb_lineage_t *        lineage = cache->lineage;
      69         105 :   fd_progcache_join_t const * ljoin   = cache->join;
      70         105 :   fd_rwlock_read( &ljoin->shmem->txn.rwlock );
      71         105 :   lineage->fork_depth = 0UL;
      72             : 
      73         105 :   ulong txn_max = fd_prog_txnp_max( ljoin->txn.pool );
      74         105 :   fd_xid_t next_xid = *xid;
      75         105 :   ulong i;
      76         141 :   for( i=0UL;; i++ ) {
      77         141 :     if( FD_UNLIKELY( i>=FD_PROGCACHE_DEPTH_MAX ) ) {
      78           0 :       FD_LOG_CRIT(( "fd_progcache_load_fork: fork depth exceeded max of %lu", (ulong)FD_PROGCACHE_DEPTH_MAX ));
      79           0 :     }
      80         141 :     uint next_idx = (uint)fd_prog_txnm_idx_query_const( ljoin->txn.map, &next_xid, UINT_MAX, ljoin->txn.pool );
      81         141 :     if( FD_UNLIKELY( next_idx==UINT_MAX ) ) break;
      82         132 :     if( FD_UNLIKELY( (ulong)next_idx >= txn_max ) )
      83           0 :       FD_LOG_CRIT(( "progcache: corruption detected (load_fork txn_idx=%u txn_max=%lu)", next_idx, txn_max ));
      84         132 :     fd_progcache_txn_t * candidate = &ljoin->txn.pool[ next_idx ];
      85             : 
      86         132 :     uint parent_idx = candidate->parent_idx;
      87         132 :     FD_TEST( parent_idx!=next_idx );
      88         132 :     lineage->fork[ i ] = next_xid;
      89         132 :     if( parent_idx==UINT_MAX ) {
      90          96 :       i++;
      91          96 :       break;
      92          96 :     }
      93          36 :     if( FD_UNLIKELY( (ulong)parent_idx >= txn_max ) )
      94           0 :       FD_LOG_CRIT(( "progcache: corruption detected (load_fork parent_idx=%u txn_max=%lu)", parent_idx, txn_max ));
      95          36 :     next_xid = ljoin->txn.pool[ parent_idx ].xid;
      96          36 :   }
      97             : 
      98         105 :   lineage->fork_depth = i;
      99             : 
     100         105 :   fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
     101         105 : }
     102             : 
     103             : static inline void
     104             : fd_progcache_load_fork( fd_progcache_t * cache,
     105         261 :                         fd_xid_t const * xid ) {
     106             :   /* Skip if already on the correct fork */
     107         261 :   fd_accdb_lineage_t * lineage = cache->lineage;
     108         261 :   if( FD_LIKELY( (!!lineage->fork_depth) & (!!fd_funk_txn_xid_eq( &lineage->fork[ 0 ], xid ) ) ) ) return;
     109         105 :   fd_progcache_load_fork_slow( cache, xid ); /* switch fork */
     110         105 : }
     111             : 
     112             : /* fd_progcache_query searches for a program cache entry on the current
     113             :    fork.  Stops short of an epoch boundary. */
     114             : 
     115             : static int
     116             : fd_progcache_search_chain( fd_progcache_t const *    cache,
     117             :                            ulong                     chain_idx,
     118             :                            fd_funk_rec_key_t const * key,
     119             :                            ulong                     revision_slot,
     120         165 :                            fd_progcache_rec_t **     out_rec ) { /* read locked */
     121         165 :   *out_rec = NULL;
     122             : 
     123         165 :   fd_progcache_join_t const *                ljoin     = cache->join;
     124         165 :   fd_accdb_lineage_t const *                 lineage   = cache->lineage;
     125         165 :   fd_prog_recm_shmem_t *                     shmap     = ljoin->rec.map->map;
     126         165 :   fd_prog_recm_shmem_private_chain_t const * chain_tbl = fd_prog_recm_shmem_private_chain_const( shmap, 0UL );
     127         165 :   fd_prog_recm_shmem_private_chain_t const * chain     = chain_tbl + chain_idx;
     128         165 :   fd_progcache_rec_t *                       rec_tbl   = ljoin->rec.pool->ele;
     129         165 :   ulong                                      rec_max   = fd_prog_recp_ele_max( ljoin->rec.pool );
     130         165 :   ulong                                      ver_cnt   = FD_VOLATILE_CONST( chain->ver_cnt );
     131             : 
     132             :   /* Start a speculative transaction for the chain containing revisions
     133             :      of the program cache key we are looking for. */
     134         165 :   ulong cnt = fd_prog_recm_private_vcnt_cnt( ver_cnt );
     135         165 :   if( FD_UNLIKELY( fd_prog_recm_private_vcnt_ver( ver_cnt )&1 ) ) {
     136           0 :     return FD_MAP_ERR_AGAIN; /* chain is locked */
     137           0 :   }
     138         165 :   FD_COMPILER_MFENCE();
     139         165 :   fd_racesan_hook( "prog_search_chain:post_ver_cnt" );
     140         165 :   uint ele_idx = chain->head_cidx;
     141             : 
     142             :   /* Walk the map chain, remember the best entry */
     143         165 :   fd_progcache_rec_t * best = NULL;
     144         207 :   for( ulong i=0UL; i<cnt; i++, ele_idx=FD_VOLATILE_CONST( rec_tbl[ ele_idx ].map_next ) ) {
     145          96 :     if( FD_UNLIKELY( (ulong)ele_idx >= rec_max ) ) return FD_MAP_ERR_AGAIN;
     146          96 :     fd_progcache_rec_t * rec = &rec_tbl[ ele_idx ];
     147             : 
     148             :     /* Skip over unrelated records (hash collision) */
     149          96 :     if( FD_UNLIKELY( !fd_funk_rec_key_eq( rec->pair.key, key ) ) ) continue;
     150             : 
     151             :     /* Skip over other revisions */
     152          96 :     if( FD_UNLIKELY( rec->slot!=revision_slot ) ) continue;
     153          54 :     fd_xid_t rec_xid[1];
     154          54 :     fd_funk_txn_xid_ld_atomic( rec_xid, rec->pair.xid );
     155          54 :     if( FD_UNLIKELY( !fd_accdb_lineage_has_xid( lineage, rec_xid ) ) ) continue;
     156             : 
     157          54 :     if( FD_UNLIKELY( rec->map_next==ele_idx ) ) return FD_MAP_ERR_AGAIN;
     158          54 :     if( FD_UNLIKELY( rec->map_next!=UINT_MAX && rec->map_next>=rec_max ) ) return FD_MAP_ERR_AGAIN;
     159          54 :     best = rec;
     160          54 :     break;
     161          54 :   }
     162         165 :   fd_racesan_hook( "prog_search_chain:pre_tryread" );
     163         165 :   if( best && FD_UNLIKELY( !fd_rwlock_tryread( &best->lock ) ) ) {
     164           0 :     return FD_MAP_ERR_AGAIN;
     165           0 :   }
     166         165 :   fd_racesan_hook( "prog_search_chain:post_tryread" );
     167             : 
     168             :   /* Retry if we were overrun */
     169         165 :   if( FD_UNLIKELY( FD_VOLATILE_CONST( chain->ver_cnt )!=ver_cnt ) ) {
     170           0 :     if( best ) fd_rwlock_unread( &best->lock );
     171           0 :     return FD_MAP_ERR_AGAIN;
     172           0 :   }
     173             : 
     174         165 :   *out_rec = best;
     175         165 :   return FD_MAP_SUCCESS;
     176         165 : }
     177             : 
     178             : static fd_progcache_rec_t * /* read locked */
     179             : fd_progcache_query( fd_progcache_t *          cache,
     180             :                     fd_xid_t const * xid,
     181             :                     fd_funk_rec_key_t const * key,
     182         165 :                     ulong                     revision_slot ) {
     183             :   /* Hash key to chain */
     184         165 :   fd_funk_xid_key_pair_t pair[1];
     185         165 :   fd_funk_txn_xid_copy( pair->xid, xid );
     186         165 :   fd_funk_rec_key_copy( pair->key, key );
     187         165 :   fd_prog_recm_t const * rec_map = cache->join->rec.map;
     188         165 :   ulong hash      = fd_funk_rec_key_hash( pair->key, rec_map->map->seed );
     189         165 :   ulong chain_idx = (hash & (rec_map->map->chain_cnt-1UL) );
     190             : 
     191             :   /* Traverse chain for candidate */
     192         165 :   fd_progcache_rec_t * rec = NULL;
     193         165 :   for(;;) {
     194         165 :     int err = fd_progcache_search_chain( cache, chain_idx, key, revision_slot, &rec );
     195         165 :     if( FD_LIKELY( err==FD_MAP_SUCCESS ) ) break;
     196           0 :     fd_racesan_hook( "prog_query:retry" );
     197           0 :     FD_SPIN_PAUSE();
     198             :     /* FIXME backoff */
     199           0 :   }
     200             : 
     201         165 :   return rec;
     202         165 : }
     203             : 
     204             : fd_progcache_rec_t * /* read locked */
     205             : fd_progcache_peek( fd_progcache_t    * cache,
     206             :                    fd_xid_t    const * xid,
     207             :                    fd_pubkey_t const * prog_addr,
     208         165 :                    ulong               revision_slot ) {
     209         165 :   if( FD_UNLIKELY( !cache || !cache->join->shmem ) ) FD_LOG_CRIT(( "NULL progcache" ));
     210         165 :   fd_progcache_load_fork( cache, xid );
     211         165 :   fd_funk_rec_key_t key[1]; fd_memcpy( key->uc, prog_addr->hash, 32UL );
     212         165 :   fd_progcache_rec_t * rec = fd_progcache_query( cache, xid, key, revision_slot );
     213         165 :   if( FD_UNLIKELY( !rec ) ) return NULL;
     214          54 :   return rec;
     215         165 : }
     216             : 
     217             : static void
     218             : fd_progcache_rec_push_tail( fd_progcache_rec_t * rec_pool,
     219             :                             fd_progcache_rec_t * rec,
     220             :                             uint *               rec_head_idx, /* write locked (txn) */
     221             :                             uint *               rec_tail_idx,
     222          21 :                             ulong                rec_max ) {
     223          21 :   uint rec_idx      = (uint)( rec - rec_pool );
     224          21 :   uint rec_prev_idx = *rec_tail_idx;
     225             : 
     226          21 :   if( FD_UNLIKELY( (ulong)rec_idx >= rec_max ) )
     227           0 :     FD_LOG_CRIT(( "progcache: corruption detected (push_tail rec_idx=%u rec_max=%lu)", rec_idx, rec_max ));
     228          21 :   if( FD_UNLIKELY( rec_prev_idx!=UINT_MAX && (ulong)rec_prev_idx >= rec_max ) )
     229           0 :     FD_LOG_CRIT(( "progcache: corruption detected (push_tail rec_prev_idx=%u rec_max=%lu)", rec_prev_idx, rec_max ));
     230             : 
     231          21 :   rec->prev_idx = rec_prev_idx;
     232          21 :   rec->next_idx = UINT_MAX;
     233             : 
     234          21 :   if( rec_prev_idx==UINT_MAX ) {
     235          21 :     *rec_head_idx = rec_idx;
     236          21 :   } else {
     237           0 :     rec_pool[ rec_prev_idx ].next_idx = rec_idx;
     238           0 :   }
     239          21 :   *rec_tail_idx = rec_idx;
     240          21 : }
     241             : 
     242             : __attribute__((warn_unused_result))
     243             : static int
     244             : fd_progcache_push( fd_progcache_join_t * cache,
     245             :                    fd_progcache_txn_t *  txn, /* read locked */
     246             :                    fd_progcache_rec_t *  rec,
     247             :                    void const *          prog_addr,
     248          81 :                    ulong                 revision_slot ) {
     249             : 
     250             :   /* Determine record's xid-key pair */
     251             : 
     252          81 :   rec->prev_idx = UINT_MAX;
     253          81 :   rec->next_idx = UINT_MAX;
     254          81 :   memcpy( rec->pair.key, prog_addr, 32UL );
     255          81 :   if( FD_UNLIKELY( txn ) ) {
     256          21 :     fd_funk_txn_xid_copy( rec->pair.xid, &txn->xid );
     257          60 :   } else {
     258          60 :     fd_funk_txn_xid_set_root( rec->pair.xid );
     259          60 :   }
     260             : 
     261             :   /* Lock rec_map chain, entering critical section */
     262             : 
     263          81 :   struct {
     264          81 :     fd_prog_recm_txn_t txn[1];
     265          81 :     fd_prog_recm_txn_private_info_t info[1];
     266          81 :   } _map_txn;
     267          81 :   fd_prog_recm_txn_t * map_txn = fd_prog_recm_txn_init( _map_txn.txn, cache->rec.map, 1UL );
     268          81 :   fd_prog_recm_txn_add( map_txn, &rec->pair, 1 );
     269          81 :   int txn_err = fd_prog_recm_txn_try( map_txn, FD_MAP_FLAG_BLOCKING );
     270          81 :   if( FD_UNLIKELY( txn_err!=FD_MAP_SUCCESS ) ) {
     271           0 :     FD_LOG_CRIT(( "Failed to insert progcache record: cannot lock funk rec map chain: %i-%s", txn_err, fd_map_strerror( txn_err ) ));
     272           0 :   }
     273          81 :   fd_racesan_hook( "prog_push:post_chain_lock" );
     274             : 
     275             :   /* Check if record exists */
     276             : 
     277          81 :   fd_prog_recm_query_t query[1];
     278          81 :   int query_err = fd_prog_recm_txn_query( cache->rec.map, &rec->pair, NULL, query, 0 );
     279          81 :   if( FD_UNLIKELY( query_err==FD_MAP_SUCCESS ) ) {
     280             :     /* Always replace existing rooted records */
     281           0 :     fd_progcache_rec_t * prev_rec = query->ele;
     282           0 :     if( fd_funk_txn_xid_eq_root( rec->pair.xid ) && prev_rec->slot < revision_slot ) {
     283           0 :       fd_rwlock_write( &prev_rec->lock );
     284           0 :       fd_prog_recm_txn_remove( cache->rec.map, &rec->pair, NULL, query, FD_MAP_FLAG_USE_HINT );
     285           0 :       fd_progcache_val_free( prev_rec, cache );
     286           0 :       fd_rwlock_unwrite( &prev_rec->lock );
     287           0 :       fd_prog_clock_remove( cache->clock.bits, (ulong)( prev_rec - cache->rec.pool->ele ) );
     288           0 :       fd_prog_recp_release( cache->rec.pool, prev_rec, 1 );
     289           0 :     } else {
     290           0 :       fd_prog_recm_txn_test( map_txn );
     291           0 :       fd_prog_recm_txn_fini( map_txn );
     292           0 :       return 0;
     293           0 :     }
     294          81 :   } else if( FD_UNLIKELY( query_err!=FD_MAP_ERR_KEY ) ) {
     295           0 :     FD_LOG_CRIT(( "fd_prog_recm_txn_query failed: %i-%s", query_err, fd_map_strerror( query_err ) ));
     296           0 :   }
     297             : 
     298             :   /* Phase 4: Insert new record */
     299             : 
     300          81 :   int insert_err = fd_prog_recm_txn_insert( cache->rec.map, rec );
     301          81 :   if( FD_UNLIKELY( insert_err!=FD_MAP_SUCCESS ) ) {
     302           0 :     FD_LOG_CRIT(( "fd_prog_recm_txn_insert failed: %i-%s", insert_err, fd_map_strerror( insert_err ) ));
     303           0 :   }
     304          81 :   fd_racesan_hook( "prog_push:post_map_insert" );
     305             : 
     306             :   /* Phase 5: Insert rec into rec_map */
     307             : 
     308          81 :   ulong rec_max = fd_prog_recp_ele_max( cache->rec.pool );
     309          81 :   if( txn ) {
     310          21 :     fd_progcache_rec_push_tail( cache->rec.pool->ele,
     311          21 :         rec,
     312          21 :         &txn->rec_head_idx,
     313          21 :         &txn->rec_tail_idx,
     314          21 :         rec_max );
     315          21 :     uint txn_idx_computed = (uint)( txn - cache->txn.pool );
     316          21 :     ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     317          21 :     if( FD_UNLIKELY( (ulong)txn_idx_computed >= txn_max ) )
     318           0 :       FD_LOG_CRIT(( "progcache: corruption detected (push txn_idx=%u txn_max=%lu)", txn_idx_computed, txn_max ));
     319          21 :     atomic_store_explicit( &rec->txn_idx, txn_idx_computed, memory_order_release );
     320          21 :   }
     321             : 
     322             :   /* Phase 6: Finish rec_map transaction */
     323             : 
     324          81 :   int test_err = fd_prog_recm_txn_test( map_txn );
     325          81 :   if( FD_UNLIKELY( test_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_txn_test failed: %i-%s", test_err, fd_map_strerror( test_err ) ));
     326          81 :   fd_prog_recm_txn_fini( map_txn );
     327             : 
     328             :   /* Phase 7: Mark record as recently accessed */
     329             : 
     330          81 :   ulong rec_clock_idx = (ulong)( rec - cache->rec.pool->ele );
     331          81 :   if( FD_UNLIKELY( rec_clock_idx >= rec_max ) )
     332           0 :     FD_LOG_CRIT(( "progcache: corruption detected (push rec_idx=%lu rec_max=%lu)", rec_clock_idx, rec_max ));
     333          81 :   fd_prog_clock_touch( cache->clock.bits, rec_clock_idx );
     334             : 
     335          81 :   return 1;
     336          81 : }
     337             : 
     338             : /* insert_params captures all environment parameters required to load a
     339             :    program revision into cache. */
     340             : 
     341             : struct insert_params {
     342             :   void const *            prog_addr;
     343             :   ulong                   revision_slot;
     344             :   fd_sbpf_elf_info_t      elf_info;
     345             :   fd_sbpf_loader_config_t config;
     346             :   fd_features_t const *   features;
     347             :   uchar const *           bin;
     348             :   ulong                   bin_sz;
     349             :   int                     peek_err;
     350             : };
     351             : 
     352             : typedef struct insert_params insert_params_t;
     353             : 
     354             : static insert_params_t *
     355             : insert_params( insert_params_t *          p,
     356             :                fd_xid_t const *           load_xid,
     357             :                void const *               prog_addr,
     358             :                fd_prog_load_env_t const * env,
     359             :                fd_accdb_ro_t *            prog_ro,
     360          81 :                fd_prog_info_t const *     info ) {
     361          81 :   memset( p, 0, sizeof(insert_params_t) );
     362             : 
     363             :   /* Derive executable info */
     364          81 :   uchar const * bin           = (uchar const *)fd_accdb_ref_data_const( prog_ro ) + info->elf_off;
     365          81 :   ulong         bin_sz        = info->elf_sz;
     366          81 :   ulong         revision_slot = fd_progcache_revision_slot( env->epoch_slot0, info->deploy_slot );
     367             : 
     368             :   /* Pre-flight checks, determine required buffer size */
     369             : 
     370          81 :   fd_features_t const * features  = env->features;
     371          81 :   ulong         const   load_slot = load_xid->ul[0];
     372          81 :   fd_prog_versions_t versions = fd_prog_versions( features, load_slot );
     373          81 :   fd_sbpf_elf_info_t elf_info;
     374          81 :   fd_sbpf_loader_config_t config = {
     375          81 :     .sbpf_min_version = versions.min_sbpf_version,
     376          81 :     .sbpf_max_version = versions.max_sbpf_version,
     377          81 :   };
     378          81 :   int peek_err = fd_sbpf_elf_peek( &elf_info, bin, bin_sz, &config );
     379             : 
     380          81 :   *p = (insert_params_t) {
     381          81 :     .prog_addr     = prog_addr,
     382          81 :     .revision_slot = revision_slot,
     383          81 :     .features      = features,
     384          81 :     .bin           = !peek_err ? bin    : NULL,
     385          81 :     .bin_sz        = !peek_err ? bin_sz : 0UL,
     386          81 :     .peek_err      = peek_err,
     387          81 :     .elf_info      = elf_info,
     388          81 :     .config        = config
     389          81 :   };
     390          81 :   return p;
     391          81 : }
     392             : 
     393             : /* fd_progcache_spill_open loads a program into the cache spill buffer.
     394             :    The spill area is an "emergency" area for temporary program loads in
     395             :    case the record pool/heap are too contended. */
     396             : 
     397             : static fd_progcache_rec_t * /* read locked */
     398             : fd_progcache_spill_open( fd_progcache_t *        cache,
     399           0 :                          insert_params_t const * params ) {
     400           0 :   fd_progcache_join_t *  join  = cache->join;
     401           0 :   fd_progcache_shmem_t * shmem = join->shmem;
     402           0 :   if( !cache->spill_active ) fd_rwlock_write( &shmem->spill.lock );
     403           0 :   else                       FD_TEST( FD_VOLATILE_CONST( shmem->spill.lock.value )==FD_RWLOCK_WRITE_LOCK );
     404             : 
     405             :   /* Allocate record */
     406             : 
     407           0 :   if( FD_UNLIKELY( shmem->spill.rec_used >= FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
     408           0 :     FD_LOG_CRIT(( "spill buffer overflow: rec_used=%u rec_max=%lu", shmem->spill.rec_used, FD_MAX_INSTRUCTION_STACK_DEPTH ));
     409           0 :   }
     410           0 :   cache->spill_active++;
     411           0 :   uint rec_idx = shmem->spill.rec_used++;
     412           0 :   shmem->spill.spad_off[ rec_idx ] = shmem->spill.spad_used;
     413           0 :   fd_progcache_rec_t * rec = &shmem->spill.rec[ rec_idx ];
     414           0 :   memset( rec, 0, sizeof(fd_progcache_rec_t) );
     415           0 :   rec->lock.value = 1; /* read lock; no concurrency, don't need CAS */
     416           0 :   rec->exists     = 1;
     417           0 :   rec->slot       = params->revision_slot;
     418             : 
     419             :   /* Load program */
     420             : 
     421           0 :   if( !params->peek_err ) {
     422           0 :     ulong off0 = fd_ulong_align_up( shmem->spill.spad_used, fd_progcache_val_align() );
     423           0 :     ulong off1 = off0 + fd_progcache_val_footprint( &params->elf_info );
     424           0 :     if( FD_UNLIKELY( off1 > FD_PROGCACHE_SPAD_MAX ) ) {
     425           0 :       FD_LOG_CRIT(( "spill buffer overflow: spad_used=%u val_sz=%lu spad_max=%lu", shmem->spill.spad_used, off1-off0, FD_PROGCACHE_SPAD_MAX ));
     426           0 :     }
     427           0 :     rec->data_gaddr = fd_wksp_gaddr_fast( join->data_base, shmem->spill.spad + off0 );
     428           0 :     rec->data_max   = (uint)( off1 - off0 );
     429             : 
     430           0 :     long dt = -fd_tickcount();
     431           0 :     if( FD_LIKELY( fd_progcache_rec_load( rec, join->data_base, &params->elf_info, &params->config, params->revision_slot, params->features, params->bin, params->bin_sz, cache->scratch, cache->scratch_sz ) ) ) {
     432             :       /* Valid program, allocate data */
     433           0 :       shmem->spill.spad_used = (uint)off1;
     434           0 :     } else {
     435           0 :       fd_progcache_rec_nx( rec );
     436           0 :     }
     437           0 :     dt += fd_tickcount();
     438           0 :     cache->metrics->cum_load_ticks += (ulong)dt;
     439             : 
     440           0 :   } else {
     441           0 :     rec->data_gaddr = 0UL;
     442           0 :     rec->data_max   = 0U;
     443           0 :   }
     444             : 
     445           0 :   cache->metrics->spill_cnt++;
     446           0 :   cache->metrics->spill_tot_sz += rec->rodata_sz;
     447             : 
     448           0 :   FD_TEST( rec->exists );
     449           0 :   return rec;
     450           0 : }
     451             : 
     452             : /* fd_progcache_insert allocates a cache entry, loads a program into it,
     453             :    and publishes the cache entry to the global index (recm).  If an OOM
     454             :    condition is detected, attempts to run the cache eviction algo, and
     455             :    finally falls back to using the spill buffer.  Returns NULL if the
     456             :    insertion raced with another thread (frees any previously allocated
     457             :    resource in that case). */
     458             : 
     459             : static fd_progcache_rec_t * /* read locked */
     460             : fd_progcache_insert( fd_progcache_t *        cache,
     461          81 :                      insert_params_t const * params ) {
     462          81 :   fd_progcache_join_t *           ljoin         = cache->join;
     463          81 :   void const *                    prog_addr     = params->prog_addr;
     464          81 :   int                             peek_err      = params->peek_err;
     465          81 :   fd_sbpf_elf_info_t const *      elf_info      = &params->elf_info;
     466          81 :   fd_sbpf_loader_config_t const * config        = &params->config;
     467          81 :   ulong                           revision_slot = params->revision_slot;
     468          81 :   fd_features_t const *           features      = params->features;
     469          81 :   uchar const *                   bin           = params->bin;
     470          81 :   ulong                           bin_sz        = params->bin_sz;
     471             : 
     472             : 
     473             :   /* Allocate record and heap space */
     474             : 
     475          81 :   fd_progcache_rec_t * rec = fd_prog_recp_acquire( ljoin->rec.pool, NULL, 1, NULL );
     476          81 :   if( FD_UNLIKELY( !rec ) ) {
     477           0 :     cache->metrics->oom_desc_cnt++;
     478           0 :     fd_prog_clock_evict( cache, 4UL, 0UL );
     479           0 :     rec = fd_prog_recp_acquire( ljoin->rec.pool, NULL, 1, NULL );
     480           0 :     if( FD_UNLIKELY( !rec ) ) {
     481             :       /* Out of memory (record table) */
     482           0 :       return fd_progcache_spill_open( cache, params );
     483           0 :     }
     484           0 :   }
     485          81 :   memset( rec, 0, sizeof(fd_progcache_rec_t) );
     486          81 :   rec->exists       = 1;
     487          81 :   rec->slot         = revision_slot;
     488          81 :   rec->txn_idx      = UINT_MAX;
     489          81 :   rec->reclaim_next = UINT_MAX;
     490             : 
     491          81 :   if( FD_LIKELY( peek_err==FD_SBPF_ELF_SUCCESS ) ) {
     492          81 :     ulong val_align     = fd_progcache_val_align();
     493          81 :     ulong val_footprint = fd_progcache_val_footprint( elf_info );
     494          81 :     if( FD_UNLIKELY( !fd_progcache_val_alloc( rec, ljoin, val_align, val_footprint ) ) ) {
     495           0 :       cache->metrics->oom_heap_cnt++;
     496           0 :       fd_prog_clock_evict( cache, 0UL, 16UL<<20 );
     497           0 :       if( FD_UNLIKELY( !fd_progcache_val_alloc( rec, ljoin, val_align, val_footprint ) ) ) {
     498             :         /* Out of memory (heap) */
     499           0 :         rec->exists = 0;
     500           0 :         fd_prog_recp_release( ljoin->rec.pool, rec, 1 );
     501           0 :         return fd_progcache_spill_open( cache, params );
     502           0 :       }
     503           0 :     }
     504          81 :   } else {
     505           0 :     fd_progcache_rec_nx( rec );
     506           0 :   }
     507             : 
     508             :   /* Publish cache entry to index */
     509             : 
     510             :   /* Acquires rec->lock before txn.rwlock (inverse of the documented
     511             :      lock order).  Safe because the record was just allocated and is not
     512             :      yet visible to other threads. */
     513          81 :   fd_rwlock_write( &rec->lock );
     514          81 :   fd_racesan_hook( "prog_insert:pre_push" );
     515          81 :   fd_xid_t const * xid = fd_lineage_xid( cache->lineage, revision_slot );
     516          81 :   fd_rwlock_read( &ljoin->shmem->txn.rwlock );
     517          81 :   fd_progcache_txn_t * txn = NULL;
     518          81 :   if( xid ) txn = (fd_progcache_txn_t *)fd_prog_txnm_ele_query_const( ljoin->txn.map, xid, NULL, ljoin->txn.pool );
     519          81 :   if( txn ) fd_rwlock_write( &txn->lock );
     520          81 :   int push_ok = fd_progcache_push( ljoin, txn, rec, prog_addr, revision_slot );
     521          81 :   if( txn ) fd_rwlock_unwrite( &txn->lock );
     522          81 :   if( FD_UNLIKELY( !push_ok ) ) {
     523           0 :     fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
     524           0 :     fd_rwlock_unwrite( &rec->lock );
     525           0 :     fd_progcache_val_free( rec, ljoin );
     526           0 :     fd_prog_recp_release( ljoin->rec.pool, rec, 1 );
     527           0 :     return NULL;
     528           0 :   }
     529          81 :   fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
     530          81 :   fd_racesan_hook( "prog_insert:pre_load" );
     531             : 
     532             :   /* Load program
     533             :      (The write lock was acquired before loading such that another
     534             :      thread trying to load the same record instead waits for us to
     535             :      complete) */
     536             : 
     537          81 :   if( FD_LIKELY( peek_err==FD_SBPF_ELF_SUCCESS ) ) {
     538          81 :     long dt = -fd_tickcount();
     539          81 :     if( FD_UNLIKELY( !fd_progcache_rec_load( rec, ljoin->data_base, elf_info, config, revision_slot, features, bin, bin_sz, cache->scratch, cache->scratch_sz ) ) ) {
     540             :       /* Not a valid program (mark cache entry as non-executable) */
     541           3 :       fd_progcache_val_free( rec, ljoin );
     542           3 :       fd_progcache_rec_nx( rec );
     543           3 :     }
     544          81 :     dt += fd_tickcount();
     545          81 :     cache->metrics->cum_load_ticks += (ulong)dt;
     546          81 :   }
     547             : 
     548          81 :   fd_rwlock_demote( &rec->lock );
     549             : 
     550          81 :   cache->metrics->fill_cnt++;
     551          81 :   cache->metrics->fill_tot_sz += rec->rodata_sz;
     552          81 :   FD_TEST( rec->exists );
     553          81 :   return rec;
     554          81 : }
     555             : 
     556             : fd_progcache_rec_t * /* read locked */
     557             : fd_progcache_pull( fd_progcache_t           * cache,
     558             :                    fd_xid_t           const * xid,
     559             :                    fd_pubkey_t        const * prog_addr,
     560             :                    fd_prog_load_env_t const * env,
     561             :                    fd_accdb_ro_t            * prog_ro,
     562          96 :                    fd_pubkey_t        const * program_owner ) {
     563          96 :   if( FD_UNLIKELY( !cache || !cache->join->shmem ) ) FD_LOG_CRIT(( "NULL progcache" ));
     564          96 :   long dt = -fd_tickcount();
     565          96 :   fd_progcache_load_fork( cache, xid );
     566          96 :   cache->metrics->lookup_cnt++;
     567             : 
     568          96 :   fd_prog_info_t info[1];
     569          96 :   if( FD_UNLIKELY( !fd_prog_info( info, prog_ro, program_owner ) ) ) return NULL;
     570          84 :   ulong revision_slot = fd_progcache_revision_slot( env->epoch_slot0, info->deploy_slot );
     571             : 
     572          84 :   insert_params_t insert[1];
     573          84 :   fd_progcache_rec_t * found_rec = NULL;
     574          84 :   for( int attempt=0;; attempt++ ) {
     575          84 :     found_rec = fd_progcache_peek( cache, xid, prog_addr, revision_slot );
     576          84 :     if( FD_LIKELY( found_rec ) ) {
     577           3 :       cache->metrics->hit_cnt++;
     578           3 :       break;
     579           3 :     }
     580          81 :     if( attempt==0 ) insert_params( insert, xid, prog_addr, env, prog_ro, info );
     581          81 :     found_rec = fd_progcache_insert( cache, insert );
     582          81 :     if( FD_LIKELY( found_rec ) ) {
     583          81 :       cache->metrics->miss_cnt++;
     584          81 :       break;
     585          81 :     }
     586           0 :     if( FD_UNLIKELY( attempt>=4 ) ) {
     587             :       /* Extremely unlikely case: four separate attempts resulted in
     588             :          contention */
     589           0 :       return fd_progcache_spill_open( cache, insert );
     590           0 :     }
     591           0 :   }
     592             : 
     593          84 :   dt += fd_tickcount();
     594          84 :   cache->metrics->cum_pull_ticks += (ulong)dt;
     595          84 :   return found_rec;
     596          84 : }
     597             : 
     598             : static void
     599           0 : fd_progcache_spill_close( fd_progcache_t * cache ) {
     600           0 :   FD_TEST( cache->spill_active );
     601           0 :   cache->spill_active--;
     602             : 
     603           0 :   fd_progcache_shmem_t * shmem = cache->join->shmem;
     604             : 
     605             :   /* Cascade: rewind rec_used and spad_used while the top record is
     606             :      closed.  This reclaims spill spad memory in LIFO order. */
     607           0 :   while( shmem->spill.rec_used > 0 &&
     608           0 :          !shmem->spill.rec[ shmem->spill.rec_used-1 ].exists ) {
     609           0 :     shmem->spill.rec_used--;
     610           0 :     shmem->spill.spad_used = shmem->spill.spad_off[ shmem->spill.rec_used ];
     611           0 :   }
     612             : 
     613           0 :   if( cache->spill_active==0 ) {
     614           0 :     fd_rwlock_t * spill_lock = &shmem->spill.lock;
     615           0 :     FD_TEST( spill_lock->value==0xFFFF );
     616           0 :     FD_TEST( shmem->spill.rec_used==0 );
     617           0 :     FD_TEST( shmem->spill.spad_used==0 );
     618           0 :     fd_rwlock_unwrite( spill_lock );
     619           0 :   }
     620           0 : }
     621             : 
     622             : void
     623             : fd_progcache_rec_close( fd_progcache_t *     cache,
     624         135 :                         fd_progcache_rec_t * rec ) {
     625         135 :   if( FD_UNLIKELY( !rec ) ) return;
     626         135 :   if( FD_UNLIKELY( !rec->exists ) ) FD_LOG_CRIT(( "use-after-free: progcache record %p is dead", (void *)rec ));
     627         135 :   FD_TEST( FD_VOLATILE_CONST( rec->lock.value )!=0 );
     628         135 :   fd_rwlock_unread( &rec->lock );
     629         135 :   fd_progcache_shmem_t * shmem = cache->join->shmem;
     630         135 :   if( rec >= shmem->spill.rec &&
     631         135 :       rec <  shmem->spill.rec + FD_MAX_INSTRUCTION_STACK_DEPTH ) {
     632           0 :     rec->exists = 0;
     633           0 :     fd_progcache_spill_close( cache );
     634           0 :   }
     635         135 : }

Generated by: LCOV version 1.14