LCOV - code coverage report
Current view: top level - funk - fd_funk_txn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 467 519 90.0 %
Date: 2025-03-20 12:08:36 Functions: 14 17 82.4 %

          Line data    Source code
       1             : #include "fd_funk.h"
       2             : 
       3             : /* Provide the actual transaction map implementation */
       4             : 
       5             : #define MAP_NAME              fd_funk_txn_map
       6  1104027921 : #define MAP_T                 fd_funk_txn_t
       7             : #define MAP_KEY_T             fd_funk_txn_xid_t
       8     1592718 : #define MAP_KEY               xid
       9             : #define MAP_KEY_EQ(k0,k1)     fd_funk_txn_xid_eq((k0),(k1))
      10   779049543 : #define MAP_KEY_HASH(k0,seed) fd_funk_txn_xid_hash((k0),(seed))
      11             : #define MAP_KEY_COPY(kd,ks)   fd_funk_txn_xid_copy((kd),(ks))
      12   744567846 : #define MAP_NEXT              map_next
      13  1534057560 : #define MAP_HASH              map_hash
      14          30 : #define MAP_MAGIC             (0xf173da2ce7172db0UL) /* Firedancer trn db version 0 */
      15             : #define MAP_IMPL_STYLE        2
      16             : #define MAP_MEMOIZE           1
      17             : #include "../util/tmpl/fd_map_giant.c"
      18             : 
      19             : /* As described in fd_funk_txn.h, like the extensive tests in verify
      20             :    (which, by its very nature, is called on maps whose integrity is
      21             :    unclear to the caller), these have been fortified again memory
      22             :    corruption (accidental or deliberate) by doing out of bounds memory
      23             :    access detection and cycle (infinite loop) detection in all the
      24             :    various operations below.
      25             : 
      26             :    This is debatably overkill but, given this code is the pointy end of
      27             :    the stick for keeping transaction histories clean (i.e. cancelling a
      28             :    wrong transaction could lose information and publishing a wrong
      29             :    transaction is even worse), the overhead for the detection is
      30             :    minimal, and these operations aren't particularly performance
      31             :    critical anyway, seems a more-than-worthwhile safeguard.  Likewise,
      32             :    it also guarantees strict algo overheads of these in the face of
      33             :    corruption.
      34             : 
      35             :    When there is a corruption issue detected realtime, it is prima facie
      36             :    evidence of either software bug, hardware fault or compromised
      37             :    process.  In all cases, these functions will refuse to proceed
      38             :    further and abort with FD_LOG_CRIT to minimize the blast radius of
      39             :    possible corruption and give the user as much details (stack trace
      40             :    and core) to diagnose the source of the issue.  This handling is
      41             :    mostly isolated to the below macros and thus easy to modify or
      42             :    disable.
      43             : 
      44             :    The corruption detection works by ensuring all map indices are in
      45             :    bounds and then applying a unique tag during various map traversals
      46             :    such that operations can detected if a transaction has already been
      47             :    encountered earlier in the traversal (and thus will create a
      48             :    cycle/infinite loop) in the shortest possible algorithm overhead to
      49             :    detect such a cycle. */
      50             : 
      51     1592718 : #define ASSERT_IN_MAP( txn_idx ) do {                          \
      52     1592718 :     if( FD_UNLIKELY( txn_idx>=txn_max ) )                      \
      53     1592718 :       FD_LOG_CRIT(( "memory corruption detected (bad_idx)" )); \
      54     1592718 :   } while(0)
      55             : 
      56     4715574 : #define ASSERT_IN_PREP( txn_idx ) do {                                                           \
      57     4715574 :     if( FD_UNLIKELY( txn_idx>=txn_max ) )                                                        \
      58     4715574 :       FD_LOG_CRIT(( "memory corruption detected (bad_idx)" ));                                   \
      59     4715574 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( &map[ txn_idx ] ), NULL ) ) ) \
      60     4715574 :       FD_LOG_CRIT(( "memory corruption detected (not in prep)" ));                               \
      61     4715574 :   } while(0)
      62             : 
      63     1824210 : #define ASSERT_UNTAGGED( txn_idx ) do {                      \
      64     1824210 :     if( FD_UNLIKELY( map[ txn_idx ].tag==tag ) )             \
      65     1824210 :       FD_LOG_CRIT(( "memory corruption detected (cycle)" )); \
      66     1824210 :   } while(0)
      67             : 
      68             : fd_funk_txn_t *
      69             : fd_funk_txn_prepare( fd_funk_t *               funk,
      70             :                      fd_funk_txn_t *           parent,
      71             :                      fd_funk_txn_xid_t const * xid,
      72     2829093 :                      int                       verbose ) {
      73             : 
      74     2829093 :   if( FD_UNLIKELY( !funk ) ) {
      75      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
      76      196464 :     return NULL;
      77      196464 :   }
      78     2632629 :   fd_funk_check_write( funk );
      79             : 
      80     2632629 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
      81             : 
      82     2632629 :   if( FD_UNLIKELY( fd_funk_txn_map_is_full( map ) ) ) {
      83          24 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "too many transactions in preparation" ));
      84          24 :     return NULL;
      85          24 :   }
      86             : 
      87     2632605 :   ulong txn_max = funk->txn_max;
      88             : 
      89     2632605 :   ulong  parent_idx;
      90     2632605 :   uint * _child_head_cidx;
      91     2632605 :   uint * _child_tail_cidx;
      92             : 
      93     2632605 :   if( FD_LIKELY( !parent ) ) { /* opt for incr pub */
      94             : 
      95     1035756 :     parent_idx = FD_FUNK_TXN_IDX_NULL;
      96             : 
      97     1035756 :     _child_head_cidx = &funk->child_head_cidx;
      98     1035756 :     _child_tail_cidx = &funk->child_tail_cidx;
      99             : 
     100     1596849 :   } else {
     101             : 
     102     1596849 :     parent_idx = (ulong)(parent - map);
     103             : 
     104     1596849 :     if( FD_UNLIKELY( (parent_idx>=txn_max) /* Out of map */ | (parent!=(map+parent_idx)) /* Bad alignment */ ) ) {
     105      196461 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "parent is not a funk transaction" ));
     106      196461 :       return NULL;
     107      196461 :     }
     108             : 
     109     1400388 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( parent ), NULL ) ) ) {
     110      188382 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "parent is not in preparation" ));
     111      188382 :       return NULL;
     112      188382 :     }
     113             : 
     114     1212006 :     _child_head_cidx = &parent->child_head_cidx;
     115     1212006 :     _child_tail_cidx = &parent->child_tail_cidx;
     116             : 
     117     1212006 :   }
     118             : 
     119     2247762 :   if( FD_UNLIKELY( !xid ) ) {
     120      196461 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL xid" ));
     121      196461 :     return NULL;
     122      196461 :   }
     123             : 
     124     2051301 :   if( FD_UNLIKELY( fd_funk_txn_xid_eq_root( xid ) ) ) {
     125           6 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid is the root" ));
     126           6 :     return NULL;
     127           6 :   }
     128             : 
     129     2051295 :   if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid, funk->last_publish ) ) ) {
     130      196455 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid is the last published" ));
     131      196455 :     return NULL;
     132      196455 :   }
     133             : 
     134     1854840 :   if( FD_UNLIKELY( fd_funk_txn_map_query( map, xid, NULL ) ) ) {
     135      262122 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "id already a transaction" ));
     136      262122 :     return NULL;
     137      262122 :   }
     138             : 
     139             :   /* Get a new transaction from the map */
     140             : 
     141     1592718 :   fd_funk_txn_t * txn     = fd_funk_txn_map_insert( map, xid );
     142     1592718 :   ulong           txn_idx = (ulong)(txn - map);
     143     1592718 :   ASSERT_IN_MAP( txn_idx );
     144             : 
     145             :   /* Join the family */
     146             : 
     147     1592718 :   ulong sibling_prev_idx = fd_funk_txn_idx( *_child_tail_cidx );
     148             : 
     149     1592718 :   int first_born = fd_funk_txn_idx_is_null( sibling_prev_idx );
     150     1592718 :   if( FD_UNLIKELY( !first_born ) ) ASSERT_IN_PREP( sibling_prev_idx ); /* opt for non-compete */
     151             : 
     152     1592718 :   txn->parent_cidx       = fd_funk_txn_cidx( parent_idx           );
     153     1592718 :   txn->child_head_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     154     1592718 :   txn->child_tail_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     155     1592718 :   txn->sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx     );
     156     1592718 :   txn->sibling_next_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     157     1592718 :   txn->stack_cidx        = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     158     1592718 :   txn->tag               = 0UL;
     159             : 
     160     1592718 :   txn->rec_head_idx = FD_FUNK_REC_IDX_NULL;
     161     1592718 :   txn->rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     162             : 
     163             :   /* TODO: consider branchless impl */
     164     1592718 :   if( FD_LIKELY( first_born ) ) *_child_head_cidx                         = fd_funk_txn_cidx( txn_idx ); /* opt for non-compete */
     165      796092 :   else                          map[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( txn_idx );
     166             : 
     167     1592718 :   *_child_tail_cidx = fd_funk_txn_cidx( txn_idx );
     168             : 
     169     1592718 :   return txn;
     170     1592718 : }
     171             : 
     172             : /* fd_funk_txn_cancel_childless cancels a transaction that is known
     173             :    to be childless.  Callers have already validated our input arguments.
     174             :    Assumes that cancelling in the app can't fail but that could be
     175             :    straightforward to support by giving this an error and plumbing
     176             :    through to abort the overall cancel operation when it hits a error. */
     177             : 
     178             : static void
     179             : fd_funk_txn_cancel_childless( fd_funk_t *     funk,
     180             :                               fd_funk_txn_t * map,
     181             :                               ulong           txn_max,
     182     1289097 :                               ulong           txn_idx ) {
     183             : 
     184     1289097 :   fd_funk_check_write( funk );
     185             : 
     186             :   /* Remove all records used by this transaction.  Note that we don't
     187             :      need to bother doing all the individual removal operations as we
     188             :      are removing the whole list.  We do reset the record transaction
     189             :      idx with NULL though we can detect cycles as soon as possible
     190             :      and abort. */
     191             : 
     192     1289097 :   fd_wksp_t *     wksp    = fd_funk_wksp   ( funk );
     193     1289097 :   fd_alloc_t *    alloc   = fd_funk_alloc  ( funk, wksp );
     194     1289097 :   fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp );
     195     1289097 :   ulong           rec_max = funk->rec_max;
     196             : 
     197     1289097 :   ulong rec_idx = map[ txn_idx ].rec_head_idx;
     198     4873959 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     199             : 
     200     3584862 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     201     3584862 :     if( FD_UNLIKELY( fd_funk_txn_idx( rec_map[ rec_idx ].txn_cidx )!=txn_idx ) )
     202           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     203             : 
     204     3584862 :     ulong next_idx = rec_map[ rec_idx ].next_idx;
     205     3584862 :     rec_map[ rec_idx ].txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     206             : 
     207     3584862 :     fd_funk_val_flush( &rec_map[ rec_idx ], alloc, wksp );
     208     3584862 :     fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( &rec_map[ rec_idx ] ) );
     209             : 
     210     3584862 :     rec_idx = next_idx;
     211     3584862 :   }
     212             : 
     213             :   /* Leave the family */
     214             : 
     215     1289097 :   ulong sibling_prev_idx = fd_funk_txn_idx( map[ txn_idx ].sibling_prev_cidx );
     216     1289097 :   ulong sibling_next_idx = fd_funk_txn_idx( map[ txn_idx ].sibling_next_cidx );
     217             : 
     218             :   /* TODO: Consider branchless impl */
     219             : 
     220     1289097 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_prev_idx ) ) ) { /* opt for non-compete */
     221      636330 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     222      636330 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No older sib and is a funk child, opt for incr pub */
     223      166380 :       funk->child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     224      469950 :     } else { /* No older sib and has parent */
     225      469950 :       ASSERT_IN_PREP( parent_idx );
     226      469950 :       map[ parent_idx ].child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     227      469950 :     }
     228      652767 :   } else { /* Has older sib */
     229      652767 :     ASSERT_IN_PREP( sibling_prev_idx );
     230      652767 :     map[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( sibling_next_idx );
     231      652767 :   }
     232             : 
     233     1289097 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_next_idx ) ) ) { /* opt for non-compete */
     234     1052781 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     235     1052781 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No younger sib and is a funk child, opt for incr pub */
     236      340134 :       funk->child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     237      712647 :     } else { /* No younger sib and has parent */
     238      712647 :       ASSERT_IN_PREP( parent_idx );
     239      712647 :       map[ parent_idx ].child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     240      712647 :     }
     241     1052781 :   } else { /* Has younger sib */
     242      236316 :     ASSERT_IN_PREP( sibling_next_idx );
     243      236316 :     map[ sibling_next_idx ].sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     244      236316 :   }
     245             : 
     246     1289097 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( &map[ txn_idx ] ) );
     247     1289097 : }
     248             : 
     249             : /* fd_funk_txn_cancel_family cancels a transaction and all its
     250             :    descendants in a tree-depth-first-ordered sense from youngest to
     251             :    oldest.  Callers have already validated our input arguments.  Returns
     252             :    the number of transactions canceled. */
     253             : 
     254             : static ulong
     255             : fd_funk_txn_cancel_family( fd_funk_t *     funk,
     256             :                            fd_funk_txn_t * map,
     257             :                            ulong           txn_max,
     258             :                            ulong           tag,
     259      624429 :                            ulong           txn_idx ) {
     260      624429 :   ulong cancel_cnt = 0UL;
     261             : 
     262      624429 :   map[ txn_idx ].tag = tag;
     263             : 
     264      624429 :   ulong parent_stack_idx = FD_FUNK_TXN_IDX_NULL;
     265             : 
     266     1953765 :   for(;;) {
     267             : 
     268             :     /* At this point, txn_idx appears to be valid and has been tagged. */
     269             : 
     270     1953765 :     ulong youngest_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
     271     1953765 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( youngest_idx ) ) ) { /* txn is is childless, opt for incr pub */
     272             : 
     273     1289097 :       fd_funk_txn_cancel_childless( funk, map, txn_max, txn_idx ); /* If this can fail, return cancel_cnt here on fail */
     274     1289097 :       cancel_cnt++;
     275             : 
     276     1289097 :       txn_idx = parent_stack_idx;                                  /* Pop the parent stack */
     277     1289097 :       if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* If stack is empty, we are done, opt for incr pub */
     278      664668 :       parent_stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     279      664668 :       continue;
     280     1289097 :     }
     281             : 
     282             :     /* txn has at least one child and the youngest is youngest_idx.  Tag
     283             :        the youngest child, push txn onto the parent stack and recurse
     284             :        into the youngest child. */
     285             : 
     286      664668 :     ASSERT_IN_PREP ( youngest_idx );
     287      664668 :     ASSERT_UNTAGGED( youngest_idx );
     288      664668 :     map[ youngest_idx ].tag = tag;
     289             : 
     290      664668 :     map[ txn_idx ].stack_cidx = fd_funk_txn_cidx( parent_stack_idx );
     291      664668 :     parent_stack_idx          = txn_idx;
     292             : 
     293      664668 :     txn_idx = youngest_idx;
     294      664668 :   }
     295             : 
     296      624429 :   return cancel_cnt;
     297      624429 : }
     298             : 
     299             : ulong
     300             : fd_funk_txn_cancel( fd_funk_t *     funk,
     301             :                     fd_funk_txn_t * txn,
     302     1327062 :                     int             verbose ) {
     303             : 
     304     1327062 :   if( FD_UNLIKELY( !funk ) ) {
     305      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     306      196464 :     return 0UL;
     307      196464 :   }
     308             : 
     309     1130598 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     310             : 
     311     1130598 :   ulong txn_max = funk->txn_max;
     312             : 
     313     1130598 :   ulong txn_idx = (ulong)(txn - map);
     314             : 
     315     1130598 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     316      786258 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     317      786258 :     return 0UL;
     318      786258 :   }
     319             : 
     320      344340 :   if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     321      188382 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     322      188382 :     return 0UL;
     323      188382 :   }
     324             : 
     325      155958 :   return fd_funk_txn_cancel_family( funk, map, txn_max, funk->cycle_tag++, txn_idx );
     326      344340 : }
     327             : 
     328             : /* fd_funk_txn_oldest_sibling returns the index of the oldest sibling
     329             :    in txn_idx's family.  Callers have already validated our input
     330             :    arguments.  The caller should validate the return index. */
     331             : 
     332             : static inline ulong
     333             : fd_funk_txn_oldest_sibling( fd_funk_t *     funk,
     334             :                             fd_funk_txn_t * map,
     335             :                             ulong           txn_max,
     336      303579 :                             ulong           txn_idx ) {
     337             : 
     338      303579 :   ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     339             : 
     340      303579 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) return fd_funk_txn_idx( funk->child_head_cidx ); /* opt for incr pub */
     341             : 
     342       23592 :   ASSERT_IN_PREP( parent_idx );
     343             : 
     344       23592 :   return fd_funk_txn_idx( map[ parent_idx ].child_head_cidx );
     345       23592 : }
     346             : 
     347             : /* fd_funk_txn_cancel_sibling_list cancels siblings from sibling_idx down
     348             :    to the youngest sibling inclusive in the order from youngest to
     349             :    sibling_idx.  Callers have already validated our input arguments
     350             :    except sibling_idx.  Returns the number of cancelled transactions
     351             :    (should be at least 1).  If any sibling is skip_idx, it will be not
     352             :    be cancelled but still tagged as visited.  Passing
     353             :    FD_FUNK_TXN_IDX_NULL for skip_idx will cancel all siblings from
     354             :    sibling_idx to the youngest inclusive. */
     355             : 
     356             : static ulong
     357             : fd_funk_txn_cancel_sibling_list( fd_funk_t *     funk,
     358             :                                  fd_funk_txn_t * map,
     359             :                                  ulong           txn_max,
     360             :                                  ulong           tag,
     361             :                                  ulong           sibling_idx,
     362      303579 :                                  ulong           skip_idx ) {
     363             : 
     364      303579 :   ulong cancel_stack_idx = FD_FUNK_TXN_IDX_NULL;
     365             : 
     366             :   /* Push siblings_idx and its younger siblings inclusive (skipping
     367             :      sibling skip_idx if encounter) onto the cancel stack from oldest to
     368             :      youngest (such that we cancel youngest to oldest). */
     369             : 
     370      772050 :   for(;;) {
     371             : 
     372             :     /* At this point, sibling_idx is a sibling we might want to add to
     373             :        the sibling stack.  Validate and tag it. */
     374             : 
     375      772050 :     ASSERT_IN_PREP ( sibling_idx );
     376      772050 :     ASSERT_UNTAGGED( sibling_idx );
     377      772050 :     map[ sibling_idx ].tag = tag;
     378             : 
     379      772050 :     if( FD_UNLIKELY( sibling_idx!=skip_idx ) ) { /* Not skip_idx so push onto the cancel stack, opt for non-compete */
     380      468471 :       map[ sibling_idx ].stack_cidx = fd_funk_txn_cidx( cancel_stack_idx );
     381      468471 :       cancel_stack_idx = sibling_idx;
     382      468471 :     }
     383             : 
     384      772050 :     ulong younger_idx = fd_funk_txn_idx( map[ sibling_idx ].sibling_next_cidx );
     385      772050 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( younger_idx ) ) ) break; /* opt for non-compete */
     386      468471 :     sibling_idx = younger_idx;
     387             : 
     388      468471 :   }
     389             : 
     390             :   /* Cancel all transactions and their descendants on the cancel stack */
     391             : 
     392      303579 :   ulong cancel_cnt = 0UL;
     393             : 
     394      772050 :   while( !fd_funk_txn_idx_is_null( cancel_stack_idx ) ) { /* TODO: peel first iter to make more predictable? */
     395      468471 :     ulong sibling_idx = cancel_stack_idx;
     396      468471 :     cancel_stack_idx  = fd_funk_txn_idx( map[ sibling_idx ].stack_cidx );
     397             : 
     398      468471 :     cancel_cnt += fd_funk_txn_cancel_family( funk, map, txn_max, tag, sibling_idx );
     399      468471 :   }
     400             : 
     401      303579 :   return cancel_cnt;
     402      303579 : }
     403             : 
     404             : ulong
     405             : fd_funk_txn_cancel_siblings( fd_funk_t *     funk,
     406             :                              fd_funk_txn_t * txn,
     407           0 :                              int             verbose ) {
     408             : 
     409           0 :   if( FD_UNLIKELY( !funk ) ) {
     410           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     411           0 :     return 0UL;
     412           0 :   }
     413             : 
     414           0 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     415             : 
     416           0 :   ulong txn_max = funk->txn_max;
     417             : 
     418           0 :   ulong txn_idx = (ulong)(txn - map);
     419             : 
     420           0 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     421           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     422           0 :     return 0UL;
     423           0 :   }
     424             : 
     425           0 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, txn_max, txn_idx );
     426             : 
     427           0 :   return fd_funk_txn_cancel_sibling_list( funk, map, txn_max, funk->cycle_tag++, oldest_idx, txn_idx );
     428           0 : }
     429             : 
     430             : ulong
     431             : fd_funk_txn_cancel_children( fd_funk_t *     funk,
     432             :                              fd_funk_txn_t * txn,
     433           0 :                              int             verbose ) {
     434             : 
     435           0 :   if( FD_UNLIKELY( !funk ) ) {
     436           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     437           0 :     return 0UL;
     438           0 :   }
     439             : 
     440           0 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     441             : 
     442           0 :   ulong txn_max = funk->txn_max;
     443             : 
     444           0 :   ulong oldest_idx;
     445             : 
     446           0 :   if( FD_LIKELY( txn == NULL ) ) {
     447           0 :     oldest_idx = fd_funk_txn_idx( funk->child_head_cidx ); /* opt for non-compete */
     448           0 :   } else {
     449           0 :     ulong txn_idx = (ulong)(txn - map);
     450           0 :     if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     451           0 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     452           0 :       return 0UL;
     453           0 :     }
     454             : 
     455           0 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     456           0 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     457           0 :       return 0UL;
     458           0 :     }
     459             : 
     460           0 :     oldest_idx = fd_funk_txn_idx( txn->child_head_cidx );
     461           0 :   }
     462             : 
     463           0 :   if( fd_funk_txn_idx_is_null( oldest_idx ) ) {
     464           0 :     return 0UL;
     465           0 :   }
     466             : 
     467           0 :   return fd_funk_txn_cancel_sibling_list( funk, map, txn_max, funk->cycle_tag++, oldest_idx, FD_FUNK_TXN_IDX_NULL );
     468           0 : }
     469             : 
     470             : /* Cancel all outstanding transactions */
     471             : 
     472             : ulong
     473             : fd_funk_txn_cancel_all( fd_funk_t *     funk,
     474           0 :                         int             verbose ) {
     475           0 :   return fd_funk_txn_cancel_children( funk, NULL, verbose );
     476           0 : }
     477             : 
     478             : /* fd_funk_txn_update applies the record updates in transaction txn_idx
     479             :    to another transaction or the parent transaction.  Callers have
     480             :    already validated our input arguments.
     481             : 
     482             :    On entry, the head/tail of the destination records are at
     483             :    *_dst_rec_head_idx / *_dst_rec_tail_idx.  All transactions on this
     484             :    list will have transaction id dst_xid and vice versa.  That is, this
     485             :    is the record list the last published transaction or txn_idx's
     486             :    in-prep parent transaction.
     487             : 
     488             :    On exit, the head/tail of the updated records is at
     489             :    *_dst_rec_head_idx / *_dst_rec_tail_idx.  As before, all transactions
     490             :    on this list will have transaction id dst_xid and vice versa.
     491             :    Transaction txn_idx will have an _empty_ record list.
     492             : 
     493             :    Updates in the transaction txn_idx are processed from oldest to
     494             :    youngest.  If an update erases an existing record in dest, the record
     495             :    to erase is removed from the destination records without perturbing
     496             :    the order of remaining destination records.  If an update is to
     497             :    update an existing record, the destination record value is updated
     498             :    and the order of the destination records is unchanged.  If an update
     499             :    is to create a new record, the record is appended to the list of
     500             :    existing values as youngest without changing the order of existing
     501             :    values.  If an update erases a record in an in-prep parent, the
     502             :    erasure will be moved into the parent as the youngest without
     503             :    changing the order of existing values. */
     504             : 
     505             : static void
     506             : fd_funk_txn_update( ulong *                   _dst_rec_head_idx, /* Pointer to the dst list head */
     507             :                     ulong *                   _dst_rec_tail_idx, /* Pointer to the dst list tail */
     508             :                     ulong                     dst_txn_idx,       /* Transaction index of the merge destination */
     509             :                     fd_funk_txn_xid_t const * dst_xid,           /* dst xid */
     510             :                     ulong                     txn_idx,           /* Transaction index of the records to merge */
     511             :                     ulong                     rec_max,           /* ==funk->rec_max */
     512             :                     fd_funk_txn_t *           txn_map,           /* ==fd_funk_rec_map( funk, wksp ) */
     513             :                     fd_funk_rec_t *           rec_map,           /* ==fd_funk_rec_map( funk, wksp ) */
     514             :                     fd_alloc_t *              alloc,             /* ==fd_funk_alloc( funk, wksp ) */
     515      303579 :                     fd_wksp_t *               wksp ) {           /* ==fd_funk_wksp( funk ) */
     516             :   /* We don't need to do all the individual removal pointer updates
     517             :      as we are removing the whole list from txn_idx.  */
     518             : 
     519      303579 :   ulong rec_idx = txn_map[ txn_idx ].rec_head_idx;
     520      853467 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     521             :     /* Validate rec_idx */
     522             : 
     523      549888 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     524      549888 :     if( FD_UNLIKELY( fd_funk_txn_idx( rec_map[ rec_idx ].txn_cidx )!=txn_idx ) )
     525           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     526             : 
     527      549888 :     fd_funk_rec_t * rec = &rec_map[ rec_idx ];
     528      549888 :     ulong next_rec_idx = rec->next_idx;
     529             : 
     530             :     /* See if (dst_xid,key) already exists. Remove it if it does, and then clean up the corpse.
     531             :        We can take advantage of the ordering property that children
     532             :        come before parents in the hash chain, and all elements with
     533             :        the same record key have the same hash. */
     534      549888 :     ulong * next = &rec->map_next;
     535      865986 :     for(;;) {
     536      865986 :       ulong ele_idx = fd_funk_rec_map_private_unbox_idx( *next );
     537      865986 :       if( fd_funk_rec_map_private_is_null( ele_idx ) ) break;
     538      748428 :       fd_funk_rec_t * ele = rec_map + ele_idx;
     539             : 
     540      748428 :       if( FD_LIKELY( rec->map_hash == ele->map_hash ) &&
     541      748428 :           FD_LIKELY( fd_funk_rec_key_eq( rec->pair.key, ele->pair.key ) ) &&
     542      748428 :           FD_LIKELY( fd_funk_txn_xid_eq( dst_xid, ele->pair.xid ) ) ) {
     543             :         /* Remove from the transaction */
     544      432330 :         ulong prev_idx = ele->prev_idx;
     545      432330 :         ulong next_idx = ele->next_idx;
     546      432330 :         if( fd_funk_rec_idx_is_null( prev_idx ) ) {
     547        6222 :           *_dst_rec_head_idx = next_idx;
     548      426108 :         } else {
     549      426108 :           rec_map[ prev_idx ].next_idx = next_idx;
     550      426108 :         }
     551      432330 :         if( fd_funk_rec_idx_is_null( next_idx ) ) {
     552        1161 :           *_dst_rec_tail_idx = prev_idx;
     553      431169 :         } else {
     554      431169 :           rec_map[ next_idx ].prev_idx = prev_idx;
     555      431169 :         }
     556             :         /* Clean up value */
     557      432330 :         fd_funk_val_flush( ele, alloc, wksp );
     558      432330 :         ele->txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     559             :         /* Remove from record map */
     560      432330 :         fd_funk_rec_map_private_t * priv = fd_funk_rec_map_private( rec_map );
     561      432330 :         *next = ele->map_next;
     562      432330 :         ele->map_next = priv->free_stack;
     563      432330 :         priv->free_stack = (ele_idx | (1UL<<63));
     564      432330 :         priv->key_cnt--;
     565      432330 :         break;
     566      432330 :       }
     567             : 
     568      316098 :       next = &ele->map_next;
     569      316098 :     }
     570             : 
     571             :     /* Add the new record to the transaction. We can update the xid in
     572             :        place because it is not used for hashing the element. We have
     573             :        to preserve the original element to preserve the
     574             :        newest-to-oldest ordering in the hash
     575             :        chain. fd_funk_rec_query_global relies on this subtle
     576             :        property. */
     577             : 
     578      549888 :     rec->pair.xid[0] = *dst_xid;
     579      549888 :     rec->txn_cidx = fd_funk_txn_cidx( dst_txn_idx );
     580             : 
     581      549888 :     if( fd_funk_rec_idx_is_null( *_dst_rec_head_idx ) ) {
     582       11805 :       *_dst_rec_head_idx = rec_idx;
     583       11805 :       rec->prev_idx = FD_FUNK_REC_IDX_NULL;
     584      538083 :     } else {
     585      538083 :       rec_map[ *_dst_rec_tail_idx ].next_idx = rec_idx;
     586      538083 :       rec->prev_idx = *_dst_rec_tail_idx;
     587      538083 :     }
     588      549888 :     *_dst_rec_tail_idx = rec_idx;
     589      549888 :     rec->next_idx = FD_FUNK_REC_IDX_NULL;
     590             : 
     591      549888 :     rec_idx = next_rec_idx;
     592      549888 :   }
     593             : 
     594      303579 :   txn_map[ txn_idx ].rec_head_idx = FD_FUNK_REC_IDX_NULL;
     595      303579 :   txn_map[ txn_idx ].rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     596      303579 : }
     597             : 
     598             : /* fd_funk_txn_publish_funk_child publishes a transaction that is known
     599             :    to be a child of funk.  Callers have already validated our input
     600             :    arguments.  Returns FD_FUNK_SUCCESS on success and an FD_FUNK_ERR_*
     601             :    code on failure.  (There are currently no failure cases but the
     602             :    plumbing is there if value handling requires it at some point.) */
     603             : 
     604             : static int
     605             : fd_funk_txn_publish_funk_child( fd_funk_t *     funk,
     606             :                                 fd_funk_txn_t * map,
     607             :                                 ulong           txn_max,
     608             :                                 ulong           tag,
     609      273579 :                                 ulong           txn_idx ) {
     610             : 
     611      273579 :   fd_funk_check_write( funk );
     612             : 
     613             :   /* Apply the updates in txn to the last published transactions */
     614             : 
     615      273579 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     616      273579 :   fd_funk_txn_update( &funk->rec_head_idx, &funk->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ),
     617      273579 :                       txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ),
     618      273579 :                       fd_funk_alloc( funk, wksp ), wksp );
     619             : 
     620             :   /* Cancel all competing transaction histories */
     621             : 
     622      273579 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, txn_max, txn_idx );
     623      273579 :   fd_funk_txn_cancel_sibling_list( funk, map, txn_max, tag, oldest_idx, txn_idx );
     624             : 
     625             :   /* Make all the children children of funk */
     626             : 
     627      273579 :   ulong child_head_idx = fd_funk_txn_idx( map[ txn_idx ].child_head_cidx );
     628      273579 :   ulong child_tail_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
     629             : 
     630      273579 :   ulong child_idx = child_head_idx;
     631      565662 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     632             : 
     633      292083 :     ASSERT_IN_PREP ( child_idx );
     634      292083 :     ASSERT_UNTAGGED( child_idx );
     635      292083 :     map[ child_idx ].tag = tag;
     636             : 
     637      292083 :     map[ child_idx ].parent_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     638             : 
     639      292083 :     child_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     640      292083 :   }
     641             : 
     642      273579 :   funk->child_head_cidx = fd_funk_txn_cidx( child_head_idx );
     643      273579 :   funk->child_tail_cidx = fd_funk_txn_cidx( child_tail_idx );
     644             : 
     645             :   /* Remove the mapping */
     646             : 
     647      273579 :   fd_funk_txn_xid_copy( funk->last_publish, fd_funk_txn_xid( &map[ txn_idx ] ) );
     648             : 
     649      273579 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( &map[ txn_idx ] ) );
     650             : 
     651      273579 :   return FD_FUNK_SUCCESS;
     652      273579 : }
     653             : 
     654             : ulong
     655             : fd_funk_txn_publish( fd_funk_t *     funk,
     656             :                      fd_funk_txn_t * txn,
     657     1349388 :                      int             verbose ) {
     658             : 
     659     1349388 :   if( FD_UNLIKELY( !funk ) ) {
     660      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     661      196464 :     return 0UL;
     662      196464 :   }
     663             : 
     664     1152924 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     665             : 
     666     1152924 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     667             : 
     668     1152924 :   ulong txn_max = funk->txn_max;
     669             : 
     670     1152924 :   ulong txn_idx = (ulong)(txn - map);
     671             : 
     672     1152924 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     673      786372 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     674      786372 :     return 0UL;
     675      786372 :   }
     676             : 
     677      366552 :   if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     678      188382 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     679      188382 :     return 0UL;
     680      188382 :   }
     681             : 
     682      178170 :   ulong tag = funk->cycle_tag++;
     683             : 
     684      178170 :   ulong publish_stack_idx = FD_FUNK_TXN_IDX_NULL;
     685             : 
     686      273579 :   for(;;) {
     687      273579 :     map[ txn_idx ].tag = tag;
     688             : 
     689             :     /* At this point, txn_idx is a transaction that needs to be
     690             :        published and has been tagged.  If txn is a child of funk, we are
     691             :        ready to publish txn and everything on the publish stack. */
     692             : 
     693      273579 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     694      273579 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) break; /* opt for incr pub */
     695             : 
     696             :     /* txn_idx has a parent.  Validate and tag it.  Push txn to the
     697             :        publish stack and recurse into the parent. */
     698             : 
     699       95409 :     ASSERT_IN_PREP ( parent_idx );
     700       95409 :     ASSERT_UNTAGGED( parent_idx );
     701             : 
     702       95409 :     map[ txn_idx ].stack_cidx = fd_funk_txn_cidx( publish_stack_idx );
     703       95409 :     publish_stack_idx         = txn_idx;
     704             : 
     705       95409 :     txn_idx = parent_idx;
     706       95409 :   }
     707             : 
     708      178170 :   ulong publish_cnt = 0UL;
     709             : 
     710      273579 :   for(;;) {
     711             : 
     712             :     /* At this point, all the transactions we need to publish are
     713             :        tagged, txn is the next up publish funk and publish_stack has the
     714             :        transactions to follow in order by pop.  We use a new tag for
     715             :        each publish as txn and its siblings we potentially visited in a
     716             :        previous iteration of this loop. */
     717             : 
     718      273579 :     if( FD_UNLIKELY( fd_funk_txn_publish_funk_child( funk, map, txn_max, funk->cycle_tag++, txn_idx ) ) ) break;
     719      273579 :     publish_cnt++;
     720             : 
     721      273579 :     txn_idx = publish_stack_idx;
     722      273579 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* opt for incr pub */
     723       95409 :     publish_stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     724       95409 :   }
     725             : 
     726      178170 :   return publish_cnt;
     727      178170 : }
     728             : 
     729             : int
     730             : fd_funk_txn_publish_into_parent( fd_funk_t *     funk,
     731             :                                  fd_funk_txn_t * txn,
     732       30000 :                                  int             verbose ) {
     733       30000 :   if( FD_UNLIKELY( !funk ) ) {
     734           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     735           0 :     return FD_FUNK_ERR_INVAL;
     736           0 :   }
     737       30000 :   fd_funk_check_write( funk );
     738             : 
     739       30000 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     740             : 
     741       30000 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     742             : 
     743       30000 :   ulong txn_idx = (ulong)(txn - map);
     744             : 
     745       30000 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, funk->txn_max, txn_idx );
     746       30000 :   fd_funk_txn_cancel_sibling_list( funk, map, funk->txn_max, funk->cycle_tag++, oldest_idx, txn_idx );
     747             : 
     748       30000 :   ulong parent_idx = fd_funk_txn_idx( txn->parent_cidx );
     749       30000 :   if( fd_funk_txn_idx_is_null( parent_idx ) ) {
     750             :     /* Publish to root */
     751        6408 :     if( fd_funk_txn_idx( funk->child_head_cidx ) != txn_idx || fd_funk_txn_idx( funk->child_tail_cidx ) != txn_idx )
     752           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     753        6408 :     fd_funk_txn_update( &funk->rec_head_idx, &funk->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ),
     754        6408 :                         txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ),
     755        6408 :                         fd_funk_alloc( funk, wksp ), wksp );
     756             :     /* Inherit the children */
     757        6408 :     funk->child_head_cidx = txn->child_head_cidx;
     758        6408 :     funk->child_tail_cidx = txn->child_tail_cidx;
     759       23592 :   } else {
     760       23592 :     fd_funk_txn_t * parent_txn = map + parent_idx;
     761       23592 :     if( fd_funk_txn_idx( parent_txn->child_head_cidx ) != txn_idx || fd_funk_txn_idx( parent_txn->child_tail_cidx ) != txn_idx )
     762           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     763       23592 :     fd_funk_txn_update( &parent_txn->rec_head_idx, &parent_txn->rec_tail_idx, parent_idx, &parent_txn->xid,
     764       23592 :                         txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ),
     765       23592 :                         fd_funk_alloc( funk, wksp ), wksp );
     766             :     /* Inherit the children */
     767       23592 :     parent_txn->child_head_cidx = txn->child_head_cidx;
     768       23592 :     parent_txn->child_tail_cidx = txn->child_tail_cidx;
     769       23592 :   }
     770             : 
     771             :   /* Adjust the parent pointers of the children to point to their grandparent */
     772       30000 :   ulong child_idx = fd_funk_txn_idx( txn->child_head_cidx );
     773       53619 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) {
     774       23619 :     map[ child_idx ].parent_cidx = fd_funk_txn_cidx( parent_idx );
     775       23619 :     child_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     776       23619 :   }
     777             : 
     778       30000 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( txn ) );
     779             : 
     780       30000 :   return FD_FUNK_SUCCESS;
     781       30000 : }
     782             : 
     783             : /* Unfortunately, the semantics of how to deal with the same record
     784             :    key appearing in multiple children is not defined. So, for now,
     785             :    this API is commented out. */
     786             : #if 0
     787             : int
     788             : fd_funk_txn_merge_all_children( fd_funk_t *     funk,
     789             :                                 fd_funk_txn_t * parent_txn,
     790             :                                 int             verbose ) {
     791             :   if( FD_UNLIKELY( !funk ) ) {
     792             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     793             :     return FD_FUNK_ERR_INVAL;
     794             :   }
     795             :   fd_funk_check_write( funk );
     796             : 
     797             :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     798             : 
     799             :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     800             :   ulong           txn_max = funk->txn_max;                 /* Previously verified */
     801             : 
     802             :   ulong parent_idx;
     803             :   fd_funk_txn_xid_t * parent_xid;
     804             :   uint * child_head_cidx;
     805             :   uint * child_tail_cidx;
     806             :   ulong * rec_head_idx;
     807             :   ulong * rec_tail_idx;
     808             :   if( parent_txn == NULL ) { /* Root */
     809             :     parent_idx = FD_FUNK_TXN_IDX_NULL;
     810             :     parent_xid = funk->root;
     811             :     child_head_cidx = &funk->child_head_cidx;
     812             :     child_tail_cidx = &funk->child_tail_cidx;
     813             :     rec_head_idx = &funk->rec_head_idx;
     814             :     rec_tail_idx = &funk->rec_tail_idx;
     815             :   } else {
     816             :     parent_idx = (ulong)(parent_txn - map);
     817             :     ASSERT_IN_PREP( parent_idx );
     818             :     parent_xid = &parent_txn->xid;
     819             :     child_head_cidx = &parent_txn->child_head_cidx;
     820             :     child_tail_cidx = &parent_txn->child_tail_cidx;
     821             :     rec_head_idx = &parent_txn->rec_head_idx;
     822             :     rec_tail_idx = &parent_txn->rec_tail_idx;
     823             :   }
     824             : 
     825             :   ulong child_head_idx = fd_funk_txn_idx( *child_head_cidx );
     826             :   ulong child_idx = child_head_idx;
     827             :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     828             :     /* Merge records from child into parent */
     829             : 
     830             :     fd_funk_txn_t * txn = &map[ child_idx ];
     831             :     if( FD_UNLIKELY( !fd_funk_txn_idx_is_null( fd_funk_txn_idx( txn->child_head_cidx ) ) ) ) {
     832             :       FD_LOG_WARNING(("cannot call fd_funk_txn_merge_all_children if parent_txn has grandchildren"));
     833             :       return FD_FUNK_ERR_TXN;
     834             :     }
     835             : 
     836             :     fd_funk_txn_update( rec_head_idx, rec_tail_idx, parent_idx, parent_xid,
     837             :                         child_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ),
     838             :                         fd_funk_alloc( funk, wksp ), wksp );
     839             : 
     840             :     child_idx = fd_funk_txn_idx( txn->sibling_next_cidx );
     841             :     fd_funk_txn_map_remove( map, fd_funk_txn_xid( txn ) );
     842             : 
     843             :     /* Update the pointers as we go in case we stop in the middle
     844             :      */
     845             :     *child_head_cidx = fd_funk_txn_cidx( child_idx );
     846             :     if( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) {
     847             :       map[ child_idx ].sibling_prev_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     848             :     }
     849             :   }
     850             : 
     851             :   *child_tail_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     852             : 
     853             :   return FD_FUNK_SUCCESS;
     854             : }
     855             : #endif
     856             : 
     857             : /* Return the first record in a transaction. Returns NULL if the
     858             :    transaction has no records yet. */
     859             : 
     860             : FD_FN_PURE fd_funk_rec_t const *
     861             : fd_funk_txn_first_rec( fd_funk_t *           funk,
     862     4861050 :                        fd_funk_txn_t const * txn ) {
     863     4861050 :   ulong rec_idx;
     864     4861050 :   if( FD_UNLIKELY( NULL == txn ))
     865      255000 :     rec_idx = funk->rec_head_idx;
     866     4606050 :   else
     867     4606050 :     rec_idx = txn->rec_head_idx;
     868     4861050 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     869     3154092 :   fd_funk_rec_t const * rec_map = fd_funk_rec_map( funk, fd_funk_wksp( funk ) );
     870     3154092 :   return rec_map + rec_idx;
     871     4861050 : }
     872             : 
     873             : /* Return the next record in a transaction. Returns NULL if the
     874             :    transaction has no more records. */
     875             : 
     876             : FD_FN_PURE fd_funk_rec_t const *
     877             : fd_funk_txn_next_rec( fd_funk_t *           funk,
     878    46014141 :                       fd_funk_rec_t const * rec ) {
     879    46014141 :   ulong rec_idx = rec->next_idx;
     880    46014141 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     881    42860049 :   fd_funk_rec_t const * rec_map = fd_funk_rec_map( funk, fd_funk_wksp( funk ) );
     882    42860049 :   return rec_map + rec_idx;
     883    46014141 : }
     884             : 
     885             : fd_funk_txn_xid_t
     886    19115325 : fd_funk_generate_xid(void) {
     887    19115325 :   fd_funk_txn_xid_t xid;
     888    19115325 :   static FD_TL ulong seq = 0;
     889    19115325 :   xid.ul[0] =
     890    19115325 :     (fd_log_cpu_id() + 1U)*3138831853UL +
     891    19115325 :     (fd_log_thread_id() + 1U)*9180195821UL +
     892    19115325 :     (++seq)*6208101967UL;
     893    19115325 :   xid.ul[1] = ((ulong)fd_tickcount())*2810745731UL;
     894    19115325 :   return xid;
     895    19115325 : }
     896             : 
     897             : int
     898     9692196 : fd_funk_txn_verify( fd_funk_t * funk ) {
     899     9692196 :   fd_wksp_t *     wksp    = fd_funk_wksp( funk );          /* Previously verified */
     900     9692196 :   fd_funk_txn_t * map     = fd_funk_txn_map( funk, wksp ); /* Previously verified */
     901     9692196 :   ulong           txn_max = funk->txn_max;                 /* Previously verified */
     902             : 
     903     9692196 :   ulong funk_child_head_idx = fd_funk_txn_idx( funk->child_head_cidx ); /* Previously verified */
     904     9692196 :   ulong funk_child_tail_idx = fd_funk_txn_idx( funk->child_tail_cidx ); /* Previously verified */
     905             : 
     906     9692196 :   fd_funk_txn_xid_t const * last_publish = funk->last_publish; /* Previously verified */
     907             : 
     908   609292950 : # define TEST(c) do {                                                                           \
     909   609292950 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_FUNK_ERR_INVAL; } \
     910   609292950 :   } while(0)
     911             : 
     912     9692196 : # define IS_VALID( idx ) ( (idx==FD_FUNK_TXN_IDX_NULL) || \
     913     9692196 :                            ((idx<txn_max) && (!fd_funk_txn_xid_eq( fd_funk_txn_xid( &map[idx] ), last_publish ))) )
     914             : 
     915     9692196 :   TEST( !fd_funk_txn_map_verify( map ) );
     916             : 
     917     9692196 :   ulong free_cnt = txn_max - fd_funk_txn_map_key_cnt( map );
     918             : 
     919             :   /* Tag all transactions as not visited yet */
     920             : 
     921   345894948 :   for( ulong txn_idx=0UL; txn_idx<txn_max; txn_idx++ ) map[ txn_idx ].tag = 0UL;
     922             : 
     923             :   /* Visit all transactions in preparation, traversing from oldest to
     924             :      youngest. */
     925             : 
     926     9692196 :   ulong prep_cnt = 0UL;
     927     9692196 :   do {
     928             : 
     929             :     /* Push all children of funk to the stack */
     930             : 
     931     9692196 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
     932     9692196 :     ulong child_idx = funk_child_head_idx;
     933    32391261 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     934             : 
     935             :       /* Make sure valid idx, not tagged (detects cycles) and child
     936             :          knows it is a child of funk.  Then tag as visited / in-prep,
     937             :          push to stack and update prep_cnt */
     938             : 
     939    22699065 :       TEST( IS_VALID( child_idx ) );
     940    22699065 :       TEST( !map[ child_idx ].tag );
     941    22699065 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( map[ child_idx ].parent_cidx ) ) );
     942    22699065 :       map[ child_idx ].tag        = 1UL;
     943    22699065 :       map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     944    22699065 :       stack_idx                   = child_idx;
     945    22699065 :       prep_cnt++;
     946             : 
     947    22699065 :       ulong next_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     948    22699065 :       if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( map[ next_idx ].sibling_prev_cidx )==child_idx );
     949    22699065 :       child_idx = next_idx;
     950    22699065 :     }
     951             : 
     952    92035881 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
     953             : 
     954             :       /* Pop the next transaction to traverse */
     955             : 
     956    82343685 :       ulong txn_idx = stack_idx;
     957    82343685 :       stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     958             : 
     959             :       /* Push all children of txn to the stack */
     960             : 
     961    82343685 :       ulong child_idx = fd_funk_txn_idx( map[ txn_idx ].child_head_cidx );
     962   141988305 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     963             : 
     964             :         /* Make sure valid idx, not tagged (detects cycles) and child
     965             :            knows it is a child of txn_idx.  Then tag as visited /
     966             :            in-prep, push to stack and update prep_cnt */
     967             : 
     968    59644620 :         TEST( IS_VALID( child_idx ) );
     969    59644620 :         TEST( !map[ child_idx ].tag );
     970    59644620 :         TEST( fd_funk_txn_idx( map[ child_idx ].parent_cidx )==txn_idx );
     971    59644620 :         map[ child_idx ].tag        = 1UL;
     972    59644620 :         map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     973    59644620 :         stack_idx                   = child_idx;
     974    59644620 :         prep_cnt++;
     975             : 
     976    59644620 :         ulong next_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     977    59644620 :         if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( map[ next_idx ].sibling_prev_cidx )==child_idx );
     978    59644620 :         child_idx = next_idx;
     979    59644620 :       }
     980    82343685 :     }
     981             : 
     982     9692196 :   } while(0);
     983             : 
     984     9692196 :   TEST( (free_cnt+prep_cnt)==txn_max );
     985             : 
     986             :   /* Do it again with a youngest to oldest traversal to test reverse
     987             :      link integrity */
     988             : 
     989     9692196 :   prep_cnt = 0UL;
     990     9692196 :   do {
     991             : 
     992             :     /* Push all children of funk to the stack */
     993             : 
     994     9692196 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
     995     9692196 :     ulong child_idx = funk_child_tail_idx;
     996    32391261 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     997             : 
     998             :       /* Make sure valid idx, tagged as visited above (detects cycles)
     999             :          and child knows it is a child of funk.  Then tag as visited /
    1000             :          in-prep, push to stack and update prep_cnt */
    1001             : 
    1002    22699065 :       TEST( IS_VALID( child_idx ) );
    1003    22699065 :       TEST( map[ child_idx ].tag==1UL );
    1004    22699065 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( map[ child_idx ].parent_cidx ) ) );
    1005    22699065 :       map[ child_idx ].tag        = 2UL;
    1006    22699065 :       map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1007    22699065 :       stack_idx                   = child_idx;
    1008    22699065 :       prep_cnt++;
    1009             : 
    1010    22699065 :       ulong prev_idx = fd_funk_txn_idx( map[ child_idx ].sibling_prev_cidx );
    1011    22699065 :       if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( map[ prev_idx ].sibling_next_cidx )==child_idx );
    1012    22699065 :       child_idx = prev_idx;
    1013    22699065 :     }
    1014             : 
    1015    92035881 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
    1016             : 
    1017             :       /* Pop the next transaction to traverse */
    1018             : 
    1019    82343685 :       ulong txn_idx = stack_idx;
    1020    82343685 :       stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
    1021             : 
    1022             :       /* Push all children of txn to the stack */
    1023             : 
    1024    82343685 :       ulong child_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
    1025   141988305 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1026             : 
    1027             :         /* Make sure valid idx, tagged as visited above (detects cycles)
    1028             :            and child knows it is a child of txn_idx.  Then, tag as
    1029             :            visited / in-prep, push to stack and update prep_cnt */
    1030             : 
    1031    59644620 :         TEST( IS_VALID( child_idx ) );
    1032    59644620 :         TEST( map[ child_idx ].tag==1UL );
    1033    59644620 :         TEST( fd_funk_txn_idx( map[ child_idx ].parent_cidx )==txn_idx );
    1034    59644620 :         map[ child_idx ].tag        = 2UL;
    1035    59644620 :         map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1036    59644620 :         stack_idx                   = child_idx;
    1037    59644620 :         prep_cnt++;
    1038             : 
    1039    59644620 :         ulong prev_idx = fd_funk_txn_idx( map[ child_idx ].sibling_prev_cidx );
    1040    59644620 :         if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( map[ prev_idx ].sibling_next_cidx )==child_idx );
    1041    59644620 :         child_idx = prev_idx;
    1042    59644620 :       }
    1043    82343685 :     }
    1044     9692196 :   } while(0);
    1045             : 
    1046     9692196 :   TEST( (free_cnt+prep_cnt)==txn_max );
    1047             : 
    1048     9692196 :   TEST( fd_funk_txn_cnt( map )==prep_cnt );
    1049             : 
    1050     9692196 : # undef IS_VALID
    1051     9692196 : # undef TEST
    1052             : 
    1053     9692196 :   return FD_FUNK_SUCCESS;
    1054     9692196 : }
    1055             : 
    1056             : #undef ASSERT_UNTAGGED
    1057             : #undef ASSERT_IN_PREP
    1058             : #undef ASSERT_IN_MAP

Generated by: LCOV version 1.14