LCOV - code coverage report
Current view: top level - disco/stem - fd_stem.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 301 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 1004 0.0 %

          Line data    Source code
       1             : #include "fd_stem.h"
       2             : 
       3             : /* fd_stem provides services to multiplex multiple streams of input
       4             :    fragments and present them to a mix of reliable and unreliable
       5             :    consumers as though they were generated by multiple different
       6             :    multi-stream producers.  The code can be included to generate
       7             :    a definition of stem_run which can be called as a tile main run
       8             :    loop.
       9             : 
      10             :    The template supports various callback functions which can be
      11             :    defined like #define STEM_CALLBACK_BEFORE_FRAG before_frag to
      12             :    tune the behavior of the stem_run loop.  The callbacks are:
      13             : 
      14             :      SHOULD_SHUTDOWN
      15             :    It is called at the beginning of each iteration of the stem run loop,
      16             :    and if it returns non-zero, the stem will exit the run loop and
      17             :    return from the stem_run function.  This is useful for shutting down
      18             :    the tile.
      19             : 
      20             :      DURING_HOUSEKEEPING
      21             :    Is called during the housekeeping routine, which happens infrequently
      22             :    on a schedule determined by the stem (based on the lazy parameter,
      23             :    see fd_tempo.h for more information).  It is appropriate to do
      24             :    slightly expensive things here that wouldn't be OK to do in the main
      25             :    loop, like updating sequence numbers that are shared with other tiles
      26             :    (e.g. synchronization information), or sending batched information
      27             :    somewhere.  The ctx is a user-provided context object from when the
      28             :    stem was initialized.
      29             : 
      30             :      METRICS_WRITE
      31             :    By convention, tiles may wish to accumulate high traffic metrics
      32             :    locally so they don't cause a lot of cache coherency traffic, and
      33             :    then periodically publish them to external observers.  This callback
      34             :    is here to support that use case.  It occurs infrequently during the
      35             :    housekeeping loop, and is called inside a compiler fence to ensure
      36             :    the writes do not get reordered, which may be important for observers
      37             :    or monitoring tools.  The ctx is a user-provided context object from
      38             :    when the stem tile was initialized.
      39             : 
      40             :      BEFORE_CREDIT
      41             :    Is called every iteration of the stem run loop, whether there is a
      42             :    new frag ready to receive or not.  This callback is also still
      43             :    invoked even if the stem is backpressured and cannot read any new
      44             :    fragments while waiting for downstream consumers to catch up.  This
      45             :    callback is useful for things that need to occur even if no new frags
      46             :    are being handled.  For example, servicing network connections could
      47             :    happen here.  The ctx is a user-provided context object from when the
      48             :    stem tile was initialized.  The stem is the stem which is invoking
      49             :    this callback. The stem should only be used for calling
      50             :    fd_stem_publish to publish a fragment to downstream consumers.
      51             : 
      52             :    The charge_busy argument is 0 by default, and should be set to 1 if
      53             :    the before_credit function is doing work that should be accounted for
      54             :    as part of the tiles busy indicator.
      55             : 
      56             :      CHECK_CREDIT
      57             :    Is called every iteration of the stem run loop. This callback
      58             :    overrides the default backpressure check (can the run loop produce
      59             :    STEM_BURST frags on any out link without exceeding flow control
      60             :    credits).
      61             : 
      62             :       AFTER_CREDIT
      63             :    Is called every iteration of the stem run loop, whether there is a
      64             :    new frag ready to receive or not, except in cases where the stem is
      65             :    backpressured by a downstream consumer and would not be able to
      66             :    publish.  The callback might be used for publishing new fragments to
      67             :    downstream consumers in the main loop which are not in response to an
      68             :    incoming fragment.  For example, code that collects incoming
      69             :    fragments over a period of 1 second and joins them together before
      70             :    publishing a large block fragment downstream, would publish the block
      71             :    here. The ctx is a user-provided context object from when the stem
      72             :    tile was initialized.  The stem is the stem which is invoking this
      73             :    callback. The stem should only be used for calling fd_stem_publish to
      74             :    publish a fragment to downstream consumers.
      75             : 
      76             :    The opt_poll_in argument determines if the stem should proceed with
      77             :    checking for new fragments to consumer, or should `continue` the main
      78             :    stem loop to do credit checking again.  This could be used if the
      79             :    after_credit function publishes, and the flow control needs to be
      80             :    checked again.  By default, opt_poll_in is true and the stem will
      81             :    poll for fragments right away without rerunning the loop or checking
      82             :    for credits.
      83             : 
      84             :    The charge_busy argument is 0 by default, and should be set to 1 if
      85             :    the after_credit function is doing work that should be accounted for
      86             :    as part of the tiles busy indicator.
      87             : 
      88             :       BEFORE_FRAG
      89             :    Is called immediately whenever a new fragment has been detected that
      90             :    was published by an upstream producer.  The signature and sequence
      91             :    number (sig and seq) provided as arguments are read atomically from
      92             :    shared memory, so must both match each other from the published
      93             :    fragment (aka. they will not be torn or partially overwritten).
      94             :    in_idx is an index in [0, num_ins) indicating which producer
      95             :    published the fragment. No fragment data has been read yet here, nor
      96             :    has other metadata, for example the size or timestamps of the
      97             :    fragment.  Mainly this callback is useful for deciding whether to
      98             :    filter the fragment based on its signature.  If the return value is
      99             :    non-zero, the frag will be skipped completely, no fragment data will
     100             :    be read, and the in will be advanced so that we now wait for the next
     101             :    fragment.  If the return value is -1, then the frag is returned back
     102             :    to the message queue and will be reprocessed.  The ctx is a
     103             :    user-provided context object from when the stem tile was initialized.
     104             : 
     105             :       DURING_FRAG
     106             :    Is called after the stem has received a new frag from an in, but
     107             :    before the stem has checked that it was overrun.  This callback is
     108             :    not invoked if the stem is backpressured, as it would not try and
     109             :    read a frag from an in in the first place (instead, leaving it on the
     110             :    in mcache to backpressure the upstream producer).  in_idx will be the
     111             :    index of the in that the frag was received from, skipping any unpolled
     112             :    links. If the producer of the frags is respecting flow control, it is
     113             :    safe to read frag data in any of the callbacks, but it is suggested to
     114             :    copy or read frag data within this callback, as if the producer does
     115             :    not respect flow control, the frag may be torn or corrupt due to an
     116             :    overrun by the reader.  If the frag being read from has been
     117             :    overwritten while this callback is running, the frag will be ignored
     118             :    and the stem will not call the after_frag function. Instead it will
     119             :    recover from the overrun and continue with new frags.  This function
     120             :    cannot fail.  The ctx is a user-provided context object from when the
     121             :    stem tile was initialized. seq, sig, chunk, and sz are the respective
     122             :    fields from the mcache fragment that was received.  If the producer
     123             :    is not respecting flow control, these may be corrupt or torn and
     124             :    should not be trusted, except for seq which is read atomically.
     125             : 
     126             :       RETURNABLE_FRAG
     127             :    Is called after the stem has received a new frag from an in, and
     128             :    assumes that the stem cannot be overrun.  This special callback can
     129             :    instruct the stem not to advance the input sequence number, and
     130             :    instead return the fragment to the stem to be processed again.  This
     131             :    is useful for processing partial data from fragments without copying
     132             :    it.  This callback is unsafe in general contexts, since it assumes
     133             :    that the frag will not be overwritten while the callback is running,
     134             :    and that the frag data is valid throughout the function call.  It
     135             :    should only be used when the stem is guaranteed to not be overrun.
     136             :    This callback is not invoked if the stem is backpressured, as it
     137             :    would not try and read a frag from an in in the first place (instead,
     138             :    leaving it on the in mcache to backpressure the upstream producer).
     139             :    in_idx will be the index of the in that the frag was received from.
     140             :    seq, sig, chunk, and sz are the respective fields from the mcache
     141             :    fragment that was received.  tsorig and tspub are the timestamps of
     142             :    the fragment that was received, and are read atomically from shared
     143             :    memory, so must both match each other from the published fragment
     144             :    (aka. they will not be torn or partially overwritten).  The ctx is a
     145             :    user-provided context object from when the stem tile was initialized.
     146             :    The callback should return 1 if the fragment was not fully processed
     147             :    and should be returned to the stem for further processing, or 0 if
     148             :    the fragment was fully processed and the consumer link should be
     149             :    advanced.
     150             : 
     151             :       AFTER_FRAG
     152             :    Is called immediately after the DURING_FRAG, along with an additional
     153             :    check that the reader was not overrun while handling the frag.  If
     154             :    the reader was overrun, the frag is abandoned and this function is
     155             :    not called.  This callback is not invoked if the stem is
     156             :    backpressured, as it would not read a frag in the first place.
     157             :    in_idx will be the index of the in that the frag was received from,
     158             :    skipping any unpolled links. You should not read the frag data directly
     159             :    here, as it might still get overrun, instead it should be copied out of
     160             :    the frag during the read callback if needed later. This function cannot
     161             :    fail. The ctx is a user-provided context object from when the stem tile
     162             :    was initialized.  stem should only be used for calling fd_stem_publish
     163             :    to publish a fragment to downstream consumers.  seq is the sequence
     164             :    number of the fragment that was read from the input mcache. sig, sz,
     165             :    tsorig, and tspub are the respective fields from the mcache fragment
     166             :    that was received.  If the producer is not respecting flow control,
     167             :    these may be corrupt or torn and should not be trusted.
     168             : 
     169             :       AFTER_POLL_OVERRUN
     170             :    Is called when an overrun is detected while polling for new frags.
     171             :    This callback is not called when an overrun is detected in
     172             :    during_frag. */
     173             : 
     174             : #include "../../util/log/fd_log.h"
     175             : #include "../topo/fd_topo.h"
     176             : #include "../metrics/fd_metrics.h"
     177             : #include "../../tango/fd_tango.h"
     178             : 
     179             : #ifndef STEM_NAME
     180             : #define STEM_NAME stem
     181             : #endif
     182           0 : #define STEM_(n) FD_EXPAND_THEN_CONCAT3(STEM_NAME,_,n)
     183             : 
     184             : #ifndef STEM_BURST
     185             : #error "STEM_BURST must be defined"
     186             : #endif
     187             : 
     188             : #ifndef STEM_CALLBACK_CONTEXT_TYPE
     189             : #error "STEM_CALLBACK_CONTEXT_TYPE must be defined"
     190             : #endif
     191             : 
     192             : #ifndef STEM_CALLBACK_CONTEXT_ALIGN
     193             : #error "STEM_CALLBACK_CONTEXT_ALIGN must be defined"
     194             : #endif
     195             : 
     196             : #ifndef STEM_LAZY
     197           0 : #define STEM_LAZY (0L)
     198             : #endif
     199             : 
     200             : /* STEM_ALWAYS_PARK makes the tile park on its NEXT_DEADLINE even in
     201             :    performance mode, where nothing rings it, so its ins wait for the
     202             :    deadline too.
     203             : 
     204             :    STEM_NEVER_PARK keeps the tile spinning even if it has no fragments
     205             :    to process and would ordinarily try to park.  This is typically for
     206             :    tiles which don't yet play nicely with the sleep infrastructure or do
     207             :    custom waiting. */
     208             : 
     209             : #ifndef STEM_ALWAYS_PARK
     210             : #define STEM_ALWAYS_PARK 0
     211             : #endif
     212             : 
     213             : #ifndef STEM_NEVER_PARK
     214             : #define STEM_NEVER_PARK 0
     215             : #endif
     216             : 
     217             : #if STEM_ALWAYS_PARK && STEM_NEVER_PARK
     218             : #error "STEM_ALWAYS_PARK and STEM_NEVER_PARK are exclusive"
     219             : #endif
     220             : 
     221           0 : #define STEM_SHUTDOWN_SEQ (ULONG_MAX-1UL)
     222             : 
     223             : static inline void
     224             : STEM_(credit_ring)( fd_stem_sleep_t const * sleep,
     225           0 :                     ulong                   in_idx ) {
     226           0 :   ulong producer = sleep->in_producer[ in_idx ];
     227           0 :   if( FD_UNLIKELY( producer==ULONG_MAX ) ) return;
     228           0 :   __atomic_thread_fence( __ATOMIC_SEQ_CST );
     229           0 :   if( FD_UNLIKELY( FD_VOLATILE_CONST( sleep->shmem->credit_bits[ producer>>6 ] ) & (1UL<<(producer&63UL)) ) ) fd_sleep_ring( sleep->shmem, producer );
     230           0 : }
     231             : 
     232             : /* STEM_(in_dirty) is 1 if in has credits or diagnostics not yet
     233             :    published (in_update on a clean in rewrites the same values). */
     234             : 
     235             : static inline int
     236           0 : STEM_(in_dirty)( fd_stem_tile_in_t const * in ) {
     237           0 :   uint const * accum = in->accum;
     238           0 :   return ( __atomic_load_n( in->fseq, __ATOMIC_RELAXED )!=in->seq )
     239           0 :        | (int)(accum[0]|accum[1]|accum[2]|accum[3]|accum[4]|accum[5]);
     240           0 : }
     241             : 
     242             : static inline __attribute__((always_inline)) void
     243           0 : STEM_(in_update)( fd_stem_tile_in_t * in ) {
     244           0 :   __atomic_store_n( in->fseq, in->seq, __ATOMIC_RELEASE );
     245             : 
     246           0 :   volatile ulong * metrics = fd_metrics_link_in( fd_metrics_base_tl, in->idx );
     247             : 
     248           0 :   uint *  accum = in->accum;
     249           0 :   ulong a0 = (ulong)accum[0]; ulong a1 = (ulong)accum[1]; ulong a2 = (ulong)accum[2];
     250           0 :   ulong a3 = (ulong)accum[3]; ulong a4 = (ulong)accum[4]; ulong a5 = (ulong)accum[5];
     251           0 :   FD_COMPILER_MFENCE();
     252           0 :   metrics[0] += a0;           metrics[1] += a1;           metrics[2] += a2;
     253           0 :   metrics[3] += a3;           metrics[4] += a4;           metrics[5] += a5;
     254           0 :   FD_COMPILER_MFENCE();
     255           0 :   accum[0] = 0U;              accum[1] = 0U;              accum[2] = 0U;
     256           0 :   accum[3] = 0U;              accum[4] = 0U;              accum[5] = 0U;
     257           0 : }
     258             : 
     259             : /* STEM_(out_publish) publishes out_idx's sync word and seq_mirror.
     260             :    Callers skip an out whose seq has not moved (out_seq_pub), which
     261             :    also keeps the stem off outs another client publishes.  The stem is
     262             :    the sole writer of its mirrors, so the store needs no guard load. */
     263             : 
     264             : static inline void
     265             : STEM_(out_publish)( fd_stem_sleep_t const * sleep,
     266             :                     fd_frag_meta_t *        mcache,
     267             :                     ulong                   out_idx,
     268           0 :                     ulong                   seq ) {
     269           0 :   fd_mcache_seq_update( fd_mcache_seq_laddr( mcache ), seq );
     270           0 :   if( sleep->shmem ) __atomic_store_n( &sleep->shmem->seq_mirror[ sleep->out_link_id[ out_idx ] ], seq, __ATOMIC_RELEASE );
     271           0 : }
     272             : 
     273             : static inline int
     274             : STEM_(park)( STEM_CALLBACK_CONTEXT_TYPE * ctx,
     275             :              fd_stem_sleep_t const *      sleep,
     276             :              fd_stem_tile_in_t *          in,
     277             :              ulong                        in_cnt,
     278             :              fd_frag_meta_t **            out_mcache,
     279             :              ulong                        out_cnt,
     280             :              ulong const *                out_seq,
     281             :              ulong *                      out_seq_pub,
     282             :              ulong                        cons_cnt,
     283             :              ulong const **               cons_fseq,
     284             :              ulong const *                cons_seq,
     285             :              int                          backpressured,
     286             :              long                         deadline_hint,
     287             :              long                         cap_ticks,
     288             :              long                         min_ticks,
     289             :              double                       tick_per_ns,
     290           0 :              long                         now ) {
     291           0 :   (void)ctx;
     292             : 
     293           0 :   if( FD_LIKELY( !backpressured ) ) {
     294           0 :     for( ulong i=0UL; i<in_cnt; i++ ) {
     295             :       /* If there's a frag to read on some in, then we don't park. */
     296           0 :       if( FD_UNLIKELY( fd_seq_diff( in[ i ].seq, fd_frag_meta_seq_query( in[ i ].mline ) )<=0L ) ) return 0;
     297           0 :     }
     298           0 :   }
     299             : 
     300             :   /* Backpressured, waker readiness and already due work wait for
     301             :      after_credit, which runs once credits return and ring us.  A
     302             :      before_credit tile still does them while backpressured. */
     303             : #ifdef STEM_CALLBACK_BEFORE_CREDIT
     304           0 :   int bp_defer = 0;
     305             : #else
     306           0 :   int bp_defer = backpressured;
     307           0 : #endif
     308           0 :   if( FD_LIKELY( !bp_defer && sleep->waker_fseq ) ) {
     309           0 :     if( FD_UNLIKELY( fd_fseq_query( sleep->waker_fseq )==1UL ) ) return 0;
     310           0 :   }
     311             : 
     312             : #ifdef STEM_CALLBACK_PREVENT_PARK
     313           0 :   if( FD_UNLIKELY( STEM_CALLBACK_PREVENT_PARK( ctx ) ) ) return 0;
     314           0 : #endif
     315             : 
     316             : #ifdef STEM_CALLBACK_SHOULD_SHUTDOWN
     317           0 :   if( FD_UNLIKELY( STEM_CALLBACK_SHOULD_SHUTDOWN( ctx ) ) ) return 0;
     318           0 : #endif
     319             : 
     320             : 
     321           0 :   long due = deadline_hint;
     322             : #ifdef STEM_CALLBACK_NEXT_DEADLINE
     323           0 :   long cb_due = STEM_CALLBACK_NEXT_DEADLINE( ctx );
     324           0 :   if( FD_UNLIKELY( bp_defer && cb_due<=now ) ) cb_due = LONG_MAX;
     325             :   due = fd_long_min( due, cb_due );
     326             : #endif
     327           0 :   long deadline = fd_long_min( now+cap_ticks, due );
     328           0 :   if( FD_UNLIKELY( deadline-now<min_ticks ) ) return 0; /* already close enough to deadline, don't waste a syscall */
     329             : 
     330             :   /* We are now going to park ... flush all state before.  Skip the
     331             :      ins and outs with nothing to flush: a clean in's producer was
     332             :      already rung when our fseq last moved. */
     333           0 :   for( ulong i=0UL; i<in_cnt; i++ ) {
     334           0 :     if( FD_LIKELY( !STEM_(in_dirty)( &in[ i ] ) ) ) continue;
     335           0 :     STEM_(in_update)( &in[ i ] );
     336           0 :     if( FD_LIKELY( sleep->shmem ) ) STEM_(credit_ring)( sleep, in[ i ].idx );
     337           0 :   }
     338           0 :   for( ulong o=0UL; o<out_cnt; o++ ) {
     339           0 :     if( FD_LIKELY( out_seq_pub[ o ]==out_seq[ o ] ) ) continue; /* idle, or never published by this stem */
     340           0 :     STEM_(out_publish)( sleep, out_mcache[ o ], o, out_seq[ o ] );
     341           0 :     out_seq_pub[ o ] = out_seq[ o ];
     342           0 :   }
     343             : 
     344           0 :   ulong _word;
     345           0 :   ulong * word;
     346           0 :   ulong my_w;
     347           0 :   ulong my_bit;
     348             : 
     349           0 :   int parked = 0;
     350           0 :   if( FD_LIKELY( sleep->shmem ) ) {
     351           0 :     sleep->shmem->tile[ sleep->tile_id ].deadline = (ulong)deadline;
     352           0 :     word   = &sleep->shmem->tile[ sleep->tile_id ].word;
     353           0 :     my_w   = sleep->tile_id>>6;
     354           0 :     my_bit = 1UL<<(sleep->tile_id&63UL);
     355           0 :     FD_VOLATILE( word[0] ) = 0UL;
     356             : 
     357             :     /* Set the parked bit so publishes to us ring us; when backpressured
     358             :        set the credit bit too, which swaps publish rings for credit
     359             :        return rings. */
     360           0 :     for( ulong i=0UL; i<in_cnt; i++ ) {
     361           0 :       ulong snap = in[ i ].seq;
     362           0 :       if( FD_UNLIKELY( backpressured ) ) snap = __atomic_load_n( &sleep->shmem->seq_mirror[ sleep->in_link_id[ in[ i ].idx ] ], __ATOMIC_ACQUIRE );
     363           0 :       sleep->shmem->seq_snap[ sleep->tile_id ][ in[ i ].idx ] = snap;
     364           0 :     }
     365           0 :     if( FD_UNLIKELY( backpressured ) ) __atomic_fetch_or( &sleep->shmem->credit_bits[ my_w ], my_bit, __ATOMIC_SEQ_CST );
     366           0 :     __atomic_fetch_or( &sleep->shmem->parked_bits[ my_w ], my_bit, __ATOMIC_SEQ_CST );
     367           0 :     parked = 1;
     368           0 :   } else {
     369             :     /* No shmem, so no one can wake us.  We will just sleep until the
     370             :        deadline.  This only happens for ALWAYS_PARK tiles when running
     371             :        in spin mode. */
     372           0 :     word = &_word;
     373           0 :     my_w = 0UL;
     374           0 :     my_bit = 0UL;
     375           0 :     FD_VOLATILE( word[0] ) = 0UL;
     376           0 :     __atomic_thread_fence( __ATOMIC_SEQ_CST );
     377           0 :   }
     378             : 
     379             :   /* Last chance checks to reduce race-window for a lost wake.  If there
     380             :      is nothing to abort the park, then park.  There's a short race
     381             :      between checking and parking, which cannot be avoided, and is
     382             :      bounded by the mwaitx tile which periodically sweeps and wakes us
     383             :      if we raced here. */
     384             : 
     385           0 :   int pending = 0;
     386           0 :   if( FD_LIKELY( parked && !backpressured ) ) {
     387           0 :     for( ulong i=0UL; i<in_cnt; i++ ) {
     388           0 :       pending |= fd_seq_diff( in[ i ].seq, fd_frag_meta_seq_query( in[ i ].mline ) )<=0L;
     389           0 :     }
     390           0 :   } else if( FD_LIKELY( parked ) ) {
     391             :     /* a credit return between the caller's snapshot and the credit
     392             :        bit went unrung; only its fseq shows it */
     393           0 :     for( ulong c=0UL; c<cons_cnt; c++ ) {
     394           0 :       pending |= __atomic_load_n( cons_fseq[ c ], __ATOMIC_ACQUIRE )!=cons_seq[ c ];
     395           0 :     }
     396           0 :   }
     397           0 :   if( FD_LIKELY( !bp_defer && sleep->waker_fseq ) ) pending |= fd_fseq_query( sleep->waker_fseq )==1UL;
     398             : #ifdef STEM_CALLBACK_SHOULD_SHUTDOWN
     399           0 :   pending |= STEM_CALLBACK_SHOULD_SHUTDOWN( ctx );
     400             : #endif
     401             : 
     402           0 :   if( FD_UNLIKELY( pending ) ) {
     403           0 :     FD_VOLATILE( word[0] ) = 1UL;
     404           0 :     if( FD_LIKELY( parked ) ) {
     405           0 :       __atomic_fetch_and( &sleep->shmem->parked_bits[ my_w ], ~my_bit, __ATOMIC_SEQ_CST );
     406           0 :       if( FD_UNLIKELY( backpressured ) ) __atomic_fetch_and( &sleep->shmem->credit_bits[ my_w ], ~my_bit, __ATOMIC_SEQ_CST );
     407           0 :     }
     408           0 :     FD_MCNT_INC( TILE, PARK_ATTEMPTED_PENDING, 1UL );
     409           0 :     return backpressured ? -1 : 0;
     410           0 :   }
     411             : 
     412             :   /* Only a due inside the cap arms a timer, the sweep enforces the cap */
     413           0 :   long timer = fd_long_if( parked & (due>deadline), LONG_MAX, deadline );
     414             : #ifdef STEM_CALLBACK_PARK_WAIT
     415           0 :   int cause = STEM_CALLBACK_PARK_WAIT( ctx, word, timer, tick_per_ns );
     416             : #else
     417             :   int cause = fd_sleep_park_wait( word, timer, tick_per_ns );
     418             : #endif
     419             : 
     420             :   /* Park completed, either due to a ring or a deadline.  Now clear the
     421             :      parked bit and publish the reason.  There's a race here with the
     422             :      ringer but it's harmless, it might cause one more spurious wake. */
     423           0 :   FD_VOLATILE( word[0] ) = 1UL;
     424           0 :   if( FD_LIKELY( sleep->shmem && parked ) ) {
     425           0 :     __atomic_fetch_and( &sleep->shmem->parked_bits[ my_w ], ~my_bit, __ATOMIC_SEQ_CST );
     426           0 :     if( FD_UNLIKELY( backpressured ) ) __atomic_fetch_and( &sleep->shmem->credit_bits[ my_w ], ~my_bit, __ATOMIC_SEQ_CST );
     427           0 :   }
     428           0 :   if( FD_LIKELY( cause==FD_SLEEP_UNPARK_RING ) ) FD_MCNT_INC( TILE, PARK_ATTEMPTED_RING,     1UL );
     429           0 :   else                                           FD_MCNT_INC( TILE, PARK_ATTEMPTED_DEADLINE, 1UL );
     430           0 :   return 1;
     431           0 : }
     432             : 
     433             : static inline __attribute__((always_inline)) void
     434             : STEM_(cr_refresh)( ulong             out_cnt,
     435             :                    ulong const *     out_depth,
     436             :                    ulong const *     out_seq,
     437             :                    ulong *           cr_avail,
     438             :                    ulong *           min_cr_avail,
     439             :                    ulong             cr_max,
     440             :                    ulong             cons_cnt,
     441             :                    ulong const *     cons_out,
     442             :                    ulong const *     cons_seq,
     443             :                    volatile ulong ** cons_slow,
     444           0 :                    ulong             in_backp ) {
     445           0 :   if( FD_LIKELY( cons_cnt ) ) {
     446           0 :     ulong slowest_cons = ULONG_MAX;
     447           0 :     *min_cr_avail = cr_max;
     448           0 :     for( ulong out_idx=0; out_idx<out_cnt; out_idx++ ) {
     449           0 :       cr_avail[ out_idx ] = out_depth[ out_idx ];
     450           0 :     }
     451             : 
     452           0 :     for( ulong cons_idx=0UL; cons_idx<cons_cnt; cons_idx++ ) {
     453           0 :       ulong out_idx = cons_out[ cons_idx ];
     454             : 
     455             :       /* Read the fseq boot value (ULONG_MAX) as sequence 0, not -1,
     456             :          else the producer is one credit short until the consumer
     457             :          boots. */
     458           0 :       ulong cseq = fd_ulong_if( cons_seq[ cons_idx ]==ULONG_MAX, 0UL, cons_seq[ cons_idx ] );
     459           0 :       ulong cons_cr_avail = (ulong)fd_long_max( (long)out_depth[ out_idx ]-fd_long_max( fd_seq_diff( out_seq[ out_idx ], cseq ), 0L ), 0L );
     460             : 
     461             :       /* If a reliable consumer exits, they can set the credit return
     462             :          fseq to STEM_SHUTDOWN_SEQ to indicate they are no longer
     463             :          actively consuming. */
     464           0 :       cons_cr_avail = fd_ulong_if( cons_seq[ cons_idx ]==STEM_SHUTDOWN_SEQ, out_depth[ out_idx ], cons_cr_avail );
     465           0 :       slowest_cons = fd_ulong_if( cons_cr_avail<*min_cr_avail, cons_idx, slowest_cons );
     466             : 
     467           0 :       cr_avail[ out_idx ] = fd_ulong_min( cr_avail[ out_idx ], cons_cr_avail );
     468           0 :       *min_cr_avail       = fd_ulong_min( cons_cr_avail, *min_cr_avail );
     469           0 :     }
     470             : 
     471             :     /* See notes above about use of quasi-atomic diagnostic accum.
     472             :        Skip a +=0: it would still take the consumer's line. */
     473           0 :     if( FD_UNLIKELY( in_backp && slowest_cons!=ULONG_MAX ) ) {
     474           0 :       FD_COMPILER_MFENCE();
     475           0 :       (*cons_slow[ slowest_cons ]) += in_backp;
     476           0 :       FD_COMPILER_MFENCE();
     477           0 :     }
     478           0 :   }
     479           0 : }
     480             : 
     481             : static inline void
     482             : STEM_(park_attempt)( STEM_CALLBACK_CONTEXT_TYPE * ctx,
     483             :                      fd_stem_sleep_t const *      cfg,
     484             :                      fd_stem_tile_in_t *          in,
     485             :                      ulong                        in_cnt,
     486             :                      fd_frag_meta_t **            out_mcache,
     487             :                      ulong                        out_cnt,
     488             :                      ulong const *                out_depth,
     489             :                      ulong const *                out_seq,
     490             :                      ulong *                      out_seq_pub,
     491             :                      ulong *                      cr_avail,
     492             :                      ulong *                      min_cr_avail,
     493             :                      ulong                        cr_max,
     494             :                      ulong                        cons_cnt,
     495             :                      ulong const **               cons_fseq,
     496             :                      ulong *                      cons_seq,
     497             :                      ulong const *                cons_out,
     498             :                      volatile ulong **            cons_slow,
     499             :                      ulong                        event_cnt,
     500             :                      ushort const *               event_map,
     501             :                      ulong *                      event_seq,
     502             :                      ulong                        async_min,
     503             :                      long                         hk_due,
     504             :                      long                         cap_ticks,
     505             :                      long                         min_ticks,
     506             :                      double                       tick_per_ns,
     507             :                      ulong *                      metric_regime_ticks,
     508             :                      long *                       now,
     509             :                      long *                       then,
     510             :                      ulong                        regime,
     511             :                      int                          backpressured,
     512           0 :                      long                         deadline_hint ) {
     513           0 :   int slept = STEM_(park)( ctx, cfg, in, in_cnt, out_mcache, out_cnt, out_seq, out_seq_pub, cons_cnt, cons_fseq, cons_seq,
     514           0 :                            backpressured, deadline_hint, cap_ticks, min_ticks, tick_per_ns, *now );
     515           0 :   if( FD_UNLIKELY( !slept ) ) return; /* found work */
     516             : 
     517           0 :   long next   = fd_tickcount();
     518           0 :   long waited = next - *now;
     519           0 :   metric_regime_ticks[ regime ] += (ulong)waited;
     520           0 :   *now = next;
     521           0 :   if( FD_UNLIKELY( backpressured || waited>=(long)async_min ) ) {
     522             :     /* If we slept shorter than a housekeeping interval, we didn't miss
     523             :        any credit refresh housekeeping events, but otherwise we might
     524             :        have missed many, so refill them immediately here.  A
     525             :        backpressure park is woken by the consumer's credit return, so
     526             :        reload regardless, and turn cons_seq into cr_avail now, or we
     527             :        re-park on the stale count until the timer. */
     528           0 :     for( ulong cons_idx=0UL; cons_idx<cons_cnt; cons_idx++ ) {
     529           0 :       ulong this_cons_seq = __atomic_load_n( cons_fseq[ cons_idx ], __ATOMIC_ACQUIRE );
     530           0 :       cons_seq[ cons_idx ] = this_cons_seq;
     531             : #ifdef STEM_CALLBACK_RECV_CREDIT
     532           0 :       STEM_CALLBACK_RECV_CREDIT( ctx, cons_out[ cons_idx ], out_seq[ cons_out[ cons_idx ] ], this_cons_seq );
     533             : #endif
     534           0 :     }
     535           0 :     if( FD_UNLIKELY( (*now-hk_due)>=0L ) ) {
     536             :       /* A lazy interval passed, full housekeeping is due now. */
     537           0 :       for( ulong k=0UL; k<event_cnt; k++ ) {
     538           0 :         if( event_map[ k ]==cons_cnt ) {
     539           0 :           *event_seq = k;
     540           0 :           break;
     541           0 :         }
     542           0 :       }
     543           0 :       *then = *now;
     544           0 :     } else {
     545             :       /* Credits only, nothing else is due yet. */
     546           0 :       STEM_(cr_refresh)( out_cnt, out_depth, out_seq, cr_avail, min_cr_avail, cr_max, cons_cnt, cons_out, cons_seq, cons_slow, 0UL );
     547           0 :       *then = *now + (long)async_min;
     548           0 :     }
     549           0 :   }
     550           0 : }
     551             : 
     552             : FD_FN_PURE static inline ulong
     553           0 : STEM_(scratch_align)( void ) {
     554           0 :   return FD_STEM_SCRATCH_ALIGN;
     555           0 : }
     556             : 
     557             : FD_FN_PURE static inline ulong
     558             : STEM_(scratch_footprint)( ulong in_cnt,
     559             :                           ulong out_cnt,
     560           0 :                           ulong cons_cnt ) {
     561           0 :   ulong l = FD_LAYOUT_INIT;
     562           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_stem_tile_in_t), in_cnt*sizeof(fd_stem_tile_in_t)     );  /* in */
     563           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),             out_cnt*sizeof(ulong)                ); /* cr_avail */
     564           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),             out_cnt*sizeof(ulong)                ); /* out_depth */
     565           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),             out_cnt*sizeof(ulong)                ); /* out_seq */
     566           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),             out_cnt*sizeof(ulong)                ); /* out_seq_pub */
     567           0 :   l = FD_LAYOUT_APPEND( l, alignof(int),               out_cnt*sizeof(int)                  ); /* out_reliable */
     568           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong const *),     cons_cnt*sizeof(ulong const *)       ); /* cons_fseq */
     569           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong *),           cons_cnt*sizeof(ulong *)             ); /* cons_slow */
     570           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),             cons_cnt*sizeof(ulong)               ); /* cons_out */
     571           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),             cons_cnt*sizeof(ulong)               ); /* cons_seq */
     572           0 :   const ulong event_cnt = in_cnt + 1UL + cons_cnt;
     573           0 :   l = FD_LAYOUT_APPEND( l, alignof(ushort),            event_cnt*sizeof(ushort)             ); /* event_map */
     574           0 :   return FD_LAYOUT_FINI( l, STEM_(scratch_align)() );
     575           0 : }
     576             : 
     577             : #if !STEM_ALWAYS_PARK
     578             : #define STEM_RUN1_NAME   run1_spin
     579             : #define STEM_SLEEP_PARKS 0
     580             : #include "fd_stem_run1.c"
     581             : #undef  STEM_SLEEP_PARKS
     582             : #undef  STEM_RUN1_NAME
     583             : #endif
     584             : 
     585             : #if !STEM_NEVER_PARK
     586             : #define STEM_RUN1_NAME   run1_park
     587             : #define STEM_SLEEP_PARKS 1
     588             : #include "fd_stem_run1.c"
     589             : #undef  STEM_SLEEP_PARKS
     590             : #undef  STEM_RUN1_NAME
     591             : #endif
     592             : 
     593             : #if !STEM_ALWAYS_PARK
     594             : static __attribute__((noinline)) void
     595             : STEM_(run_spin)( ulong                        in_cnt,
     596             :                   fd_frag_meta_t const **      in_mcache,
     597             :                   ulong **                     in_fseq,
     598             :                   ulong                        out_cnt,
     599             :                   fd_frag_meta_t **            out_mcache,
     600             :                   ulong                        cons_cnt,
     601             :                   ulong *                      _cons_out,
     602             :                   ulong **                     _cons_fseq,
     603             :                   volatile ulong **            _cons_slow,
     604             :                   ulong                        burst,
     605             :                   long                         lazy,
     606             :                   fd_rng_t *                   rng,
     607             :                   void *                       scratch,
     608             :                   STEM_CALLBACK_CONTEXT_TYPE * ctx,
     609           0 :                   fd_stem_sleep_t const *      sleep ) {
     610           0 :   STEM_(run1_spin)( in_cnt, in_mcache, in_fseq, out_cnt, out_mcache, cons_cnt, _cons_out, _cons_fseq, _cons_slow, burst, lazy, rng, scratch, ctx, sleep );
     611           0 : }
     612             : #endif
     613             : 
     614             : #if !STEM_NEVER_PARK
     615             : static __attribute__((noinline)) void
     616             : STEM_(run_park)( ulong                        in_cnt,
     617             :                   fd_frag_meta_t const **      in_mcache,
     618             :                   ulong **                     in_fseq,
     619             :                   ulong                        out_cnt,
     620             :                   fd_frag_meta_t **            out_mcache,
     621             :                   ulong                        cons_cnt,
     622             :                   ulong *                      _cons_out,
     623             :                   ulong **                     _cons_fseq,
     624             :                   volatile ulong **            _cons_slow,
     625             :                   ulong                        burst,
     626             :                   long                         lazy,
     627             :                   fd_rng_t *                   rng,
     628             :                   void *                       scratch,
     629             :                   STEM_CALLBACK_CONTEXT_TYPE * ctx,
     630           0 :                   fd_stem_sleep_t const *      sleep ) {
     631           0 :   STEM_(run1_park)( in_cnt, in_mcache, in_fseq, out_cnt, out_mcache, cons_cnt, _cons_out, _cons_fseq, _cons_slow, burst, lazy, rng, scratch, ctx, sleep );
     632           0 : }
     633             : #endif
     634             : 
     635             : static inline void
     636             : STEM_(run1)( ulong                        in_cnt,
     637             :              fd_frag_meta_t const **      in_mcache,
     638             :              ulong **                     in_fseq,
     639             :              ulong                        out_cnt,
     640             :              fd_frag_meta_t **            out_mcache,
     641             :              ulong                        cons_cnt,
     642             :              ulong *                      _cons_out,
     643             :              ulong **                     _cons_fseq,
     644             :              volatile ulong **            _cons_slow,
     645             :              ulong                        burst,
     646             :              long                         lazy,
     647             :              fd_rng_t *                   rng,
     648             :              void *                       scratch,
     649             :              STEM_CALLBACK_CONTEXT_TYPE * ctx,
     650           0 :              fd_stem_sleep_t const *      sleep ) {
     651             : #if STEM_ALWAYS_PARK
     652           0 :   STEM_(run_park)( in_cnt, in_mcache, in_fseq, out_cnt, out_mcache, cons_cnt, _cons_out, _cons_fseq, _cons_slow, burst, lazy, rng, scratch, ctx, sleep );
     653             : #elif STEM_NEVER_PARK
     654           0 :   STEM_(run_spin)( in_cnt, in_mcache, in_fseq, out_cnt, out_mcache, cons_cnt, _cons_out, _cons_fseq, _cons_slow, burst, lazy, rng, scratch, ctx, sleep );
     655             : #else
     656           0 :   if( sleep->shmem ) STEM_(run_park)( in_cnt, in_mcache, in_fseq, out_cnt, out_mcache, cons_cnt, _cons_out, _cons_fseq, _cons_slow, burst, lazy, rng, scratch, ctx, sleep );
     657           0 :   else               STEM_(run_spin)( in_cnt, in_mcache, in_fseq, out_cnt, out_mcache, cons_cnt, _cons_out, _cons_fseq, _cons_slow, burst, lazy, rng, scratch, ctx, sleep );
     658             : #endif
     659           0 : }
     660             : 
     661             : FD_FN_UNUSED static void
     662             : STEM_(run)( fd_topo_t *      topo,
     663           0 :             fd_topo_tile_t * tile ) {
     664           0 :   const fd_frag_meta_t * in_mcache[ FD_TOPO_MAX_LINKS ];
     665           0 :   ulong * in_fseq[ FD_TOPO_MAX_TILE_IN_LINKS ];
     666             : 
     667           0 :   ulong polled_in_cnt = 0UL;
     668           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
     669           0 :     if( FD_UNLIKELY( !tile->in_link_poll[ i ] ) ) continue;
     670             : 
     671           0 :     in_mcache[ polled_in_cnt ] = topo->links[ tile->in_link_id[ i ] ].mcache;
     672           0 :     FD_TEST( in_mcache[ polled_in_cnt ] );
     673           0 :     in_fseq[ polled_in_cnt ]   = tile->in_link_fseq[ i ];
     674           0 :     FD_TEST( in_fseq[ polled_in_cnt ] );
     675           0 :     polled_in_cnt += 1;
     676           0 :   }
     677             : 
     678           0 :   fd_frag_meta_t * out_mcache[ FD_TOPO_MAX_LINKS ];
     679           0 :   for( ulong i=0UL; i<tile->out_cnt; i++ ) {
     680           0 :     out_mcache[ i ] = topo->links[ tile->out_link_id[ i ] ].mcache;
     681           0 :     FD_TEST( out_mcache[ i ] );
     682           0 :   }
     683             : 
     684           0 :   fd_stem_sleep_t sleep[ 1 ] = {{ .shmem = NULL }};
     685           0 :   if( FD_UNLIKELY( topo->sleep_obj_id!=ULONG_MAX ) ) {
     686           0 :     sleep->shmem = fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) );
     687           0 :     FD_TEST( sleep->shmem );
     688           0 :     sleep->tile_id     = tile->id;
     689           0 :     sleep->waker_fseq  = tile->is_waker_client ? fd_fseq_join( fd_topo_obj_laddr( topo, tile->waker_fseq_obj_id ) ) : NULL;
     690           0 :     FD_TEST( !tile->is_waker_client || sleep->waker_fseq );
     691           0 :     sleep->out_link_id = tile->out_link_id;
     692             : 
     693           0 :     ulong polled_idx = 0UL;
     694           0 :     for( ulong i=0UL; i<tile->in_cnt; i++ ) {
     695           0 :       if( FD_LIKELY( !tile->in_link_poll[ i ] ) ) continue;
     696           0 :       sleep->in_link_id [ polled_idx ] = tile->in_link_id[ i ];
     697           0 :       sleep->in_producer[ polled_idx ] = tile->in_link_reliable[ i ] ? fd_topo_find_link_producer( topo, &topo->links[ tile->in_link_id[ i ] ] ) : ULONG_MAX;
     698           0 :       polled_idx++;
     699           0 :     }
     700             : 
     701           0 :     ulong pair_cnt = 0UL;
     702           0 :     for( ulong i=0UL; i<tile->out_cnt; i++ ) {
     703           0 :       sleep->wake_off[ i ] = (ushort)pair_cnt;
     704           0 :       pair_cnt += fd_sleep_wake_table( sleep->wake+pair_cnt, topo, tile->out_link_id[ i ] );
     705           0 :     }
     706           0 :     sleep->wake_off[ tile->out_cnt ] = (ushort)pair_cnt;
     707           0 :   }
     708             : 
     709           0 :   ulong   reliable_cons_cnt = 0UL;
     710           0 :   ulong   cons_out[ FD_TOPO_MAX_LINKS ];
     711           0 :   ulong * cons_fseq[ FD_TOPO_MAX_LINKS ];
     712           0 :   volatile ulong * cons_slow[ FD_TOPO_MAX_LINKS ];
     713           0 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     714           0 :     fd_topo_tile_t * consumer_tile = &topo->tiles[ i ];
     715           0 :     ulong polled_in_idx = 0UL;
     716           0 :     for( ulong j=0UL; j<consumer_tile->in_cnt; j++ ) {
     717           0 :       int is_polled = consumer_tile->in_link_poll[ j ];
     718           0 :       for( ulong k=0UL; k<tile->out_cnt; k++ ) {
     719           0 :         if( FD_UNLIKELY( consumer_tile->in_link_id[ j ]==tile->out_link_id[ k ] && consumer_tile->in_link_reliable[ j ] ) ) {
     720           0 :           cons_out[ reliable_cons_cnt ] = k;
     721           0 :           cons_fseq[ reliable_cons_cnt ] = consumer_tile->in_link_fseq[ j ];
     722           0 :           FD_TEST( cons_fseq[ reliable_cons_cnt ] );
     723           0 :           cons_slow[ reliable_cons_cnt ] = fd_metrics_link_in( consumer_tile->metrics, polled_in_idx ) + FD_METRICS_COUNTER_LINK_BACKPRESSURE_BLAMED_OFF;
     724           0 :           reliable_cons_cnt++;
     725             :           /* Need to test this, since each link may connect to many outs,
     726             :              you could construct a topology which has more than this
     727             :              consumers of links. */
     728           0 :           FD_TEST( reliable_cons_cnt<FD_TOPO_MAX_LINKS );
     729           0 :         }
     730           0 :       }
     731           0 :       if( FD_LIKELY( is_polled ) ) polled_in_idx++;
     732           0 :     }
     733           0 :   }
     734             : 
     735             :   /* The stem rng is only used internally for shuffling event/input
     736             :      polling ordering and for setting the housekeeping timer.  It is
     737             :      never exposed to tile callbacks.  As a result, no cryptographic
     738             :      quality is needed.  fd_tickcount() provides per-run entropy,
     739             :      tile->id guarantees per-tile uniqueness, and fd_ulong_hash (a
     740             :      Murmur3 finalizer) gives near-perfect avalanche so even a 1-bit
     741             :      input difference flips ~50% of output bits.  fd_rng_secure is not
     742             :      used here because STEM_(run) executes after the seccomp sandbox
     743             :      is applied. */
     744           0 :   fd_rng_t rng[1];
     745           0 :   FD_TEST( fd_rng_join( fd_rng_new( rng, (uint)fd_ulong_hash( (ulong)fd_tickcount() + tile->id ), 0UL ) ) );
     746             : 
     747           0 :   STEM_CALLBACK_CONTEXT_TYPE * ctx = (STEM_CALLBACK_CONTEXT_TYPE*)fd_ulong_align_up( (ulong)fd_topo_obj_laddr( topo, tile->tile_obj_id ), STEM_CALLBACK_CONTEXT_ALIGN );
     748             : 
     749           0 :   uchar __attribute__((aligned(FD_STEM_SCRATCH_ALIGN))) stem_scratch[ STEM_(scratch_footprint)( polled_in_cnt, tile->out_cnt, reliable_cons_cnt ) ];
     750             : 
     751           0 :   STEM_(run1)( polled_in_cnt,
     752           0 :                in_mcache,
     753           0 :                in_fseq,
     754           0 :                tile->out_cnt,
     755           0 :                out_mcache,
     756           0 :                reliable_cons_cnt,
     757           0 :                cons_out,
     758           0 :                cons_fseq,
     759           0 :                cons_slow,
     760           0 :                STEM_BURST,
     761           0 :                STEM_LAZY,
     762           0 :                rng,
     763           0 :                stem_scratch,
     764           0 :                ctx,
     765           0 :                sleep );
     766             : 
     767             : #ifdef STEM_CALLBACK_METRICS_WRITE
     768             :   /* Write final metrics state before shutting down */
     769           0 :   FD_COMPILER_MFENCE();
     770           0 :   STEM_CALLBACK_METRICS_WRITE( ctx );
     771           0 :   FD_COMPILER_MFENCE();
     772             : #endif
     773             : 
     774           0 :   if( FD_LIKELY( tile->allow_shutdown ) ) {
     775           0 :     for( ulong i=0UL; i<tile->in_cnt; i++ ) {
     776           0 :       if( FD_UNLIKELY( !tile->in_link_poll[ i ] || !tile->in_link_reliable[ i ] ) ) continue;
     777             : 
     778             :       /* Return infinite credits on any reliable consumer links so that
     779             :          producers now no longer expect us to consume. */
     780           0 :       ulong fseq_id = tile->in_link_fseq_obj_id[ i ];
     781           0 :       ulong * fseq = fd_fseq_join( fd_topo_obj_laddr( topo, fseq_id ) );
     782           0 :       FD_TEST( fseq );
     783           0 :       __atomic_store_n( fseq, STEM_SHUTDOWN_SEQ, __ATOMIC_RELEASE );
     784           0 :     }
     785             : 
     786           0 :     if( FD_UNLIKELY( sleep->shmem ) ) {
     787             :       /* Leave the sleep object as if we never parked, to prevent
     788             :          producers repeatedly trying to wake up. */
     789           0 :       FD_VOLATILE( sleep->shmem->tile[ sleep->tile_id ].word ) = 1UL;
     790           0 :       __atomic_fetch_and( &sleep->shmem->doorbell   [ sleep->tile_id>>6 ], ~(1UL<<(sleep->tile_id&63UL)), __ATOMIC_SEQ_CST );
     791           0 :       __atomic_fetch_and( &sleep->shmem->parked_bits[ sleep->tile_id>>6 ], ~(1UL<<(sleep->tile_id&63UL)), __ATOMIC_SEQ_CST );
     792           0 :     }
     793           0 :   }
     794           0 : }
     795             : 
     796             : #undef STEM_NAME
     797             : #undef STEM_
     798             : #undef STEM_BURST
     799             : #undef STEM_ALWAYS_PARK
     800             : #undef STEM_NEVER_PARK
     801             : #undef STEM_CALLBACK_CONTEXT_TYPE
     802             : #undef STEM_CALLBACK_CONTEXT_ALIGN
     803             : #undef STEM_LAZY
     804             : #undef STEM_STICKY_POLL_MAX
     805             : #undef STEM_CALLBACK_SHOULD_SHUTDOWN
     806             : #undef STEM_CALLBACK_DURING_HOUSEKEEPING
     807             : #undef STEM_CALLBACK_METRICS_WRITE
     808             : #undef STEM_CALLBACK_NEXT_DEADLINE
     809             : #undef STEM_CALLBACK_PREVENT_PARK
     810             : #undef STEM_CALLBACK_PARK_WAIT
     811             : #undef STEM_CALLBACK_RECV_CREDIT
     812             : #undef STEM_CALLBACK_CHECK_CREDIT
     813             : #undef STEM_CALLBACK_BEFORE_CREDIT
     814             : #undef STEM_CALLBACK_AFTER_CREDIT
     815             : #undef STEM_CALLBACK_BEFORE_FRAG
     816             : #undef STEM_CALLBACK_DURING_FRAG
     817             : #undef STEM_CALLBACK_RETURNABLE_FRAG
     818             : #undef STEM_CALLBACK_AFTER_FRAG
     819             : #undef STEM_CALLBACK_AFTER_POLL_OVERRUN

Generated by: LCOV version 1.14