LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_admin.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 92 385 23.9 %
Date: 2026-10-22 18:10:43 Functions: 4 13 30.8 %

          Line data    Source code
       1             : #include "fd_progcache.h"
       2             : #include "fd_progcache_admin.h"
       3             : #include "fd_progcache_base.h"
       4             : #include "fd_progcache_clock.h"
       5             : #include "fd_progcache_rec.h"
       6             : #include "fd_progcache_reclaim.h"
       7             : #include "fd_progcache_xid.h"
       8             : #include "../../util/racesan/fd_racesan_target.h"
       9             : 
      10             : /* FIXME get rid of this thread-local */
      11             : FD_TL fd_progcache_admin_metrics_t fd_progcache_admin_metrics_g;
      12             : 
      13             : /* Transaction-level operations.  txn_pool and txn_map are serialized by
      14             :    txn.rwlock: written here under the exclusive lock, read shared by the user
      15             :    and eviction paths. */
      16             : 
      17             : fd_progcache_fork_id_t
      18             : fd_progcache_attach_child( fd_progcache_join_t *  cache,
      19      119916 :                            fd_progcache_fork_id_t parent_fork_id ) {
      20      119916 :   if( FD_UNLIKELY( !cache ) ) FD_LOG_CRIT(( "invalid arguments" ));
      21             : 
      22      119916 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
      23      119916 :   if( FD_UNLIKELY( fd_prog_txnp_free( cache->txn.pool )==0UL ) ) {
      24           0 :     FD_LOG_ERR(( "fd_progcache_attach_child failed: transaction object pool out of memory" ));
      25           0 :   }
      26             : 
      27      119916 :   ulong  txn_max = fd_prog_txnp_max( cache->txn.pool );
      28      119916 :   ulong  parent_idx;
      29      119916 :   uint * _child_head_idx;
      30      119916 :   uint * _child_tail_idx;
      31             : 
      32      119916 :   fd_progcache_fork_id_t root = __atomic_load_n( &cache->shmem->txn.root, memory_order_relaxed );
      33      119916 :   if( FD_UNLIKELY( parent_fork_id == root ) ) {
      34             : 
      35       63945 :     parent_idx = FD_PROGCACHE_TXN_IDX_NULL;
      36             : 
      37       63945 :     _child_head_idx = &cache->shmem->txn.child_head_idx;
      38       63945 :     _child_tail_idx = &cache->shmem->txn.child_tail_idx;
      39             : 
      40       63945 :   } else {
      41             : 
      42       55971 :     parent_idx = fd_prog_txnm_idx_query( cache->txn.map, &parent_fork_id, ULONG_MAX, cache->txn.pool );
      43       55971 :     if( FD_UNLIKELY( parent_idx==ULONG_MAX ) ) {
      44           0 :       FD_LOG_CRIT(( "fd_progcache_attach_child failed: user provided invalid parent fork_id %lu", parent_fork_id ));
      45           0 :     }
      46       55971 :     if( FD_UNLIKELY( parent_idx >= txn_max ) )
      47           0 :       FD_LOG_CRIT(( "progcache: corruption detected (attach_child parent_idx=%lu txn_max=%lu)", parent_idx, txn_max ));
      48             : 
      49       55971 :     _child_head_idx = &cache->txn.pool[ parent_idx ].child_head_idx;
      50       55971 :     _child_tail_idx = &cache->txn.pool[ parent_idx ].child_tail_idx;
      51             : 
      52       55971 :   }
      53             : 
      54      119916 :   uint txn_idx = (uint)fd_prog_txnp_idx_acquire( cache->txn.pool );
      55      119916 :   if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) FD_LOG_ERR(( "fd_progcache_attach_child failed: transaction object pool out of memory" ));
      56      119916 :   fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
      57      119916 :   txn->xid = __atomic_add_fetch( &cache->shmem->txn.seq, 1UL, memory_order_relaxed );
      58             : 
      59      119916 :   uint sibling_prev_idx = *_child_tail_idx;
      60             : 
      61      119916 :   int first_born = sibling_prev_idx==UINT_MAX;
      62      119916 :   if( FD_UNLIKELY( !first_born && (ulong)sibling_prev_idx >= txn_max ) )
      63           0 :     FD_LOG_CRIT(( "progcache: corruption detected (attach_child sibling_prev_idx=%u txn_max=%lu)", sibling_prev_idx, txn_max ));
      64             : 
      65      119916 :   txn->parent_idx       = (uint)parent_idx;
      66      119916 :   txn->child_head_idx   = UINT_MAX;
      67      119916 :   txn->child_tail_idx   = UINT_MAX;
      68      119916 :   txn->sibling_prev_idx = (uint)sibling_prev_idx;
      69      119916 :   txn->sibling_next_idx = UINT_MAX;
      70             : 
      71      119916 :   txn->rec_head_idx = UINT_MAX;
      72      119916 :   txn->rec_tail_idx = UINT_MAX;
      73             : 
      74             :   /* TODO: consider branchless impl */
      75      120436 :   if( FD_LIKELY( first_born ) ) *_child_head_idx            = (uint)txn_idx; /* opt for non-compete */
      76 >1844*10^16 :   else cache->txn.pool[ sibling_prev_idx ].sibling_next_idx = (uint)txn_idx;
      77             : 
      78      119916 :   *_child_tail_idx = (uint)txn_idx;
      79             : 
      80      119916 :   fd_prog_txnm_idx_insert( cache->txn.map, txn_idx, cache->txn.pool );
      81             : 
      82      119916 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
      83      119916 :   return txn->xid;
      84      119916 : }
      85             : 
      86             : static void
      87             : fd_progcache_cancel_one( fd_progcache_join_t * cache,
      88           0 :                          fd_progcache_txn_t *  txn ) {
      89           0 :   ulong rec_max = cache->rec.max;
      90           0 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
      91             : 
      92           0 :   fd_rwlock_write( &txn->lock );
      93             : 
      94           0 :   if( FD_UNLIKELY( txn->child_head_idx!=UINT_MAX ||
      95           0 :                    txn->child_tail_idx!=UINT_MAX ) ) {
      96           0 :     FD_LOG_CRIT(( "fd_progcache_cancel failed: txn at %p with fork_id %lu has children (data corruption?)",
      97           0 :                   (void *)txn, txn->xid ));
      98           0 :   }
      99             : 
     100             :   /* Remove records */
     101             : 
     102           0 :   for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; ) {
     103           0 :     if( FD_UNLIKELY( (ulong)idx >= rec_max ) )
     104           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_one rec_idx=%u rec_max=%lu)", idx, rec_max ));
     105           0 :     fd_progcache_rec_t * rec = &cache->rec.ele[ idx ];
     106           0 :     uint next_idx = rec->next_idx;
     107           0 :     if( FD_UNLIKELY( next_idx!=UINT_MAX && (ulong)next_idx >= rec_max ) )
     108           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_one next_idx=%u rec_max=%lu)", next_idx, rec_max ));
     109           0 :     atomic_store_explicit( &rec->txn_idx, UINT_MAX, memory_order_release );
     110           0 :     fd_racesan_hook( "prog_cancel_one:post_orphan" );
     111           0 :     fd_prog_delete_rec( cache, rec );
     112           0 :     idx = next_idx;
     113           0 :   }
     114             : 
     115           0 :   txn->rec_head_idx = UINT_MAX;
     116           0 :   txn->rec_tail_idx = UINT_MAX;
     117             : 
     118             :   /* Remove transaction from fork graph */
     119             : 
     120           0 :   uint self_idx = (uint)( txn - cache->txn.pool );
     121           0 :   uint prev_idx = txn->sibling_prev_idx;
     122           0 :   uint next_idx = txn->sibling_next_idx;
     123           0 :   if( next_idx!=UINT_MAX ) {
     124           0 :     if( FD_UNLIKELY( (ulong)next_idx >= txn_max ) )
     125           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_one sibling_next_idx=%u txn_max=%lu)", next_idx, txn_max ));
     126           0 :     cache->txn.pool[ next_idx ].sibling_prev_idx = prev_idx;
     127           0 :   }
     128           0 :   if( prev_idx!=UINT_MAX ) {
     129           0 :     if( FD_UNLIKELY( (ulong)prev_idx >= txn_max ) )
     130           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_one sibling_prev_idx=%u txn_max=%lu)", prev_idx, txn_max ));
     131           0 :     cache->txn.pool[ prev_idx ].sibling_next_idx = next_idx;
     132           0 :   }
     133           0 :   if( txn->parent_idx!=UINT_MAX ) {
     134           0 :     if( FD_UNLIKELY( (ulong)txn->parent_idx >= txn_max ) )
     135           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_one parent_idx=%u txn_max=%lu)", txn->parent_idx, txn_max ));
     136           0 :     fd_progcache_txn_t * parent = &cache->txn.pool[ txn->parent_idx ];
     137           0 :     if( parent->child_head_idx==self_idx ) parent->child_head_idx = next_idx;
     138           0 :     if( parent->child_tail_idx==self_idx ) parent->child_tail_idx = prev_idx;
     139           0 :   } else {
     140           0 :     if( cache->shmem->txn.child_head_idx==self_idx ) cache->shmem->txn.child_head_idx = next_idx;
     141           0 :     if( cache->shmem->txn.child_tail_idx==self_idx ) cache->shmem->txn.child_tail_idx = prev_idx;
     142           0 :   }
     143             : 
     144             :   /* Remove transaction from index */
     145             : 
     146           0 :   if( FD_UNLIKELY( !fd_prog_txnm_ele_remove( cache->txn.map, &txn->xid, NULL, cache->txn.pool ) ) ) {
     147           0 :     FD_LOG_CRIT(( "fd_progcache_cancel failed: fd_prog_txnm_ele_remove(%lu) failed", txn->xid ));
     148           0 :   }
     149             : 
     150             :   /* Free transaction object */
     151             : 
     152           0 :   fd_rwlock_unwrite( &txn->lock );
     153           0 :   fd_prog_txnp_ele_release( cache->txn.pool, txn );
     154           0 : }
     155             : 
     156             : /* Cancels txn and all children */
     157             : 
     158             : static void
     159             : fd_progcache_cancel_tree( fd_progcache_join_t * cache,
     160           0 :                           fd_progcache_txn_t *  txn ) {
     161           0 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     162           0 :   for(;;) {
     163           0 :     uint child_idx = txn->child_head_idx;
     164           0 :     if( child_idx==UINT_MAX ) break;
     165           0 :     if( FD_UNLIKELY( (ulong)child_idx >= txn_max ) )
     166           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_tree child_idx=%u txn_max=%lu)", child_idx, txn_max ));
     167           0 :     fd_progcache_txn_t * child = &cache->txn.pool[ child_idx ];
     168           0 :     fd_progcache_cancel_tree( cache, child );
     169           0 :   }
     170           0 :   fd_progcache_cancel_one( cache, txn );
     171           0 : }
     172             : 
     173             : /* Cancels all left/right siblings */
     174             : 
     175             : static void
     176             : fd_progcache_cancel_prev_list( fd_progcache_join_t * cache,
     177           0 :                                fd_progcache_txn_t *  txn ) {
     178           0 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     179           0 :   uint cur_idx = txn->sibling_prev_idx;
     180           0 :   while( cur_idx!=UINT_MAX ) {
     181           0 :     if( FD_UNLIKELY( (ulong)cur_idx >= txn_max ) )
     182           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_prev_list txn_idx=%u txn_max=%lu)", cur_idx, txn_max ));
     183           0 :     fd_progcache_txn_t * sibling = &cache->txn.pool[ cur_idx ];
     184           0 :     uint next = sibling->sibling_prev_idx;
     185           0 :     fd_progcache_cancel_tree( cache, sibling );
     186           0 :     cur_idx = next;
     187           0 :   }
     188           0 : }
     189             : 
     190             : static void
     191             : fd_progcache_cancel_next_list( fd_progcache_join_t * cache,
     192           0 :                                fd_progcache_txn_t *  txn ) {
     193           0 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     194           0 :   uint cur_idx = txn->sibling_next_idx;
     195           0 :   while( cur_idx!=UINT_MAX ) {
     196           0 :     if( FD_UNLIKELY( (ulong)cur_idx >= txn_max ) )
     197           0 :       FD_LOG_CRIT(( "progcache: corruption detected (cancel_next_list txn_idx=%u txn_max=%lu)", cur_idx, txn_max ));
     198           0 :     fd_progcache_txn_t * sibling = &cache->txn.pool[ cur_idx ];
     199           0 :     uint next = sibling->sibling_next_idx;
     200           0 :     fd_progcache_cancel_tree( cache, sibling );
     201           0 :     cur_idx = next;
     202           0 :   }
     203           0 : }
     204             : 
     205             : /* fd_progcache_txn_publish_one merges an in-prep transaction whose
     206             :    parent is the last published, into the parent. */
     207             : 
     208             : static void
     209             : fd_progcache_txn_publish_one( fd_progcache_join_t * cache,
     210           0 :                               fd_progcache_txn_t *  txn ) {
     211             : 
     212             :   /* Phase 1: Mark transaction as "last published" */
     213             : 
     214           0 :   fd_progcache_fork_id_t const fork_id = txn->xid;
     215           0 :   if( FD_UNLIKELY( txn->parent_idx!=UINT_MAX ) ) {
     216           0 :     FD_LOG_CRIT(( "fd_progcache_publish failed: txn with fork_id %lu is not a child of the last published txn", fork_id ));
     217           0 :   }
     218           0 :   fd_racesan_hook( "prog_publish_one:pre_xid_store" );
     219           0 :   __atomic_store_n( &cache->shmem->txn.root, fork_id, memory_order_release );
     220             : 
     221             :   /* Phase 2: Drain inserters from transaction */
     222             : 
     223           0 :   fd_rwlock_write( &txn->lock );
     224             : 
     225             :   /* Phase 3: Detach records */
     226             : 
     227           0 :   ulong rec_max = cache->rec.max;
     228           0 :   for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; ) {
     229           0 :     if( FD_UNLIKELY( (ulong)idx >= rec_max ) )
     230           0 :       FD_LOG_CRIT(( "progcache: corruption detected (publish_one rec_idx=%u rec_max=%lu)", idx, rec_max ));
     231           0 :     uint next_idx = cache->rec.ele[ idx ].next_idx;
     232           0 :     if( FD_UNLIKELY( next_idx!=UINT_MAX && (ulong)next_idx >= rec_max ) )
     233           0 :       FD_LOG_CRIT(( "progcache: corruption detected (publish_one next_idx=%u rec_max=%lu)", next_idx, rec_max ));
     234           0 :     atomic_store_explicit( &cache->rec.ele[ idx ].txn_idx, UINT_MAX, memory_order_release );
     235           0 :     fd_racesan_hook( "prog_publish_one:post_detach" );
     236           0 :     fd_progcache_admin_metrics_g.root_cnt++;
     237           0 :     idx = next_idx;
     238           0 :   }
     239             : 
     240           0 :   txn->rec_head_idx = UINT_MAX;
     241           0 :   txn->rec_tail_idx = UINT_MAX;
     242             : 
     243             :   /* Phase 4: Remove transaction from fork graph */
     244             : 
     245           0 :   { /* Adjust the parent pointers of the children to point to "last published" */
     246           0 :     ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     247           0 :     ulong child_idx = txn->child_head_idx;
     248           0 :     while( child_idx!=UINT_MAX ) {
     249           0 :       if( FD_UNLIKELY( child_idx >= txn_max ) )
     250           0 :         FD_LOG_CRIT(( "progcache: corruption detected (publish_one child_idx=%lu txn_max=%lu)", child_idx, txn_max ));
     251           0 :       cache->txn.pool[ child_idx ].parent_idx = UINT_MAX;
     252           0 :       child_idx = cache->txn.pool[ child_idx ].sibling_next_idx;
     253           0 :     }
     254           0 :   }
     255             : 
     256             :   /* Phase 5: Remove transaction from index */
     257             : 
     258           0 :   if( FD_UNLIKELY( fd_prog_txnm_idx_remove( cache->txn.map, &txn->xid, ULONG_MAX, cache->txn.pool )==ULONG_MAX ) ) {
     259           0 :     FD_LOG_CRIT(( "fd_progcache_publish failed: fd_prog_txnm_idx_remove(%lu) failed", txn->xid ));
     260           0 :   }
     261             : 
     262             :   /* Phase 6: Free transaction object */
     263             : 
     264           0 :   fd_rwlock_unwrite( &txn->lock );
     265           0 :   txn->parent_idx       = UINT_MAX;
     266           0 :   txn->sibling_prev_idx = UINT_MAX;
     267           0 :   txn->sibling_next_idx = UINT_MAX;
     268           0 :   txn->child_head_idx   = UINT_MAX;
     269           0 :   txn->child_tail_idx   = UINT_MAX;
     270           0 :   fd_prog_txnp_ele_release( cache->txn.pool, txn );
     271           0 : }
     272             : 
     273             : void
     274             : fd_progcache_advance_root( fd_progcache_join_t *  cache,
     275           0 :                            fd_progcache_fork_id_t fork_id ) {
     276           0 :   if( FD_UNLIKELY( !cache ) ) FD_LOG_CRIT(( "invalid arguments" ));
     277             : 
     278             :   /* Detach records from txns without acquiring record locks */
     279             : 
     280           0 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
     281             : 
     282           0 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     283           0 :   uint txn_idx = (uint)fd_prog_txnm_idx_query( cache->txn.map, &fork_id, UINT_MAX, cache->txn.pool );
     284           0 :   if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) {
     285           0 :     FD_LOG_CRIT(( "fd_progcache_advance_root failed: invalid fork_id %lu", fork_id ));
     286           0 :   }
     287           0 :   if( FD_UNLIKELY( (ulong)txn_idx >= txn_max ) )
     288           0 :     FD_LOG_CRIT(( "progcache: corruption detected (advance_root txn_idx=%u txn_max=%lu)", txn_idx, txn_max ));
     289           0 :   fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
     290           0 :   if( FD_UNLIKELY( txn->parent_idx!=UINT_MAX ) ) {
     291           0 :     FD_LOG_CRIT(( "fd_progcache_advance_root: parent of txn %lu is not root", fork_id ));
     292           0 :   }
     293             : 
     294           0 :   fd_progcache_cancel_prev_list( cache, txn );
     295           0 :   fd_progcache_cancel_next_list( cache, txn );
     296             : 
     297           0 :   txn->sibling_prev_idx = UINT_MAX;
     298           0 :   txn->sibling_next_idx = UINT_MAX;
     299           0 :   cache->shmem->txn.child_head_idx = txn->child_head_idx;
     300           0 :   cache->shmem->txn.child_tail_idx = txn->child_tail_idx;
     301             : 
     302           0 :   fd_progcache_txn_publish_one( cache, txn );
     303             : 
     304           0 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     305           0 : }
     306             : 
     307             : void
     308             : fd_progcache_cancel_fork( fd_progcache_join_t *  cache,
     309           0 :                           fd_progcache_fork_id_t fork_id ) {
     310           0 :   if( FD_UNLIKELY( !cache ) ) {
     311           0 :     FD_LOG_CRIT(( "invalid arguments" ));
     312           0 :   }
     313             : 
     314           0 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
     315             : 
     316           0 :   fd_progcache_txn_t * txn = fd_prog_txnm_ele_query( cache->txn.map, &fork_id, NULL, cache->txn.pool );
     317           0 :   if( FD_UNLIKELY( !txn ) ) {
     318           0 :     FD_LOG_CRIT(( "fd_progcache_cancel failed: invalid fork_id %lu", fork_id ));
     319           0 :   }
     320           0 :   fd_progcache_cancel_tree( cache, txn );
     321             : 
     322           0 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     323           0 : }
     324             : 
     325             : /* reset_rec_map frees all records in a progcache instance. */
     326             : 
     327             : static void
     328       64070 : reset_rec_map( fd_progcache_join_t * cache ) {
     329       64070 :   ulong chain_cnt = fd_prog_recm_chain_cnt( cache->rec.map );
     330     8185273 :   for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
     331     8121203 :     for(
     332     8121203 :         fd_prog_recm_iter_t iter = fd_prog_recm_iter( cache->rec.map, chain_idx );
     333     8125891 :         !fd_prog_recm_iter_done( iter );
     334     8121203 :     ) {
     335        4688 :       fd_progcache_rec_t * rec = fd_prog_recm_iter_ele( iter );
     336        4688 :       ulong next = fd_prog_recm_private_idx( rec->map_next );
     337             : 
     338        4688 :       fd_prog_recm_query_t rec_query[1];
     339        4688 :       int err = fd_prog_recm_remove( cache->rec.map, &rec->pair, NULL, rec_query, FD_MAP_FLAG_BLOCKING );
     340        4688 :       if( FD_UNLIKELY( err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed (%i-%s)", err, fd_map_strerror( err ) ));
     341        4688 :       if( FD_UNLIKELY( !fd_rwlock_trywrite( &rec->lock ) ) )
     342           0 :         FD_LOG_CRIT(( "fd_progcache_reset requires quiescence: record still read-locked" ));
     343        4688 :       fd_progcache_rec_release( cache, rec );
     344             : 
     345        4688 :       iter.ele_idx = next;
     346        4688 :     }
     347     8121203 :   }
     348       64070 : }
     349             : 
     350             : /* clear_txn_list does a depth-first traversal of the txn tree.
     351             :    Removes all txns. */
     352             : 
     353             : static void
     354             : clear_txn_list( fd_progcache_join_t * join,
     355      184528 :                 uint                  txn_head_idx ) {
     356      184528 :   ulong txn_max = fd_prog_txnp_max( join->txn.pool );
     357      304988 :   for( uint idx = txn_head_idx; idx!=UINT_MAX; ) {
     358      120460 :     if( FD_UNLIKELY( (ulong)idx >= txn_max ) )
     359           0 :       FD_LOG_CRIT(( "progcache: corruption detected (clear_txn_list txn_idx=%u txn_max=%lu)", idx, txn_max ));
     360      120460 :     fd_progcache_txn_t * txn = &join->txn.pool[ idx ];
     361      120460 :     uint next_idx  = txn->sibling_next_idx;
     362      120460 :     uint child_idx = txn->child_head_idx;
     363      120460 :     txn->rec_head_idx     = UINT_MAX;
     364      120460 :     txn->rec_tail_idx     = UINT_MAX;
     365      120460 :     txn->child_head_idx   = UINT_MAX;
     366      120460 :     txn->child_tail_idx   = UINT_MAX;
     367      120460 :     txn->parent_idx       = UINT_MAX;
     368      120460 :     txn->sibling_prev_idx = UINT_MAX;
     369      120460 :     txn->sibling_next_idx = UINT_MAX;
     370      120460 :     clear_txn_list( join, child_idx );
     371      120460 :     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" ));
     372      120460 :     fd_prog_txnp_ele_release( join->txn.pool, txn );
     373      120460 :     idx = next_idx;
     374      120460 :   }
     375      184528 : }
     376             : 
     377             : void
     378       63996 : fd_progcache_reset( fd_progcache_join_t * cache ) {
     379             :   /* Zombies are not in the map, so reset_rec_map cannot see them.  Collect
     380             :      them first; one that survives the sweep is held by an active reader. */
     381       63996 :   fd_prog_reclaim_work( cache );
     382    11136652 :   for( ulong i=0UL; i<cache->rec.max; i++ ) {
     383    11072656 :     uchar st = __atomic_load_n( &cache->rec.ele[ i ].state, __ATOMIC_RELAXED );
     384    11072656 :     if( FD_UNLIKELY( ( st & ( FD_PROGCACHE_REC_LIVE|FD_PROGCACHE_REC_MAPPED ) )==FD_PROGCACHE_REC_LIVE ) )
     385           0 :       FD_LOG_CRIT(( "fd_progcache_reset requires quiescence: record %lu awaits collection (active readers?)", i ));
     386    11072656 :   }
     387       63996 :   if( FD_UNLIKELY( cache->shmem->spill.lock.value || cache->shmem->spill.rec_used || cache->shmem->spill.spad_used ) )
     388           0 :     FD_LOG_CRIT(( "fd_progcache_reset requires quiescence: spill in use" ));
     389       63996 :   clear_txn_list( cache, cache->shmem->txn.child_head_idx );
     390       63996 :   cache->shmem->txn.child_head_idx = UINT_MAX;
     391       63996 :   cache->shmem->txn.child_tail_idx = UINT_MAX;
     392       63996 :   reset_rec_map( cache );
     393       63996 :   cache->shmem->txn.root = fd_progcache_fork_id_initial();
     394       63996 : }
     395             : 
     396             : static int
     397             : fd_progcache_verify_siblings( fd_progcache_txn_t * pool,
     398             :                               ulong                txn_max,
     399             :                               uint                 head_idx,
     400             :                               uint                 tail_idx,
     401             :                               uint                 expected_parent_idx,
     402             :                               uint *               stack,
     403           0 :                               ulong *              stack_top ) {
     404             : 
     405           0 : # define TEST(c) do {                                                    \
     406           0 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return -1; } \
     407           0 :   } while(0)
     408             : 
     409           0 :   TEST( (head_idx==UINT_MAX)==(tail_idx==UINT_MAX) );
     410             : 
     411           0 :   uint last_idx = UINT_MAX;
     412           0 :   for( uint idx = head_idx; idx!=UINT_MAX; ) {
     413           0 :     TEST( idx<txn_max );
     414           0 :     fd_progcache_txn_t * child = &pool[ idx ];
     415           0 :     TEST( !child->tag );
     416           0 :     TEST( child->parent_idx==expected_parent_idx );
     417           0 :     child->tag = 1;
     418           0 :     TEST( *stack_top<FD_PROGCACHE_DEPTH_MAX );
     419           0 :     stack[ (*stack_top)++ ] = idx;
     420           0 :     last_idx = idx;
     421           0 :     uint next_idx = child->sibling_next_idx;
     422           0 :     if( next_idx!=UINT_MAX ) {
     423           0 :       TEST( next_idx<txn_max );
     424           0 :       TEST( pool[ next_idx ].sibling_prev_idx==idx );
     425           0 :     }
     426           0 :     idx = next_idx;
     427           0 :   }
     428           0 :   TEST( last_idx==tail_idx );
     429             : 
     430           0 : # undef TEST
     431             : 
     432           0 :   return 0;
     433           0 : }
     434             : 
     435             : int
     436           0 : fd_progcache_verify( fd_progcache_join_t * join ) {
     437             : 
     438           0 : # define TEST(c) do {                                                    \
     439           0 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return -1; } \
     440           0 :   } while(0)
     441             : 
     442           0 :   TEST( join );
     443             : 
     444           0 :   fd_progcache_shmem_t * shmem = join->shmem;
     445           0 :   TEST( shmem );
     446           0 :   TEST( shmem->magic==FD_PROGCACHE_SHMEM_MAGIC );
     447           0 :   TEST( shmem->wksp_tag );
     448             : 
     449           0 :   TEST( !fd_prog_recm_verify( join->rec.map ) );
     450             : 
     451           0 :   ulong rec_max = join->rec.max;
     452           0 :   fd_progcache_rec_t * rec0 = join->rec.ele;
     453             : 
     454           0 :   ulong txn_max = fd_prog_txnp_max( join->txn.pool );
     455           0 :   TEST( !fd_prog_txnm_verify( join->txn.map, txn_max, join->txn.pool ) );
     456             : 
     457           0 :   for( ulong i=0UL; i<txn_max; i++ ) join->txn.pool[ i ].tag = 0;
     458             : 
     459           0 :   uint  stack[ FD_PROGCACHE_DEPTH_MAX ];
     460           0 :   ulong stack_top = 0UL;
     461             : 
     462           0 :   TEST( !fd_progcache_verify_siblings( join->txn.pool, txn_max,
     463           0 :       shmem->txn.child_head_idx, shmem->txn.child_tail_idx,
     464           0 :       UINT_MAX, stack, &stack_top ) );
     465             : 
     466           0 :   while( stack_top ) {
     467           0 :     uint txn_idx = stack[ --stack_top ];
     468           0 :     fd_progcache_txn_t * txn = &join->txn.pool[ txn_idx ];
     469           0 :     TEST( !fd_progcache_verify_siblings( join->txn.pool, txn_max,
     470           0 :         txn->child_head_idx, txn->child_tail_idx,
     471           0 :         txn_idx, stack, &stack_top ) );
     472           0 :   }
     473             : 
     474           0 :   for( ulong i=0UL; i<txn_max; i++ ) {
     475           0 :     if( !join->txn.pool[ i ].tag ) continue;
     476           0 :     fd_progcache_txn_t * txn = &join->txn.pool[ i ];
     477             : 
     478           0 :     TEST( (txn->rec_head_idx==UINT_MAX)==(txn->rec_tail_idx==UINT_MAX) );
     479             : 
     480           0 :     ulong rec_cnt = 0UL;
     481           0 :     uint  prev    = UINT_MAX;
     482           0 :     for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; ) {
     483           0 :       TEST( idx<rec_max );
     484           0 :       TEST( rec_cnt<rec_max ); /* cycle detection */
     485           0 :       fd_progcache_rec_t * rec = &rec0[ idx ];
     486           0 :       TEST( rec->prev_idx==prev );
     487           0 :       TEST( rec->exists );
     488           0 :       prev = idx;
     489           0 :       idx  = rec->next_idx;
     490           0 :       rec_cnt++;
     491           0 :     }
     492           0 :     TEST( prev==txn->rec_tail_idx );
     493           0 :   }
     494             : 
     495             :   /* A record is mapped, a zombie, free, or in flight -- never two. */
     496           0 :   ulong mapped_cnt = 0UL;
     497           0 :   ulong free_cnt   = 0UL;
     498             : 
     499           0 :   ulong chain_cnt = fd_prog_recm_chain_cnt( join->rec.map );
     500           0 :   for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
     501           0 :     for(
     502           0 :         fd_prog_recm_iter_t iter = fd_prog_recm_iter( join->rec.map, chain_idx );
     503           0 :         !fd_prog_recm_iter_done( iter );
     504           0 :         iter = fd_prog_recm_iter_next( iter )
     505           0 :     ) {
     506           0 :       fd_progcache_rec_t * rec = fd_prog_recm_iter_ele( iter );
     507           0 :       TEST( rec->exists );
     508             : 
     509             :       /* Verify state is LIVE for mapped records */
     510           0 :       ulong rec_idx = (ulong)( rec - rec0 );
     511           0 :       TEST( rec_idx<rec_max );
     512           0 :       uchar st = __atomic_load_n( &rec->state, __ATOMIC_RELAXED );
     513             :       /* Mapped means LIVE, or LOADING while its publisher finishes. */
     514           0 :       TEST( st & ( FD_PROGCACHE_REC_LIVE | FD_PROGCACHE_REC_LOADING ) );
     515             :       /* Detached means rooted, so the load is over. */
     516           0 :       if( atomic_load_explicit( &rec->txn_idx, memory_order_acquire )==UINT_MAX )
     517           0 :         TEST( st & FD_PROGCACHE_REC_LIVE );
     518           0 :       TEST( st & FD_PROGCACHE_REC_MAPPED );
     519           0 :       TEST( (ulong)rec->size_class==fd_progcache_rec_class( shmem, rec_idx ) );
     520           0 :       mapped_cnt++;
     521           0 :       TEST( rec->lock.value!=FD_RWLOCK_WRITE_LOCK ); /* push relies on this */
     522           0 :     }
     523           0 :   }
     524             : 
     525             :   /* A free record is write-locked, dead, and in its own class's list. */
     526           0 :   for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
     527           0 :     ulong base      = shmem->cache.rec_base [ c ];
     528           0 :     ulong class_max = shmem->cache.class_max[ c ];
     529             : 
     530           0 :     ulong cnt = 0UL;
     531           0 :     uint  idx = (uint)( shmem->cache.free_top[ c ].ver_top & (ulong)UINT_MAX );
     532           0 :     while( idx!=UINT_MAX ) {
     533           0 :       TEST( (ulong)idx>=base && (ulong)idx<base+class_max );
     534           0 :       fd_progcache_rec_t * rec = &rec0[ idx ];
     535           0 :       TEST( !rec->exists );
     536           0 :       TEST( rec->lock.value==FD_RWLOCK_WRITE_LOCK );
     537           0 :       TEST( !__atomic_load_n( &rec->state, __ATOMIC_RELAXED ) );
     538           0 :       TEST( cnt<class_max ); /* cycle detection */
     539           0 :       cnt++;
     540           0 :       idx = rec->free_next;
     541           0 :     }
     542           0 :     TEST( cnt<=class_max );
     543           0 :     TEST( cnt==shmem->cache.free_cnt[ c ].val );
     544           0 :     free_cnt += cnt;
     545           0 :   }
     546             : 
     547           0 :   TEST( mapped_cnt+free_cnt<=rec_max );
     548             : 
     549           0 : # undef TEST
     550             : 
     551           0 :   return 0;
     552           0 : }

Generated by: LCOV version 1.14