LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_admin.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 130 430 30.2 %
Date: 2026-03-19 18:19:27 Functions: 5 22 22.7 %

          Line data    Source code
       1             : #include "fd_progcache.h"
       2             : #include "fd_progcache_admin.h"
       3             : #include "fd_progcache_rec.h"
       4             : #include "fd_prog_load.h"
       5             : #include "../runtime/program/fd_bpf_loader_program.h"
       6             : #include "../runtime/program/fd_loader_v4_program.h"
       7             : #include "../runtime/fd_system_ids.h"
       8             : #include "../../util/wksp/fd_wksp_private.h"
       9             : 
      10             : FD_TL fd_progcache_admin_metrics_t fd_progcache_admin_metrics_g;
      11             : 
      12             : /* Algorithm to estimate size of cache metadata structures (rec_pool
      13             :    object pool and rec_map hashchain table).
      14             : 
      15             :    FIXME Carefully balance this */
      16             : 
      17             : static ulong
      18             : fd_progcache_est_rec_max1( ulong wksp_footprint,
      19           0 :                            ulong mean_cache_entry_size ) {
      20           0 :   return wksp_footprint / mean_cache_entry_size;
      21           0 : }
      22             : 
      23             : ulong
      24             : fd_progcache_est_rec_max( ulong wksp_footprint,
      25           0 :                           ulong mean_cache_entry_size ) {
      26           0 :   ulong est = fd_progcache_est_rec_max1( wksp_footprint, mean_cache_entry_size );
      27           0 :   if( FD_UNLIKELY( est>(1UL<<31) ) ) FD_LOG_ERR(( "fd_progcache_est_rec_max(wksp_footprint=%lu,mean_cache_entry_size=%lu) failed: invalid parameters", wksp_footprint, mean_cache_entry_size ));
      28           0 :   return fd_ulong_max( est, 2048UL );
      29           0 : }
      30             : 
      31             : /* Begin transaction-level operations.  It is assumed that txn data
      32             :    structures are not concurrently modified.  This includes txn_pool and
      33             :    txn_map. */
      34             : 
      35             : void
      36             : fd_progcache_txn_attach_child( fd_progcache_join_t *      cache,
      37             :                                fd_progcache_xid_t const * xid_parent,
      38       55100 :                                fd_progcache_xid_t const * xid_new ) {
      39       55100 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
      40             : 
      41       55100 :   if( FD_UNLIKELY( fd_prog_txnm_idx_query_const( cache->txn.map, xid_new, ULONG_MAX, cache->txn.pool )!=ULONG_MAX ) ) {
      42           0 :     FD_LOG_ERR(( "fd_progcache_txn_attach_child failed: xid %lu:%lu already in use",
      43           0 :                  xid_new->ul[0], xid_new->ul[1] ));
      44           0 :   }
      45       55100 :   if( FD_UNLIKELY( fd_prog_txnp_free( cache->txn.pool )==0UL ) ) {
      46           0 :     FD_LOG_ERR(( "fd_progcache_txn_attach_child failed: transaction object pool out of memory" ));
      47           0 :   }
      48             : 
      49       55100 :   ulong  parent_idx;
      50       55100 :   uint * _child_head_idx;
      51       55100 :   uint * _child_tail_idx;
      52             : 
      53       55100 :   if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid_parent, cache->shmem->txn.last_publish ) ) ) {
      54             : 
      55       30172 :     parent_idx = FD_FUNK_TXN_IDX_NULL;
      56             : 
      57       30172 :     _child_head_idx = &cache->shmem->txn.child_head_idx;
      58       30172 :     _child_tail_idx = &cache->shmem->txn.child_tail_idx;
      59             : 
      60       30172 :   } else {
      61             : 
      62       24928 :     parent_idx = fd_prog_txnm_idx_query( cache->txn.map, xid_parent, ULONG_MAX, cache->txn.pool );
      63       24928 :     if( FD_UNLIKELY( parent_idx==ULONG_MAX ) ) {
      64           0 :       FD_LOG_CRIT(( "fd_funk_txn_prepare failed: user provided invalid parent XID %lu:%lu",
      65           0 :                     xid_parent->ul[0], xid_parent->ul[1] ));
      66           0 :     }
      67             : 
      68       24928 :     _child_head_idx = &cache->txn.pool[ parent_idx ].child_head_idx;
      69       24928 :     _child_tail_idx = &cache->txn.pool[ parent_idx ].child_tail_idx;
      70             : 
      71       24928 :   }
      72             : 
      73       55100 :   uint txn_idx = (uint)fd_prog_txnp_idx_acquire( cache->txn.pool );
      74       55100 :   if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) FD_LOG_ERR(( "fd_funk_txn_prepare failed: transaction object pool out of memory" ));
      75       55100 :   fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
      76       55100 :   fd_funk_txn_xid_copy( &txn->xid, xid_new );
      77             : 
      78       55100 :   uint sibling_prev_idx = *_child_tail_idx;
      79             : 
      80       55100 :   int first_born = sibling_prev_idx==UINT_MAX;
      81             : 
      82       55100 :   txn->parent_idx       = (uint)parent_idx;
      83       55100 :   txn->child_head_idx   = UINT_MAX;
      84       55100 :   txn->child_tail_idx   = UINT_MAX;
      85       55100 :   txn->sibling_prev_idx = (uint)sibling_prev_idx;
      86       55100 :   txn->sibling_next_idx = UINT_MAX;
      87             : 
      88       55100 :   txn->rec_head_idx = UINT_MAX;
      89       55100 :   txn->rec_tail_idx = UINT_MAX;
      90             : 
      91             :   /* TODO: consider branchless impl */
      92       55100 :   if( FD_LIKELY( first_born ) ) *_child_head_idx            = (uint)txn_idx; /* opt for non-compete */
      93           3 :   else cache->txn.pool[ sibling_prev_idx ].sibling_next_idx = (uint)txn_idx;
      94             : 
      95       55100 :   *_child_tail_idx = (uint)txn_idx;
      96             : 
      97       55100 :   fd_prog_txnm_idx_insert( cache->txn.map, txn_idx, cache->txn.pool );
      98             : 
      99       55100 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     100             : 
     101       55100 :   FD_LOG_INFO(( "progcache xid %lu:%lu: created with parent %lu:%lu",
     102       55100 :                 xid_new   ->ul[0], xid_new   ->ul[1],
     103       55100 :                 xid_parent->ul[0], xid_parent->ul[1] ));
     104       55100 : }
     105             : 
     106             : static void
     107             : fd_progcache_rec_list_append( fd_progcache_rec_t * rec_pool,
     108             :                               uint *               list_head,
     109             :                               uint *               list_tail,
     110             :                               uint                 rec_head,
     111           0 :                               uint                 rec_tail ) {
     112           0 :   if( rec_head==UINT_MAX ) return;
     113           0 :   if( *list_tail!=UINT_MAX ) {
     114           0 :     rec_pool[ *list_tail ].next_idx = rec_head;
     115           0 :   } else {
     116           0 :     *list_head = rec_head;
     117           0 :   }
     118           0 :   *list_tail = rec_tail;
     119           0 : }
     120             : 
     121             : static void
     122             : fd_progcache_txn_cancel_one( fd_progcache_join_t * cache,
     123             :                              fd_progcache_txn_t *  txn,
     124             :                              uint *                cancel_head,
     125           0 :                              uint *                cancel_tail ) {
     126           0 :   FD_LOG_INFO(( "progcache txn laddr=%p xid %lu:%lu: cancel", (void *)txn, txn->xid.ul[0], txn->xid.ul[1] ));
     127           0 :   fd_rwlock_write( &txn->lock );
     128             : 
     129           0 :   if( FD_UNLIKELY( txn->child_head_idx!=UINT_MAX ||
     130           0 :                    txn->child_tail_idx!=UINT_MAX ) ) {
     131           0 :     FD_LOG_CRIT(( "fd_progcache_txn_cancel failed: txn at %p with xid %lu:%lu has children (data corruption?)",
     132           0 :                   (void *)txn, txn->xid.ul[0], txn->xid.ul[1] ));
     133           0 :   }
     134             : 
     135             :   /* Remove records */
     136             : 
     137           0 :   for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; idx = cache->rec.pool->ele[ idx ].next_idx ) {
     138           0 :     fd_progcache_rec_t * rec = &cache->rec.pool->ele[ idx ];
     139           0 :     if( rec->exists ) {
     140           0 :       fd_prog_recm_query_t query[1];
     141           0 :       int remove_err = fd_prog_recm_remove( cache->rec.map, &rec->pair, NULL, query, FD_MAP_FLAG_BLOCKING );
     142           0 :       if( FD_UNLIKELY( remove_err ) ) FD_LOG_CRIT(( "fd_funk_rec_map_remove failed: %i-%s", remove_err, fd_map_strerror( remove_err ) ));
     143           0 :     }
     144           0 :   }
     145             : 
     146             :   /* Detach record list from txn and append to cancel list */
     147             : 
     148           0 :   fd_progcache_rec_list_append( cache->rec.pool->ele,
     149           0 :                                 cancel_head, cancel_tail,
     150           0 :                                 txn->rec_head_idx, txn->rec_tail_idx );
     151           0 :   txn->rec_head_idx = UINT_MAX;
     152           0 :   txn->rec_tail_idx = UINT_MAX;
     153             : 
     154             :   /* Remove transaction from fork graph */
     155             : 
     156           0 :   uint self_idx = (uint)( txn - cache->txn.pool );
     157           0 :   uint prev_idx = txn->sibling_prev_idx;
     158           0 :   uint next_idx = txn->sibling_next_idx;
     159           0 :   if( next_idx!=UINT_MAX ) {
     160           0 :     cache->txn.pool[ next_idx ].sibling_prev_idx = prev_idx;
     161           0 :   }
     162           0 :   if( prev_idx!=UINT_MAX ) {
     163           0 :     cache->txn.pool[ prev_idx ].sibling_next_idx = next_idx;
     164           0 :   }
     165           0 :   if( txn->parent_idx!=UINT_MAX ) {
     166           0 :     fd_progcache_txn_t * parent = &cache->txn.pool[ txn->parent_idx ];
     167           0 :     if( parent->child_head_idx==self_idx ) parent->child_head_idx = next_idx;
     168           0 :     if( parent->child_tail_idx==self_idx ) parent->child_tail_idx = prev_idx;
     169           0 :   } else {
     170           0 :     if( cache->shmem->txn.child_head_idx==self_idx ) cache->shmem->txn.child_head_idx = next_idx;
     171           0 :     if( cache->shmem->txn.child_tail_idx==self_idx ) cache->shmem->txn.child_tail_idx = prev_idx;
     172           0 :   }
     173             : 
     174             :   /* Remove transaction from index */
     175             : 
     176           0 :   if( FD_UNLIKELY( !fd_prog_txnm_ele_remove( cache->txn.map, &txn->xid, NULL, cache->txn.pool ) ) ) {
     177           0 :     FD_LOG_CRIT(( "fd_progcache_txn_cancel failed: fd_funk_txn_map_remove(%lu:%lu) failed",
     178           0 :                   txn->xid.ul[0], txn->xid.ul[1] ));
     179           0 :   }
     180             : 
     181             :   /* Free transaction object */
     182             : 
     183           0 :   fd_rwlock_unwrite( &txn->lock );
     184           0 :   fd_prog_txnp_ele_release( cache->txn.pool, txn );
     185           0 : }
     186             : 
     187             : /* Cancels txn and all children */
     188             : 
     189             : static void
     190             : fd_progcache_txn_cancel_tree( fd_progcache_join_t * cache,
     191             :                               fd_progcache_txn_t *  txn,
     192             :                               uint *                cancel_head,
     193           0 :                               uint *                cancel_tail ) {
     194           0 :   for(;;) {
     195           0 :     uint child_idx = txn->child_head_idx;
     196           0 :     if( child_idx==UINT_MAX ) break;
     197           0 :     fd_progcache_txn_t * child = &cache->txn.pool[ child_idx ];
     198           0 :     fd_progcache_txn_cancel_tree( cache, child, cancel_head, cancel_tail );
     199           0 :   }
     200           0 :   fd_progcache_txn_cancel_one( cache, txn, cancel_head, cancel_tail );
     201           0 : }
     202             : 
     203             : /* Cancels all left/right siblings */
     204             : 
     205             : static void
     206             : fd_progcache_txn_cancel_prev_list( fd_progcache_join_t * cache,
     207             :                                    fd_progcache_txn_t *  txn,
     208             :                                    uint *                cancel_head,
     209           0 :                                    uint *                cancel_tail ) {
     210           0 :   uint cur_idx = txn->sibling_prev_idx;
     211           0 :   while( cur_idx!=UINT_MAX ) {
     212           0 :     fd_progcache_txn_t * sibling = &cache->txn.pool[ cur_idx ];
     213           0 :     uint next = sibling->sibling_prev_idx;
     214           0 :     fd_progcache_txn_cancel_tree( cache, sibling, cancel_head, cancel_tail );
     215           0 :     cur_idx = next;
     216           0 :   }
     217           0 : }
     218             : 
     219             : static void
     220             : fd_progcache_txn_cancel_next_list( fd_progcache_join_t * cache,
     221             :                                    fd_progcache_txn_t *  txn,
     222             :                                    uint *                cancel_head,
     223           0 :                                    uint *                cancel_tail ) {
     224           0 :   uint cur_idx = txn->sibling_next_idx;
     225           0 :   while( cur_idx!=UINT_MAX ) {
     226           0 :     fd_progcache_txn_t * sibling = &cache->txn.pool[ cur_idx ];
     227           0 :     uint next = sibling->sibling_next_idx;
     228           0 :     fd_progcache_txn_cancel_tree( cache, sibling, cancel_head, cancel_tail );
     229           0 :     cur_idx = next;
     230           0 :   }
     231           0 : }
     232             : 
     233             : /* Drain readers and free cancelled records */
     234             : 
     235             : static void
     236             : fd_progcache_txn_cancel_release( fd_progcache_join_t * cache,
     237           0 :                                  uint                  head ) {
     238           0 :   while( head!=UINT_MAX ) {
     239           0 :     fd_progcache_rec_t * rec = &cache->rec.pool->ele[ head ];
     240           0 :     uint next = rec->next_idx;
     241             : 
     242           0 :     if( FD_LIKELY( rec->exists ) ) {
     243           0 :       fd_rwlock_write( &rec->lock );
     244           0 :       fd_progcache_val_free( rec, cache );
     245           0 :       rec->exists = 0;
     246           0 :       fd_rwlock_unwrite( &rec->lock );
     247           0 :     }
     248           0 :     fd_prog_recp_release( cache->rec.pool, rec, 1 );
     249             : 
     250           0 :     head = next;
     251           0 :   }
     252           0 : }
     253             : 
     254             : /* fd_progcache_gc_root cleans up a stale "rooted" version of a
     255             :    record. */
     256             : 
     257             : static void
     258             : fd_progcache_gc_root( fd_progcache_join_t *          cache,
     259           0 :                       fd_funk_xid_key_pair_t const * pair ) {
     260             :   /* Phase 1: Remove record from map if found */
     261             : 
     262           0 :   fd_prog_recm_query_t query[1];
     263           0 :   int rm_err = fd_prog_recm_remove( cache->rec.map, pair, NULL, query, FD_MAP_FLAG_BLOCKING );
     264           0 :   if( rm_err==FD_MAP_ERR_KEY ) return;
     265           0 :   if( FD_UNLIKELY( rm_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed: %i-%s", rm_err, fd_map_strerror( rm_err ) ));
     266           0 :   FD_COMPILER_MFENCE();
     267             : 
     268             :   /* Phase 2: Drain readers */
     269             : 
     270           0 :   fd_rwlock_write( &query->ele->lock );
     271           0 :   fd_progcache_rec_t * old_rec = query->ele;
     272           0 :   memset( &old_rec->pair, 0, sizeof(fd_funk_xid_key_pair_t) );
     273           0 :   FD_COMPILER_MFENCE();
     274             : 
     275             :   /* Phase 3: Free record */
     276             : 
     277           0 :   fd_progcache_val_free( old_rec, cache );
     278           0 :   old_rec->exists = 0;
     279           0 :   fd_rwlock_unwrite( &old_rec->lock );
     280           0 :   fd_prog_recp_release( cache->rec.pool, old_rec, 1 );
     281           0 :   fd_progcache_admin_metrics_g.gc_root_cnt++;
     282           0 : }
     283             : 
     284             : /* fd_progcache_gc_invalidation cleans up a "cache invalidate" record */
     285             : 
     286             : static void
     287             : fd_progcache_gc_invalidation( fd_progcache_join_t * cache,
     288           0 :                               fd_progcache_rec_t *  rec ) { /* no lock */
     289             : 
     290             :   /* Phase 1: Remove record from map if found */
     291             : 
     292           0 :   fd_funk_xid_key_pair_t pair = rec->pair;
     293           0 :   fd_prog_recm_query_t query[1];
     294           0 :   int rm_err = fd_prog_recm_remove( cache->rec.map, &pair, NULL, query, FD_MAP_FLAG_BLOCKING );
     295           0 :   if( rm_err==FD_MAP_ERR_KEY ) return;
     296           0 :   if( FD_UNLIKELY( rm_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed: %i-%s", rm_err, fd_map_strerror( rm_err ) ));
     297           0 :   if( FD_UNLIKELY( query->ele!=rec ) ) {
     298           0 :     FD_LOG_CRIT(( "Found record collision in program cache: xid=%lu:%lu key=%016lx%016lx%016lx%016lx ele0=%u ele1=%u",
     299           0 :                   pair.xid->ul[0], pair.xid->ul[1],
     300           0 :                   fd_ulong_bswap( pair.key->ul[0] ),
     301           0 :                   fd_ulong_bswap( pair.key->ul[1] ),
     302           0 :                   fd_ulong_bswap( pair.key->ul[2] ),
     303           0 :                   fd_ulong_bswap( pair.key->ul[3] ),
     304           0 :                   (uint)( query->ele - cache->rec.pool->ele ),
     305           0 :                   (uint)( rec        - cache->rec.pool->ele ) ));
     306           0 :   }
     307             : 
     308             :   /* Phase 2: Wait for readers to drain */
     309             : 
     310           0 :   fd_rwlock_write( &rec->lock );
     311             : 
     312             :   /* Phase 3: Invalidate and free record */
     313             : 
     314           0 :   memset( &rec->pair, 0, sizeof(fd_funk_xid_key_pair_t) );
     315           0 :   FD_COMPILER_MFENCE();
     316             : 
     317           0 :   rec->map_next = UINT_MAX;
     318           0 :   fd_progcache_val_free( rec, cache );
     319           0 :   rec->exists = 0;
     320           0 :   fd_rwlock_unwrite( &rec->lock );
     321           0 :   fd_prog_recp_release( cache->rec.pool, rec, 1 );
     322           0 :   fd_progcache_admin_metrics_g.gc_root_cnt++;
     323           0 : }
     324             : 
     325             : /* Move list of records to root txn (advance_root)
     326             : 
     327             :    For each record to be rooted:
     328             :    - gc_root to remove any shadowed rooted revision
     329             :    - drain readers
     330             :    - release if invalidation (which are now a no-op) */
     331             : 
     332             : static void
     333             : fd_progcache_txn_publish_release( fd_progcache_join_t * cache,
     334           0 :                                   uint                  head ) {
     335           0 :   while( head!=UINT_MAX ) {
     336           0 :     fd_progcache_rec_t * rec = &cache->rec.pool->ele[ head ];
     337           0 :     uint next = rec->next_idx;
     338             : 
     339           0 :     if( FD_UNLIKELY( !rec->exists ) ) {
     340           0 :       fd_prog_recp_release( cache->rec.pool, rec, 1 );
     341           0 :       head = next;
     342           0 :       continue;
     343           0 :     }
     344             : 
     345             :     /* Evict previous root value from hash chain */
     346           0 :     fd_funk_xid_key_pair_t pair[1];
     347           0 :     fd_funk_rec_key_copy( pair->key, rec->pair.key );
     348           0 :     fd_funk_txn_xid_set_root( pair->xid );
     349           0 :     fd_progcache_gc_root( cache, pair );
     350             : 
     351             :     /* Detach from txn list */
     352           0 :     rec->prev_idx = UINT_MAX;
     353           0 :     rec->next_idx = UINT_MAX;
     354             : 
     355           0 :     if( FD_UNLIKELY( rec->invalidate ) ) {
     356             :       /* Drop cache invalidate records */
     357           0 :       fd_progcache_gc_invalidation( cache, rec );
     358           0 :     } else {
     359             :       /* Migrate record to root */
     360           0 :       fd_rwlock_write( &rec->lock );
     361           0 :       fd_progcache_xid_t const root = { .ul = { ULONG_MAX, ULONG_MAX } };
     362           0 :       fd_funk_txn_xid_st_atomic( rec->pair.xid, &root );
     363           0 :       fd_rwlock_unwrite( &rec->lock );
     364           0 :       fd_progcache_admin_metrics_g.root_cnt++;
     365           0 :     }
     366             : 
     367           0 :     head = next;
     368           0 :   }
     369           0 : }
     370             : 
     371             : /* fd_progcache_txn_publish_one merges an in-prep transaction whose
     372             :    parent is the last published, into the parent. */
     373             : 
     374             : static uint
     375             : fd_progcache_txn_publish_one( fd_progcache_join_t * cache,
     376           0 :                               fd_progcache_txn_t *  txn ) {
     377             : 
     378             :   /* Phase 1: Mark transaction as "last published" */
     379             : 
     380           0 :   fd_progcache_xid_t const xid = txn->xid;
     381           0 :   FD_LOG_INFO(( "progcache txn laddr=%p xid %lu:%lu: publish", (void *)txn, xid.ul[0], xid.ul[1] ));
     382           0 :   if( FD_UNLIKELY( txn->parent_idx!=UINT_MAX ) ) {
     383           0 :     FD_LOG_CRIT(( "fd_progcache_publish failed: txn with xid %lu:%lu is not a child of the last published txn", xid.ul[0], xid.ul[1] ));
     384           0 :   }
     385           0 :   fd_funk_txn_xid_st_atomic( cache->shmem->txn.last_publish, &xid );
     386             : 
     387             :   /* Phase 2: Drain inserters from transaction */
     388             : 
     389           0 :   fd_rwlock_write( &txn->lock );
     390             : 
     391             :   /* Phase 3: Detach records */
     392             : 
     393           0 :   uint rec_head = txn->rec_head_idx;
     394           0 :   txn->rec_head_idx = UINT_MAX;
     395           0 :   txn->rec_tail_idx = UINT_MAX;
     396             : 
     397             :   /* Phase 4: Remove transaction from fork graph */
     398             : 
     399           0 :   { /* Adjust the parent pointers of the children to point to "last published" */
     400           0 :     ulong child_idx = txn->child_head_idx;
     401           0 :     while( child_idx!=UINT_MAX ) {
     402           0 :       cache->txn.pool[ child_idx ].parent_idx = UINT_MAX;
     403           0 :       child_idx = cache->txn.pool[ child_idx ].sibling_next_idx;
     404           0 :     }
     405           0 :   }
     406             : 
     407             :   /* Phase 5: Remove transaction from index */
     408             : 
     409           0 :   if( FD_UNLIKELY( fd_prog_txnm_idx_remove( cache->txn.map, &txn->xid, ULONG_MAX, cache->txn.pool )==ULONG_MAX ) ) {
     410           0 :     FD_LOG_CRIT(( "fd_progcache_publish failed: fd_funk_txn_map_remove(%lu:%lu) failed",
     411           0 :                   xid.ul[0], xid.ul[1] ));
     412           0 :   }
     413             : 
     414             :   /* Phase 6: Free transaction object */
     415             : 
     416           0 :   fd_rwlock_unwrite( &txn->lock );
     417           0 :   txn->parent_idx       = UINT_MAX;
     418           0 :   txn->sibling_prev_idx = UINT_MAX;
     419           0 :   txn->sibling_next_idx = UINT_MAX;
     420           0 :   txn->child_head_idx   = UINT_MAX;
     421           0 :   txn->child_tail_idx   = UINT_MAX;
     422           0 :   fd_prog_txnp_ele_release( cache->txn.pool, txn );
     423             : 
     424           0 :   return rec_head;
     425           0 : }
     426             : 
     427             : void
     428             : fd_progcache_txn_advance_root( fd_progcache_join_t *     cache,
     429           0 :                                fd_funk_txn_xid_t const * xid ) {
     430             : 
     431             :   /* Detach records from txns without acquiring record locks */
     432             : 
     433           0 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
     434             : 
     435           0 :   uint txn_idx = (uint)fd_prog_txnm_idx_query( cache->txn.map, xid, UINT_MAX, cache->txn.pool );
     436           0 :   if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) {
     437           0 :     FD_LOG_CRIT(( "fd_progcache_txn_advance_root failed: invalid XID %lu:%lu",
     438           0 :                   xid->ul[0], xid->ul[1] ));
     439           0 :   }
     440           0 :   fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
     441           0 :   if( FD_UNLIKELY( txn->parent_idx!=UINT_MAX ) ) {
     442           0 :     FD_LOG_CRIT(( "fd_progcache_txn_advance_root: parent of txn %lu:%lu is not root", xid->ul[0], xid->ul[1] ));
     443           0 :   }
     444             : 
     445           0 :   uint cancel_head = UINT_MAX;
     446           0 :   uint cancel_tail = UINT_MAX;
     447           0 :   fd_progcache_txn_cancel_prev_list( cache, txn, &cancel_head, &cancel_tail );
     448           0 :   fd_progcache_txn_cancel_next_list( cache, txn, &cancel_head, &cancel_tail );
     449             : 
     450           0 :   txn->sibling_prev_idx = UINT_MAX;
     451           0 :   txn->sibling_next_idx = UINT_MAX;
     452           0 :   cache->shmem->txn.child_head_idx = txn->child_head_idx;
     453           0 :   cache->shmem->txn.child_tail_idx = txn->child_tail_idx;
     454             : 
     455           0 :   uint publish_head = fd_progcache_txn_publish_one( cache, txn );
     456             : 
     457           0 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     458             : 
     459             :   /* Update records */
     460             : 
     461           0 :   fd_progcache_txn_cancel_release ( cache, cancel_head  );
     462           0 :   fd_progcache_txn_publish_release( cache, publish_head );
     463           0 : }
     464             : 
     465             : void
     466             : fd_progcache_txn_cancel( fd_progcache_join_t *      cache,
     467           0 :                          fd_progcache_xid_t const * xid ) {
     468             : 
     469           0 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
     470           0 :   fd_progcache_txn_t * txn = fd_prog_txnm_ele_query( cache->txn.map, xid, NULL, cache->txn.pool );
     471           0 :   if( FD_UNLIKELY( !txn ) ) {
     472           0 :     FD_LOG_CRIT(( "fd_progcache_txn_cancel failed: invalid XID %lu:%lu",
     473           0 :                   xid->ul[0], xid->ul[1] ));
     474           0 :   }
     475           0 :   uint cancel_head = UINT_MAX;
     476           0 :   uint cancel_tail = UINT_MAX;
     477           0 :   fd_progcache_txn_cancel_tree( cache, txn, &cancel_head, &cancel_tail );
     478           0 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     479             : 
     480           0 :   fd_progcache_txn_cancel_release( cache, cancel_head );
     481           0 : }
     482             : 
     483             : /* reset_txn_list does a depth-first traversal of the txn tree.
     484             :    Detaches all recs from txns by emptying rec linked lists. */
     485             : 
     486             : static void
     487             : reset_txn_list( fd_progcache_join_t * cache,
     488           0 :                 uint                  txn_head_idx ) {
     489           0 :   for( uint idx = txn_head_idx; idx!=UINT_MAX; ) {
     490           0 :     fd_progcache_txn_t * txn = &cache->txn.pool[ idx ];
     491           0 :     txn->rec_head_idx = UINT_MAX;
     492           0 :     txn->rec_tail_idx = UINT_MAX;
     493           0 :     reset_txn_list( cache, txn->child_head_idx );
     494           0 :     idx = txn->sibling_next_idx;
     495           0 :   }
     496           0 : }
     497             : 
     498             : /* reset_rec_map frees all records in a funk instance. */
     499             : 
     500             : static void
     501       30150 : reset_rec_map( fd_progcache_join_t * cache ) {
     502       30150 :   ulong chain_cnt = fd_prog_recm_chain_cnt( cache->rec.map );
     503    15340834 :   for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
     504    15310684 :     for(
     505    15310684 :         fd_prog_recm_iter_t iter = fd_prog_recm_iter( cache->rec.map, chain_idx );
     506    15340484 :         !fd_prog_recm_iter_done( iter );
     507    15310684 :     ) {
     508       29800 :       fd_progcache_rec_t * rec = fd_prog_recm_iter_ele( iter );
     509       29800 :       ulong next = fd_prog_recm_private_idx( rec->map_next );;
     510             : 
     511       29805 :       if( rec->exists ) {
     512       29805 :         fd_prog_recm_query_t rec_query[1];
     513       29805 :         int err = fd_prog_recm_remove( cache->rec.map, &rec->pair, NULL, rec_query, FD_MAP_FLAG_BLOCKING );
     514       29805 :         fd_funk_rec_key_t key; fd_funk_rec_key_copy( &key, rec->pair.key );
     515       29805 :         if( FD_UNLIKELY( err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed (%i-%s)", err, fd_map_strerror( err ) ));
     516       29805 :         fd_progcache_val_free( rec, cache );
     517       29805 :       }
     518             : 
     519       29800 :       rec->exists = 0;
     520       29800 :       fd_prog_recp_release( cache->rec.pool, rec, 1 );
     521       29800 :       iter.ele_idx = next;
     522       29800 :     }
     523    15310684 :   }
     524       30150 : }
     525             : 
     526             : void
     527           0 : fd_progcache_reset( fd_progcache_join_t * cache ) {
     528           0 :   reset_txn_list( cache, cache->shmem->txn.child_head_idx );
     529           0 :   reset_rec_map( cache );
     530           0 : }
     531             : 
     532             : /* clear_txn_list does a depth-first traversal of the txn tree.
     533             :    Removes all txns. */
     534             : 
     535             : static void
     536             : clear_txn_list( fd_progcache_join_t * join,
     537       85198 :                 uint                  txn_head_idx ) {
     538      140257 :   for( uint idx = txn_head_idx; idx!=UINT_MAX; ) {
     539       55059 :     fd_progcache_txn_t * txn = &join->txn.pool[ idx ];
     540       55059 :     uint next_idx  = txn->sibling_next_idx;
     541       55059 :     uint child_idx = txn->child_head_idx;
     542       55059 :     txn->rec_head_idx     = UINT_MAX;
     543       55059 :     txn->rec_tail_idx     = UINT_MAX;
     544       55059 :     txn->child_head_idx   = UINT_MAX;
     545       55059 :     txn->child_tail_idx   = UINT_MAX;
     546       55059 :     txn->parent_idx       = UINT_MAX;
     547       55059 :     txn->sibling_prev_idx = UINT_MAX;
     548       55059 :     txn->sibling_next_idx = UINT_MAX;
     549       55059 :     clear_txn_list( join, child_idx );
     550       55059 :     if( FD_UNLIKELY( !fd_prog_txnm_ele_remove( join->txn.map, &txn->xid, NULL, join->txn.pool ) ) ) FD_LOG_CRIT(( "fd_prog_txnm_ele_remove failed" ));
     551       55059 :     fd_prog_txnp_ele_release( join->txn.pool, txn );
     552       55059 :     idx = next_idx;
     553       55059 :   }
     554       85198 : }
     555             : 
     556             : void
     557       30154 : fd_progcache_clear( fd_progcache_join_t * cache ) {
     558       30154 :   clear_txn_list( cache, cache->shmem->txn.child_head_idx );
     559       30154 :   cache->shmem->txn.child_head_idx = UINT_MAX;
     560       30154 :   cache->shmem->txn.child_tail_idx = UINT_MAX;
     561       30154 :   reset_rec_map( cache );
     562       30154 : }
     563             : 
     564             : void
     565             : fd_progcache_inject_rec( fd_progcache_join_t *     cache,
     566             :                          void const *              prog_addr,
     567             :                          void const *              prog_owner,
     568             :                          fd_account_meta_t const * progdata_meta,
     569             :                          fd_features_t const *     features,
     570             :                          ulong                     slot,
     571             :                          uchar *                   scratch,
     572       31041 :                          ulong                     scratch_sz ) {
     573             : 
     574             :   /* XID overview:
     575             : 
     576             :      - load_xid:   tip of fork currently being executed
     577             :      - modify_xid: xid in which program was last modified / deployed
     578             :      - txn->xid:   xid in which program cache entry is inserted
     579             : 
     580             :      slot(load_xid) > slot(entry_xid) >= slot(txn->xid) */
     581             : 
     582             :   /* Acquire reference to ELF binary data */
     583             : 
     584       31041 :   uchar const * elf_bin    = NULL;
     585       31041 :   ulong         elf_bin_sz = progdata_meta->dlen;
     586       31041 :   if( !memcmp( prog_owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) {
     587       11188 :     if( FD_UNLIKELY( elf_bin_sz<PROGRAMDATA_METADATA_SIZE ) ) return;
     588             : 
     589        8521 :     elf_bin     = (uchar const *)fd_account_data( progdata_meta ) + PROGRAMDATA_METADATA_SIZE;
     590        8521 :     elf_bin_sz -= PROGRAMDATA_METADATA_SIZE;
     591       19853 :   } else if( !memcmp( prog_owner, fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) ) {
     592         381 :     if( FD_UNLIKELY( elf_bin_sz<LOADER_V4_PROGRAM_DATA_OFFSET ) ) return;
     593             : 
     594         381 :     elf_bin     = (uchar const *)fd_account_data( progdata_meta ) + LOADER_V4_PROGRAM_DATA_OFFSET;
     595         381 :     elf_bin_sz -= LOADER_V4_PROGRAM_DATA_OFFSET;
     596       19472 :   } else if( !memcmp( prog_owner, fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) ||
     597       19478 :              !memcmp( prog_owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) ) {
     598       19478 :     elf_bin = (uchar const *)fd_account_data( progdata_meta );
     599       19478 :   }
     600       28374 :   if( FD_UNLIKELY( !elf_bin ) ) return;
     601             : 
     602             :   /* Allocate a rec */
     603             : 
     604       28374 :   fd_progcache_rec_t * rec = fd_prog_recp_acquire( cache->rec.pool, NULL, 0, NULL );
     605       28374 :   if( FD_UNLIKELY( !rec ) ) {
     606           0 :     FD_LOG_ERR(( "Program cache is out of memory: fd_funk_rec_pool_acquire failed (rec_max=%lu)",
     607           0 :                  fd_prog_recp_ele_max( cache->rec.pool ) ));
     608           0 :   }
     609       28374 :   memset( rec, 0, sizeof(fd_progcache_rec_t) );
     610       28374 :   rec->exists = 1;
     611       28374 :   rec->slot   = slot;
     612             : 
     613       28374 :   rec->prev_idx = UINT_MAX;
     614       28374 :   rec->next_idx = UINT_MAX;
     615       28374 :   memcpy( rec->pair.key, prog_addr, 32UL );
     616       28374 :   fd_funk_txn_xid_set_root( rec->pair.xid );
     617             : 
     618             :   /* Load program */
     619             : 
     620       28374 :   ulong const load_slot = slot;
     621       28374 :   fd_prog_versions_t versions = fd_prog_versions( features, load_slot );
     622       28374 :   fd_sbpf_loader_config_t config = {
     623       28374 :     .sbpf_min_version = versions.min_sbpf_version,
     624       28374 :     .sbpf_max_version = versions.max_sbpf_version,
     625       28374 :   };
     626       28374 :   fd_sbpf_elf_info_t elf_info[1];
     627             : 
     628       28374 :   if( FD_LIKELY( fd_sbpf_elf_peek( elf_info, elf_bin, elf_bin_sz, &config )==FD_SBPF_ELF_SUCCESS ) ) {
     629        9000 :     ulong val_sz = fd_progcache_val_footprint( elf_info );
     630        9000 :     if( FD_UNLIKELY( !fd_progcache_val_alloc( rec, cache, fd_progcache_val_align(), val_sz ) ) ) {
     631             :       /* This is a test API, so no need to do cache eviction */
     632           0 :       FD_LOG_ERR(( "Program cache is out of memory: fd_alloc_malloc failed (requested sz=%lu)", val_sz ));
     633           0 :     }
     634             : 
     635        9000 :     if( FD_UNLIKELY( !fd_progcache_rec_load( rec, cache->wksp, elf_info, &config, load_slot, features, elf_bin, elf_bin_sz, scratch, scratch_sz ) ) ) {
     636         128 :       fd_progcache_val_free( rec, cache );
     637         128 :       fd_progcache_rec_nx( rec );
     638         128 :     }
     639       19374 :   } else {
     640       19374 :     fd_progcache_rec_nx( rec );
     641       19374 :   }
     642             : 
     643             :   /* Publish cache entry to funk index */
     644             : 
     645       28374 :   int insert_err = fd_prog_recm_txn_insert( cache->rec.map, rec );
     646       28374 :   if( FD_UNLIKELY( insert_err!=FD_MAP_SUCCESS ) ) {
     647           0 :     FD_LOG_CRIT(( "fd_funk_rec_map_txn_insert failed: %i-%s", insert_err, fd_map_strerror( insert_err ) ));
     648           0 :   }
     649       28374 : }
     650             : 
     651             : void
     652           0 : fd_progcache_wksp_metrics_update( fd_progcache_join_t * cache ) {
     653           0 :   fd_wksp_t * wksp = cache->wksp;
     654           0 :   if( FD_UNLIKELY( fd_wksp_private_lock( wksp ) ) ) FD_LOG_CRIT(( "fd_wksp_private_lock failed" ));
     655             : 
     656           0 :   fd_wksp_private_pinfo_t * pinfo = fd_wksp_private_pinfo( wksp );
     657           0 :   ulong part_max  = wksp->part_max;
     658           0 :   ulong cycle_tag = wksp->cycle_tag++;
     659             : 
     660           0 :   ulong free_part_cnt    = 0UL;
     661           0 :   ulong free_sz     = 0UL;
     662           0 :   ulong total_sz    = 0UL;
     663           0 :   ulong free_part_max = 0UL;
     664             : 
     665           0 :   ulong i = fd_wksp_private_pinfo_idx( wksp->part_head_cidx );
     666           0 :   while( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
     667           0 :     if( FD_UNLIKELY( i>=part_max ) || FD_UNLIKELY( pinfo[ i ].cycle_tag==cycle_tag ) ) {
     668           0 :       FD_LOG_CRIT(( "corrupt wksp detected" ));
     669           0 :     }
     670           0 :     pinfo[ i ].cycle_tag = cycle_tag; /* mark i as visited */
     671           0 :     ulong part_sz  = fd_wksp_private_pinfo_sz( pinfo + i );
     672           0 :     ulong part_tag = pinfo[ i ].tag;
     673           0 :     ulong free_psz = fd_ulong_if( !part_tag, part_sz, 0UL );
     674           0 :     free_part_cnt  += !part_tag;
     675           0 :     free_sz        += free_psz;
     676           0 :     total_sz       += part_sz;
     677           0 :     free_part_max   = fd_ulong_max( free_part_max, free_psz );
     678           0 :     i = fd_wksp_private_pinfo_idx( pinfo[ i ].next_cidx );
     679           0 :   }
     680           0 :   fd_wksp_private_unlock( wksp );
     681             : 
     682           0 :   fd_progcache_admin_metrics_t * m = &fd_progcache_admin_metrics_g;
     683           0 :   m->wksp.free_part_cnt = free_part_cnt;
     684           0 :   m->wksp.free_sz       = free_sz;
     685           0 :   m->wksp.total_sz      = total_sz;
     686           0 :   m->wksp.free_part_max = free_part_max;
     687           0 : }

Generated by: LCOV version 1.14