LCOV - code coverage report
Current view: top level - funk - fd_funk_rec.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 176 282 62.4 %
Date: 2025-10-26 04:44:17 Functions: 7 9 77.8 %

          Line data    Source code
       1             : #include "fd_funk_private.h"
       2             : #include "../util/racesan/fd_racesan_target.h"
       3             : 
       4             : /* Provide the actual record map implementation */
       5             : 
       6             : #define POOL_NAME       fd_funk_rec_pool
       7        1005 : #define POOL_ELE_T      fd_funk_rec_t
       8             : #define POOL_IDX_T      uint
       9         141 : #define POOL_NEXT       map_next
      10             : #define POOL_IMPL_STYLE 2
      11             : #define POOL_LAZY       1
      12             : #include "../util/tmpl/fd_pool_para.c"
      13             : 
      14             : #define MAP_NAME              fd_funk_rec_map
      15         687 : #define MAP_ELE_T             fd_funk_rec_t
      16           6 : #define MAP_KEY_T             fd_funk_xid_key_pair_t
      17         114 : #define MAP_KEY               pair
      18             : #define MAP_KEY_EQ(k0,k1)     fd_funk_xid_key_pair_eq((k0),(k1))
      19             : #define MAP_KEY_HASH(k0,seed) fd_funk_xid_key_pair_hash((k0),(seed))
      20         129 : #define MAP_IDX_T             uint
      21         273 : #define MAP_NEXT              map_next
      22         111 : #define MAP_MAGIC             (0xf173da2ce77ecdb0UL) /* Firedancer rec db version 0 */
      23         402 : #define MAP_CNT_WIDTH         FD_FUNK_REC_MAP_CNT_WIDTH
      24             : #define MAP_IMPL_STYLE        2
      25             : #define MAP_PEDANTIC          1
      26             : #include "../util/tmpl/fd_map_chain_para.c"
      27             : 
      28             : static fd_funk_txn_t *
      29             : fd_funk_rec_txn_borrow( fd_funk_t const *         funk,
      30             :                         fd_funk_txn_xid_t const * xid,
      31        4194 :                         fd_funk_txn_map_query_t * query ) {
      32        4194 :   memset( query, 0, sizeof(fd_funk_txn_map_query_t) );
      33        4194 :   if( fd_funk_txn_xid_eq( xid, funk->shmem->last_publish ) ) return NULL;
      34             : 
      35        4188 :   fd_funk_txn_map_query_t txn_query[1];
      36        4188 :   for(;;) {
      37        4188 :     int txn_query_err = fd_funk_txn_map_query_try( funk->txn_map, xid, NULL, txn_query, 0 );
      38        4188 :     if( FD_UNLIKELY( txn_query_err==FD_MAP_ERR_AGAIN ) ) continue;
      39        4188 :     if( FD_UNLIKELY( txn_query_err!=FD_MAP_SUCCESS ) ) {
      40           0 :       FD_LOG_CRIT(( "fd_funk_rec op failed: txn_map_query_try(%lu:%lu) error %i-%s",
      41           0 :                    xid->ul[0], xid->ul[1],
      42           0 :                    txn_query_err, fd_map_strerror( txn_query_err ) ));
      43           0 :     }
      44        4188 :     break;
      45        4188 :   }
      46        4188 :   fd_funk_txn_t * txn = fd_funk_txn_map_query_ele( txn_query );
      47        4188 :   uint txn_state = FD_VOLATILE_CONST( txn->state );
      48        4188 :   if( FD_UNLIKELY( txn_state!=FD_FUNK_TXN_STATE_ACTIVE ) ) {
      49           0 :     FD_LOG_CRIT(( "fd_funk_rec op failed: txn %p %lu:%lu state is %u-%s",
      50           0 :                   (void *)txn, xid->ul[0], xid->ul[1],
      51           0 :                   txn_state, fd_funk_txn_state_str( txn_state ) ));
      52           0 :   }
      53        4188 :   return txn;
      54        4188 : }
      55             : 
      56             : static void
      57        4194 : fd_funk_rec_txn_release( fd_funk_txn_map_query_t const * query ) {
      58        4194 :   if( !query->ele ) return;
      59           0 :   if( FD_UNLIKELY( fd_funk_txn_map_query_test( query )!=FD_MAP_SUCCESS ) ) {
      60           0 :     FD_LOG_CRIT(( "fd_funk_rec_txn_release: fd_funk_txn_map_query_test failed (data race detected?)" ));
      61           0 :   }
      62           0 : }
      63             : 
      64             : static void
      65             : fd_funk_rec_key_set_pair( fd_funk_xid_key_pair_t *  key_pair,
      66             :                           fd_funk_txn_xid_t const * xid,
      67        4194 :                           fd_funk_rec_key_t const * key ) {
      68        4194 :   fd_funk_txn_xid_copy( key_pair->xid, xid );
      69        4194 :   fd_funk_rec_key_copy( key_pair->key, key );
      70        4194 : }
      71             : 
      72             : fd_funk_rec_t *
      73             : fd_funk_rec_query_try( fd_funk_t *               funk,
      74             :                        fd_funk_txn_xid_t const * xid,
      75             :                        fd_funk_rec_key_t const * key,
      76           0 :                        fd_funk_rec_query_t *     query ) {
      77           0 :   if( FD_UNLIKELY( !funk  ) ) FD_LOG_CRIT(( "NULL funk"  ));
      78           0 :   if( FD_UNLIKELY( !xid   ) ) FD_LOG_CRIT(( "NULL xid"   ));
      79           0 :   if( FD_UNLIKELY( !key   ) ) FD_LOG_CRIT(( "NULL key"   ));
      80           0 :   if( FD_UNLIKELY( !query ) ) FD_LOG_CRIT(( "NULL query" ));
      81             : 
      82           0 :   fd_funk_xid_key_pair_t pair[1];
      83           0 :   if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid, funk->shmem->last_publish ) ) ) {
      84           0 :     fd_funk_txn_xid_set_root( pair->xid );
      85           0 :   } else {
      86           0 :     fd_funk_txn_xid_copy( pair->xid, xid );
      87           0 :   }
      88           0 :   fd_funk_rec_key_copy( pair->key, key );
      89             : 
      90           0 :   for(;;) {
      91           0 :     int err = fd_funk_rec_map_query_try( funk->rec_map, pair, NULL, query, 0 );
      92           0 :     if( err == FD_MAP_SUCCESS )   break;
      93           0 :     if( err == FD_MAP_ERR_KEY )   return NULL;
      94           0 :     if( err == FD_MAP_ERR_AGAIN ) continue;
      95           0 :     FD_LOG_CRIT(( "query returned err %d", err ));
      96           0 :   }
      97           0 :   return fd_funk_rec_map_query_ele( query );
      98           0 : }
      99             : 
     100             : fd_funk_rec_t const *
     101             : fd_funk_rec_query_try_global( fd_funk_t const *         funk,
     102             :                               fd_funk_txn_xid_t const * xid,
     103             :                               fd_funk_rec_key_t const * key,
     104             :                               fd_funk_txn_xid_t *       txn_out,
     105        4194 :                               fd_funk_rec_query_t *     query ) {
     106        4194 :   if( FD_UNLIKELY( !funk  ) ) FD_LOG_CRIT(( "NULL funk"  ));
     107        4194 :   if( FD_UNLIKELY( !key   ) ) FD_LOG_CRIT(( "NULL key"   ));
     108        4194 :   if( FD_UNLIKELY( !query ) ) FD_LOG_CRIT(( "NULL query" ));
     109             : 
     110             :   /* Attach to the funk transaction */
     111             : 
     112        4194 :   fd_funk_txn_map_query_t txn_query[1];
     113        4194 :   fd_funk_txn_t * txn = fd_funk_rec_txn_borrow( funk, xid, txn_query );
     114             : 
     115             :   /* Look for the first element in the hash chain with the right
     116             :      record key. This takes advantage of the fact that elements with
     117             :      the same record key appear on the same hash chain in order of
     118             :      newest to oldest. */
     119             : 
     120        4194 :   fd_funk_xid_key_pair_t pair[1];
     121        4194 :   fd_funk_rec_key_set_pair( pair, xid, key );
     122             : 
     123        4194 :   fd_funk_rec_map_t const * rec_map = funk->rec_map;
     124             : 
     125        4194 :   ulong hash = fd_funk_rec_map_key_hash( pair, rec_map->map->seed );
     126        4194 :   ulong chain_idx = (hash & (rec_map->map->chain_cnt-1UL) );
     127        4194 :   fd_funk_rec_map_shmem_private_chain_t * chain = fd_funk_rec_map_shmem_private_chain( rec_map->map, hash );
     128             : 
     129             :   /* Start a speculative record map transaction */
     130             : 
     131        4194 :   struct {
     132        4194 :     fd_funk_rec_map_txn_t txn;
     133        4194 :     fd_funk_rec_map_txn_private_info_t lock[1];
     134        4194 :   } txn_mem;
     135        4194 :   fd_funk_rec_map_txn_t * rec_txn;
     136        4194 :   fd_funk_rec_t const * res;
     137             : 
     138        4194 : retry:
     139        4194 :   res = NULL;
     140        4194 :   rec_txn = fd_funk_rec_map_txn_init( &txn_mem.txn, (void *)rec_map, 1UL );
     141        4194 :   txn_mem.lock[ 0 ].chain = chain;
     142        4194 :   txn_mem.txn.spec_cnt++;
     143        4194 :   if( FD_UNLIKELY( fd_funk_rec_map_txn_try( rec_txn, FD_MAP_FLAG_BLOCKING )!=FD_MAP_SUCCESS ) ) {
     144           0 :     FD_LOG_CRIT(( "fd_funk_rec_map_txn_try failed" ));
     145           0 :   }
     146             : 
     147        4194 :   query->ele     = NULL;
     148        4194 :   query->chain   = chain;
     149        4194 :   query->ver_cnt = txn_mem.lock[0].ver_cnt;
     150             : 
     151        4194 :   for( fd_funk_rec_map_iter_t iter = fd_funk_rec_map_iter( funk->rec_map, chain_idx );
     152        4197 :        !fd_funk_rec_map_iter_done( iter );
     153        4194 :        iter = fd_funk_rec_map_iter_next( iter ) ) {
     154         522 :     fd_funk_rec_t const * ele = fd_funk_rec_map_iter_ele_const( iter );
     155         522 :     if( FD_LIKELY( fd_funk_rec_key_eq( key, ele->pair.key ) ) ) {
     156             : 
     157             :       /* For cur_txn in path from [txn] to [root] where root is NULL */
     158             : 
     159         540 :       for( fd_funk_txn_t const * cur_txn = txn; ; cur_txn = fd_funk_txn_parent( cur_txn, funk->txn_pool ) ) {
     160             :         /* If record ele is part of transaction cur_txn, we have a
     161             :            match. According to the property above, this will be the
     162             :            youngest descendent in the transaction stack. */
     163             : 
     164         540 :         int match = FD_UNLIKELY( cur_txn ) ? /* opt for root find (FIXME: eliminate branch with cmov into txn_xid_eq?) */
     165         534 :           fd_funk_txn_xid_eq( &cur_txn->xid, ele->pair.xid ) :
     166         540 :           fd_funk_txn_xid_eq_root( ele->pair.xid );
     167             : 
     168         540 :         if( FD_LIKELY( match ) ) {
     169         519 :           if( txn_out ) {
     170          42 :             *txn_out = *( cur_txn ? &cur_txn->xid : fd_funk_last_publish( funk ) );
     171          42 :           }
     172         519 :           query->ele = (fd_funk_rec_t *)ele;
     173         519 :           res = query->ele;
     174         519 :           goto found;
     175         519 :         }
     176             : 
     177          21 :         if( cur_txn == NULL ) break;
     178          21 :       }
     179         522 :     }
     180         522 :   }
     181             : 
     182        4194 : found:
     183        4194 :   if( FD_UNLIKELY( fd_funk_rec_map_txn_test( rec_txn )!=FD_MAP_SUCCESS ) ) goto retry;
     184        4194 :   if( FD_LIKELY( txn ) ) fd_funk_txn_xid_assert( txn, pair->xid );
     185        4194 :   fd_funk_rec_txn_release( txn_query );
     186        4194 :   return res;
     187        4194 : }
     188             : 
     189             : fd_funk_rec_t *
     190             : fd_funk_rec_prepare( fd_funk_t *               funk,
     191             :                      fd_funk_txn_xid_t const * xid,
     192             :                      fd_funk_rec_key_t const * key,
     193             :                      fd_funk_rec_prepare_t *   prepare,
     194           0 :                      int *                     opt_err ) {
     195           0 :   if( FD_UNLIKELY( !funk    ) ) FD_LOG_CRIT(( "NULL funk"    ));
     196           0 :   if( FD_UNLIKELY( !xid     ) ) FD_LOG_CRIT(( "NULL xid"     ));
     197           0 :   if( FD_UNLIKELY( !prepare ) ) FD_LOG_CRIT(( "NULL prepare" ));
     198             : 
     199           0 :   fd_funk_txn_map_query_t txn_query[1];
     200           0 :   fd_funk_txn_t * txn = fd_funk_rec_txn_borrow( funk, xid, txn_query );
     201             : 
     202           0 :   memset( prepare, 0, sizeof(fd_funk_rec_prepare_t) );
     203             : 
     204           0 :   if( !txn ) { /* Modifying last published */
     205           0 :     if( FD_UNLIKELY( fd_funk_last_publish_is_frozen( funk ) ) ) {
     206           0 :       fd_int_store_if( !!opt_err, opt_err, FD_FUNK_ERR_FROZEN );
     207           0 :       fd_funk_rec_txn_release( txn_query );
     208           0 :       return NULL;
     209           0 :     }
     210           0 :   } else {
     211           0 :     if( FD_UNLIKELY( fd_funk_txn_is_frozen( txn ) ) ) {
     212           0 :       fd_int_store_if( !!opt_err, opt_err, FD_FUNK_ERR_FROZEN );
     213           0 :       fd_funk_rec_txn_release( txn_query );
     214           0 :       return NULL;
     215           0 :     }
     216           0 :   }
     217             : 
     218           0 :   fd_funk_rec_t * rec = prepare->rec = fd_funk_rec_pool_acquire( funk->rec_pool, NULL, 1, opt_err );
     219           0 :   if( opt_err && *opt_err == FD_POOL_ERR_CORRUPT ) {
     220           0 :     FD_LOG_CRIT(( "corrupt element returned from funk rec pool" ));
     221           0 :   }
     222           0 :   if( FD_UNLIKELY( !rec ) ) {
     223           0 :     fd_int_store_if( !!opt_err, opt_err, FD_FUNK_ERR_REC );
     224           0 :     fd_funk_rec_txn_release( txn_query );
     225           0 :     return rec;
     226           0 :   }
     227             : 
     228           0 :   fd_funk_val_init( rec );
     229           0 :   if( txn == NULL ) {
     230           0 :     fd_funk_txn_xid_set_root( rec->pair.xid );
     231           0 :   } else {
     232           0 :     fd_funk_txn_xid_copy( rec->pair.xid, &txn->xid );
     233           0 :     prepare->rec_head_idx = &txn->rec_head_idx;
     234           0 :     prepare->rec_tail_idx = &txn->rec_tail_idx;
     235           0 :   }
     236           0 :   fd_funk_rec_key_copy( rec->pair.key, key );
     237           0 :   rec->tag      = 0;
     238           0 :   rec->prev_idx = FD_FUNK_REC_IDX_NULL;
     239           0 :   rec->next_idx = FD_FUNK_REC_IDX_NULL;
     240           0 :   fd_funk_rec_txn_release( txn_query );
     241           0 :   return rec;
     242           0 : }
     243             : 
     244             : #if FD_HAS_ATOMIC
     245             : 
     246             : static void
     247             : fd_funk_rec_push_tail( fd_funk_t *             funk,
     248          45 :                        fd_funk_rec_prepare_t * prepare ) {
     249          45 :   fd_funk_rec_t * rec = prepare->rec;
     250          45 :   uint rec_idx = (uint)( rec - funk->rec_pool->ele );
     251          45 :   uint * rec_head_idx = prepare->rec_head_idx;
     252          45 :   uint * rec_tail_idx = prepare->rec_tail_idx;
     253             : 
     254          45 :   for(;;) {
     255             : 
     256             :     /* Doubly linked list append.  Robust in the event of concurrent
     257             :        publishes.  Iteration during publish not supported.  Sequence:
     258             :        - Identify tail element
     259             :        - Set new element's prev and next pointers
     260             :        - Set tail element's next pointer
     261             :        - Set tail pointer */
     262             : 
     263          45 :     uint rec_prev_idx = FD_VOLATILE_CONST( *rec_tail_idx );
     264          45 :     rec->prev_idx = rec_prev_idx;
     265          45 :     FD_COMPILER_MFENCE();
     266             : 
     267          45 :     uint * next_idx_p;
     268          45 :     if( fd_funk_rec_idx_is_null( rec_prev_idx ) ) {
     269          45 :       next_idx_p = rec_head_idx;
     270          45 :     } else {
     271           0 :       next_idx_p = &funk->rec_pool->ele[ rec_prev_idx ].next_idx;
     272           0 :     }
     273             : 
     274          45 :     fd_racesan_hook( "funk_rec_push_tail:next_cas" );
     275          45 :     if( FD_UNLIKELY( !__sync_bool_compare_and_swap( next_idx_p, FD_FUNK_REC_IDX_NULL, rec_idx ) ) ) {
     276             :       /* Another thread beat us to the punch */
     277           0 :       FD_SPIN_PAUSE();
     278           0 :       continue;
     279           0 :     }
     280             : 
     281          45 :     fd_racesan_hook( "funk_rec_push_tail:tail_cas" );
     282          45 :     if( FD_UNLIKELY( !__sync_bool_compare_and_swap( rec_tail_idx, rec_prev_idx, rec_idx ) ) ) {
     283             :       /* This CAS is guaranteed to succeed if the previous CAS passed. */
     284           0 :       FD_LOG_CRIT(( "Irrecoverable data race encountered while appending to txn rec list (invariant violation?): cas(%p,%u,%u)",
     285           0 :                     (void *)rec_tail_idx, rec_prev_idx, rec_idx ));
     286           0 :     }
     287             : 
     288          45 :     break;
     289          45 :   }
     290          45 : }
     291             : 
     292             : #else
     293             : 
     294             : static void
     295             : fd_funk_rec_push_tail( fd_funk_t *             funk,
     296             :                        fd_funk_rec_prepare_t * prepare ) {
     297             :   fd_funk_rec_t * rec = prepare->rec;
     298             :   uint rec_idx      = (uint)( rec - funk->rec_pool->ele );
     299             :   uint * rec_head_idx = prepare->rec_head_idx;
     300             :   uint * rec_tail_idx = prepare->rec_tail_idx;
     301             :   uint rec_prev_idx = *rec_tail_idx;
     302             :   *rec_tail_idx = rec_idx;
     303             :   rec->prev_idx = rec_prev_idx;
     304             :   rec->next_idx = FD_FUNK_REC_IDX_NULL;
     305             :   if( fd_funk_rec_idx_is_null( rec_prev_idx ) ) {
     306             :     *rec_head_idx = rec_idx;
     307             :   } else {
     308             :     funk->rec_pool->ele[ rec_prev_idx ].next_idx = rec_idx;
     309             :   }
     310             : }
     311             : 
     312             : #endif
     313             : 
     314             : void
     315             : fd_funk_rec_publish( fd_funk_t *             funk,
     316          45 :                      fd_funk_rec_prepare_t * prepare ) {
     317          45 :   fd_funk_rec_t * rec = prepare->rec;
     318          45 :   rec->prev_idx = FD_FUNK_REC_IDX_NULL;
     319          45 :   rec->next_idx = FD_FUNK_REC_IDX_NULL;
     320             : 
     321          45 :   if( prepare->rec_head_idx ) {
     322          45 :     fd_funk_rec_push_tail( funk, prepare );
     323          45 :   }
     324             : 
     325          45 :   fd_racesan_hook( "funk_rec_publish:map_insert" );
     326          45 :   int insert_err = fd_funk_rec_map_insert( funk->rec_map, rec, FD_MAP_FLAG_BLOCKING );
     327          45 :   if( insert_err ) {
     328           0 :     FD_LOG_CRIT(( "fd_funk_rec_map_insert failed (%i-%s)", insert_err, fd_map_strerror( insert_err ) ));
     329           0 :   }
     330          45 : }
     331             : 
     332             : int
     333         249 : fd_funk_rec_verify( fd_funk_t * funk ) {
     334         249 :   fd_funk_rec_map_t *  rec_map  = funk->rec_map;
     335         249 :   fd_funk_rec_pool_t * rec_pool = funk->rec_pool;
     336         249 :   ulong rec_max = fd_funk_rec_pool_ele_max( rec_pool );
     337             : 
     338      838272 : # define TEST(c) do {                                                                           \
     339      838272 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_FUNK_ERR_INVAL; } \
     340      838272 :   } while(0)
     341             : 
     342         249 :   TEST( !fd_funk_rec_map_verify( rec_map ) );
     343         249 :   TEST( !fd_funk_rec_pool_verify( rec_pool ) );
     344             : 
     345             :   /* Iterate (again) over all records in use */
     346             : 
     347         249 :   fd_funk_rec_map_shmem_private_chain_t * chain_tbl =
     348         249 :     fd_funk_rec_map_shmem_private_chain( rec_map->map, 0UL );
     349         249 :   ulong chain_cnt = fd_funk_rec_map_chain_cnt( rec_map );
     350      837999 :   for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
     351      837750 :     ulong chain_ele_cnt = fd_funk_rec_map_private_vcnt_cnt( chain_tbl[ chain_idx ].ver_cnt );
     352      837750 :     ulong chain_ele_idx = 0UL;
     353      837750 :     for( fd_funk_rec_map_iter_t iter = fd_funk_rec_map_iter( rec_map, chain_idx );
     354      837756 :          !fd_funk_rec_map_iter_done( iter );
     355      837750 :          iter = fd_funk_rec_map_iter_next( iter ), chain_ele_idx++ ) {
     356           6 :       fd_funk_rec_t const * rec = fd_funk_rec_map_iter_ele_const( iter );
     357             : 
     358             :       /* Make sure every record either links up with the last published
     359             :          transaction or an in-prep transaction and the flags are sane. */
     360             : 
     361           6 :       fd_funk_txn_xid_t const * xid = fd_funk_rec_xid( rec );
     362             : 
     363           6 :       if( fd_funk_txn_xid_eq_root( xid ) ) { /* This is a record from the last published transaction */
     364             : 
     365           6 :         TEST( fd_funk_txn_xid_eq_root( xid ) );
     366             :         /* No record linked list at the root txn */
     367           6 :         TEST( fd_funk_rec_idx_is_null( rec->prev_idx ) );
     368           6 :         TEST( fd_funk_rec_idx_is_null( rec->next_idx ) );
     369             : 
     370           6 :       } else { /* This is a record from an in-prep transaction */
     371             : 
     372           0 :         TEST( fd_funk_txn_query( xid, funk->txn_map ) );
     373             : 
     374           0 :       }
     375           6 :     }
     376      837750 :     TEST( chain_ele_idx==chain_ele_cnt );
     377      837750 :   }
     378             : 
     379             :   /* Clear record tags and then verify membership */
     380             : 
     381     1675737 :   for( ulong rec_idx=0UL; rec_idx<rec_max; rec_idx++ ) rec_pool->ele[ rec_idx ].tag = 0;
     382             : 
     383         249 :   do {
     384         249 :     fd_funk_all_iter_t iter[1];
     385         255 :     for( fd_funk_all_iter_new( funk, iter ); !fd_funk_all_iter_done( iter ); fd_funk_all_iter_next( iter ) ) {
     386           6 :       fd_funk_rec_t * rec = fd_funk_all_iter_ele( iter );
     387           6 :       if( fd_funk_txn_xid_eq_root( rec->pair.xid ) ) {
     388           6 :         TEST( !rec->tag );
     389           6 :         rec->tag = 1;
     390           6 :       }
     391           6 :     }
     392             : 
     393         249 :     fd_funk_txn_all_iter_t txn_iter[1];
     394         795 :     for( fd_funk_txn_all_iter_new( funk, txn_iter ); !fd_funk_txn_all_iter_done( txn_iter ); fd_funk_txn_all_iter_next( txn_iter ) ) {
     395         546 :       fd_funk_txn_t const * txn = fd_funk_txn_all_iter_ele_const( txn_iter );
     396             : 
     397         546 :       uint rec_idx = txn->rec_head_idx;
     398         546 :       while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     399           0 :         TEST( (rec_idx<rec_max) && !rec_pool->ele[ rec_idx ].tag );
     400           0 :         rec_pool->ele[ rec_idx ].tag = 1;
     401           0 :         fd_funk_rec_query_t query[1];
     402           0 :         fd_funk_rec_t const * rec2 = fd_funk_rec_query_try( funk, &txn->xid, rec_pool->ele[ rec_idx ].pair.key, query );
     403           0 :         TEST( rec2 == rec_pool->ele + rec_idx );
     404           0 :         uint next_idx = rec_pool->ele[ rec_idx ].next_idx;
     405           0 :         if( !fd_funk_rec_idx_is_null( next_idx ) ) TEST( rec_pool->ele[ next_idx ].prev_idx==rec_idx );
     406           0 :         rec_idx = next_idx;
     407           0 :       }
     408         546 :     }
     409         249 :   } while(0);
     410             : 
     411         249 :   do {
     412         249 :     fd_funk_txn_all_iter_t txn_iter[1];
     413         795 :     for( fd_funk_txn_all_iter_new( funk, txn_iter ); !fd_funk_txn_all_iter_done( txn_iter ); fd_funk_txn_all_iter_next( txn_iter ) ) {
     414         546 :       fd_funk_txn_t const * txn = fd_funk_txn_all_iter_ele_const( txn_iter );
     415             : 
     416         546 :       uint  rec_idx = txn->rec_tail_idx;
     417         546 :       while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     418           0 :         TEST( (rec_idx<rec_max) && rec_pool->ele[ rec_idx ].tag );
     419           0 :         rec_pool->ele[ rec_idx ].tag = 0;
     420           0 :         uint prev_idx = rec_pool->ele[ rec_idx ].prev_idx;
     421           0 :         if( !fd_funk_rec_idx_is_null( prev_idx ) ) TEST( rec_pool->ele[ prev_idx ].next_idx==rec_idx );
     422           0 :         rec_idx = prev_idx;
     423           0 :       }
     424         546 :     }
     425         249 :   } while(0);
     426             : 
     427         249 :   fd_funk_all_iter_t iter[1];
     428         255 :   for( fd_funk_all_iter_new( funk, iter ); !fd_funk_all_iter_done( iter ); fd_funk_all_iter_next( iter ) ) {
     429           6 :     fd_funk_rec_t const * rec = fd_funk_all_iter_ele( iter );
     430           6 :     FD_TEST( rec->tag == fd_funk_txn_xid_eq_root( rec->pair.xid ) );
     431           6 :   }
     432             : 
     433         249 : # undef TEST
     434             : 
     435         249 :   return FD_FUNK_SUCCESS;
     436         249 : }

Generated by: LCOV version 1.14