LCOV - code coverage report
Current view: top level - discof/backup - fd_snaprd_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 61 198 30.8 %
Date: 2026-11-12 04:25:03 Functions: 2 24 8.3 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : /* fd_snaprd_tile.c is a worker thread for reading accdb disk data.
       3             :    This tile is typically either sleeping or doing iowait. */
       4             : 
       5             : #include "fd_backup.h"
       6             : #include "../../disco/metrics/fd_metrics.h"
       7             : #include "../../disco/topo/fd_topo.h"
       8             : #include "../../flamenco/accdb/fd_accdb.h"
       9             : #include "../../flamenco/accdb/fd_accdb_shmem.h"
      10             : #include "../../tango/fseq/fd_fseq.h"
      11             : #include <errno.h>
      12             : #include <fcntl.h>
      13             : #include <unistd.h>
      14             : #include <stdatomic.h>
      15             : #include <time.h>
      16             : #include <linux/futex.h>
      17             : #include "generated/fd_snaprd_tile_seccomp.h"
      18             : 
      19           0 : #define SNAPRD_STATE_IDLE    0
      20         126 : #define SNAPRD_STATE_READ    1
      21           6 : #define SNAPRD_STATE_DONE    2
      22           0 : #define SNAPRD_STATE_CAPTURE 3 /* sampling accdb partition bounds */
      23           0 : #define SNAPRD_STATE_DRAIN   4 /* waiting for in-flight accdb writes to land */
      24             : 
      25          30 : #define STEM_BURST 1UL
      26             : #define SNAPRD_PART_MAX (1UL<<13)
      27             : 
      28             : FD_STATIC_ASSERT( (FD_BACKUP_RD_MTU % 4096UL)==0UL, rd_mtu_direct_io );
      29             : 
      30             : struct fd_snaprd {
      31             :   uint state;
      32             : 
      33             :   /* control signal from snapmk, which wakes up the tile */
      34             :   atomic_ulong * in_ctl;
      35             :   ulong          in_ctl_seq;
      36             : 
      37             :   ulong idle_cnt;
      38             : 
      39             :   fd_accdb_shmem_t const * accdb;
      40             : 
      41             :   /* these are valid while reading a snapshot of accdb partition bounds */
      42             :   struct {
      43             :     ulong file_off;
      44             :     ulong sz;
      45             :   } part[ SNAPRD_PART_MAX ];
      46             :   ulong part_cnt;
      47             :   ulong part_idx;
      48             :   ulong part_cur;      /* cursor in [0,part_sz] */
      49             :   ulong part_sz;       /* byte size of partition */
      50             :   ulong part_file_off; /* accdb file offset of partition */
      51             : 
      52             :   fd_accdb_shmem_writer_barrier_t barrier[ 1 ]; /* writers whose pwritev2 has yet to land */
      53             : 
      54             :   struct {
      55             :     void * mem;
      56             :     ulong  chunk0;
      57             :     ulong  wmark;
      58             :     ulong  chunk;
      59             :     ulong  mtu;
      60             :   } out;
      61             : 
      62             :   struct {
      63             :     ulong bytes_read;
      64             :     ulong export_progress_bytes;
      65             :     ulong export_total_bytes;
      66             :     ulong io_blocked_ticks;
      67             :   } metrics;
      68             : 
      69             :   int accdb_direct_fd;
      70             : };
      71             : 
      72             : typedef struct fd_snaprd fd_snaprd_t;
      73             : 
      74             : static ulong
      75             : populate_allowed_fds( fd_topo_t const *      topo,
      76             :                       fd_topo_tile_t const * tile,
      77             :                       ulong                  out_fds_cnt,
      78           0 :                       int *                  out_fds ) {
      79           0 :   fd_snaprd_t const * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
      80           0 :   if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
      81           0 :   ulong out_cnt = 0UL;
      82           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
      83           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
      84           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
      85           0 :   out_fds[ out_cnt++ ] = FD_ACCDB_FD_RO; /* accounts db readonly fd */
      86           0 :   out_fds[ out_cnt++ ] = ctx->accdb_direct_fd; /* accounts db, O_DIRECT */
      87           0 :   return out_cnt;
      88           0 : }
      89             : 
      90             : static ulong
      91             : populate_allowed_seccomp( fd_topo_t const *      topo,
      92             :                           fd_topo_tile_t const * tile,
      93             :                           ulong                  out_cnt,
      94           0 :                           struct sock_filter *   out ) {
      95           0 :   fd_snaprd_t const * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
      96           0 :   populate_sock_filter_policy_fd_snaprd_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->accdb_direct_fd );
      97           0 :   return sock_filter_policy_fd_snaprd_tile_instr_cnt;
      98           0 : }
      99             : 
     100             : static void
     101             : privileged_init( fd_topo_t const *      topo,
     102           0 :                  fd_topo_tile_t const * tile ) {
     103           0 :   fd_snaprd_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     104             : 
     105           0 :   char path[ 64 ];
     106           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/proc/self/fd/%d", FD_ACCDB_FD_RO ) );
     107           0 :   ctx->accdb_direct_fd = open( path, O_RDONLY|O_DIRECT|O_NOATIME|O_CLOEXEC );
     108           0 :   if( FD_UNLIKELY( ctx->accdb_direct_fd<0 ) ) FD_LOG_ERR(( "open(accounts.db, O_DIRECT) failed (%i-%s); the filesystem holding [paths.accounts] must support direct IO", errno, fd_io_strerror( errno ) ));
     109           0 : }
     110             : 
     111             : FD_FN_CONST static inline ulong
     112           0 : scratch_align( void ) {
     113           0 :   return alignof(fd_snaprd_t);
     114           0 : }
     115             : 
     116             : FD_FN_PURE static inline ulong
     117           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     118           0 :   (void)tile;
     119           0 :   return sizeof(fd_snaprd_t);
     120           0 : }
     121             : 
     122             : static void
     123             : unprivileged_init( fd_topo_t const *      topo,
     124           0 :                    fd_topo_tile_t const * tile ) {
     125           0 :   fd_snaprd_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     126             : 
     127           0 :   ctx->state      = SNAPRD_STATE_IDLE;
     128           0 :   ctx->in_ctl     = NULL;
     129           0 :   ctx->in_ctl_seq = 0UL;
     130           0 :   ctx->idle_cnt   = 0UL;
     131           0 :   memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
     132             : 
     133             :   /* snaprd queries accdb partition info to figure out where to read */
     134           0 :   void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->snaprd.accdb_obj_id );
     135           0 :   ctx->accdb = fd_accdb_shmem_join( _accdb_shmem );
     136           0 :   FD_TEST( ctx->accdb );
     137             : 
     138           0 :   FD_CHECK_ERR( tile->out_cnt==1UL, "topology mismatch" );
     139           0 :   fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ 0 ] ];
     140           0 :   FD_CHECK_ERR( !strcmp( out_link->name, "snaprd_out" ), "topology mismatch" );
     141           0 :   FD_CHECK_ERR( out_link->mtu && out_link->mtu<=UINT_MAX, "topology mismatch" );
     142             : 
     143             : 
     144           0 :   ulong snapmk_tile_id = fd_topo_find_tile( topo, "snapmk", 0UL );
     145           0 :   FD_CHECK_ERR( snapmk_tile_id!=ULONG_MAX, "missing snapmk tile" );
     146           0 :   fd_topo_tile_t const * snapmk_tile = &topo->tiles[ snapmk_tile_id ];
     147             : 
     148           0 :   ulong snapmk_in_idx = fd_topo_find_tile_in_link( topo, snapmk_tile, "snaprd_out", out_link->kind_id );
     149           0 :   FD_CHECK_ERR( snapmk_in_idx!=ULONG_MAX, "missing snapmk input link for snaprd_out" );
     150           0 :   FD_CHECK_ERR( snapmk_tile->in_link_reliable[ snapmk_in_idx ], "snaprd_out consumer is not reliable" );
     151             : 
     152           0 :   ulong * fseq = fd_fseq_join( fd_topo_obj_laddr( topo, snapmk_tile->in_link_fseq_obj_id[ snapmk_in_idx ] ) );
     153           0 :   FD_TEST( fseq );
     154           0 :   ctx->in_ctl = fd_fseq_app_laddr( fseq );
     155           0 :   FD_STATIC_ASSERT( sizeof(ulong)<=FD_FSEQ_APP_FOOTPRINT, fseq_app_space );
     156           0 :   ctx->in_ctl_seq = atomic_load_explicit( ctx->in_ctl, memory_order_acquire );
     157             : 
     158           0 :   ctx->out.mem    = topo->workspaces[ topo->objs[ out_link->dcache_obj_id ].wksp_id ].wksp;
     159           0 :   ctx->out.chunk0 = fd_dcache_compact_chunk0( ctx->out.mem, out_link->dcache );
     160           0 :   ctx->out.wmark  = fd_dcache_compact_wmark( ctx->out.mem, out_link->dcache, out_link->mtu );
     161           0 :   ctx->out.chunk  = ctx->out.chunk0;
     162           0 :   ctx->out.mtu    = out_link->mtu;
     163           0 : }
     164             : 
     165             : static int
     166          18 : next_partition( fd_snaprd_t * ctx ) {
     167          24 :   while( ctx->part_idx<ctx->part_cnt ) {
     168          15 :     ulong part_idx = ctx->part_idx++;
     169          15 :     if( FD_UNLIKELY( !ctx->part[ part_idx ].sz ) ) continue;
     170           9 :     ctx->part_cur      = 0UL;
     171           9 :     ctx->part_sz       = ctx->part[ part_idx ].sz;
     172           9 :     ctx->part_file_off = ctx->part[ part_idx ].file_off;
     173           9 :     return 1;
     174          15 :   }
     175             : 
     176           9 :   ctx->part_cur      = 0UL;
     177           9 :   ctx->part_sz       = 0UL;
     178           9 :   ctx->part_file_off = 0UL;
     179           9 :   return 0;
     180          18 : }
     181             : 
     182             : /* backup_disk_capture determines which accdb partition ranges must be
     183             :    read to produce a snapshot. */
     184             : 
     185             : static int
     186           0 : backup_disk_capture( fd_snaprd_t * ctx ) {
     187           0 :   FD_CHECK_CRIT( fd_accdb_snapshot_sync_state( fd_accdb_shmem_snapshot_sync( ctx->accdb ) )==FD_ACCDB_SNAPSHOT_SYNC_RUNNING,
     188           0 :                  "backup capture outside snapshot_sync RUNNING" );
     189             : 
     190             :   /* Accounts evicted during the cache pass may still be on their way
     191             :      to disk, so wait for those writers before sampling the bounds. */
     192           0 :   if( FD_UNLIKELY( fd_accdb_shmem_writer_barrier_poll( ctx->accdb, ctx->barrier ) ) ) return 0;
     193             : 
     194           0 :   ulong part_max = fd_accdb_shmem_partition_max( ctx->accdb );
     195           0 :   if( FD_UNLIKELY( part_max>SNAPRD_PART_MAX ) ) {
     196           0 :     FD_LOG_ERR(( "accdb partition count %lu exceeds snaprd capacity %lu", part_max, SNAPRD_PART_MAX ));
     197           0 :   }
     198             : 
     199           0 :   ulong part_sz = fd_accdb_shmem_partition_sz( ctx->accdb );
     200             : 
     201           0 :   ctx->part_cnt = 0UL;
     202           0 :   ulong export_total_bytes = 0UL;
     203           0 :   for( ulong i=0UL; i<part_max; i++ ) {
     204             :     /* the accdb partitions might grow after we save offsets into
     205             :        ctx->part, but we can safely ignore any future data (newly added
     206             :        rooted accounts will have been saved from cache, and non-rooted
     207             :        accounts are ignored regardless) */
     208             : 
     209           0 :     fd_accdb_shmem_partition_info_t info[ 1 ];
     210           0 :     fd_accdb_shmem_partition_info( ctx->accdb, i, info );
     211             : 
     212           0 :     ulong sz = info->write_offset_raw;
     213           0 :     if( FD_UNLIKELY( info->is_write_head && sz>part_sz ) ) return 0; /* mid-rotation, retry */
     214           0 :     FD_CHECK_CRIT( sz<=part_sz, "accdb partition write_offset exceeds partition size" );
     215             : 
     216           0 :     if( !sz ) continue;
     217           0 :     if( FD_UNLIKELY( info->compaction_offset>=sz ) ) continue; /* already compacted, partition effectively dead */
     218           0 :     ctx->part[ ctx->part_cnt ].file_off = i*part_sz;
     219           0 :     ctx->part[ ctx->part_cnt ].sz       = sz;
     220           0 :     ctx->part_cnt++;
     221           0 :     export_total_bytes += sz;
     222           0 :   }
     223           0 :   ctx->metrics.export_progress_bytes = 0UL;
     224           0 :   ctx->metrics.export_total_bytes    = export_total_bytes;
     225             : 
     226             :   /* need to wait for these writers to complete */
     227           0 :   fd_accdb_shmem_writer_barrier_capture( ctx->accdb, ctx->barrier );
     228           0 :   return 1;
     229           0 : }
     230             : 
     231             : /* backup_disk_drain waits out the writes that were in flight when
     232             :    backup_disk_capture sampled the partition bounds. */
     233             : 
     234             : static void
     235           0 : backup_disk_drain( fd_snaprd_t * ctx ) {
     236           0 :   if( FD_UNLIKELY( fd_accdb_shmem_writer_barrier_poll( ctx->accdb, ctx->barrier ) ) ) return;
     237             : 
     238             :   /* An accdb with no data on disk yields an empty stream, which snapmk
     239             :      still has to see terminated (a zero size frag carrying eom). */
     240           0 :   ctx->part_idx = 0UL;
     241           0 :   next_partition( ctx );
     242           0 :   ctx->state = SNAPRD_STATE_READ;
     243           0 : }
     244             : 
     245             : static void
     246             : before_credit( fd_snaprd_t *       ctx,
     247             :                fd_stem_context_t * stem,
     248           0 :                int *               charge_busy ) {
     249           0 :   (void)charge_busy;
     250             : 
     251           0 :   ulong ctl_cur  = atomic_load_explicit( ctx->in_ctl, memory_order_acquire );
     252           0 :   ulong ctl_prev = ctx->in_ctl_seq;
     253           0 :   if( FD_LIKELY( ctl_prev==ctl_cur ) ) {
     254           0 :     if( FD_UNLIKELY( ctx->idle_cnt++ > 16384UL && !stem->sleep ) ) fd_log_sleep( (long)1e6 );
     255           0 :     return;
     256           0 :   }
     257             : 
     258             :   /* new backup job */
     259           0 :   ctx->in_ctl_seq = ctl_cur;
     260           0 :   ctx->state      = SNAPRD_STATE_CAPTURE;
     261           0 :   ctx->idle_cnt   = 0UL;
     262           0 :   *charge_busy = 1;
     263             : 
     264           0 :   fd_accdb_shmem_writer_barrier_capture( ctx->accdb, ctx->barrier );
     265           0 : }
     266             : 
     267             : static void
     268             : after_credit( fd_snaprd_t *       ctx,
     269             :               fd_stem_context_t * stem,
     270             :               int *               opt_poll_in,
     271          30 :               int *               charge_busy ) {
     272          30 :   if( FD_UNLIKELY( ctx->state==SNAPRD_STATE_CAPTURE ) ) {
     273           0 :     *charge_busy = 1;
     274           0 :     if( FD_UNLIKELY( !backup_disk_capture( ctx ) ) ) return;
     275           0 :     ctx->state = SNAPRD_STATE_DRAIN;
     276           0 :     return;
     277           0 :   }
     278             : 
     279          30 :   if( FD_UNLIKELY( ctx->state==SNAPRD_STATE_DRAIN ) ) {
     280           0 :     *charge_busy = 1;
     281           0 :     backup_disk_drain( ctx );
     282           0 :     return;
     283           0 :   }
     284             : 
     285          30 :   if( FD_UNLIKELY( ctx->state!=SNAPRD_STATE_READ ) ) return;
     286             : 
     287          30 :   FD_CHECK_CRIT( *stem->cr_avail <= UINT_MAX, "cr_avail underflow" );
     288          30 :   FD_CHECK_CRIT( ctx->part_cur <= ctx->part_sz, "partition cursor overflow" );
     289          30 :   FD_CHECK_CRIT( fd_accdb_snapshot_sync_state( fd_accdb_shmem_snapshot_sync( ctx->accdb ) )==FD_ACCDB_SNAPSHOT_SYNC_RUNNING,
     290          30 :                  "accdb resumed compaction while backup read in progress" );
     291             : 
     292          30 :   ulong burst_rem = STEM_BURST;
     293          60 :   while( ctx->state==SNAPRD_STATE_READ && stem->cr_avail[ 0 ] && burst_rem-- ) {
     294          30 :     ulong frag_sz = fd_ulong_min( ctx->out.mtu, ctx->part_sz-ctx->part_cur );
     295          30 :     ulong read_len = fd_ulong_align_up( frag_sz, 4096UL );
     296             : 
     297          30 :     ulong   chunk   = ctx->out.chunk;
     298          30 :     uchar * out     = fd_chunk_to_laddr( ctx->out.mem, chunk );
     299          30 :     ulong   src_off = ctx->part_file_off + ctx->part_cur;
     300             : 
     301          30 :     ulong read_sz = 0UL;
     302          30 :     long  t0      = fd_tickcount();
     303         120 :     while( read_sz<read_len ) {
     304          90 :       long res = pread( ctx->accdb_direct_fd, out+read_sz, read_len-read_sz, (long)(src_off+read_sz) );
     305          90 :       if( FD_UNLIKELY( res<0L && (errno==EINTR || errno==EAGAIN || errno==EWOULDBLOCK) ) ) continue;
     306          90 :       if( FD_UNLIKELY( res<0L ) ) {
     307           0 :         FD_LOG_ERR(( "pread failed: %i-%s", errno, fd_io_strerror( errno ) ));
     308           0 :       }
     309          90 :       if( FD_UNLIKELY( !res ) ) {
     310           0 :         FD_LOG_ERR(( "pread returned EOF at offset %lu", src_off+read_sz ));
     311           0 :       }
     312          90 :       read_sz += (ulong)res;
     313          90 :     }
     314          30 :     long t1 = fd_tickcount();
     315          30 :     ctx->metrics.bytes_read            += read_sz;
     316          30 :     ctx->metrics.export_progress_bytes += frag_sz;
     317          30 :     if( FD_LIKELY( frag_sz ) ) ctx->metrics.io_blocked_ticks += (ulong)( t1-t0 );
     318             : 
     319          30 :     ctx->part_cur += frag_sz;
     320             : 
     321          30 :     int eom = 0;
     322          30 :     if( FD_UNLIKELY( ctx->part_cur==ctx->part_sz && !next_partition( ctx ) ) ) {
     323           6 :       ctx->state = SNAPRD_STATE_DONE;
     324           6 :       eom = 1;
     325           6 :     }
     326             : 
     327          30 :     ulong sz    = fd_ulong_min( frag_sz, (ulong)USHORT_MAX );
     328          30 :     ulong ctl   = fd_frag_meta_ctl( FD_BACKUP_ORIG_DISK_FRAG, 0, eom, 0 );
     329          30 :     ulong tspub = frag_sz;
     330          30 :     fd_stem_publish( stem, 0UL, src_off, chunk, sz, ctl, 0UL, tspub );
     331          30 :     ctx->out.chunk = fd_dcache_compact_next( chunk, read_len, ctx->out.chunk0, ctx->out.wmark );
     332             : 
     333          30 :     ctx->idle_cnt = 0UL;
     334          30 :     *charge_busy = 1;
     335          30 :     *opt_poll_in = 0;
     336          30 :   }
     337          30 : }
     338             : 
     339             : static void
     340           0 : metrics_write( fd_snaprd_t * ctx ) {
     341           0 :   FD_MCNT_SET  ( SNAPRD, DISK_READ_BYTES,             ctx->metrics.bytes_read             );
     342           0 :   FD_MGAUGE_SET( SNAPRD, EXPORT_PROGRESS_BYTES,       ctx->metrics.export_progress_bytes );
     343           0 :   FD_MGAUGE_SET( SNAPRD, EXPORT_EXPECTED_BYTES,       ctx->metrics.export_total_bytes    );
     344           0 :   FD_MCNT_SET  ( SNAPRD, IO_BLOCKED_DURATION_NANOS,   ctx->metrics.io_blocked_ticks      );
     345           0 : }
     346             : 
     347           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_snaprd_t
     348           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_snaprd_t)
     349           0 : #define STEM_CALLBACK_BEFORE_CREDIT before_credit
     350           0 : #define STEM_CALLBACK_AFTER_CREDIT  after_credit
     351           0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
     352             : #include "../../disco/stem/fd_stem.c"
     353             : 
     354             : fd_topo_run_tile_t fd_tile_snaprd = {
     355             :   .name                     = "snaprd",
     356             :   .populate_allowed_fds     = populate_allowed_fds,
     357             :   .populate_allowed_seccomp = populate_allowed_seccomp,
     358             :   .scratch_align            = scratch_align,
     359             :   .scratch_footprint        = scratch_footprint,
     360             :   .privileged_init          = privileged_init,
     361             :   .unprivileged_init        = unprivileged_init,
     362             :   .run                      = stem_run,
     363             : };

Generated by: LCOV version 1.14