LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_admin.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 329 386 85.2 %
Date: 2026-09-17 04:28:31 Functions: 13 13 100.0 %

          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        4575 :                            fd_progcache_fork_id_t parent_fork_id ) {
      20        4575 :   if( FD_UNLIKELY( !cache ) ) FD_LOG_CRIT(( "invalid arguments" ));
      21             : 
      22        4575 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
      23        4575 :   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        4575 :   ulong  txn_max = fd_prog_txnp_max( cache->txn.pool );
      28        4575 :   ulong  parent_idx;
      29        4575 :   uint * _child_head_idx;
      30        4575 :   uint * _child_tail_idx;
      31             : 
      32        4575 :   fd_progcache_fork_id_t root = __atomic_load_n( &cache->shmem->txn.root, memory_order_relaxed );
      33        4575 :   if( FD_UNLIKELY( parent_fork_id == root ) ) {
      34             : 
      35        4368 :     parent_idx = FD_PROGCACHE_TXN_IDX_NULL;
      36             : 
      37        4368 :     _child_head_idx = &cache->shmem->txn.child_head_idx;
      38        4368 :     _child_tail_idx = &cache->shmem->txn.child_tail_idx;
      39             : 
      40        4368 :   } else {
      41             : 
      42         207 :     parent_idx = fd_prog_txnm_idx_query( cache->txn.map, &parent_fork_id, ULONG_MAX, cache->txn.pool );
      43         207 :     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         207 :     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         207 :     _child_head_idx = &cache->txn.pool[ parent_idx ].child_head_idx;
      50         207 :     _child_tail_idx = &cache->txn.pool[ parent_idx ].child_tail_idx;
      51             : 
      52         207 :   }
      53             : 
      54        4575 :   uint txn_idx = (uint)fd_prog_txnp_idx_acquire( cache->txn.pool );
      55        4575 :   if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) FD_LOG_ERR(( "fd_progcache_attach_child failed: transaction object pool out of memory" ));
      56        4575 :   fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
      57        4575 :   txn->xid = __atomic_add_fetch( &cache->shmem->txn.seq, 1UL, memory_order_relaxed );
      58             : 
      59        4575 :   uint sibling_prev_idx = *_child_tail_idx;
      60             : 
      61        4575 :   int first_born = sibling_prev_idx==UINT_MAX;
      62        4575 :   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        4575 :   txn->parent_idx       = (uint)parent_idx;
      66        4575 :   txn->child_head_idx   = UINT_MAX;
      67        4575 :   txn->child_tail_idx   = UINT_MAX;
      68        4575 :   txn->sibling_prev_idx = (uint)sibling_prev_idx;
      69        4575 :   txn->sibling_next_idx = UINT_MAX;
      70             : 
      71        4575 :   txn->rec_head_idx = UINT_MAX;
      72        4575 :   txn->rec_tail_idx = UINT_MAX;
      73             : 
      74             :   /* TODO: consider branchless impl */
      75        4575 :   if( FD_LIKELY( first_born ) ) *_child_head_idx            = (uint)txn_idx; /* opt for non-compete */
      76         201 :   else cache->txn.pool[ sibling_prev_idx ].sibling_next_idx = (uint)txn_idx;
      77             : 
      78        4575 :   *_child_tail_idx = (uint)txn_idx;
      79             : 
      80        4575 :   fd_prog_txnm_idx_insert( cache->txn.map, txn_idx, cache->txn.pool );
      81             : 
      82        4575 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
      83        4575 :   return txn->xid;
      84        4575 : }
      85             : 
      86             : static void
      87             : fd_progcache_cancel_one( fd_progcache_join_t * cache,
      88         369 :                          fd_progcache_txn_t *  txn ) {
      89         369 :   ulong rec_max = cache->rec.max;
      90         369 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
      91             : 
      92         369 :   fd_rwlock_write( &txn->lock );
      93             : 
      94         369 :   if( FD_UNLIKELY( txn->child_head_idx!=UINT_MAX ||
      95         369 :                    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        2610 :   for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; ) {
     103        2241 :     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        2241 :     fd_progcache_rec_t * rec = &cache->rec.ele[ idx ];
     106        2241 :     uint next_idx = rec->next_idx;
     107        2241 :     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        2241 :     atomic_store_explicit( &rec->txn_idx, UINT_MAX, memory_order_release );
     110        2241 :     fd_racesan_hook( "prog_cancel_one:post_orphan" );
     111        2241 :     fd_prog_delete_rec( cache, rec );
     112        2241 :     idx = next_idx;
     113        2241 :   }
     114             : 
     115         369 :   txn->rec_head_idx = UINT_MAX;
     116         369 :   txn->rec_tail_idx = UINT_MAX;
     117             : 
     118             :   /* Remove transaction from fork graph */
     119             : 
     120         369 :   uint self_idx = (uint)( txn - cache->txn.pool );
     121         369 :   uint prev_idx = txn->sibling_prev_idx;
     122         369 :   uint next_idx = txn->sibling_next_idx;
     123         369 :   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         369 :   if( prev_idx!=UINT_MAX ) {
     129         201 :     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         201 :     cache->txn.pool[ prev_idx ].sibling_next_idx = next_idx;
     132         201 :   }
     133         369 :   if( txn->parent_idx!=UINT_MAX ) {
     134          57 :     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          57 :     fd_progcache_txn_t * parent = &cache->txn.pool[ txn->parent_idx ];
     137          57 :     if( parent->child_head_idx==self_idx ) parent->child_head_idx = next_idx;
     138          57 :     if( parent->child_tail_idx==self_idx ) parent->child_tail_idx = prev_idx;
     139         312 :   } else {
     140         312 :     if( cache->shmem->txn.child_head_idx==self_idx ) cache->shmem->txn.child_head_idx = next_idx;
     141         312 :     if( cache->shmem->txn.child_tail_idx==self_idx ) cache->shmem->txn.child_tail_idx = prev_idx;
     142         312 :   }
     143             : 
     144             :   /* Remove transaction from index */
     145             : 
     146         369 :   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         369 :   fd_rwlock_unwrite( &txn->lock );
     153         369 :   fd_prog_txnp_ele_release( cache->txn.pool, txn );
     154         369 : }
     155             : 
     156             : /* Cancels txn and all children */
     157             : 
     158             : static void
     159             : fd_progcache_cancel_tree( fd_progcache_join_t * cache,
     160         369 :                           fd_progcache_txn_t *  txn ) {
     161         369 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     162         375 :   for(;;) {
     163         375 :     uint child_idx = txn->child_head_idx;
     164         375 :     if( child_idx==UINT_MAX ) break;
     165           6 :     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           6 :     fd_progcache_txn_t * child = &cache->txn.pool[ child_idx ];
     168           6 :     fd_progcache_cancel_tree( cache, child );
     169           6 :   }
     170         369 :   fd_progcache_cancel_one( cache, txn );
     171         369 : }
     172             : 
     173             : /* Cancels all left/right siblings */
     174             : 
     175             : static void
     176             : fd_progcache_cancel_prev_list( fd_progcache_join_t * cache,
     177         147 :                                fd_progcache_txn_t *  txn ) {
     178         147 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     179         147 :   uint cur_idx = txn->sibling_prev_idx;
     180         147 :   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         147 : }
     189             : 
     190             : static void
     191             : fd_progcache_cancel_next_list( fd_progcache_join_t * cache,
     192         147 :                                fd_progcache_txn_t *  txn ) {
     193         147 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     194         147 :   uint cur_idx = txn->sibling_next_idx;
     195         147 :   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         147 : }
     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         147 :                               fd_progcache_txn_t *  txn ) {
     211             : 
     212             :   /* Phase 1: Mark transaction as "last published" */
     213             : 
     214         147 :   fd_progcache_fork_id_t const fork_id = txn->xid;
     215         147 :   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         147 :   fd_racesan_hook( "prog_publish_one:pre_xid_store" );
     219         147 :   __atomic_store_n( &cache->shmem->txn.root, fork_id, memory_order_release );
     220             : 
     221             :   /* Phase 2: Drain inserters from transaction */
     222             : 
     223         147 :   fd_rwlock_write( &txn->lock );
     224             : 
     225             :   /* Phase 3: Detach records */
     226             : 
     227         147 :   ulong rec_max = cache->rec.max;
     228         660 :   for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; ) {
     229         513 :     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         513 :     uint next_idx = cache->rec.ele[ idx ].next_idx;
     232         513 :     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         513 :     atomic_store_explicit( &cache->rec.ele[ idx ].txn_idx, UINT_MAX, memory_order_release );
     235         513 :     fd_racesan_hook( "prog_publish_one:post_detach" );
     236         513 :     fd_progcache_admin_metrics_g.root_cnt++;
     237         513 :     idx = next_idx;
     238         513 :   }
     239             : 
     240         147 :   txn->rec_head_idx = UINT_MAX;
     241         147 :   txn->rec_tail_idx = UINT_MAX;
     242             : 
     243             :   /* Phase 4: Remove transaction from fork graph */
     244             : 
     245         147 :   { /* Adjust the parent pointers of the children to point to "last published" */
     246         147 :     ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     247         147 :     ulong child_idx = txn->child_head_idx;
     248         153 :     while( child_idx!=UINT_MAX ) {
     249           6 :       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           6 :       cache->txn.pool[ child_idx ].parent_idx = UINT_MAX;
     252           6 :       child_idx = cache->txn.pool[ child_idx ].sibling_next_idx;
     253           6 :     }
     254         147 :   }
     255             : 
     256             :   /* Phase 5: Remove transaction from index */
     257             : 
     258         147 :   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         147 :   fd_rwlock_unwrite( &txn->lock );
     265         147 :   txn->parent_idx       = UINT_MAX;
     266         147 :   txn->sibling_prev_idx = UINT_MAX;
     267         147 :   txn->sibling_next_idx = UINT_MAX;
     268         147 :   txn->child_head_idx   = UINT_MAX;
     269         147 :   txn->child_tail_idx   = UINT_MAX;
     270         147 :   fd_prog_txnp_ele_release( cache->txn.pool, txn );
     271         147 : }
     272             : 
     273             : void
     274             : fd_progcache_advance_root( fd_progcache_join_t *  cache,
     275         147 :                            fd_progcache_fork_id_t fork_id ) {
     276         147 :   if( FD_UNLIKELY( !cache ) ) FD_LOG_CRIT(( "invalid arguments" ));
     277             : 
     278             :   /* Detach records from txns without acquiring record locks */
     279             : 
     280         147 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
     281             : 
     282         147 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     283         147 :   uint txn_idx = (uint)fd_prog_txnm_idx_query( cache->txn.map, &fork_id, UINT_MAX, cache->txn.pool );
     284         147 :   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         147 :   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         147 :   fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
     290         147 :   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         147 :   fd_progcache_cancel_prev_list( cache, txn );
     295         147 :   fd_progcache_cancel_next_list( cache, txn );
     296             : 
     297         147 :   txn->sibling_prev_idx = UINT_MAX;
     298         147 :   txn->sibling_next_idx = UINT_MAX;
     299         147 :   cache->shmem->txn.child_head_idx = txn->child_head_idx;
     300         147 :   cache->shmem->txn.child_tail_idx = txn->child_tail_idx;
     301             : 
     302         147 :   fd_progcache_txn_publish_one( cache, txn );
     303             : 
     304         147 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     305         147 : }
     306             : 
     307             : void
     308             : fd_progcache_cancel_fork( fd_progcache_join_t *  cache,
     309         363 :                           fd_progcache_fork_id_t fork_id ) {
     310         363 :   if( FD_UNLIKELY( !cache ) ) {
     311           0 :     FD_LOG_CRIT(( "invalid arguments" ));
     312           0 :   }
     313             : 
     314         363 :   fd_rwlock_write( &cache->shmem->txn.rwlock );
     315             : 
     316         363 :   fd_progcache_txn_t * txn = fd_prog_txnm_ele_query( cache->txn.map, &fork_id, NULL, cache->txn.pool );
     317         363 :   if( FD_UNLIKELY( !txn ) ) {
     318           0 :     FD_LOG_CRIT(( "fd_progcache_cancel failed: invalid fork_id %lu", fork_id ));
     319           0 :   }
     320         363 :   fd_progcache_cancel_tree( cache, txn );
     321             : 
     322         363 :   fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
     323         363 : }
     324             : 
     325             : /* reset_rec_map frees all records in a progcache instance. */
     326             : 
     327             : static void
     328        3957 : reset_rec_map( fd_progcache_join_t * cache ) {
     329        3957 :   ulong chain_cnt = fd_prog_recm_chain_cnt( cache->rec.map );
     330      510453 :   for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
     331      506496 :     for(
     332      506496 :         fd_prog_recm_iter_t iter = fd_prog_recm_iter( cache->rec.map, chain_idx );
     333      508953 :         !fd_prog_recm_iter_done( iter );
     334      506496 :     ) {
     335        2457 :       fd_progcache_rec_t * rec = fd_prog_recm_iter_ele( iter );
     336        2457 :       ulong next = fd_prog_recm_private_idx( rec->map_next );
     337             : 
     338        2457 :       fd_prog_recm_query_t rec_query[1];
     339        2457 :       int err = fd_prog_recm_remove( cache->rec.map, &rec->pair, NULL, rec_query, FD_MAP_FLAG_BLOCKING );
     340        2457 :       if( FD_UNLIKELY( err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed (%i-%s)", err, fd_map_strerror( err ) ));
     341        2457 :       if( FD_UNLIKELY( !fd_rwlock_trywrite( &rec->lock ) ) )
     342           0 :         FD_LOG_CRIT(( "fd_progcache_reset requires quiescence: record still read-locked" ));
     343        2457 :       fd_progcache_rec_release( cache, rec );
     344             : 
     345        2457 :       iter.ele_idx = next;
     346        2457 :     }
     347      506496 :   }
     348        3957 : }
     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        7959 :                 uint                  txn_head_idx ) {
     356        7959 :   ulong txn_max = fd_prog_txnp_max( join->txn.pool );
     357       11961 :   for( uint idx = txn_head_idx; idx!=UINT_MAX; ) {
     358        4002 :     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        4002 :     fd_progcache_txn_t * txn = &join->txn.pool[ idx ];
     361        4002 :     uint next_idx  = txn->sibling_next_idx;
     362        4002 :     uint child_idx = txn->child_head_idx;
     363        4002 :     txn->rec_head_idx     = UINT_MAX;
     364        4002 :     txn->rec_tail_idx     = UINT_MAX;
     365        4002 :     txn->child_head_idx   = UINT_MAX;
     366        4002 :     txn->child_tail_idx   = UINT_MAX;
     367        4002 :     txn->parent_idx       = UINT_MAX;
     368        4002 :     txn->sibling_prev_idx = UINT_MAX;
     369        4002 :     txn->sibling_next_idx = UINT_MAX;
     370        4002 :     clear_txn_list( join, child_idx );
     371        4002 :     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        4002 :     fd_prog_txnp_ele_release( join->txn.pool, txn );
     373        4002 :     idx = next_idx;
     374        4002 :   }
     375        7959 : }
     376             : 
     377             : void
     378        3957 : 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        3957 :   fd_prog_reclaim_work( cache );
     382      688518 :   for( ulong i=0UL; i<cache->rec.max; i++ ) {
     383      684561 :     uchar st = __atomic_load_n( &cache->rec.ele[ i ].state, __ATOMIC_RELAXED );
     384      684561 :     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      684561 :   }
     387        3957 :   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        3957 :   clear_txn_list( cache, cache->shmem->txn.child_head_idx );
     390        3957 :   cache->shmem->txn.child_head_idx = UINT_MAX;
     391        3957 :   cache->shmem->txn.child_tail_idx = UINT_MAX;
     392        3957 :   reset_rec_map( cache );
     393        3957 :   cache->shmem->txn.root = fd_progcache_fork_id_initial();
     394        3957 :   cache->shmem->txn.seq  = fd_progcache_fork_id_initial();
     395        3957 : }
     396             : 
     397             : static int
     398             : fd_progcache_verify_siblings( fd_progcache_txn_t * pool,
     399             :                               ulong                txn_max,
     400             :                               uint                 head_idx,
     401             :                               uint                 tail_idx,
     402             :                               uint                 expected_parent_idx,
     403             :                               uint *               stack,
     404         243 :                               ulong *              stack_top ) {
     405             : 
     406         702 : # define TEST(c) do {                                                    \
     407         702 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return -1; } \
     408         702 :   } while(0)
     409             : 
     410         243 :   TEST( (head_idx==UINT_MAX)==(tail_idx==UINT_MAX) );
     411             : 
     412         243 :   uint last_idx = UINT_MAX;
     413         297 :   for( uint idx = head_idx; idx!=UINT_MAX; ) {
     414          54 :     TEST( idx<txn_max );
     415          54 :     fd_progcache_txn_t * child = &pool[ idx ];
     416          54 :     TEST( !child->tag );
     417          54 :     TEST( child->parent_idx==expected_parent_idx );
     418          54 :     child->tag = 1;
     419          54 :     TEST( *stack_top<FD_PROGCACHE_DEPTH_MAX );
     420          54 :     stack[ (*stack_top)++ ] = idx;
     421          54 :     last_idx = idx;
     422          54 :     uint next_idx = child->sibling_next_idx;
     423          54 :     if( next_idx!=UINT_MAX ) {
     424           0 :       TEST( next_idx<txn_max );
     425           0 :       TEST( pool[ next_idx ].sibling_prev_idx==idx );
     426           0 :     }
     427          54 :     idx = next_idx;
     428          54 :   }
     429         243 :   TEST( last_idx==tail_idx );
     430             : 
     431         243 : # undef TEST
     432             : 
     433         243 :   return 0;
     434         243 : }
     435             : 
     436             : int
     437         192 : fd_progcache_verify( fd_progcache_join_t * join ) {
     438             : 
     439      172911 : # define TEST(c) do {                                                    \
     440      172911 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return -1; } \
     441      172911 :   } while(0)
     442             : 
     443         192 :   TEST( join );
     444             : 
     445         192 :   fd_progcache_shmem_t * shmem = join->shmem;
     446         192 :   TEST( shmem );
     447         192 :   TEST( shmem->magic==FD_PROGCACHE_SHMEM_MAGIC );
     448         189 :   TEST( shmem->wksp_tag );
     449             : 
     450         189 :   TEST( !fd_prog_recm_verify( join->rec.map ) );
     451             : 
     452         189 :   ulong rec_max = join->rec.max;
     453         189 :   fd_progcache_rec_t * rec0 = join->rec.ele;
     454             : 
     455         189 :   ulong txn_max = fd_prog_txnp_max( join->txn.pool );
     456         189 :   TEST( !fd_prog_txnm_verify( join->txn.map, txn_max, join->txn.pool ) );
     457             : 
     458        3645 :   for( ulong i=0UL; i<txn_max; i++ ) join->txn.pool[ i ].tag = 0;
     459             : 
     460         189 :   uint  stack[ FD_PROGCACHE_DEPTH_MAX ];
     461         189 :   ulong stack_top = 0UL;
     462             : 
     463         189 :   TEST( !fd_progcache_verify_siblings( join->txn.pool, txn_max,
     464         189 :       shmem->txn.child_head_idx, shmem->txn.child_tail_idx,
     465         189 :       UINT_MAX, stack, &stack_top ) );
     466             : 
     467         243 :   while( stack_top ) {
     468          54 :     uint txn_idx = stack[ --stack_top ];
     469          54 :     fd_progcache_txn_t * txn = &join->txn.pool[ txn_idx ];
     470          54 :     TEST( !fd_progcache_verify_siblings( join->txn.pool, txn_max,
     471          54 :         txn->child_head_idx, txn->child_tail_idx,
     472          54 :         txn_idx, stack, &stack_top ) );
     473          54 :   }
     474             : 
     475        3549 :   for( ulong i=0UL; i<txn_max; i++ ) {
     476        3366 :     if( !join->txn.pool[ i ].tag ) continue;
     477          54 :     fd_progcache_txn_t * txn = &join->txn.pool[ i ];
     478             : 
     479          54 :     TEST( (txn->rec_head_idx==UINT_MAX)==(txn->rec_tail_idx==UINT_MAX) );
     480             : 
     481          54 :     ulong rec_cnt = 0UL;
     482          54 :     uint  prev    = UINT_MAX;
     483         534 :     for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; ) {
     484         486 :       TEST( idx<rec_max );
     485         486 :       TEST( rec_cnt<rec_max ); /* cycle detection */
     486         486 :       fd_progcache_rec_t * rec = &rec0[ idx ];
     487         486 :       TEST( rec->prev_idx==prev );
     488         483 :       TEST( rec->exists );
     489         480 :       prev = idx;
     490         480 :       idx  = rec->next_idx;
     491         480 :       rec_cnt++;
     492         480 :     }
     493          48 :     TEST( prev==txn->rec_tail_idx );
     494          48 :   }
     495             : 
     496             :   /* A record is mapped, a zombie, free, or in flight -- never two. */
     497         183 :   ulong mapped_cnt = 0UL;
     498         183 :   ulong free_cnt   = 0UL;
     499             : 
     500         183 :   ulong chain_cnt = fd_prog_recm_chain_cnt( join->rec.map );
     501       25530 :   for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
     502       25350 :     for(
     503       25350 :         fd_prog_recm_iter_t iter = fd_prog_recm_iter( join->rec.map, chain_idx );
     504       26370 :         !fd_prog_recm_iter_done( iter );
     505       25350 :         iter = fd_prog_recm_iter_next( iter )
     506       25350 :     ) {
     507        1023 :       fd_progcache_rec_t * rec = fd_prog_recm_iter_ele( iter );
     508        1023 :       TEST( rec->exists );
     509             : 
     510             :       /* Verify state is LIVE for mapped records */
     511        1023 :       ulong rec_idx = (ulong)( rec - rec0 );
     512        1023 :       TEST( rec_idx<rec_max );
     513        1023 :       uchar st = __atomic_load_n( &rec->state, __ATOMIC_RELAXED );
     514             :       /* Mapped means LIVE, or LOADING while its publisher finishes. */
     515        1023 :       TEST( st & ( FD_PROGCACHE_REC_LIVE | FD_PROGCACHE_REC_LOADING ) );
     516             :       /* Detached means rooted, so the load is over. */
     517        1020 :       if( atomic_load_explicit( &rec->txn_idx, memory_order_acquire )==UINT_MAX )
     518         543 :         TEST( st & FD_PROGCACHE_REC_LIVE );
     519        1020 :       TEST( st & FD_PROGCACHE_REC_MAPPED );
     520        1020 :       TEST( (ulong)rec->size_class==fd_progcache_rec_class( shmem, rec_idx ) );
     521        1020 :       mapped_cnt++;
     522        1020 :       TEST( rec->lock.value!=FD_RWLOCK_WRITE_LOCK ); /* push relies on this */
     523        1020 :     }
     524       25350 :   }
     525             : 
     526             :   /* A free record is write-locked, dead, and in its own class's list. */
     527        1260 :   for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
     528        1080 :     ulong base      = shmem->cache.rec_base [ c ];
     529        1080 :     ulong class_max = shmem->cache.class_max[ c ];
     530             : 
     531        1080 :     ulong cnt = 0UL;
     532        1080 :     uint  idx = (uint)( shmem->cache.free_top[ c ].ver_top & (ulong)UINT_MAX );
     533       33174 :     while( idx!=UINT_MAX ) {
     534       32094 :       TEST( (ulong)idx>=base && (ulong)idx<base+class_max );
     535       32094 :       fd_progcache_rec_t * rec = &rec0[ idx ];
     536       32094 :       TEST( !rec->exists );
     537       32094 :       TEST( rec->lock.value==FD_RWLOCK_WRITE_LOCK );
     538       32094 :       TEST( !__atomic_load_n( &rec->state, __ATOMIC_RELAXED ) );
     539       32094 :       TEST( cnt<class_max ); /* cycle detection */
     540       32094 :       cnt++;
     541       32094 :       idx = rec->free_next;
     542       32094 :     }
     543        1080 :     TEST( cnt<=class_max );
     544        1080 :     TEST( cnt==shmem->cache.free_cnt[ c ].val );
     545        1080 :     free_cnt += cnt;
     546        1080 :   }
     547             : 
     548         180 :   TEST( mapped_cnt+free_cnt<=rec_max );
     549             : 
     550         180 : # undef TEST
     551             : 
     552         180 :   return 0;
     553         180 : }

Generated by: LCOV version 1.14