Line data Source code
1 : /* The snapmk tile coordinates snapshot production.
2 :
3 : snapmk discovers accounts (zero copy) and generates compression jobs
4 : for downstream snapzp tiles (load balanced).
5 :
6 : snapmk uses snaprd as a worker thread. The snapmk's snaprd_out fseq
7 : has an application region, which indicates when snaprd should spring
8 : into action. Once activated, it reads all accdb partitions into a
9 : ring buffer (mcache/dcache). snaprd is a separate worker thread
10 : because otherwise, snapmk would be blocked on synchronous I/O and
11 : waste time copying data.
12 :
13 : Finally, snapmk interfaces with the replay tile. The replay tile
14 : instructs when to produce a snapshot, snapmk takes ownership of the
15 : pipeline / state machine, and notifies the replay tile when snap
16 : production is done.
17 :
18 : ### File management
19 :
20 : All snap producer tile processes (or threads) in a Firedancer
21 : instance share a fixed pool of file descriptors for snapshot file
22 : descriptors.
23 :
24 : These FDs are visible under proper snapshot file names
25 : (e.g. "snapshot-433132190-8cSwtWwmkj3oaXPsyUxhL1QkuaAVY1S4vRhqL6EJQ99Z.tar.zst")
26 : or placeholders (e.g. ".snapshot-x67.partial"). Old snapshots
27 : eventually get recycled.
28 :
29 : The snapmk tile manages these file descriptors (decides which snaps
30 : to select for new creations, which ones to recycle, etc).
31 :
32 : ### Zero copy snaprd->mk->zp path
33 :
34 : The snaprd tile reads accdb data into memory at high bandwidth.
35 : The snapmk tile does streaming parsing of this input data, and then
36 : distributes compression jobs to downstream snapzp tiles. Those
37 : compression jobs are mere pointers to the snaprd data. Thus, snaprd
38 : must not only backpressure on slow snapmk but slow snapzps too.
39 :
40 : The snapmk tile achieves this by tracking the snapzp job -> snaprd
41 : frag seq in rd_shadow. */
42 :
43 : #define _GNU_SOURCE
44 : #define ZSTD_STATIC_LINKING_ONLY
45 : #include <zstd.h>
46 : #include <errno.h>
47 : #include <fcntl.h>
48 : #include <stdio.h>
49 : #include <linux/futex.h>
50 : #include <sys/syscall.h>
51 : #include <sys/types.h>
52 : #include <sys/stat.h>
53 : #include <unistd.h>
54 : #include <stdatomic.h>
55 :
56 : #include "fd_snapmk_tile.h"
57 :
58 : #include "fd_backup.h"
59 : #include "fd_snap_pool.h"
60 : #include "fd_backup_cache.h"
61 : #include "fd_backup_disk.h"
62 : #include "fd_backup_shmem.h"
63 : #include "fd_ssmanifest_writer.h"
64 : #include "fd_txncache_writer.h"
65 : #include "../fd_startup.h"
66 : #include "../replay/fd_replay_tile.h"
67 : #include "../restore/utils/fd_ssarchive.h"
68 : #include "../../disco/metrics/fd_metrics.h"
69 : #include "../../disco/stem/fd_stem.h"
70 : #include "../../disco/topo/fd_topo.h"
71 : #include "../../disco/events/generated/fd_event_gen.h"
72 : #include "../../flamenco/runtime/fd_system_ids.h"
73 : #include "../../tango/fseq/fd_fseq.h"
74 :
75 : #include <time.h> /* CLOCK_REALTIME */
76 : #include "generated/fd_snapmk_tile_seccomp.h"
77 :
78 : /* Raw state values exported by the metrics (states= enums) */
79 : FD_STATIC_ASSERT( SNAPMK_STATE_IDLE==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_IDLE_IDX, metrics_state );
80 : FD_STATIC_ASSERT( SNAPMK_STATE_START==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_START_IDX, metrics_state );
81 : FD_STATIC_ASSERT( SNAPMK_STATE_TAR_HEADERS==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_TAR_HEADERS_IDX, metrics_state );
82 : FD_STATIC_ASSERT( SNAPMK_STATE_MANIFEST==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_MANIFEST_IDX, metrics_state );
83 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_CACHE==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_CACHE_IDX, metrics_state );
84 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_CACHE_FLUSH==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_CACHE_FLUSH_IDX, metrics_state );
85 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_CACHE_FINISH==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_CACHE_FINISH_IDX, metrics_state );
86 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_DISK==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_DISK_IDX, metrics_state );
87 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_DISK_FLUSH==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_DISK_FLUSH_IDX, metrics_state );
88 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_DISK_FINISH==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_DISK_FINISH_IDX, metrics_state );
89 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_DELTA==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_DELTA_IDX, metrics_state );
90 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_DELTA_FLUSH==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_DELTA_FLUSH_IDX, metrics_state );
91 : FD_STATIC_ASSERT( SNAPMK_STATE_ACCDB_DELTA_FINISH==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_ACCDB_DELTA_FINISH_IDX, metrics_state );
92 : FD_STATIC_ASSERT( SNAPMK_STATE_STATUS_CACHE==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_STATUS_CACHE_IDX, metrics_state );
93 : FD_STATIC_ASSERT( SNAPMK_STATE_EOF_MARKER==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_EOF_MARKER_IDX, metrics_state );
94 : FD_STATIC_ASSERT( SNAPMK_STATE_DONE==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_DONE_IDX, metrics_state );
95 : FD_STATIC_ASSERT( SNAPMK_STATE_FAIL==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_FAIL_IDX, metrics_state );
96 : FD_STATIC_ASSERT( SNAPMK_STATE_SLEEP==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_SLEEP_IDX, metrics_state );
97 : FD_STATIC_ASSERT( SNAPMK_STATE_STARTUP==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_STARTUP_IDX, metrics_state );
98 : FD_STATIC_ASSERT( SNAPMK_STATE_STARTUP_BURST==FD_METRICS_ENUM_SNAPMK_JOB_STATE_V_STARTUP_BURST_IDX, metrics_state );
99 :
100 0 : #define RAW_BUF_SZ (32UL<<20)
101 0 : #define COMP_BUF_SZ ZSTD_COMPRESSBOUND( RAW_BUF_SZ )
102 :
103 : /* FD_SNAPMK_ZP_DEPTH must match the snapmk_zp link depth in topology.c.
104 : Asserted at init. Sizes the per-link snaprd-seq shadow rings. */
105 0 : #define FD_SNAPMK_ZP_DEPTH 1024
106 :
107 : /* Max number of reliable snapmk_out consumers */
108 : #define SNAPMK_OUT_CONS_MAX 64
109 :
110 : /* SNAPMK_STEM_BURST: number of snapmk_out frags published in one event
111 : loop cycle (snapmk_zp links are exempt) */
112 0 : #define SNAPMK_STEM_BURST 3UL
113 0 : #define SNAPMK_STEM_LAZY 8700UL
114 :
115 : /* power saving (sleeping) */
116 : #define IDLE_THRES (16384UL) /* no of idle busy loop iters before sleeping */
117 0 : #define IDLE_SLEEP ((long)1e6) /* sleep duration (nanoseconds) */
118 :
119 : struct fd_snapmk {
120 : uint state;
121 :
122 : fd_backup_cache_t acc_cache[1];
123 : visited_set_t * visited_set;
124 :
125 : /* snapshot files */
126 :
127 : int snap_fd; /* current snapshot (-1 if idle) */
128 : int snap_dir_fd; /* dirfd to prevent hijacking */
129 : char snap_dir [ PATH_MAX ];
130 : char final_name[ FD_SNAP_NAME_MAX ];
131 : uint snap_idx; /* in pool */
132 : uint snap_max;
133 : uint snap_full_max; /* <=snap_max */
134 : fd_backup_inode_t pool [ FD_SNAP_MAX ];
135 : ulong pool_sz[ FD_SNAP_MAX ];
136 : ulong final_sz;
137 :
138 : /* snapzp worker threads */
139 :
140 : ulong zp_cnt; /* [0,zp_cnt] out links are to zp */
141 : ulong const * zp_cons_fseq[ SNAPZP_TILE_MAX ];
142 : atomic_ulong * file_off_p;
143 : atomic_ulong * appendvec_slot_ticket_p;
144 :
145 : /* snaprd worker thread */
146 :
147 : atomic_ulong * rd_fseq;
148 : ulong rd_tile_id; /* snaprd, rung when a credit return finds it parked on backpressure */
149 : atomic_ulong * rd_ctl;
150 : ulong rd_seq; /* seq of the snaprd frag last parsed */
151 : ulong rd_seq_cache; /* last watermark published to snaprd */
152 : fd_wksp_t * rd_in_mem;
153 : ulong rd_in_mtu;
154 : ulong rd_fseq_dummy;
155 :
156 : /* per zp out link shadow ring: rd_shadow[i][seq%depth] = snaprd seq
157 : referenced by the mk_zp frag published at seq on link i */
158 : ulong * rd_shadow[ SNAPZP_TILE_MAX ];
159 :
160 : struct {
161 : ulong out_idx;
162 : void * mem;
163 : ulong chunk;
164 : ulong chunk0;
165 : ulong wmark;
166 :
167 : ulong const * cons_fseq[ SNAPMK_OUT_CONS_MAX ];
168 : ulong cons_cnt;
169 : ulong * seq_prod;
170 : } out;
171 :
172 : ulong zp_rr_idx; /* round-robin cursor over zp out links */
173 : ulong zp_ready; /* bit set */
174 : ulong zp_flush_pending; /* bit set */
175 : ulong zp_barrier[ SNAPZP_TILE_MAX ];
176 :
177 : fd_banks_t * banks;
178 : fd_bank_t * bank;
179 : fd_pubkey_t leader; /* slot leader of bank */
180 : fd_txncache_t * txncache;
181 : fd_ssmanifest_writer_t manifest_writer[1];
182 : fd_txncache_writer_t txncache_writer[1];
183 :
184 : ulong manifest_pad;
185 : ulong manifest_sz;
186 :
187 : ulong status_cache_sz; /* bytes serialized so far */
188 : ulong status_cache_expected_sz; /* size the writer counted up front, written into the TAR header */
189 : void * status_cache_writer_arena;
190 : ulong status_cache_writer_arena_sz;
191 :
192 : int zstd_frame_open; /* compressor has started a frame that an e_end has not yet closed */
193 : ulong zstd_data_frame_cnt;
194 : ulong zstd_padding_sz;
195 : ulong uncompressed_sz;
196 : long start_time;
197 : long accounts_start_time;
198 : long end_time;
199 : uchar snap_hash[ 32 ];
200 : ulong snapshot_compress_ticks0;
201 : ulong snapshot_io_blocked_ticks0;
202 : ulong last_snapshot_create_slot;
203 :
204 : int incremental;
205 : ulong base_slot;
206 :
207 : struct {
208 : fd_accdb_delta_t const * pool;
209 : uint const * chain;
210 : ulong chain_cnt;
211 : ulong chain_idx;
212 : uint ele_idx;
213 : ulong ele_cnt;
214 : ulong ele_total;
215 : } delta;
216 :
217 : /* replay in link */
218 :
219 : fd_wksp_t * replay_in_mem;
220 : ulong const * replay_in_seq_prod; /* replay_snapmk producer seq */
221 : ulong replay_in_seq_cons; /* next expected replay_snapmk seq */
222 : ulong idle_iter; /* busy loop iters spent in IDLE */
223 :
224 : /* IPC */
225 : struct {
226 : void * mem;
227 : ulong chunk0;
228 : ulong wmark;
229 : ulong chunk;
230 : } zp_out[ FD_TOPO_MAX_TILE_OUT_LINKS ];
231 : fd_backup_cache_msg_t scan_batch[1];
232 : ushort in_kind[ FD_TOPO_MAX_TILE_IN_LINKS ];
233 :
234 : fd_backup_overrun_t * overrun;
235 : fd_backup_stats_t * stats;
236 : fd_snapmk_accparse_t accparse[1];
237 :
238 : /* disk batch staging (FD_BACKUP_ORIG_ACC_DISK_BATCH). A batch is
239 : staged out of the parser, then flushed to a zp tile once an output
240 : link has credit. disk_batch_pending guards against re-staging while
241 : a staged batch is awaiting credit. */
242 : fd_backup_disk_batch_msg_t disk_batch[1];
243 : ulong disk_batch_base_gaddr;
244 : int disk_batch_pending;
245 : int disk_out_idx; /* snapzp output */
246 :
247 : /* account data cache */
248 : uchar * cache [ FD_ACCDB_CACHE_CLASS_CNT ];
249 : ulong cache_max[ FD_ACCDB_CACHE_CLASS_CNT ];
250 :
251 : /* accdb shared memory */
252 : fd_accdb_t * accdb;
253 : fd_accdb_shmem_t * accdb_shmem;
254 : fd_accdb_fork_shmem_t const * accdb_shfork;
255 : fd_accdb_fork_id_t const * accdb_root_fork;
256 : ulong * accdb_snapshot_sync;
257 :
258 : fd_sleep_t * accdb_sleep;
259 : ulong accdb_tile_id;
260 :
261 : /* output buffer */
262 : ZSTD_CCtx * zst;
263 : ZSTD_inBuffer raw_buf;
264 : ZSTD_outBuffer comp_buf;
265 : uchar raw [ RAW_BUF_SZ ];
266 : uchar comp[ COMP_BUF_SZ ];
267 :
268 : /* startup related */
269 : fd_startup_gate_t startup_gate[1];
270 : ulong startup_pool_idx;
271 :
272 : struct {
273 : ulong snapshots_started[ FD_METRICS_ENUM_SNAP_TYPE_CNT ];
274 : ulong snapshots_finished[ FD_METRICS_ENUM_SNAP_TYPE_CNT ];
275 : ulong last_snapshot_slot_started_full;
276 : ulong last_snapshot_slot_started_incremental;
277 : ulong last_snapshot_slot_finished_full;
278 : ulong last_snapshot_slot_finished_incremental;
279 : ulong bytes_compressed;
280 : ulong bytes_written;
281 : ulong io_blocked_ticks;
282 : ulong compress_ticks;
283 : } metrics;
284 : };
285 :
286 : typedef struct fd_snapmk fd_snapmk_t;
287 :
288 0 : #define IN_KIND_REPLAY 1
289 0 : #define IN_KIND_SNAPRD 2
290 :
291 : FD_FN_CONST static inline ulong
292 0 : scratch_align( void ) {
293 0 : return fd_ulong_max( fd_ulong_max( alignof(fd_snapmk_t), 32UL ), fd_txncache_align() );
294 0 : }
295 :
296 : FD_FN_PURE static inline ulong
297 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
298 0 : ulong max_live_slots = tile->snapmk.max_live_slots;
299 :
300 0 : ulong zp_cnt = tile->out_cnt - 1UL; /* last out link is snapmk_out */
301 :
302 0 : ulong l = FD_LAYOUT_INIT;
303 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_snapmk_t), sizeof(fd_snapmk_t) );
304 0 : l = FD_LAYOUT_APPEND( l, 32UL, ZSTD_estimateCStreamSize( FD_BACKUP_ZSTD_LEVEL ) );
305 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( max_live_slots ) );
306 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), zp_cnt*FD_SNAPMK_ZP_DEPTH*sizeof(ulong) );
307 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_writer_arena_align(), fd_txncache_writer_arena_sz( tile->snapmk.max_txn_per_slot ) );
308 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( max_live_slots, 0 ) );
309 0 : return FD_LAYOUT_FINI( l, scratch_align() );
310 0 : }
311 :
312 : static void
313 : privileged_init( fd_topo_t const * topo,
314 0 : fd_topo_tile_t const * tile ) {
315 0 : FD_SCRATCH_ALLOC_INIT( l, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
316 0 : fd_snapmk_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapmk_t), sizeof(fd_snapmk_t) );
317 0 : memset( ctx, 0, sizeof(fd_snapmk_t) );
318 0 : fd_memset( ctx->pool_sz, 0, sizeof(ctx->pool_sz) );
319 :
320 0 : fd_cstr_ncpy( ctx->snap_dir, tile->snapmk.snapshots_path, PATH_MAX );
321 :
322 0 : int dir_fd = open( ctx->snap_dir, O_RDONLY|O_DIRECTORY );
323 0 : if( FD_UNLIKELY( dir_fd<0 ) ) {
324 0 : FD_LOG_ERR(( "open(%s) failed: %s", ctx->snap_dir, fd_io_strerror( errno ) ));
325 0 : }
326 0 : ctx->snap_dir_fd = dir_fd;
327 0 : ctx->snap_fd = -1;
328 :
329 0 : ctx->snap_full_max = tile->snapmk.max_full_snapshots_to_keep;
330 0 : ctx->snap_max = tile->snapmk.max_full_snapshots_to_keep+
331 0 : tile->snapmk.max_incremental_snapshots_to_keep;
332 0 : ctx->snap_idx = UINT_MAX;
333 0 : }
334 :
335 : static ulong
336 : populate_allowed_fds( fd_topo_t const * topo,
337 : fd_topo_tile_t const * tile,
338 : ulong out_fds_cnt,
339 0 : int * out_fds ) {
340 0 : fd_snapmk_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
341 0 : if( FD_UNLIKELY( out_fds_cnt<6UL+(ulong)ctx->snap_max ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
342 0 : ulong out_cnt = 0UL;
343 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
344 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
345 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
346 0 : out_fds[ out_cnt++ ] = ctx->snap_dir_fd;
347 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RO;
348 0 : out_fds[ out_cnt++ ] = FD_VOTE_STAKES_FD; /* vote stakes disk spill */
349 0 : out_fds[ out_cnt++ ] = FD_TXNCACHE_FD; /* transaction cache spill */
350 0 : for( uint i=0U; i<ctx->snap_max; i++ )
351 0 : out_fds[ out_cnt++ ] = FD_SNAP_FD( i ); /* snapshot pool */
352 0 : return out_cnt;
353 0 : }
354 :
355 : static ulong
356 : populate_allowed_seccomp( fd_topo_t const * topo,
357 : fd_topo_tile_t const * tile,
358 : ulong out_cnt,
359 0 : struct sock_filter * out ) {
360 0 : fd_snapmk_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
361 0 : populate_sock_filter_policy_fd_snapmk_tile(
362 0 : out_cnt, out,
363 0 : (uint)fd_log_private_logfile_fd(),
364 0 : (uint)ctx->snap_dir_fd,
365 0 : (uint)FD_SNAP_FD( 0 ), (uint)FD_SNAP_FD( ctx->snap_max-1U ),
366 0 : (uint)FD_ACCDB_FD_RO,
367 0 : (uint)FD_VOTE_STAKES_FD,
368 0 : (uint)FD_TXNCACHE_FD );
369 0 : return sock_filter_policy_fd_snapmk_tile_instr_cnt;
370 0 : }
371 :
372 : static void
373 : unprivileged_init( fd_topo_t const * topo,
374 0 : fd_topo_tile_t const * tile ) {
375 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
376 0 : ulong max_live_slots = tile->snapmk.max_live_slots;
377 :
378 0 : ulong zp_cnt = tile->out_cnt - 1UL;
379 :
380 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
381 0 : fd_snapmk_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapmk_t), sizeof(fd_snapmk_t) );
382 0 : void * _zstd = FD_SCRATCH_ALLOC_APPEND( l, 32UL, ZSTD_estimateCStreamSize( FD_BACKUP_ZSTD_LEVEL ) );
383 0 : void * _txnc_lj = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( max_live_slots ) );
384 0 : ulong * _rd_shdw = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), zp_cnt*FD_SNAPMK_ZP_DEPTH*sizeof(ulong) );
385 0 : void * _arena = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_writer_arena_align(), fd_txncache_writer_arena_sz( tile->snapmk.max_txn_per_slot ) );
386 0 : void * _accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( max_live_slots, 0 ) );
387 0 : ulong end = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
388 0 : ctx->status_cache_writer_arena = _arena;
389 0 : ctx->status_cache_writer_arena_sz = fd_txncache_writer_arena_sz( tile->snapmk.max_txn_per_slot );
390 0 : FD_CHECK_CRIT( end==(ulong)scratch + scratch_footprint( tile ), "bug when calculating tile memory layout" );
391 :
392 0 : for( ulong i=0UL; i<zp_cnt; i++ ) {
393 0 : ctx->rd_shadow[ i ] = _rd_shdw + i*FD_SNAPMK_ZP_DEPTH;
394 0 : }
395 :
396 0 : ctx->state = SNAPMK_STATE_STARTUP;
397 0 : ctx->replay_in_seq_cons = ULONG_MAX;
398 0 : ctx->idle_iter = 0UL;
399 :
400 0 : fd_startup_gate_init( ctx->startup_gate, topo, tile->in_cnt );
401 0 : ctx->startup_pool_idx = 0UL;
402 :
403 0 : ctx->incremental = 0;
404 0 : ctx->base_slot = ULONG_MAX;
405 0 : void * _backup = fd_topo_obj_laddr( topo, tile->snapmk.visited_set_obj_id );
406 0 : ctx->visited_set = fd_backup_set ( _backup );
407 0 : ctx->overrun = fd_backup_overrun( _backup );
408 0 : ctx->stats = fd_backup_stats ( _backup );
409 0 : FD_TEST( ctx->visited_set );
410 0 : FD_TEST( ctx->overrun );
411 0 : FD_TEST( ctx->stats );
412 :
413 0 : ulong banks_obj_id = tile->snapmk.banks_obj_id;
414 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
415 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
416 0 : FD_TEST( ctx->banks );
417 :
418 0 : fd_txncache_shmem_t * tc_shmem = fd_txncache_shmem_join( fd_topo_obj_laddr( topo, tile->snapmk.txncache_obj_id ) );
419 0 : FD_TEST( tc_shmem );
420 0 : ctx->txncache = fd_txncache_join( fd_txncache_new( _txnc_lj, tc_shmem, FD_TXNCACHE_FD ) );
421 0 : FD_TEST( ctx->txncache );
422 :
423 0 : ulong * zp_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->snapmk.zp_fseq_id ) ); FD_TEST( zp_fseq );
424 0 : atomic_ulong * zp_app = fd_fseq_app_laddr( zp_fseq );
425 0 : ctx->file_off_p = &zp_app[ 0 ];
426 0 : ctx->appendvec_slot_ticket_p = &zp_app[ 1 ];
427 :
428 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->snapmk.accdb_obj_id );
429 0 : fd_accdb_shmem_t * accdb_shmem_ro = fd_accdb_shmem_join( _accdb_shmem );
430 0 : FD_TEST( accdb_shmem_ro );
431 0 : ctx->accdb_shmem = accdb_shmem_ro;
432 0 : ctx->accdb_snapshot_sync = &accdb_shmem_ro->snapshot_sync;
433 0 : ctx->accdb_sleep = topo->sleep_obj_id!=ULONG_MAX ? fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ) : NULL;
434 0 : ctx->accdb_tile_id = fd_topo_find_tile( topo, "accdb", 0UL );
435 0 : ulong * epoch_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->snapmk.accdb_epoch_obj_id ) );
436 0 : FD_TEST( epoch_fseq );
437 0 : ctx->accdb = fd_accdb_join_readonly( _accdb, accdb_shmem_ro, epoch_fseq, FD_ACCDB_FD_RO );
438 0 : FD_TEST( ctx->accdb );
439 0 : fd_backup_cache_join( ctx->acc_cache, accdb_shmem_ro, epoch_fseq );
440 0 : {
441 0 : FD_SCRATCH_ALLOC_INIT( l, accdb_shmem_ro );
442 0 : FD_SCRATCH_ALLOC_APPEND( l, FD_ACCDB_SHMEM_ALIGN, sizeof(fd_accdb_shmem_t) );
443 0 : ctx->accdb_shfork = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_fork_shmem_t), max_live_slots*sizeof(fd_accdb_fork_shmem_t) );
444 0 : }
445 0 : ctx->accdb_root_fork = &accdb_shmem_ro->root_fork_id;
446 :
447 0 : ctx->delta.chain = (uint const *)( (uchar const *)accdb_shmem_ro + accdb_shmem_ro->delta.chain_off );
448 0 : ctx->delta.pool = (fd_accdb_delta_t const *)( (uchar const *)accdb_shmem_ro + accdb_shmem_ro->delta.ele_off );
449 0 : ctx->delta.chain_cnt = accdb_shmem_ro->delta.chain_cnt;
450 0 : ctx->delta.chain_idx = 0UL;
451 0 : ctx->delta.ele_idx = UINT_MAX;
452 0 : ctx->delta.ele_cnt = 0UL;
453 0 : ctx->delta.ele_total = 0UL;
454 :
455 0 : for( ulong i=0UL; i < tile->in_cnt; i++ ) {
456 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
457 0 : if( 0==strcmp( link->name, "replay_snapmk" ) ) {
458 0 : FD_TEST( !ctx->in_kind[ i ] );
459 0 : ctx->in_kind[ i ] = IN_KIND_REPLAY;
460 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
461 0 : ctx->replay_in_mem = link_wksp->wksp;
462 0 : ctx->replay_in_seq_prod = fd_mcache_seq_laddr_const( link->mcache );
463 0 : } else if( 0==strcmp( link->name, "snaprd_out" ) ) {
464 0 : FD_TEST( !ctx->in_kind[ i ] );
465 0 : ctx->in_kind[ i ] = IN_KIND_SNAPRD;
466 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
467 0 : FD_CHECK_CRIT( link->mtu<=UINT_MAX, "oob MTU" );
468 0 : ctx->rd_in_mem = link_wksp->wksp;
469 0 : ctx->rd_in_mtu = link->mtu;
470 : /* fseq used for cnc and flow control of snaprd tile */
471 0 : ulong * fseq = tile->in_link_fseq[ i ];
472 0 : FD_CHECK_ERR( fseq, "no fseq for snaprd_out link" );
473 0 : ctx->rd_fseq = (atomic_ulong *)fseq;
474 0 : ctx->rd_ctl = fd_fseq_app_laddr( fseq );
475 0 : ctx->rd_tile_id = fd_topo_find_link_producer( topo, link );
476 0 : FD_STATIC_ASSERT( sizeof(ulong)<=FD_FSEQ_APP_FOOTPRINT, fseq_app_space );
477 0 : } else {
478 0 : FD_LOG_ERR(( "Unexpected input link \"%s\"", link->name ));
479 0 : }
480 0 : }
481 0 : FD_CHECK_ERR( ctx->replay_in_mem, "missing replay_snapmk link" );
482 0 : ctx->replay_in_seq_cons = __atomic_load_n( ctx->replay_in_seq_prod, __ATOMIC_ACQUIRE );
483 0 : FD_CHECK_ERR( ctx->rd_in_mem, "missing snaprd_out link" );
484 :
485 0 : FD_TEST( tile->out_cnt >= 2 );
486 0 : FD_TEST( tile->out_cnt <= FD_TOPO_MAX_TILE_OUT_LINKS );
487 0 : ctx->zp_cnt = tile->out_cnt - 1UL;
488 0 : for( ulong i=0UL; i < ctx->zp_cnt; i++ ) {
489 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
490 0 : if( 0!=strcmp( link->name, "snapmk_zp" ) ) {
491 0 : FD_LOG_ERR(( "Unexpected output link \"%s\"", link->name ));
492 0 : }
493 0 : FD_TEST( link->mcache );
494 0 : FD_TEST( fd_mcache_depth( link->mcache )==FD_SNAPMK_ZP_DEPTH );
495 0 : ctx->zp_out[ i ].mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
496 0 : ctx->zp_out[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->zp_out[ i ].mem, link->dcache );
497 0 : ctx->zp_out[ i ].wmark = fd_dcache_compact_wmark ( ctx->zp_out[ i ].mem, link->dcache, link->mtu );
498 0 : ctx->zp_out[ i ].chunk = ctx->zp_out[ i ].chunk0;
499 :
500 0 : for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
501 0 : fd_topo_tile_t const * consumer = &topo->tiles[ j ];
502 0 : for( ulong k=0UL; k<consumer->in_cnt; k++ ) {
503 0 : if( FD_LIKELY( consumer->in_link_id[ k ]!=tile->out_link_id[ i ] ) ) continue;
504 0 : if( FD_UNLIKELY( !consumer->in_link_reliable[ k ] ) ) continue;
505 0 : FD_TEST( !ctx->zp_cons_fseq[ i ] );
506 0 : ctx->zp_cons_fseq[ i ] = consumer->in_link_fseq[ k ];
507 0 : }
508 0 : }
509 0 : FD_TEST( ctx->zp_cons_fseq[ i ] );
510 0 : }
511 :
512 0 : ctx->out.out_idx = tile->out_cnt - 1UL;
513 0 : fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ ctx->out.out_idx ] ];
514 0 : if( 0!=strcmp( out_link->name, "snapmk_out" ) ) {
515 0 : FD_LOG_ERR(( "Unexpected output link \"%s\"", out_link->name ));
516 0 : }
517 0 : FD_CHECK_ERR( out_link->mtu >= sizeof(fd_snapmk_msg_t), "snapmk_out link MTU too small" );
518 0 : ctx->out.mem = fd_wksp_containing( out_link->dcache );
519 0 : ctx->out.chunk0 = fd_dcache_compact_chunk0( ctx->out.mem, out_link->dcache );
520 0 : ctx->out.wmark = fd_dcache_compact_wmark ( ctx->out.mem, out_link->dcache, out_link->mtu );
521 0 : ctx->out.chunk = ctx->out.chunk0;
522 :
523 0 : FD_TEST( out_link->mcache );
524 0 : ctx->out.seq_prod = fd_mcache_seq_laddr( out_link->mcache );
525 0 : ctx->out.cons_cnt = 0UL;
526 0 : for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
527 0 : fd_topo_tile_t const * consumer = &topo->tiles[ j ];
528 0 : for( ulong k=0UL; k<consumer->in_cnt; k++ ) {
529 0 : if( FD_LIKELY( consumer->in_link_id[ k ]!=tile->out_link_id[ ctx->out.out_idx ] ) ) continue;
530 0 : if( FD_UNLIKELY( !consumer->in_link_reliable[ k ] ) ) continue;
531 0 : FD_CHECK_ERR( ctx->out.cons_cnt<SNAPMK_OUT_CONS_MAX, "too many snapmk_out consumers" );
532 0 : FD_TEST( consumer->in_link_fseq[ k ] );
533 0 : ctx->out.cons_fseq[ ctx->out.cons_cnt++ ] = consumer->in_link_fseq[ k ];
534 0 : }
535 0 : }
536 :
537 0 : ctx->zst = ZSTD_initStaticCStream( _zstd, ZSTD_estimateCStreamSize( FD_BACKUP_ZSTD_LEVEL ) );
538 0 : FD_TEST( ctx->zst );
539 0 : ulong zst_err;
540 0 : zst_err = ZSTD_CCtx_setParameter( ctx->zst, ZSTD_c_compressionLevel, FD_BACKUP_ZSTD_LEVEL );
541 0 : if( FD_UNLIKELY( ZSTD_isError( zst_err ) ) ) {
542 0 : FD_LOG_ERR(( "ZSTD_CCtx_setParameter(ZSTD_c_compressionLevel) failed: %s", ZSTD_getErrorName( zst_err ) ));
543 0 : }
544 0 : ctx->raw_buf = (ZSTD_inBuffer ){ .src = ctx->raw, .size = 0UL };
545 0 : ctx->comp_buf = (ZSTD_outBuffer){ .dst = ctx->comp, .size = COMP_BUF_SZ };
546 :
547 0 : ctx->rd_fseq[0] = 0UL;
548 0 : ctx->rd_seq = 0UL;
549 0 : ctx->rd_seq_cache = ULONG_MAX;
550 0 : }
551 :
552 : /* zip_reset discards any buffered data and assumes that the compression
553 : stream is clean (last frame was finished). */
554 :
555 : static void
556 0 : zip_reset( fd_snapmk_t * ctx ) {
557 0 : ctx->raw_buf.size = 0UL;
558 0 : ctx->raw_buf.pos = 0UL;
559 0 : ctx->zstd_frame_open = 0;
560 0 : }
561 :
562 : /* zip_append adds bytes into the input buffer (opens a new frame if
563 : none is open). Panics if raw_buf is out of buf space; it is the
564 : caller's responsibility to guarantee that data_sz is small enough. */
565 :
566 : static void
567 : zip_append( fd_snapmk_t * ctx,
568 : void const * data,
569 0 : ulong data_sz ) {
570 0 : if( FD_UNLIKELY( !data_sz ) ) return;
571 0 : FD_CHECK_CRIT( ctx->raw_buf.size + data_sz <= RAW_BUF_SZ, "insufficient raw buffer space" );
572 0 : fd_memcpy( ctx->raw + ctx->raw_buf.size, data, data_sz );
573 0 : ctx->raw_buf.size += data_sz;
574 0 : }
575 :
576 : /* zip_flush provides the input buffer to the Zstandard compressor.
577 : Depending on directive, it ...
578 : (ZSTD_e_continue) ... optimistically does compression work
579 : (ZSTD_e_flush) ... drains/empties the input buffer without ending
580 : the current frame
581 : (ZSTD_e_end) ... ends the current frame.
582 : Does not sync the underlying file descriptor. */
583 :
584 : static void
585 : zip_flush( fd_snapmk_t * ctx,
586 0 : ZSTD_EndDirective directive ) {
587 :
588 : /* Compress chunk */
589 0 : ulong raw_pos = ctx->raw_buf.pos;
590 0 : long t0 = fd_tickcount();
591 0 : ulong ret = ZSTD_compressStream2( ctx->zst, &ctx->comp_buf, &ctx->raw_buf, directive );
592 0 : long t1 = fd_tickcount();
593 0 : if( FD_UNLIKELY( ZSTD_isError( ret ) ) ) {
594 0 : FD_LOG_ERR(( "ZSTD_compressStream2 failed: %s", ZSTD_getErrorName( ret ) ));
595 0 : }
596 : /* A non-zero return after ZSTD_e_end means the frame epilogue is
597 : still pending in the compressor (comp_buf too small), which would
598 : corrupt the stream once raw frames or the next frame are written
599 : after it. */
600 0 : FD_CHECK_ERR( !( directive==ZSTD_e_end && ret ), "ZSTD_e_end left compressed data pending" );
601 0 : ctx->zstd_frame_open = directive!=ZSTD_e_end && ( ctx->zstd_frame_open || ctx->raw_buf.pos>raw_pos );
602 0 : ctx->metrics.bytes_compressed += ctx->raw_buf.pos - raw_pos;
603 0 : ctx->uncompressed_sz += ctx->raw_buf.pos - raw_pos;
604 0 : ctx->metrics.compress_ticks += (ulong)( t1-t0 );
605 :
606 : /* Move uncompressed bytes to left */
607 0 : if( ctx->raw_buf.pos < ctx->raw_buf.size ) {
608 0 : memmove( ctx->raw,
609 0 : ctx->raw + ctx->raw_buf.pos,
610 0 : ctx->raw_buf.size - ctx->raw_buf.pos );
611 0 : ctx->raw_buf.size -= ctx->raw_buf.pos;
612 0 : ctx->raw_buf.pos = 0UL;
613 0 : } else {
614 0 : ctx->raw_buf.size = 0UL;
615 0 : ctx->raw_buf.pos = 0UL;
616 0 : }
617 :
618 : /* Write compressed bytes to file */
619 0 : ulong comp_wr_;
620 0 : ulong comp_sz = ctx->comp_buf.pos;
621 0 : t0 = fd_tickcount();
622 0 : int wr_err = fd_io_write(
623 0 : ctx->snap_fd,
624 0 : ctx->comp,
625 0 : comp_sz, comp_sz,
626 0 : &comp_wr_ );
627 0 : t1 = fd_tickcount();
628 0 : if( FD_UNLIKELY( wr_err ) ) {
629 0 : FD_LOG_ERR(( "fd_io_write failed: %s", fd_io_strerror( wr_err ) ));
630 0 : }
631 0 : if( FD_UNLIKELY( comp_wr_ != comp_sz ) ) {
632 0 : FD_LOG_ERR(( "fd_io_write did not write full buffer (expected %lu bytes, wrote %lu bytes)", comp_sz, comp_wr_ ));
633 0 : }
634 0 : ctx->metrics.bytes_written += comp_wr_;
635 0 : ctx->metrics.io_blocked_ticks += (ulong)( t1-t0 );
636 0 : ctx->pool_sz[ ctx->snap_idx ] += comp_wr_;
637 0 : ctx->comp_buf.pos = 0UL;
638 0 : ctx->comp_buf.size = COMP_BUF_SZ;
639 0 : ctx->zstd_data_frame_cnt += directive==ZSTD_e_end;
640 0 : }
641 :
642 : /* zip_end_frame ends the current frame, if any is open or any input
643 : is buffered. Skips the call otherwise, which would emit an empty
644 : frame. */
645 :
646 : static void
647 0 : zip_end_frame( fd_snapmk_t * ctx ) {
648 0 : if( ctx->zstd_frame_open || ctx->raw_buf.size ) zip_flush( ctx, ZSTD_e_end );
649 0 : }
650 :
651 : /* zip_align aligns the Zstandard compressed stream by 512 bytes using
652 : skippable frames. */
653 :
654 : static void
655 0 : zip_align( fd_snapmk_t * ctx ) {
656 0 : long off = lseek( ctx->snap_fd, 0L, SEEK_CUR );
657 0 : if( FD_UNLIKELY( off<0L ) ) {
658 0 : FD_LOG_ERR(( "lseek failed: %i-%s", errno, fd_io_strerror( errno ) ));
659 0 : }
660 0 : ulong uoff = (ulong)off;
661 : /* Align using skippable frame */
662 0 : ulong aoff = fd_ulong_align_up( uoff, 4096UL );
663 0 : ulong pad_sz = aoff - uoff;
664 0 : if( FD_UNLIKELY( pad_sz>0UL && pad_sz<8UL ) ) {
665 0 : aoff += 4096UL;
666 0 : pad_sz += 4096UL;
667 0 : }
668 0 : if( pad_sz>0UL ) {
669 0 : long t0 = fd_tickcount();
670 0 : uchar frame_hdr[ 8 ];
671 0 : FD_STORE( uint, frame_hdr, ZSTD_MAGIC_SKIPPABLE_START );
672 0 : FD_STORE( uint, frame_hdr+4, (uint)( pad_sz-8 ) );
673 0 : ulong wr_sz_;
674 0 : int err = fd_io_write( ctx->snap_fd, frame_hdr, 8UL, 8UL, &wr_sz_ );
675 0 : if( FD_UNLIKELY( err ) ) {
676 0 : FD_LOG_ERR(( "fd_io_write failed: %i-%s", err, fd_io_strerror( err ) ));
677 0 : }
678 0 : static uchar const zero[ 4096UL ] = {0};
679 0 : err = fd_io_write( ctx->snap_fd, zero, pad_sz-8UL, pad_sz-8UL, &wr_sz_ );
680 0 : if( FD_UNLIKELY( err ) ) {
681 0 : FD_LOG_ERR(( "fd_io_write failed: %i-%s", err, fd_io_strerror( err ) ));
682 0 : }
683 0 : long t1 = fd_tickcount();
684 0 : ctx->metrics.bytes_written += pad_sz;
685 0 : ctx->metrics.io_blocked_ticks += (ulong)( t1-t0 );
686 0 : ctx->pool_sz[ ctx->snap_idx ] += pad_sz;
687 0 : ctx->zstd_padding_sz += pad_sz;
688 0 : }
689 0 : atomic_store_explicit( ctx->file_off_p, aoff, memory_order_release );
690 0 : }
691 :
692 : /* snapmk_status_cache_tar_hdr builds the TAR header of the status cache
693 : file for a payload of sz bytes. */
694 :
695 : static fd_tar_meta_t *
696 : snapmk_status_cache_tar_hdr( fd_tar_meta_t * meta,
697 0 : ulong sz ) {
698 0 : fd_backup_tar_file_hdr( meta, sz );
699 0 : if( FD_UNLIKELY( !fd_tar_meta_set_size( meta, sz ) ) ) {
700 0 : FD_LOG_ERR(( "status cache (%lu bytes) too large for a TAR size field", sz ));
701 0 : }
702 0 : fd_cstr_ncpy( meta->name, "snapshots/status_cache", sizeof(meta->name) );
703 0 : fd_tar_meta_set_chksum( meta );
704 0 : return meta;
705 0 : }
706 :
707 : /* snapmk_status_cache_prepare starts status cache serialization and
708 : writes its TAR header. The writer counts the status cache up front,
709 : so its size is known before the first byte is streamed. */
710 :
711 : static void
712 0 : snapmk_status_cache_prepare( fd_snapmk_t * ctx ) {
713 0 : fd_bank_t * bank = ctx->bank;
714 :
715 : /* The raw buffer is discarded by zip_reset below, borrow it to read
716 : the SlotHistory sysvar. */
717 0 : ulong slot_history_lamports;
718 0 : int slot_history_exec;
719 0 : uchar slot_history_owner[ 32UL ];
720 0 : ulong slot_history_sz = 0UL;
721 0 : FD_STATIC_ASSERT( RAW_BUF_SZ>=FD_RUNTIME_ACC_SZ_MAX, raw_buf_sz );
722 0 : fd_accdb_read_one_nocache( ctx->accdb, bank->accdb_fork_id, fd_sysvar_slot_history_id.uc,
723 0 : &slot_history_lamports, &slot_history_exec, slot_history_owner,
724 0 : ctx->raw, &slot_history_sz );
725 0 : if( FD_UNLIKELY( !slot_history_lamports ) ) slot_history_sz = 0UL;
726 0 : fd_txncache_writer_init( ctx->txncache_writer, ctx->txncache, bank->f.slot, ctx->raw, slot_history_sz, ctx->status_cache_writer_arena, ctx->status_cache_writer_arena_sz );
727 0 : ctx->status_cache_sz = 0UL;
728 0 : ctx->status_cache_expected_sz = fd_txncache_writer_serialized_sz( ctx->txncache_writer );
729 :
730 0 : zip_reset( ctx );
731 0 : fd_tar_meta_t meta;
732 0 : snapmk_status_cache_tar_hdr( &meta, ctx->status_cache_expected_sz );
733 0 : zip_append( ctx, &meta, sizeof(fd_tar_meta_t) );
734 0 : }
735 :
736 : /* snapmk_status_cache does a unit of status cache serialization and
737 : compression work. Returns 0 once status cache compression is fully
738 : done (TAR/Zstandard out stream clean). Otherwise, returns 1, which
739 : implies another work call is needed. */
740 :
741 : static int
742 0 : snapmk_status_cache( fd_snapmk_t * ctx ) {
743 0 : if( FD_UNLIKELY( ctx->raw_buf.size + FD_TXNCACHE_WRITER_BUF_MIN > RAW_BUF_SZ ) ) {
744 0 : zip_flush( ctx, ZSTD_e_continue );
745 0 : return 1;
746 0 : }
747 0 : ulong buf_rem = RAW_BUF_SZ - ctx->raw_buf.size;
748 0 : uchar * chunk = ctx->raw + ctx->raw_buf.size;
749 0 : ulong chunk_sz = fd_txncache_writer_serialize( ctx->txncache_writer, chunk, buf_rem );
750 0 : if( FD_UNLIKELY( !chunk_sz ) ) { /* done serializing? */
751 0 : FD_CHECK_CRIT( ctx->status_cache_sz==ctx->status_cache_expected_sz, "status cache size does not match its TAR header" );
752 0 : zip_flush( ctx, ZSTD_e_continue );
753 0 : ulong pad = fd_ulong_align_up( ctx->status_cache_sz, sizeof(fd_tar_meta_t) ) - ctx->status_cache_sz;
754 0 : if( pad ) {
755 0 : static uchar const zero[ sizeof(fd_tar_meta_t) ] = {0};
756 0 : zip_append( ctx, zero, pad );
757 0 : }
758 0 : zip_end_frame( ctx );
759 0 : ctx->state = SNAPMK_STATE_EOF_MARKER;
760 0 : return 0;
761 0 : }
762 0 : ctx->raw_buf.size += chunk_sz;
763 0 : ctx->status_cache_sz += chunk_sz;
764 0 : zip_flush( ctx, ZSTD_e_continue );
765 0 : return 1;
766 0 : }
767 :
768 : /* snapmk_eof_marker writes a compressed "end of TAR stream" marker.
769 : Assumes clean tar and Zstandard stream. */
770 :
771 : static void
772 0 : snapmk_eof_marker( fd_snapmk_t * ctx ) {
773 0 : FD_CHECK_ERR( ctx->raw_buf.size==0UL, "Zstandard stream unclean" );
774 0 : ctx->raw_buf.pos = 0UL;
775 0 : ctx->raw_buf.size = 1024UL;
776 0 : fd_memset( ctx->raw, 0, 1024UL );
777 0 : zip_flush( ctx, ZSTD_e_end );
778 0 : }
779 :
780 : /* snapmk_done_rename renames the "partial" snapshot file to a proper
781 : "snapshot-*.tar.zst" or "incremental-snapshot-*-*.tar.zst" file. */
782 :
783 : static void
784 0 : snapmk_done_rename( fd_snapmk_t * ctx ) {
785 0 : long file_sz = lseek( ctx->snap_fd, 0L, SEEK_END );
786 0 : if( FD_UNLIKELY( file_sz<0L ) ) {
787 0 : FD_LOG_ERR(( "lseek failed: %s", fd_io_strerror( errno ) ));
788 0 : }
789 0 : ctx->final_sz = (ulong)file_sz;
790 0 : ctx->pool_sz[ ctx->snap_idx ] = ctx->final_sz;
791 :
792 0 : fd_backup_inode_t * inode = &ctx->pool[ ctx->snap_idx ];
793 0 : struct flock lock = {
794 0 : .l_type = F_UNLCK,
795 0 : .l_whence = SEEK_SET
796 0 : };
797 0 : if( FD_UNLIKELY( fcntl( ctx->snap_fd, F_SETLK, &lock ) ) ) {
798 0 : FD_LOG_ERR(( "fcntl(F_UNLCK, %s) failed: %i-%s",
799 0 : inode->name, errno, fd_io_strerror( errno ) ));
800 0 : }
801 0 : if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->snap_dir_fd, inode->name,
802 0 : ctx->snap_dir_fd, ctx->final_name, 0U ) ) ) {
803 0 : FD_LOG_ERR(( "renameat2(%s, %s) failed: %s", inode->name, ctx->final_name, fd_io_strerror( errno ) ));
804 0 : }
805 0 : fd_cstr_ncpy( inode->name, ctx->final_name, sizeof(inode->name) );
806 :
807 0 : if( FD_UNLIKELY( ctx->incremental ) ) {
808 0 : inode->full_slot = ctx->base_slot;
809 0 : inode->incr_slot = ctx->bank->f.slot;
810 0 : ctx->metrics.last_snapshot_slot_finished_incremental = ctx->bank->f.slot;
811 0 : ctx->metrics.snapshots_finished[ FD_METRICS_ENUM_SNAP_TYPE_V_INCREMENTAL_IDX ]++;
812 0 : } else {
813 0 : inode->full_slot = ctx->bank->f.slot;
814 0 : inode->incr_slot = ULONG_MAX;
815 0 : ctx->base_slot = ctx->bank->f.slot;
816 0 : ctx->metrics.last_snapshot_slot_finished_full = ctx->bank->f.slot;
817 0 : ctx->metrics.snapshots_finished[ FD_METRICS_ENUM_SNAP_TYPE_V_FULL_IDX ]++;
818 0 : }
819 :
820 0 : ctx->snap_fd = -1;
821 :
822 0 : ctx->end_time = fd_log_wallclock();
823 0 : FD_LOG_INFO(( "%s snapshot created in %.3f seconds (%s/%s, %.3f GB)",
824 0 : ctx->incremental ? "incremental" : "full",
825 0 : (double)( ctx->end_time - ctx->start_time )/1e9,
826 0 : ctx->snap_dir, ctx->final_name,
827 0 : (double)file_sz/1e9 ));
828 :
829 0 : ctx->state = SNAPMK_STATE_DONE;
830 0 : }
831 :
832 : /* snapshot_sync_advance requests replay to advance the snapshot sync
833 : state machine. */
834 :
835 : static void
836 : snapshot_sync_transition( fd_snapmk_t * ctx,
837 : ulong state_from,
838 : ulong state_req,
839 0 : ulong state_to ) {
840 0 : while( FD_UNLIKELY( fd_accdb_snapshot_sync_state( ctx->accdb_snapshot_sync )!=state_from ) ) FD_YIELD();
841 0 : fd_accdb_snapshot_sync_advance( ctx->accdb_snapshot_sync, state_req );
842 0 : if( FD_UNLIKELY( ctx->accdb_sleep ) ) fd_sleep_ring( ctx->accdb_sleep, ctx->accdb_tile_id );
843 0 : while( FD_UNLIKELY( fd_accdb_snapshot_sync_state( ctx->accdb_snapshot_sync )!=state_to ) ) FD_YIELD();
844 0 : }
845 :
846 : static ulong
847 : snapshot_sync_request( fd_snapmk_t * ctx,
848 : ulong state_from,
849 0 : ulong state_req ) {
850 0 : while( FD_UNLIKELY( fd_accdb_snapshot_sync_state( ctx->accdb_snapshot_sync )!=state_from ) ) FD_YIELD();
851 0 : fd_accdb_snapshot_sync_advance( ctx->accdb_snapshot_sync, state_req );
852 0 : if( FD_UNLIKELY( ctx->accdb_sleep ) ) fd_sleep_ring( ctx->accdb_sleep, ctx->accdb_tile_id );
853 0 : for(;;) {
854 0 : ulong state = fd_accdb_snapshot_sync_state( ctx->accdb_snapshot_sync );
855 0 : if( FD_LIKELY( state!=state_req ) ) return state;
856 0 : FD_YIELD();
857 0 : }
858 0 : }
859 :
860 : static inline ulong
861 : zp_publish( fd_snapmk_t * ctx,
862 : fd_stem_context_t * stem,
863 : ulong out_idx,
864 : ulong sig,
865 : ulong chunk,
866 : ulong sz,
867 : ulong ctl,
868 : ulong tsorig,
869 0 : ulong tspub ) {
870 0 : FD_TEST( out_idx<ctx->zp_cnt );
871 0 : fd_frag_meta_t * mcache = stem->mcaches[ out_idx ];
872 0 : ulong depth = stem->depths [ out_idx ];
873 0 : ulong * seqp = &stem->seqs [ out_idx ];
874 0 : ulong seq = *seqp;
875 0 : # if FD_HAS_AVX
876 0 : fd_mcache_publish_avx( mcache, depth, seq, sig, chunk, sz, ctl, tsorig, tspub );
877 : # elif FD_HAS_ARM
878 : fd_mcache_publish_arm( mcache, depth, seq, sig, chunk, sz, ctl, tsorig, tspub );
879 : # else
880 : fd_mcache_publish ( mcache, depth, seq, sig, chunk, sz, ctl, tsorig, tspub );
881 : # endif
882 0 : ulong cr_avail = fd_ulong_sat_sub( stem->cr_avail[ out_idx ], 1UL );
883 0 : stem->cr_avail[ out_idx ] = cr_avail;
884 0 : *stem->min_cr_avail = fd_ulong_min( cr_avail, *stem->min_cr_avail );
885 0 : if( FD_UNLIKELY( !cr_avail ) ) ctx->zp_ready &= ~fd_ulong_mask_bit( (int)out_idx );
886 0 : *seqp = fd_seq_inc( seq, 1UL );
887 0 : if( FD_UNLIKELY( stem->sleep ) ) fd_sleep_wake_check( stem->sleep, stem->wake+stem->wake_off[ out_idx ], (ulong)(stem->wake_off[ out_idx+1UL ]-stem->wake_off[ out_idx ]) );
888 0 : return seq;
889 0 : }
890 :
891 : /* broadcast is called repeatedly until a message has been sent to all
892 : snapzp tiles. Returns 1 if the message was sent to all tiles, 0
893 : otherwise (call again).
894 :
895 : broadcast_prepare must be called once before attempting to broadcast.
896 :
897 : Typically, a barrier is installed before the broadcast (wait for all
898 : snapzp tiles to catch up before broadcasting). Then, another
899 : barrier is installed at the broadcast (wait for all snapzp tiles to
900 : ACK the broadcast before continuing). */
901 :
902 : static void
903 0 : broadcast_prepare( fd_snapmk_t * ctx ) {
904 0 : ctx->zp_flush_pending = fd_ulong_mask( 0, (int)ctx->zp_cnt-1 );
905 0 : }
906 :
907 : static int
908 : broadcast( fd_snapmk_t * ctx,
909 : fd_stem_context_t * stem,
910 : ulong ctl,
911 0 : int * charge_busy ) {
912 0 : int did_work = 0;
913 0 : ulong zp_cnt = ctx->zp_cnt;
914 0 : for( ulong i=0UL; i<zp_cnt; i++ ) {
915 0 : if( !fd_ulong_extract_bit( ctx->zp_flush_pending, (int)i ) ) continue;
916 0 : if( !stem->cr_avail[ i ] ) continue;
917 0 : zp_publish( ctx, stem, i, 0UL, 0UL, 0UL, ctl, 0UL, 0UL );
918 0 : ctx->zp_barrier[ i ] = stem->seqs[ i ]; /* FINISH barrier */
919 0 : ctx->zp_flush_pending &= ~fd_ulong_mask_bit( (int)i );
920 0 : *charge_busy = 1;
921 0 : did_work = 1;
922 0 : }
923 0 : if( (!ctx->zp_flush_pending) & (!did_work) ) {
924 0 : return 1;
925 0 : }
926 0 : return 0;
927 0 : }
928 :
929 : /* barrier_install blocks this tile until all snapzp tiles have caught
930 : up with the last published messages. */
931 :
932 : static void
933 : barrier_install( fd_snapmk_t * ctx,
934 0 : fd_stem_context_t const * stem ) {
935 0 : ulong zp_cnt = ctx->zp_cnt;
936 0 : for( ulong i=0UL; i<zp_cnt; i++ ) {
937 0 : ctx->zp_barrier[ i ] = stem->seqs[ i ]; /* FLUSH barrier */
938 0 : }
939 0 : }
940 :
941 : /* zp_rr_next picks a snapzp tile for a new job (ULONG_MAX if none are
942 : ready). FIXME rewrite this O(1) with rotate+find_lsb. */
943 :
944 : static ulong
945 0 : zp_rr_next( fd_snapmk_t * ctx ) {
946 0 : ulong n = ctx->zp_cnt;
947 0 : for( ulong k=0UL; k<n; k++ ) {
948 0 : ulong idx = ctx->zp_rr_idx;
949 0 : ctx->zp_rr_idx = fd_ulong_if( ctx->zp_rr_idx+1UL>=n, 0UL, ctx->zp_rr_idx+1UL );
950 0 : if( ctx->zp_ready & (1UL<<idx) ) return idx;
951 0 : }
952 0 : return ULONG_MAX;
953 0 : }
954 :
955 : /* zp_alloc allocates a message payload on snapzp_mk[ out_idx ]. */
956 :
957 : static inline void *
958 : zp_alloc( fd_snapmk_t * ctx,
959 : ulong out_idx,
960 : ulong sz,
961 0 : ulong * chunk ) {
962 0 : FD_TEST( sz );
963 0 : FD_TEST( out_idx<ctx->zp_cnt );
964 0 : *chunk = ctx->zp_out[ out_idx ].chunk;
965 0 : void * laddr = fd_chunk_to_laddr( ctx->zp_out[ out_idx ].mem, *chunk );
966 0 : ctx->zp_out[ out_idx ].chunk =
967 0 : fd_dcache_compact_next( *chunk, sz, ctx->zp_out[ out_idx ].chunk0, ctx->zp_out[ out_idx ].wmark );
968 0 : return laddr;
969 0 : }
970 :
971 : /* rd_ack sends read acknowledgements to snaprd. */
972 :
973 : static void
974 : rd_ack( fd_snapmk_t * ctx,
975 0 : fd_stem_context_t const * stem ) {
976 0 : ulong zp_cnt = ctx->zp_cnt;
977 0 : ulong rd_seq = ctx->rd_seq;
978 0 : for( ulong i=0UL; i<zp_cnt; i++ ) {
979 : /* Must be the live fseq, not stem's cached cons_seq. Publishes are
980 : gated on the live consumer position (zp_sync_cr_avail), so pub can
981 : outrun stem's cached cons_seq by more than the link depth. Indexing
982 : rd_shadow with a stale cons would then read a slot that a newer
983 : publish has already wrapped onto, yielding a floor that is too new
984 : and releasing snaprd buffers that snapzp is still reading. */
985 0 : ulong cons = fd_fseq_query( ctx->zp_cons_fseq[ i ] );
986 0 : ulong pub = stem->seqs[ i ];
987 0 : if( FD_UNLIKELY( !fd_seq_lt( cons, pub ) ) ) continue;
988 : /* snapzp ack for 'cons' means that this snapzp has fully consumed
989 : up to snaprd seq 'floor'. */
990 0 : ulong floor = ctx->rd_shadow[ i ][ cons & (FD_SNAPMK_ZP_DEPTH-1) ];
991 0 : rd_seq = fd_seq_lt( floor, rd_seq ) ? floor : rd_seq;
992 0 : }
993 0 : if( rd_seq != ctx->rd_seq_cache ) {
994 0 : ctx->rd_seq_cache = rd_seq;
995 0 : atomic_store_explicit( ctx->rd_fseq, rd_seq, memory_order_release );
996 : /* early credit return: ring snaprd if it parked on our credits */
997 0 : if( FD_UNLIKELY( stem->sleep ) ) {
998 0 : __atomic_thread_fence( __ATOMIC_SEQ_CST );
999 0 : if( FD_UNLIKELY( FD_VOLATILE_CONST( stem->sleep->credit_bits[ ctx->rd_tile_id>>6 ] ) & (1UL<<(ctx->rd_tile_id&63UL)) ) ) fd_sleep_ring( stem->sleep, ctx->rd_tile_id );
1000 0 : }
1001 0 : }
1002 0 : }
1003 :
1004 : /* recv_credit is called whenever consumer flow control credits are
1005 : refreshed. */
1006 :
1007 : static void
1008 : recv_credit( fd_snapmk_t * ctx,
1009 : ulong out_idx,
1010 : ulong out_seq,
1011 0 : ulong cons_seq ) {
1012 0 : if( out_idx < ctx->zp_cnt ) {
1013 0 : long in_flight = fd_long_max( fd_seq_diff( out_seq, cons_seq ), 0L );
1014 0 : long cr_avail = FD_SNAPMK_ZP_DEPTH - in_flight;
1015 0 : ctx->zp_ready |= fd_ulong_if( cr_avail>0L, 1UL<<out_idx, 0UL );
1016 0 : }
1017 0 : }
1018 :
1019 : /* zp_sync_cr_avail syncs zp_ready and stem credit accounting. */
1020 :
1021 : static void
1022 : zp_sync_cr_avail( fd_snapmk_t * ctx,
1023 0 : fd_stem_context_t const * stem ) {
1024 0 : ulong zp_cnt = ctx->zp_cnt;
1025 0 : for( ulong i=0UL; i<zp_cnt; i++ ) {
1026 0 : if( FD_LIKELY( stem->cr_avail[ i ] ) ) continue; /* stem already agrees */
1027 0 : if( FD_LIKELY( !fd_ulong_extract_bit( ctx->zp_ready, (int)i ) ) ) continue;
1028 0 : ulong cons = fd_fseq_query( ctx->zp_cons_fseq[ i ] );
1029 0 : long in_flight = fd_long_max( fd_seq_diff( stem->seqs[ i ], cons ), 0L );
1030 0 : ulong cr_avail = (ulong)fd_long_max( (long)FD_SNAPMK_ZP_DEPTH-in_flight, 0L );
1031 0 : stem->cr_avail[ i ] = cr_avail;
1032 0 : if( FD_UNLIKELY( !cr_avail ) ) ctx->zp_ready &= ~fd_ulong_mask_bit( (int)i );
1033 0 : }
1034 0 : }
1035 :
1036 : /* clean_overruns recovers from torn cache reads by downstream snapzp tiles.
1037 : Marks the affected accounts as 'not visited' so they are retried later. */
1038 :
1039 : static void
1040 0 : clean_overruns( fd_snapmk_t * ctx ) {
1041 0 : fd_backup_overrun_t * q = ctx->overrun;
1042 0 : for(;;) {
1043 0 : uint pos = q->tail;
1044 0 : fd_backup_overrun_slot_t * slot = &q->slot[ pos & (FD_BACKUP_OVERRUN_DEPTH-1U) ];
1045 0 : if( FD_LIKELY( __atomic_load_n( &slot->seq, __ATOMIC_ACQUIRE )!=pos ) ) break;
1046 :
1047 0 : uint acc_idx = slot->acc_idx;
1048 0 : __atomic_store_n( &slot->seq, pos+FD_BACKUP_OVERRUN_DEPTH-1U, __ATOMIC_RELEASE );
1049 0 : q->tail = pos+1U;
1050 :
1051 0 : fd_backup_visited_remove( ctx->visited_set, (ulong)acc_idx );
1052 0 : }
1053 0 : }
1054 :
1055 : /* check_credit runs every run loop iteration. It specifies custom flow
1056 : control behavior. */
1057 :
1058 : static void
1059 : check_credit( fd_snapmk_t * ctx,
1060 : fd_stem_context_t * stem,
1061 : int * charge_busy,
1062 0 : int * is_backpressured ) {
1063 0 : (void)stem; (void)charge_busy; (void)is_backpressured;
1064 :
1065 0 : if( FD_LIKELY( ctx->state!=SNAPMK_STATE_ACCDB_DISK ) ) {
1066 0 : clean_overruns( ctx );
1067 0 : }
1068 :
1069 0 : if( FD_LIKELY( ctx->state!=SNAPMK_STATE_IDLE &&
1070 0 : ctx->state!=SNAPMK_STATE_SLEEP ) ) {
1071 0 : zp_sync_cr_avail( ctx, stem );
1072 0 : }
1073 :
1074 0 : switch( ctx->state ) {
1075 0 : case SNAPMK_STATE_IDLE:
1076 0 : case SNAPMK_STATE_SLEEP:
1077 0 : break;
1078 :
1079 : /* these state send jobs to snapzp tiles */
1080 0 : case SNAPMK_STATE_ACCDB_DISK:
1081 0 : rd_ack( ctx, stem );
1082 0 : if( FD_UNLIKELY( ctx->disk_out_idx>=0 ) ) {
1083 0 : if( FD_UNLIKELY( !fd_ulong_extract_bit( ctx->zp_ready, ctx->disk_out_idx ) ) ) {
1084 0 : *is_backpressured = 1;
1085 0 : return;
1086 0 : }
1087 0 : }
1088 0 : __attribute__((fallthrough));
1089 0 : case SNAPMK_STATE_START:
1090 0 : case SNAPMK_STATE_ACCDB_CACHE:
1091 0 : case SNAPMK_STATE_ACCDB_DELTA:
1092 0 : if( FD_UNLIKELY( !ctx->zp_ready ) ) {
1093 0 : *is_backpressured = 1;
1094 0 : return;
1095 0 : }
1096 0 : *is_backpressured = 0; /* undo stem backpressure */
1097 0 : break;
1098 :
1099 : /* these states broadcast */
1100 0 : case SNAPMK_STATE_ACCDB_CACHE_FLUSH:
1101 0 : case SNAPMK_STATE_ACCDB_CACHE_FINISH:
1102 0 : case SNAPMK_STATE_ACCDB_DISK_FLUSH:
1103 0 : case SNAPMK_STATE_ACCDB_DISK_FINISH:
1104 0 : case SNAPMK_STATE_ACCDB_DELTA_FLUSH:
1105 0 : case SNAPMK_STATE_ACCDB_DELTA_FINISH: {
1106 : /* wait for snapzp tiles to acknowledge zp_barrier[*] */
1107 0 : *is_backpressured = 0;
1108 0 : for( ulong i=0UL; i < ctx->zp_cnt; i++ ) {
1109 0 : if( FD_UNLIKELY( fd_seq_lt( fd_fseq_query( ctx->zp_cons_fseq[ i ] ), ctx->zp_barrier[ i ] ) ) ) {
1110 0 : *is_backpressured = 1;
1111 0 : return;
1112 0 : }
1113 0 : }
1114 0 : break;
1115 0 : }
1116 0 : default:
1117 : /* use default backpressure mechanism */
1118 0 : break;
1119 0 : }
1120 0 : }
1121 :
1122 : /* snapmk_tar_headers writes out the first few fixed parts of a snapshot
1123 : file. */
1124 :
1125 : static void
1126 0 : snapmk_tar_headers( fd_snapmk_t * ctx ) {
1127 0 : ulong slot = ctx->bank->f.slot;
1128 :
1129 0 : ctx->raw_buf.pos = ctx->raw_buf.size = 0UL;
1130 0 : uchar * p = ctx->raw;
1131 0 : fd_tar_meta_t meta;
1132 :
1133 0 : fd_backup_tar_file_hdr( &meta, 5UL );
1134 0 : fd_cstr_ncpy( meta.name, "version", sizeof(meta.name) );
1135 0 : fd_tar_meta_set_chksum( &meta );
1136 0 : memcpy( p, &meta, sizeof(fd_tar_meta_t) );
1137 0 : p += sizeof(fd_tar_meta_t);
1138 :
1139 0 : memcpy( p, "1.2.0", 5UL );
1140 0 : memset( p+5, 0, 512UL-5UL );
1141 0 : p += 512UL;
1142 :
1143 0 : fd_backup_tar_dir_hdr( &meta );
1144 0 : fd_cstr_ncpy( meta.name, "snapshots/", sizeof(meta.name) );
1145 0 : fd_tar_meta_set_chksum( &meta );
1146 0 : memcpy( p, &meta, sizeof(fd_tar_meta_t) );
1147 0 : p += sizeof(fd_tar_meta_t);
1148 :
1149 0 : fd_backup_tar_dir_hdr( &meta );
1150 0 : fd_cstr_printf_check( meta.name, sizeof(meta.name), NULL, "snapshots/%lu/", slot );
1151 0 : fd_tar_meta_set_chksum( &meta );
1152 0 : memcpy( p, &meta, sizeof(fd_tar_meta_t) );
1153 0 : p += sizeof(fd_tar_meta_t);
1154 :
1155 0 : ctx->manifest_sz = ctx->manifest_writer->serialized_sz;
1156 0 : fd_backup_tar_file_hdr( &meta, ctx->manifest_sz );
1157 0 : fd_cstr_printf_check( meta.name, sizeof(meta.name), NULL, "snapshots/%lu/%lu", slot, slot );
1158 0 : fd_tar_meta_set_chksum( &meta );
1159 0 : memcpy( p, &meta, sizeof(fd_tar_meta_t) );
1160 0 : p += sizeof(fd_tar_meta_t);
1161 0 : ctx->raw_buf.size = (ulong)( p - ctx->raw );
1162 0 : ctx->manifest_pad = fd_ulong_align_up( ctx->manifest_sz, 512UL ) - ctx->manifest_sz;
1163 :
1164 0 : zip_flush( ctx, ZSTD_e_end );
1165 0 : }
1166 :
1167 : /* snapmk_manifest_chunk writes out a chunk of snapshot manifest data.
1168 : Returns 1 if there is more work to do, 0 if the snapshot manifest was
1169 : fully written. */
1170 :
1171 : static int
1172 0 : snapmk_manifest_chunk( fd_snapmk_t * ctx ) {
1173 0 : if( FD_UNLIKELY( ctx->raw_buf.size + FD_SSMANIFEST_BUF_MIN > RAW_BUF_SZ ) ) {
1174 0 : zip_flush( ctx, ZSTD_e_continue );
1175 0 : return 1;
1176 0 : }
1177 0 : ulong buf_rem = RAW_BUF_SZ - ctx->raw_buf.size;
1178 0 : ulong chunk_sz = fd_snap_manifest_serialize(
1179 0 : ctx->manifest_writer,
1180 0 : ctx->raw + ctx->raw_buf.size,
1181 0 : buf_rem );
1182 0 : ctx->raw_buf.size += chunk_sz;
1183 0 : if( FD_LIKELY( chunk_sz ) ) return 1;
1184 :
1185 : /* Done compressing manifest */
1186 0 : zip_flush( ctx, ZSTD_e_continue );
1187 0 : if( ctx->manifest_pad ) {
1188 0 : fd_memset( ctx->raw, 0, ctx->manifest_pad );
1189 0 : ctx->raw_buf.size = ctx->manifest_pad;
1190 0 : }
1191 0 : zip_flush( ctx, ZSTD_e_end );
1192 0 : zip_align( ctx );
1193 0 : ctx->accounts_start_time = fd_log_wallclock();
1194 0 : return 0;
1195 0 : }
1196 :
1197 : /* snapmk_accdb_cache schedules accdb cache work. Returns 1 if there is
1198 : more work to do, 0 otherwise. */
1199 :
1200 : static int
1201 : snapmk_accdb_cache( fd_snapmk_t * ctx,
1202 0 : fd_stem_context_t * stem ) {
1203 0 : ulong out_idx = zp_rr_next( ctx );
1204 0 : fd_backup_cache_msg_t * frag = ctx->scan_batch;
1205 0 : frag = fd_backup_cache_scan( ctx->acc_cache, frag );
1206 0 : if( FD_UNLIKELY( !frag ) ) return 0;
1207 :
1208 : /* remove duplicates
1209 : first pass (fast), ILP-friendly/vectorizable check */
1210 0 : for( ulong i=0UL; i<FD_BACKUP_CACHE_PARA; i++ ) {
1211 0 : uint acc_idx = frag->acc_idx[ i ];
1212 0 : if( acc_idx==UINT_MAX ) continue;
1213 0 : if( FD_UNLIKELY( fd_backup_visited_test( ctx->visited_set, (ulong)acc_idx ) ) ) {
1214 0 : frag->acc_idx[ i ] = UINT_MAX;
1215 0 : }
1216 0 : }
1217 :
1218 : /* second pass: intra-batch conflict detect */
1219 0 : for( ulong i=0UL; i<FD_BACKUP_CACHE_PARA; i++ ) {
1220 0 : uint acc_idx = frag->acc_idx[ i ];
1221 0 : if( acc_idx==UINT_MAX ) continue;
1222 0 : if( FD_UNLIKELY( fd_backup_visited_test( ctx->visited_set, (ulong)acc_idx ) ) ) {
1223 0 : frag->acc_idx[ i ] = UINT_MAX;
1224 0 : memset( frag->pubkey[ i ].uc, 0, sizeof(fd_pubkey_t) );
1225 0 : continue;
1226 0 : }
1227 0 : fd_backup_visited_insert( ctx->visited_set, (ulong)acc_idx );
1228 0 : }
1229 :
1230 : /* publish a batch of cached accounts */
1231 0 : ulong chunk;
1232 0 : void * payload = zp_alloc( ctx, (ulong)out_idx, sizeof(fd_backup_cache_msg_t), &chunk );
1233 0 : fd_memcpy( payload, frag, sizeof(fd_backup_cache_msg_t) );
1234 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_ACC_CACHE, 0, 0, 0 );
1235 0 : zp_publish( ctx, stem, (ulong)out_idx, 0UL, chunk, sizeof(fd_backup_cache_msg_t), ctl, 0UL, 0UL );
1236 :
1237 0 : return 1;
1238 0 : }
1239 :
1240 : /* snapmk_accdb_delta drains a batch of incremental snapshot accounts
1241 : and passes them to snapzp for a compression job. */
1242 :
1243 : static int
1244 : snapmk_accdb_delta( fd_snapmk_t * ctx,
1245 0 : fd_stem_context_t * stem ) {
1246 0 : ulong out_idx = zp_rr_next( ctx );
1247 :
1248 0 : fd_backup_delta_msg_t batch = {0};
1249 0 : while( batch.cnt<FD_BACKUP_CACHE_PARA ) {
1250 0 : if( FD_UNLIKELY( ctx->delta.ele_idx==UINT_MAX ) ) {
1251 0 : if( FD_UNLIKELY( ctx->delta.chain_idx>=ctx->delta.chain_cnt ) ) break;
1252 0 : ctx->delta.ele_idx = __atomic_load_n( &ctx->delta.chain[ ctx->delta.chain_idx++ ], __ATOMIC_ACQUIRE );
1253 0 : continue;
1254 0 : }
1255 0 : fd_accdb_delta_t const * cur = &ctx->delta.pool[ ctx->delta.ele_idx ];
1256 0 : ctx->delta.ele_idx = __atomic_load_n( &cur->next, __ATOMIC_RELAXED );
1257 0 : fd_memcpy( &batch.pubkey[ batch.cnt ], cur->pubkey, sizeof(fd_pubkey_t) );
1258 0 : batch.cnt++;
1259 0 : }
1260 0 : if( FD_UNLIKELY( !batch.cnt ) ) return 0;
1261 0 : ctx->delta.ele_cnt += batch.cnt;
1262 :
1263 0 : ulong chunk;
1264 0 : void * payload = zp_alloc( ctx, out_idx, sizeof(fd_backup_delta_msg_t), &chunk );
1265 0 : fd_memcpy( payload, &batch, sizeof(fd_backup_delta_msg_t) );
1266 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_ACC_DELTA, 0, 0, 0 );
1267 0 : zp_publish( ctx, stem, out_idx, 0UL, chunk, sizeof(fd_backup_delta_msg_t), ctl, 0UL, 0UL );
1268 :
1269 0 : return 1;
1270 0 : }
1271 :
1272 : /* snapmk_replay_sleep sleeps until the replay tile publishes a new
1273 : snapshot command or IDLE_SLEEP nanoseconds pass. */
1274 :
1275 : static void
1276 0 : snapmk_replay_sleep( fd_snapmk_t * ctx ) {
1277 0 : struct timespec const ts = { .tv_sec = (IDLE_SLEEP)/(long)1e9, .tv_nsec = (IDLE_SLEEP)%(long)1e9 };
1278 0 : long res = syscall( SYS_futex, (uint *)ctx->replay_in_seq_prod, FUTEX_WAIT, (uint)ctx->replay_in_seq_cons, &ts );
1279 0 : if( res==0 || (res==-1 && errno==EAGAIN) ) {
1280 : /* stop sleeping */
1281 0 : ctx->state = SNAPMK_STATE_IDLE;
1282 0 : ctx->idle_iter = 0UL;
1283 0 : } else if( res==-1 && errno!=ETIMEDOUT ) {
1284 0 : FD_LOG_ERR(( "FUTEX_WAIT failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1285 0 : }
1286 0 : }
1287 :
1288 : /* snapmk_msg_alloc allocates space for a snapmk_out payload. */
1289 :
1290 : static fd_snapmk_msg_t *
1291 0 : snapmk_msg_alloc( fd_snapmk_t * ctx ) {
1292 0 : return fd_chunk_to_laddr( ctx->out.mem, ctx->out.chunk );
1293 0 : }
1294 :
1295 : /* wake all reliable consumers by unconditionally waking them (snapsv
1296 : tiles). This is inefficient (does a FUTEX_WAKE syscall), but
1297 : acceptable given the very low frag production rate. */
1298 :
1299 : static void
1300 : snapmk_out_wake( fd_snapmk_t * ctx,
1301 0 : fd_stem_context_t * stem ) {
1302 0 : fd_mcache_seq_update( ctx->out.seq_prod, stem->seqs[ ctx->out.out_idx ] );
1303 0 : if( FD_UNLIKELY( -1==syscall( SYS_futex, (uint *)ctx->out.seq_prod, FUTEX_WAKE, INT_MAX, NULL, NULL, 0 ) ) ) {
1304 0 : FD_LOG_ERR(( "FUTEX_WAKE failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1305 0 : }
1306 0 : }
1307 :
1308 : /* snapmk_msg_publish publishes a msg and frag on snapmk_out. */
1309 :
1310 : static void
1311 : snapmk_msg_publish( fd_snapmk_t * ctx,
1312 : fd_stem_context_t * stem,
1313 0 : ulong msg_type ) {
1314 0 : ulong sz;
1315 0 : switch( msg_type ) { /* known at compile time */
1316 0 : case FD_SNAPMK_MSG_CREATED: sz = sizeof(fd_snapmk_msg_created_t); break;
1317 0 : case FD_SNAPMK_MSG_DELETED: sz = sizeof(fd_snapmk_msg_deleted_t); break;
1318 0 : case FD_SNAPMK_MSG_STARTED: sz = sizeof(fd_snapmk_msg_started_t); break;
1319 0 : case FD_SNAPMK_MSG_FAILED: sz = sizeof(fd_snapmk_msg_failed_t); break;
1320 0 : case FD_SNAPMK_MSG_FOUND: sz = sizeof(fd_snapmk_msg_found_t); break;
1321 0 : default:
1322 0 : FD_LOG_CRIT(( "invalid msg_type %lu", msg_type ));
1323 0 : }
1324 0 : ulong chunk = ctx->out.chunk;
1325 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
1326 0 : fd_stem_publish( stem, ctx->out.out_idx, msg_type, chunk, sz, 0UL, 0UL, tspub );
1327 0 : ctx->out.chunk = fd_dcache_compact_next( chunk, sz, ctx->out.chunk0, ctx->out.wmark );
1328 0 : snapmk_out_wake( ctx, stem );
1329 0 : }
1330 :
1331 : static int
1332 0 : snapshot_zstd_strategy( ZSTD_strategy strategy ) {
1333 0 : switch( strategy ) {
1334 0 : case ZSTD_fast: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_FAST;
1335 0 : case ZSTD_dfast: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_DFAST;
1336 0 : case ZSTD_greedy: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_GREEDY;
1337 0 : case ZSTD_lazy: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_LAZY;
1338 0 : case ZSTD_lazy2: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_LAZY2;
1339 0 : case ZSTD_btlazy2: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_BTLAZY2;
1340 0 : case ZSTD_btopt: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_BTOPT;
1341 0 : case ZSTD_btultra: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_BTULTRA;
1342 0 : case ZSTD_btultra2: return FD_EVENT_SNAPSHOT_CREATED_ZSTD_STRATEGY_BTULTRA2;
1343 0 : default: FD_LOG_CRIT(( "unexpected Zstandard strategy %i", (int)strategy ));
1344 0 : }
1345 0 : }
1346 :
1347 : static void
1348 : snapshot_event_init( fd_snapmk_t const * ctx,
1349 : int result,
1350 0 : fd_event_snapshot_created_t * ev ) {
1351 0 : *ev = (fd_event_snapshot_created_t) {
1352 0 : .result = result,
1353 0 : .slot = ctx->bank->f.slot,
1354 0 : .base_slot = ctx->incremental ? ctx->base_slot : 0UL,
1355 0 : .epoch = ctx->bank->f.epoch,
1356 0 : .block_height = ctx->bank->f.block_height,
1357 0 : .capitalization = ctx->bank->f.capitalization,
1358 0 : .transaction_count = ctx->bank->f.parent_txn_count + ctx->bank->f.txn_count,
1359 0 : .start_time = (ulong)ctx->start_time,
1360 0 : .accounts_start_time = (ulong)ctx->accounts_start_time,
1361 0 : .end_time = (ulong)ctx->end_time
1362 0 : };
1363 0 : if( FD_LIKELY( result==FD_EVENT_SNAPSHOT_CREATED_RESULT_SUCCESS ) ) {
1364 0 : ev->filename_len = fd_cstr_nlen( ctx->final_name, sizeof(ctx->final_name) );
1365 0 : fd_memcpy( ev->filename, ctx->final_name, ev->filename_len );
1366 0 : }
1367 0 : fd_memcpy( ev->hash, ctx->snap_hash, sizeof(ev->hash) );
1368 0 : fd_memcpy( ev->bank_hash, ctx->bank->f.bank_hash.uc, sizeof(ev->bank_hash) );
1369 0 : fd_memcpy( ev->block_id, ctx->bank->f.block_id.uc, sizeof(ev->block_id) );
1370 0 : fd_memcpy( ev->bank_accounts_lthash, ctx->bank->f.lthash.bytes, sizeof(ev->bank_accounts_lthash) );
1371 0 : ev->bank_accounts_lthash_len = sizeof(ev->bank_accounts_lthash);
1372 0 : ZSTD_compressionParameters zstd_params = ZSTD_getCParams( FD_BACKUP_ZSTD_LEVEL, ZSTD_CONTENTSIZE_UNKNOWN, 0UL );
1373 0 : ev->zstd_window_size = 1UL << zstd_params.windowLog;
1374 0 : ev->zstd_strategy = snapshot_zstd_strategy( zstd_params.strategy );
1375 0 : }
1376 :
1377 : /* after_credit runs every run loop iteration, provided that all out
1378 : links have at least STEM_BURST credit available, or check_credit
1379 : passed. */
1380 :
1381 : static void
1382 : after_credit( fd_snapmk_t * ctx,
1383 : fd_stem_context_t * stem,
1384 : int * poll_in,
1385 0 : int * charge_busy ) {
1386 0 : (void)poll_in;
1387 :
1388 0 : switch( ctx->state ) {
1389 0 : case SNAPMK_STATE_IDLE:
1390 0 : if( FD_UNLIKELY( ++ctx->idle_iter >= IDLE_THRES ) ) {
1391 0 : ctx->state = SNAPMK_STATE_SLEEP;
1392 0 : ctx->idle_iter = 0UL;
1393 0 : }
1394 0 : *charge_busy = 0;
1395 0 : break;
1396 0 : case SNAPMK_STATE_SLEEP: {
1397 0 : snapmk_replay_sleep( ctx );
1398 0 : *charge_busy = 0;
1399 0 : break;
1400 0 : }
1401 0 : case SNAPMK_STATE_START: {
1402 0 : ulong zp_cnt = ctx->zp_cnt;
1403 0 : int did_work = 0;
1404 0 : for( ulong i=0UL; i<zp_cnt; i++ ) {
1405 : /* FIXME use find_lsb? */
1406 0 : if( !fd_ulong_extract_bit( ctx->zp_flush_pending, (int)i ) ) continue;
1407 0 : if( !stem->cr_avail[ i ] ) continue;
1408 0 : ulong chunk;
1409 0 : fd_backup_start_msg_t * frag = zp_alloc( ctx, i, sizeof(fd_backup_start_msg_t), &chunk );
1410 0 : memset( frag, 0, sizeof(fd_backup_start_msg_t) );
1411 0 : frag->slot = ctx->bank->f.slot;
1412 0 : frag->slot_lo = ctx->incremental ? ctx->base_slot+1UL : 0UL;
1413 0 : frag->snap_idx = ctx->snap_idx;
1414 0 : frag->fork_id = ctx->bank->accdb_fork_id.val;
1415 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_START, 0, 0, 0 );
1416 0 : zp_publish( ctx, stem, i, 0UL, chunk, sizeof(fd_backup_start_msg_t), ctl, 0UL, 0UL );
1417 0 : ctx->zp_flush_pending &= ~fd_ulong_mask_bit( (int)i );
1418 0 : ctx->zp_ready &= ~fd_ulong_mask_bit( (int)i );
1419 0 : *charge_busy = 1;
1420 0 : did_work = 1;
1421 0 : }
1422 : /* all snapzp tiles have been told to start; begin writing the tar */
1423 0 : if( (!ctx->zp_flush_pending) & (!did_work) ) {
1424 0 : ctx->state = SNAPMK_STATE_TAR_HEADERS;
1425 0 : }
1426 0 : break;
1427 0 : }
1428 0 : case SNAPMK_STATE_TAR_HEADERS:
1429 0 : *charge_busy = 1;
1430 0 : snapmk_tar_headers( ctx );
1431 0 : ctx->state = SNAPMK_STATE_MANIFEST;
1432 0 : break;
1433 0 : case SNAPMK_STATE_MANIFEST:
1434 0 : *charge_busy = 1;
1435 0 : if( FD_UNLIKELY( !snapmk_manifest_chunk( ctx ) ) ) {
1436 0 : ctx->state = ctx->incremental ? SNAPMK_STATE_ACCDB_DELTA : SNAPMK_STATE_ACCDB_CACHE;
1437 0 : }
1438 0 : break;
1439 0 : case SNAPMK_STATE_ACCDB_CACHE: {
1440 0 : *charge_busy = 1;
1441 0 : if( FD_UNLIKELY( !snapmk_accdb_cache( ctx, stem ) ) ) {
1442 0 : barrier_install( ctx, stem );
1443 0 : broadcast_prepare( ctx );
1444 0 : ctx->state = SNAPMK_STATE_ACCDB_CACHE_FLUSH;
1445 0 : }
1446 0 : break;
1447 0 : }
1448 0 : case SNAPMK_STATE_ACCDB_CACHE_FLUSH: {
1449 : /* done reading from cache; now tell snapzp workers to end their
1450 : Zstandard frames */
1451 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_FLUSH, 0, 0, 0 );
1452 0 : if( broadcast( ctx, stem, ctl, charge_busy ) ) {
1453 0 : ctx->state = SNAPMK_STATE_ACCDB_CACHE_FINISH;
1454 0 : }
1455 0 : break;
1456 0 : }
1457 0 : case SNAPMK_STATE_ACCDB_CACHE_FINISH:
1458 0 : *charge_busy = 1;
1459 0 : clean_overruns( ctx );
1460 : /* done snapshotting accdb cache;
1461 : now instruct snaprd tile to start reading accdb disk data */
1462 0 : ctx->state = SNAPMK_STATE_ACCDB_DISK;
1463 0 : atomic_fetch_add_explicit( ctx->rd_ctl, 1UL, memory_order_release );
1464 0 : break;
1465 0 : case SNAPMK_STATE_ACCDB_DISK:
1466 : /* driven by returnable_frag */
1467 0 : break;
1468 0 : case SNAPMK_STATE_ACCDB_DISK_FLUSH: {
1469 : /* done reading from disk; now tell snapzp workers to end their
1470 : Zstandard frames */
1471 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_FLUSH, 0, 0, 0 );
1472 0 : if( broadcast( ctx, stem, ctl, charge_busy ) ) {
1473 0 : ctx->state = SNAPMK_STATE_ACCDB_DISK_FINISH;
1474 0 : }
1475 0 : break;
1476 0 : }
1477 0 : case SNAPMK_STATE_ACCDB_DELTA: {
1478 0 : *charge_busy = 1;
1479 0 : if( FD_UNLIKELY( !snapmk_accdb_delta( ctx, stem ) ) ) {
1480 0 : barrier_install( ctx, stem );
1481 0 : broadcast_prepare( ctx );
1482 0 : ctx->state = SNAPMK_STATE_ACCDB_DELTA_FLUSH;
1483 0 : }
1484 0 : break;
1485 0 : }
1486 0 : case SNAPMK_STATE_ACCDB_DELTA_FLUSH: {
1487 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_FLUSH, 0, 0, 0 );
1488 0 : if( broadcast( ctx, stem, ctl, charge_busy ) ) {
1489 0 : ctx->state = SNAPMK_STATE_ACCDB_DELTA_FINISH;
1490 0 : }
1491 0 : break;
1492 0 : }
1493 0 : case SNAPMK_STATE_ACCDB_DISK_FINISH:
1494 0 : case SNAPMK_STATE_ACCDB_DELTA_FINISH: {
1495 : /* accounts done, snapzp workers idle; now process status cache */
1496 0 : long file_sz = lseek( ctx->snap_fd, 0L, SEEK_END );
1497 0 : if( FD_UNLIKELY( file_sz<0L ) ) {
1498 0 : FD_LOG_ERR(( "lseek failed: %i-%s", errno, fd_io_strerror( errno ) ));
1499 0 : }
1500 0 : ctx->pool_sz[ ctx->snap_idx ] = (ulong)file_sz;
1501 0 : snapmk_status_cache_prepare( ctx );
1502 0 : ctx->state = SNAPMK_STATE_STATUS_CACHE;
1503 0 : break;
1504 0 : }
1505 0 : case SNAPMK_STATE_STATUS_CACHE:
1506 : /* process status cache piece wise */
1507 0 : *charge_busy = 1;
1508 0 : if( FD_UNLIKELY( !snapmk_status_cache( ctx ) ) ) {
1509 0 : ctx->state = SNAPMK_STATE_EOF_MARKER;
1510 0 : }
1511 0 : break;
1512 0 : case SNAPMK_STATE_EOF_MARKER:
1513 : /* all data written to snapshot, file not yet structurally clean;
1514 : now write end-of-snapshot marker */
1515 0 : *charge_busy = 1;
1516 0 : snapmk_eof_marker ( ctx );
1517 0 : snapmk_done_rename( ctx );
1518 0 : broadcast_prepare ( ctx );
1519 0 : ctx->state = SNAPMK_STATE_DONE;
1520 0 : break;
1521 0 : case SNAPMK_STATE_DONE: {
1522 : /* snapshot file complete; now broadcast "done" signal to all worker
1523 : tiles, and notify accdb/replay to resume */
1524 0 : ulong ctl = fd_frag_meta_ctl( FD_BACKUP_ORIG_DONE, 0, 1, 0 );
1525 0 : if( broadcast( ctx, stem, ctl, charge_busy ) ) {
1526 0 : snapshot_sync_transition( ctx, FD_ACCDB_SNAPSHOT_SYNC_RUNNING, FD_ACCDB_SNAPSHOT_SYNC_DONE, FD_ACCDB_SNAPSHOT_SYNC_IDLE );
1527 0 : fd_snapmk_msg_created_t * msg = &snapmk_msg_alloc( ctx )->created;
1528 0 : *msg = (fd_snapmk_msg_created_t) {
1529 0 : .slot = ctx->bank->f.slot,
1530 0 : .base_slot = ctx->incremental ? ctx->base_slot : ULONG_MAX,
1531 0 : .sz = ctx->final_sz,
1532 0 : .pool_idx = ctx->snap_idx
1533 0 : };
1534 0 : fd_cstr_ncpy( msg->name, ctx->final_name, sizeof(msg->name) );
1535 :
1536 0 : ulong account_count = 0UL;
1537 0 : ulong accounts_size = 0UL;
1538 0 : ulong tombstone_count = 0UL;
1539 0 : ulong cached_accounts_count = 0UL;
1540 0 : ulong disk_accounts_count = 0UL;
1541 0 : ulong zstd_data_frame_count = ctx->zstd_data_frame_cnt;
1542 0 : ulong zstd_padding_size = ctx->zstd_padding_sz;
1543 0 : ulong uncompressed_size = ctx->uncompressed_sz;
1544 0 : ulong compress_ticks = ctx->metrics.compress_ticks - ctx->snapshot_compress_ticks0;
1545 0 : ulong io_blocked_ticks = ctx->metrics.io_blocked_ticks - ctx->snapshot_io_blocked_ticks0;
1546 0 : for( ulong i=0UL; i<ctx->zp_cnt; i++ ) {
1547 0 : fd_backup_worker_stats_t const * stats = &ctx->stats->worker[ i ];
1548 0 : uncompressed_size += __atomic_load_n( &stats->uncompressed_sz, __ATOMIC_ACQUIRE );
1549 0 : account_count += __atomic_load_n( &stats->account_cnt, __ATOMIC_RELAXED );
1550 0 : accounts_size += __atomic_load_n( &stats->account_sz, __ATOMIC_RELAXED );
1551 0 : tombstone_count += __atomic_load_n( &stats->tombstone_cnt, __ATOMIC_RELAXED );
1552 0 : cached_accounts_count += __atomic_load_n( &stats->cached_account_cnt, __ATOMIC_RELAXED );
1553 0 : disk_accounts_count += __atomic_load_n( &stats->disk_account_cnt, __ATOMIC_RELAXED );
1554 0 : zstd_data_frame_count += __atomic_load_n( &stats->zstd_data_frame_cnt, __ATOMIC_RELAXED );
1555 0 : zstd_padding_size += __atomic_load_n( &stats->zstd_padding_sz, __ATOMIC_RELAXED );
1556 0 : compress_ticks += __atomic_load_n( &stats->compress_ticks, __ATOMIC_RELAXED );
1557 0 : io_blocked_ticks += __atomic_load_n( &stats->io_blocked_ticks, __ATOMIC_RELAXED );
1558 0 : }
1559 0 : FD_TEST( account_count==tombstone_count+cached_accounts_count+disk_accounts_count );
1560 0 : fd_event_snapshot_created_t ev[1];
1561 0 : snapshot_event_init( ctx, FD_EVENT_SNAPSHOT_CREATED_RESULT_SUCCESS, ev );
1562 0 : ev->account_count = account_count;
1563 0 : ev->tombstone_count = tombstone_count;
1564 0 : ev->cached_accounts_count = cached_accounts_count;
1565 0 : ev->disk_accounts_count = disk_accounts_count;
1566 0 : ev->manifest_size = ctx->manifest_sz;
1567 0 : ev->accounts_size = accounts_size;
1568 0 : ev->status_cache_size = ctx->status_cache_sz;
1569 0 : ev->compressed_size = ctx->final_sz;
1570 0 : ev->uncompressed_size = uncompressed_size;
1571 0 : ev->zstd_data_frame_count = zstd_data_frame_count;
1572 0 : ev->zstd_padding_size = zstd_padding_size;
1573 0 : ev->duration_compress_nanos = fd_metrics_convert_ticks_to_nanoseconds( compress_ticks );
1574 0 : ev->duration_io_blocked_nanos = fd_metrics_convert_ticks_to_nanoseconds( io_blocked_ticks );
1575 :
1576 0 : snapmk_msg_publish( ctx, stem, FD_SNAPMK_MSG_CREATED );
1577 0 : fd_event_report_snapshot_created( ev );
1578 :
1579 0 : ctx->state = SNAPMK_STATE_SLEEP;
1580 0 : ctx->snap_idx = UINT_MAX;
1581 0 : }
1582 0 : break;
1583 0 : }
1584 0 : case SNAPMK_STATE_FAIL: {
1585 0 : fd_event_snapshot_created_t ev[1];
1586 0 : snapshot_event_init( ctx, FD_EVENT_SNAPSHOT_CREATED_RESULT_TOO_MANY_INCREMENTAL_ACCOUNTS, ev );
1587 0 : snapmk_msg_alloc( ctx )->failed = (fd_snapmk_msg_failed_t) {
1588 0 : .slot = ctx->bank->f.slot,
1589 0 : .base_slot = ctx->base_slot
1590 0 : };
1591 0 : snapmk_msg_publish( ctx, stem, FD_SNAPMK_MSG_FAILED );
1592 0 : fd_event_report_snapshot_created( ev );
1593 0 : ctx->snap_idx = UINT_MAX;
1594 0 : ctx->state = SNAPMK_STATE_SLEEP;
1595 0 : *charge_busy = 1;
1596 0 : break;
1597 0 : }
1598 0 : case SNAPMK_STATE_STARTUP: /* wait for startup */
1599 0 : if( FD_UNLIKELY( fd_startup_gate_idle( ctx->startup_gate ) ) ) {
1600 0 : ctx->state = SNAPMK_STATE_STARTUP_BURST;
1601 0 : fd_snap_pool_recover( ctx->snap_dir_fd, ctx->snap_dir, ctx->pool, ctx->snap_max );
1602 0 : }
1603 0 : break;
1604 0 : case SNAPMK_STATE_STARTUP_BURST: { /* burst publish pre-existing snaps */
1605 0 : if( FD_UNLIKELY( ctx->startup_pool_idx >= ctx->snap_max ) ) {
1606 0 : ctx->state = SNAPMK_STATE_SLEEP;
1607 0 : break;
1608 0 : }
1609 0 : ulong snap_idx = ctx->startup_pool_idx++;
1610 0 : fd_backup_inode_t * inode = &ctx->pool[ snap_idx ];
1611 0 : struct stat st;
1612 0 : if( FD_UNLIKELY( 0!=syscall( SYS_fstat, FD_SNAP_FD( snap_idx ), &st ) ) ) break;
1613 0 : ctx->pool_sz[ snap_idx ] = (ulong)st.st_size;
1614 0 : if( FD_UNLIKELY( inode->full_slot==ULONG_MAX ) ) break;
1615 0 : fd_snapmk_msg_found_t * msg = &snapmk_msg_alloc( ctx )->found;
1616 0 : *msg = (fd_snapmk_msg_found_t) {
1617 0 : .slot = inode->incr_slot!=ULONG_MAX ? inode->incr_slot : inode->full_slot,
1618 0 : .base_slot = inode->incr_slot!=ULONG_MAX ? inode->full_slot : ULONG_MAX,
1619 0 : .pool_idx = (uint)snap_idx,
1620 0 : .fs_timestamp = LONG_MAX
1621 0 : };
1622 0 : fd_cstr_ncpy( msg->name, inode->name, sizeof(msg->name) );
1623 0 : msg->sz = (ulong)st.st_size;
1624 0 : msg->fs_timestamp = ((long)st.st_mtim.tv_sec*(long)1e9) + (long)st.st_mtim.tv_nsec;
1625 0 : snapmk_msg_publish( ctx, stem, FD_SNAPMK_MSG_FOUND );
1626 0 : break;
1627 0 : }
1628 0 : default:
1629 0 : FD_LOG_CRIT(( "invalid state %u", ctx->state ));
1630 0 : }
1631 0 : }
1632 :
1633 : /* snap_pool_select finds a free snapshot file descriptor for
1634 : production. */
1635 :
1636 : static uint
1637 0 : snap_pool_select( fd_snapmk_t * ctx ) {
1638 :
1639 0 : uint slot0 = ctx->incremental ? ctx->snap_full_max : 0U;
1640 0 : uint slot1 = ctx->incremental ? ctx->snap_max : ctx->snap_full_max;
1641 0 : FD_CHECK_ERR( slot0<slot1, "no snapshot file descriptors reserved" );
1642 :
1643 : /* if this snapshot already exists, recreate it */
1644 0 : for( uint i=slot0; i<slot1; i++ ) {
1645 0 : if( FD_UNLIKELY( !strcmp( ctx->pool[ i ].name, ctx->final_name ) ) ) return i;
1646 0 : }
1647 :
1648 0 : uint slot_idx = UINT_MAX;
1649 0 : ulong oldest = ULONG_MAX;
1650 0 : for( uint i=slot0; i<slot1; i++ ) {
1651 0 : if( FD_UNLIKELY( ctx->pool[ i ].full_slot==ULONG_MAX ) ) return i; /* free */
1652 0 : ulong slot = ctx->incremental ? ctx->pool[ i ].incr_slot : ctx->pool[ i ].full_slot;
1653 0 : if( slot<oldest ) {
1654 0 : oldest = slot;
1655 0 : slot_idx = i;
1656 0 : }
1657 0 : }
1658 0 : FD_CHECK_ERR( slot_idx!=UINT_MAX, "no snapshot file descriptor to recycle" );
1659 :
1660 0 : return slot_idx;
1661 0 : }
1662 :
1663 : /* snap_pool_acquire picks a new file descriptor to hold a snapshot
1664 : file. May recycle an existing snapshot. */
1665 :
1666 : static uint
1667 : snap_pool_acquire( fd_snapmk_t * ctx,
1668 0 : fd_stem_context_t * stem ) {
1669 0 : uint snap_pool_idx = snap_pool_select( ctx );
1670 :
1671 0 : if( FD_UNLIKELY( ctx->pool[ snap_pool_idx ].full_slot==ULONG_MAX ) ) return snap_pool_idx; /* free */
1672 :
1673 : /* recycle (signals consumers to unlock) */
1674 :
1675 0 : FD_CHECK_ERR( snap_pool_idx < ctx->snap_max, "invalid snap_pool_idx" );
1676 :
1677 0 : fd_backup_inode_t * inode = &ctx->pool[ snap_pool_idx ];
1678 0 : fd_snapmk_msg_deleted_t * msg = &snapmk_msg_alloc( ctx )->deleted;
1679 0 : *msg = (fd_snapmk_msg_deleted_t) {
1680 0 : .slot = inode->incr_slot!=ULONG_MAX ? inode->incr_slot : inode->full_slot,
1681 0 : .base_slot = inode->incr_slot!=ULONG_MAX ? inode->full_slot : ULONG_MAX,
1682 0 : .pool_idx = snap_pool_idx
1683 0 : };
1684 0 : fd_cstr_ncpy( msg->name, inode->name, sizeof(msg->name) );
1685 0 : snapmk_msg_publish( ctx, stem, FD_SNAPMK_MSG_DELETED );
1686 :
1687 : /* do a blocking wait for the file to become free */
1688 :
1689 0 : int snap_fd = FD_SNAP_FD( snap_pool_idx );
1690 0 : struct flock lock = {
1691 0 : .l_type = F_WRLCK,
1692 0 : .l_whence = SEEK_SET
1693 0 : };
1694 0 : if( FD_UNLIKELY( fcntl( snap_fd, F_SETLKW, &lock ) ) ) {
1695 0 : FD_LOG_ERR(( "fcntl(F_SETLKW, %s) failed: %i-%s",
1696 0 : ctx->pool[ snap_pool_idx ].name, errno, fd_io_strerror( errno ) ));
1697 0 : }
1698 :
1699 0 : FD_LOG_INFO(( "evicting old snapshot file: %s", inode->name ));
1700 0 : char partial_name[ sizeof(inode->name) ];
1701 0 : fd_snap_pool_partial_name( partial_name, snap_pool_idx );
1702 0 : if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->snap_dir_fd, inode->name,
1703 0 : ctx->snap_dir_fd, partial_name, 0U ) ) ) {
1704 0 : FD_LOG_ERR(( "renameat2(%s, %s) failed: %s", inode->name, partial_name, fd_io_strerror( errno ) ));
1705 0 : }
1706 0 : fd_cstr_ncpy( inode->name, partial_name, sizeof(inode->name) );
1707 :
1708 0 : if( FD_UNLIKELY( ftruncate( snap_fd, 0L ) ) ) {
1709 0 : FD_LOG_ERR(( "ftruncate(%s) failed: %s", inode->name, fd_io_strerror( errno ) ));
1710 0 : }
1711 0 : ctx->pool_sz[ snap_pool_idx ] = 0UL;
1712 0 : inode->full_slot = ULONG_MAX;
1713 0 : inode->incr_slot = ULONG_MAX;
1714 0 : return snap_pool_idx;
1715 0 : }
1716 :
1717 : /* snap_start boots the snap production pipeline.
1718 : Returns:
1719 : - 1 if snapshot production was started
1720 : - 0 if system is not ready yet, and start attempt should be retried
1721 : - -1 if attempt was rejected */
1722 :
1723 : static int
1724 : snap_start( fd_snapmk_t * ctx,
1725 : fd_stem_context_t * stem,
1726 0 : fd_replay_snap_start_t const * msg ) {
1727 0 : switch( ctx->state ) {
1728 0 : case SNAPMK_STATE_IDLE:
1729 0 : case SNAPMK_STATE_SLEEP:
1730 0 : break;
1731 0 : case SNAPMK_STATE_STARTUP:
1732 0 : case SNAPMK_STATE_STARTUP_BURST:
1733 0 : return 0; /* not ready yet */
1734 0 : default:
1735 0 : FD_LOG_ERR(( "invariant violation: snapshot creation requested state is %u", ctx->state ));
1736 0 : }
1737 :
1738 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx );
1739 0 : FD_TEST( bank );
1740 0 : ctx->bank = bank;
1741 :
1742 0 : int incremental = msg->slot!=msg->base_slot;
1743 0 : if( FD_UNLIKELY( incremental ) ) {
1744 0 : FD_CHECK_CRIT( msg->base_slot!=ULONG_MAX, "incremental snapshot requested without a base full snapshot" );
1745 0 : ctx->base_slot = msg->base_slot;
1746 0 : } else {
1747 0 : ctx->base_slot = ULONG_MAX;
1748 0 : }
1749 0 : ctx->incremental = incremental;
1750 0 : ctx->start_time = fd_log_wallclock();
1751 0 : ctx->accounts_start_time = 0L;
1752 0 : ctx->end_time = 0L;
1753 0 : fd_blake3_hash( ctx->bank->f.lthash.bytes, FD_LTHASH_LEN_BYTES, ctx->snap_hash );
1754 :
1755 : /* wait for accdb root to match published root */
1756 0 : fd_accdb_fork_id_t root_fork_id = bank->accdb_fork_id;
1757 0 : FD_TEST( root_fork_id.val!=USHORT_MAX );
1758 0 : if( FD_UNLIKELY( __atomic_load_n( &ctx->accdb_root_fork->val, __ATOMIC_ACQUIRE )!=root_fork_id.val ) ) {
1759 0 : return 0; /* not ready */
1760 0 : }
1761 0 : ulong root_generation = __atomic_load_n( &ctx->accdb_shfork[ root_fork_id.val ].generation, __ATOMIC_ACQUIRE );
1762 :
1763 : /* wait for accdb to disable compaction */
1764 0 : ulong sync_req = incremental ? FD_ACCDB_SNAPSHOT_SYNC_START_INCR
1765 0 : : FD_ACCDB_SNAPSHOT_SYNC_START_FULL;
1766 0 : ulong sync_ack = snapshot_sync_request( ctx, FD_ACCDB_SNAPSHOT_SYNC_IDLE, sync_req );
1767 0 : if( FD_UNLIKELY( sync_ack==FD_ACCDB_SNAPSHOT_SYNC_FAIL ) ) {
1768 0 : FD_LOG_WARNING(( "cannot create incremental snapshot, too many accounts changed (increase [snapshots.max_incremental_snapshot_accounts])" ));
1769 0 : snapshot_sync_transition( ctx, FD_ACCDB_SNAPSHOT_SYNC_FAIL, FD_ACCDB_SNAPSHOT_SYNC_DONE, FD_ACCDB_SNAPSHOT_SYNC_IDLE );
1770 0 : ctx->end_time = fd_log_wallclock();
1771 0 : ctx->state = SNAPMK_STATE_FAIL;
1772 0 : return -1;
1773 0 : }
1774 0 : if( FD_UNLIKELY( sync_ack!=FD_ACCDB_SNAPSHOT_SYNC_RUNNING ) ) {
1775 0 : FD_LOG_CRIT(( "unexpected accdb snapshot sync state %lu", sync_ack ));
1776 0 : }
1777 :
1778 : /* user might have changed available snapshots */
1779 0 : fd_snap_pool_recover( ctx->snap_dir_fd, ctx->snap_dir, ctx->pool, ctx->snap_max );
1780 :
1781 : /* final name of snap (during compression has a "partial" name) */
1782 0 : char encoded_hash[ FD_BASE58_ENCODED_32_SZ ];
1783 0 : fd_base58_encode_32( ctx->snap_hash, NULL, encoded_hash );
1784 0 : if( FD_UNLIKELY( incremental ) ) {
1785 0 : FD_TEST( fd_cstr_printf_check( ctx->final_name, FD_SNAP_NAME_MAX, NULL,
1786 0 : "incremental-snapshot-%lu-%lu-%s.tar.zst", ctx->base_slot, ctx->bank->f.slot, encoded_hash ) );
1787 0 : } else {
1788 0 : FD_TEST( fd_cstr_printf_check( ctx->final_name, FD_SNAP_NAME_MAX, NULL,
1789 0 : "snapshot-%lu-%s.tar.zst", ctx->bank->f.slot, encoded_hash ) );
1790 0 : }
1791 :
1792 0 : uint snap_idx = snap_pool_acquire( ctx, stem );
1793 0 : if( FD_UNLIKELY( snap_idx==UINT_MAX ) ) {
1794 0 : snapshot_sync_transition( ctx, FD_ACCDB_SNAPSHOT_SYNC_RUNNING, FD_ACCDB_SNAPSHOT_SYNC_DONE, FD_ACCDB_SNAPSHOT_SYNC_IDLE );
1795 0 : return 0; /* not ready */
1796 0 : }
1797 0 : ctx->snap_idx = snap_idx;
1798 0 : ctx->snap_fd = FD_SNAP_FD( ctx->snap_idx );
1799 :
1800 0 : snapmk_msg_alloc( ctx )->started = (fd_snapmk_msg_started_t) {
1801 0 : .slot = ctx->bank->f.slot,
1802 0 : .base_slot = ctx->base_slot,
1803 0 : .pool_idx = ctx->snap_idx
1804 0 : };
1805 0 : snapmk_msg_publish( ctx, stem, FD_SNAPMK_MSG_STARTED );
1806 :
1807 0 : if( FD_UNLIKELY( ftruncate( ctx->snap_fd, 0L ) ) ) {
1808 0 : FD_LOG_ERR(( "ftruncate(%s) failed: %i-%s", ctx->pool[ ctx->snap_idx ].name, errno, fd_io_strerror( errno ) ));
1809 0 : }
1810 0 : ctx->pool_sz[ ctx->snap_idx ] = 0UL;
1811 0 : if( FD_UNLIKELY( lseek( ctx->snap_fd, 0L, SEEK_SET )<0L ) ) {
1812 0 : FD_LOG_ERR(( "lseek(%s) failed: %i-%s", ctx->pool[ ctx->snap_idx ].name, errno, fd_io_strerror( errno ) ));
1813 0 : }
1814 :
1815 0 : atomic_store_explicit( ctx->file_off_p, 0UL, memory_order_relaxed );
1816 0 : atomic_store_explicit( ctx->appendvec_slot_ticket_p, ctx->bank->f.slot, memory_order_relaxed );
1817 :
1818 : /* compression buffers */
1819 :
1820 0 : ctx->raw_buf.size = 0UL;
1821 0 : ctx->raw_buf.pos = 0UL;
1822 0 : ctx->comp_buf.pos = 0UL;
1823 0 : ctx->comp_buf.size = COMP_BUF_SZ;
1824 0 : ctx->zstd_data_frame_cnt = 0UL;
1825 0 : ctx->zstd_padding_sz = 0UL;
1826 0 : ctx->uncompressed_sz = 0UL;
1827 0 : ctx->snapshot_compress_ticks0 = ctx->metrics.compress_ticks;
1828 0 : ctx->snapshot_io_blocked_ticks0 = ctx->metrics.io_blocked_ticks;
1829 0 : ulong zst_err = ZSTD_CCtx_reset( ctx->zst, ZSTD_reset_session_only );
1830 0 : if( FD_UNLIKELY( ZSTD_isError( zst_err ) ) ) {
1831 0 : FD_LOG_ERR(( "ZSTD_CCtx_reset failed: %s", ZSTD_getErrorName( zst_err ) ));
1832 0 : }
1833 :
1834 : /* misc */
1835 :
1836 0 : ctx->leader = msg->leader;
1837 0 : fd_ssmanifest_writer_init( ctx->manifest_writer, bank, &ctx->leader, ctx->accdb, root_fork_id, ctx->raw );
1838 :
1839 : /* accdb cache/disk parsers */
1840 :
1841 0 : fd_backup_cache_reset( ctx->acc_cache, root_generation );
1842 0 : *ctx->accparse = (fd_snapmk_accparse_t) {
1843 0 : .idx = ctx->acc_cache->idx, /* reset above, incl root_generation */
1844 0 : .acc_keep = 1U,
1845 0 : .visited_set = ctx->visited_set
1846 0 : };
1847 :
1848 0 : ctx->delta.chain_idx = 0UL;
1849 0 : ctx->delta.ele_idx = UINT_MAX;
1850 0 : ctx->delta.ele_cnt = 0UL;
1851 0 : ctx->delta.ele_total = incremental ? __atomic_load_n( &ctx->accdb_shmem->delta.head, __ATOMIC_RELAXED ) : 0UL;
1852 :
1853 0 : visited_set_null( ctx->visited_set );
1854 :
1855 0 : ctx->state = SNAPMK_STATE_START;
1856 0 : ctx->zp_ready = 0UL;
1857 0 : ctx->disk_out_idx = -1;
1858 0 : ctx->disk_batch_pending = 0;
1859 0 : if( FD_UNLIKELY( incremental ) ) {
1860 0 : ctx->metrics.snapshots_started[ FD_METRICS_ENUM_SNAP_TYPE_V_INCREMENTAL_IDX ]++;
1861 0 : ctx->metrics.last_snapshot_slot_started_incremental = ctx->bank->f.slot;
1862 0 : } else {
1863 0 : ctx->metrics.snapshots_started[ FD_METRICS_ENUM_SNAP_TYPE_V_FULL_IDX ]++;
1864 0 : ctx->metrics.last_snapshot_slot_started_full = ctx->bank->f.slot;
1865 0 : }
1866 0 : broadcast_prepare( ctx );
1867 :
1868 0 : if( FD_UNLIKELY( incremental ) ) {
1869 0 : FD_LOG_INFO(( "incremental snapshot creation started (slot %lu, base slot %lu)",
1870 0 : ctx->bank->f.slot, ctx->base_slot ));
1871 0 : } else {
1872 0 : FD_LOG_INFO(( "snapshot creation started (slot %lu)", ctx->bank->f.slot ));
1873 0 : }
1874 0 : return 1;
1875 0 : }
1876 :
1877 : /* snapzp_stamp_shadow tracks the snaprd frag seq corresponding to an
1878 : upcoming snapmk_zp publish. */
1879 :
1880 : static inline void
1881 : snapzp_stamp_shadow( fd_snapmk_t * ctx,
1882 : ulong out_idx,
1883 0 : ulong pub_seq ) {
1884 0 : ctx->rd_shadow[ out_idx ][ pub_seq & (FD_SNAPMK_ZP_DEPTH-1UL) ] = ctx->rd_seq;
1885 0 : }
1886 :
1887 : /* snaprd_frag ingests a new accdb disk data frag from snaprd. */
1888 :
1889 : static int
1890 : snaprd_frag( fd_snapmk_t * ctx,
1891 : fd_stem_context_t * stem,
1892 : ulong seq,
1893 : ulong sig,
1894 : ulong chunk,
1895 : ulong ctl,
1896 0 : ulong tspub ) {
1897 0 : ulong frag_sz = tspub;
1898 0 : FD_CHECK_CRIT( ctx->state==SNAPMK_STATE_ACCDB_DISK, "lifecycle bug" );
1899 0 : FD_CHECK_CRIT( fd_frag_meta_ctl_orig( ctl )==FD_BACKUP_ORIG_DISK_FRAG, "unexpected snaprd frag orig" );
1900 0 : FD_CHECK_CRIT( frag_sz<=FD_BACKUP_RD_MTU && frag_sz<UINT_MAX, "invalid snaprd frag data size" );
1901 0 : FD_CHECK_CRIT( frag_sz || fd_frag_meta_ctl_eom( ctl ), "empty snaprd frag" );
1902 :
1903 0 : fd_snapmk_accparse_t * parse = ctx->accparse;
1904 0 : ctx->rd_seq = seq;
1905 0 : if( FD_LIKELY( !parse->input_active ) ) {
1906 0 : uchar const * data = fd_chunk_to_laddr_const( ctx->rd_in_mem, chunk );
1907 0 : parse->data = data;
1908 0 : parse->data_sz = frag_sz;
1909 0 : parse->src_gaddr = fd_wksp_gaddr_fast( ctx->rd_in_mem, data );
1910 0 : parse->src_off = sig;
1911 0 : parse->frag_base_gaddr = fd_wksp_gaddr_fast( ctx->rd_in_mem, data );
1912 0 : parse->pf_cursor = data;
1913 0 : parse->input_active = 1;
1914 0 : }
1915 :
1916 0 : for(;;) {
1917 : /* (A) Flush a previously-staged batch once an output link frees up.
1918 : A batch is self-contained within one snaprd frag, so it may be
1919 : routed to any ready zp tile (no disk_out_idx pinning). */
1920 0 : if( FD_UNLIKELY( ctx->disk_batch_pending ) ) {
1921 0 : if( FD_UNLIKELY( !ctx->zp_ready ) ) return 1;
1922 0 : ulong out_idx = zp_rr_next( ctx );
1923 0 : ulong out_chunk;
1924 0 : void * payload = zp_alloc( ctx, out_idx, sizeof(fd_backup_disk_batch_msg_t), &out_chunk );
1925 0 : fd_memcpy( payload, ctx->disk_batch, sizeof(fd_backup_disk_batch_msg_t) );
1926 0 : ulong ctl_batch = fd_frag_meta_ctl( FD_BACKUP_ORIG_ACC_DISK_BATCH, 1, 1, 0 );
1927 0 : snapzp_stamp_shadow( ctx, out_idx, stem->seqs[ out_idx ] );
1928 0 : zp_publish( ctx, stem, out_idx, ctx->disk_batch_base_gaddr, out_chunk,
1929 0 : sizeof(fd_backup_disk_batch_msg_t), ctl_batch, 0UL, 0UL );
1930 0 : ctx->disk_batch_pending = 0;
1931 0 : return 1;
1932 0 : }
1933 :
1934 :
1935 : /* (B) Stage a batch of wholly-contained accounts (no straddle).
1936 : Staging consumes the accounts into ctx->disk_batch; (A) flushes
1937 : it on the next iteration once credit is available. */
1938 0 : if( FD_LIKELY( ctx->disk_out_idx < 0 ) ) {
1939 0 : ulong n = fd_snapmk_accparse_publish_batch( parse, ctx->disk_batch );
1940 0 : if( n ) {
1941 0 : ctx->disk_batch_pending = 1;
1942 0 : ctx->disk_batch_base_gaddr = parse->frag_base_gaddr;
1943 0 : continue;
1944 0 : }
1945 0 : }
1946 :
1947 : /* (C) Single-account fallback for straddling / mid-record accounts. */
1948 0 : ulong out_idx = (ulong)ctx->disk_out_idx;
1949 0 : if( FD_LIKELY( out_idx<ctx->zp_cnt ) ) {
1950 0 : if( FD_UNLIKELY( !stem->cr_avail[ out_idx ] ) ) return 1;
1951 0 : } else {
1952 0 : if( FD_UNLIKELY( !ctx->zp_ready ) ) return 1;
1953 0 : out_idx = zp_rr_next( ctx );
1954 0 : }
1955 :
1956 0 : fd_frag_meta_t meta[1];
1957 0 : if( FD_UNLIKELY( !fd_snapmk_accparse_publish( parse, meta ) ) ) {
1958 : /* A discarded straddling record can finish before the end of this
1959 : frag. Retry from (B) while the parser is at the clean boundary,
1960 : instead of consuming the rest of the frag through the scalar
1961 : fallback. */
1962 0 : if( FD_LIKELY( parse->data_sz && !parse->acc_active &&
1963 0 : !parse->meta_sz && !parse->pub_pending ) ) continue;
1964 :
1965 0 : parse->input_active = 0;
1966 : /* A prestaged batch references the current frag's bytes and must
1967 : be drained before this frag is released (publish_batch above
1968 : returns 0 only once the prestage is empty). */
1969 0 : FD_CHECK_ERR( !parse->ps_cnt, "prestaged batch outlived its frag" );
1970 0 : if( FD_UNLIKELY( fd_frag_meta_ctl_eom( ctl ) ) ) {
1971 0 : if( FD_UNLIKELY( parse->meta_sz || parse->acc_active || parse->pub_pending ) ) {
1972 0 : FD_LOG_CRIT(( "snaprd stream ended mid-account record at accdb offset %lu "
1973 0 : "(meta_sz=%u acc_active=%i acc_off=%u acc_sz=%u pub_pending=%i)",
1974 0 : parse->src_off, parse->meta_sz, parse->acc_active,
1975 0 : parse->acc_off, parse->acc_sz, parse->pub_pending ));
1976 0 : }
1977 0 : ctx->disk_out_idx = -1;
1978 0 : barrier_install( ctx, stem );
1979 0 : broadcast_prepare( ctx );
1980 0 : ctx->state = SNAPMK_STATE_ACCDB_DISK_FLUSH;
1981 0 : }
1982 0 : return 0;
1983 0 : }
1984 :
1985 : /* An account may straddle multiple snaprd frags. The first frag of
1986 : an account (som) carries the fd_backup_disk_msg_t header and pins
1987 : the account to out_idx; continuation frags carry data only and
1988 : must go to the same zp tile, until eom unpins it. */
1989 0 : int som = fd_frag_meta_ctl_som( meta->ctl );
1990 0 : int eom = fd_frag_meta_ctl_eom( meta->ctl );
1991 0 : ulong out_chunk = 0UL;
1992 0 : ulong out_sz = 0UL;
1993 0 : if( FD_UNLIKELY( som ) ) {
1994 0 : ctx->disk_out_idx = (int)out_idx;
1995 0 : fd_backup_disk_msg_t * frag = zp_alloc( ctx, out_idx, sizeof(fd_backup_disk_msg_t), &out_chunk );
1996 0 : memcpy( frag->pubkey.uc, parse->meta.pubkey, sizeof(fd_pubkey_t) );
1997 0 : memcpy( frag->owner.uc, parse->meta.owner, sizeof(fd_pubkey_t) );
1998 0 : frag->size = parse->meta.size;
1999 0 : frag->acc_idx = parse->acc_idx;
2000 0 : frag->snap_sz = parse->acc_snap_sz;
2001 0 : frag->data_sz = (uint)meta->tspub;
2002 0 : out_sz = sizeof(fd_backup_disk_msg_t);
2003 0 : }
2004 :
2005 0 : snapzp_stamp_shadow( ctx, out_idx, stem->seqs[ out_idx ] );
2006 0 : zp_publish( ctx, stem, out_idx, meta->sig, out_chunk, out_sz, meta->ctl, meta->tsorig, meta->tspub );
2007 0 : if( FD_UNLIKELY( eom ) ) ctx->disk_out_idx = -1;
2008 0 : return 1;
2009 0 : }
2010 0 : }
2011 :
2012 : /* returnable_frag is called for every input frag. */
2013 :
2014 : static int
2015 : returnable_frag( fd_snapmk_t * ctx,
2016 : ulong in_idx,
2017 : ulong seq,
2018 : ulong sig,
2019 : ulong chunk,
2020 : ulong sz,
2021 : ulong ctl,
2022 : ulong tsorig,
2023 : ulong tspub,
2024 0 : fd_stem_context_t * stem ) {
2025 0 : (void)sz; (void)tsorig;
2026 0 : fd_startup_gate_busy( ctx->startup_gate );
2027 0 : switch( ctx->in_kind[ in_idx ] ) {
2028 0 : case IN_KIND_REPLAY:
2029 0 : switch( sig ) {
2030 0 : case REPLAY_SIG_SNAP_START: {
2031 0 : fd_replay_snap_start_t const * msg = fd_chunk_to_laddr_const( ctx->replay_in_mem, chunk );
2032 0 : int res = snap_start( ctx, stem, msg );
2033 0 : if( res==0 ) return 1; /* not ready yet */
2034 0 : break;
2035 0 : }
2036 0 : default:
2037 0 : FD_LOG_ERR(( "unexpected replay_snapmk message (sig=%lu)", sig ));
2038 0 : }
2039 0 : ctx->replay_in_seq_cons = fd_seq_inc( seq, 1UL );
2040 0 : return 0;
2041 0 : case IN_KIND_SNAPRD:
2042 0 : return snaprd_frag( ctx, stem, seq, sig, chunk, ctl, tspub );
2043 0 : default:
2044 0 : FD_LOG_CRIT(( "unexpected msg from link %lu with sig %lu", in_idx, sig ));
2045 0 : }
2046 0 : }
2047 :
2048 : static void
2049 0 : metrics_write( fd_snapmk_t * ctx ) {
2050 0 : ulong disk_alloc_bytes = 0UL;
2051 0 : for( uint i=0U; i<ctx->snap_max; i++ ) disk_alloc_bytes += ctx->pool_sz[ i ];
2052 0 : if( FD_LIKELY( ctx->snap_idx<ctx->snap_max ) ) {
2053 0 : ulong file_off = atomic_load_explicit( ctx->file_off_p, memory_order_relaxed );
2054 0 : disk_alloc_bytes += fd_ulong_sat_sub( file_off, ctx->pool_sz[ ctx->snap_idx ] );
2055 0 : }
2056 :
2057 0 : FD_MGAUGE_SET ( SNAPMK, JOB_STATE, ctx->state );
2058 0 : FD_MCNT_ENUM_COPY( SNAPMK, SNAPSHOT_STARTED, ctx->metrics.snapshots_started );
2059 0 : FD_MCNT_ENUM_COPY( SNAPMK, SNAPSHOT_FINISHED, ctx->metrics.snapshots_finished );
2060 0 : FD_MGAUGE_SET ( SNAPMK, LAST_STARTED_SLOT_FULL, ctx->metrics.last_snapshot_slot_started_full );
2061 0 : FD_MGAUGE_SET ( SNAPMK, LAST_STARTED_SLOT_INCREMENTAL, ctx->metrics.last_snapshot_slot_started_incremental );
2062 0 : FD_MGAUGE_SET ( SNAPMK, LAST_FINISHED_SLOT_FULL, ctx->metrics.last_snapshot_slot_finished_full );
2063 0 : FD_MGAUGE_SET ( SNAPMK, LAST_FINISHED_SLOT_INCREMENTAL, ctx->metrics.last_snapshot_slot_finished_incremental );
2064 :
2065 0 : FD_MCNT_SET ( SNAPMK, COMPRESS_CONSUMED_BYTES, ctx->metrics.bytes_compressed );
2066 0 : FD_MCNT_SET ( SNAPMK, DISK_WRITTEN_BYTES, ctx->metrics.bytes_written );
2067 :
2068 0 : FD_MGAUGE_SET ( SNAPMK, CACHE_PROGRESS_BYTES, fd_backup_cache_scanned_bytes( ctx->acc_cache ) );
2069 0 : FD_MGAUGE_SET ( SNAPMK, CACHE_EXPECTED_BYTES, fd_backup_cache_total_bytes ( ctx->acc_cache ) );
2070 0 : FD_MGAUGE_SET ( SNAPMK, INCREMENTAL_ACCOUNT_PROGRESS, ctx->delta.ele_cnt );
2071 0 : FD_MGAUGE_SET ( SNAPMK, INCREMENTAL_ACCOUNT_EXPECTED, ctx->delta.ele_total );
2072 :
2073 0 : FD_MCNT_SET ( SNAPMK, IO_BLOCKED_DURATION_NANOS, ctx->metrics.io_blocked_ticks );
2074 0 : FD_MCNT_SET ( SNAPMK, COMPRESS_DURATION_NANOS, ctx->metrics.compress_ticks );
2075 0 : FD_MGAUGE_SET ( SNAPMK, DISK_ALLOCATED_BYTES, disk_alloc_bytes );
2076 :
2077 0 : FD_MGAUGE_SET ( SNAPMK, INCREMENTAL_ACCOUNT_USED, __atomic_load_n( &ctx->accdb_shmem->delta.head, __ATOMIC_RELAXED ) );
2078 0 : FD_MGAUGE_SET ( SNAPMK, INCREMENTAL_ACCOUNT_CAPACITY, ctx->accdb_shmem->delta.ele_max );
2079 0 : }
2080 :
2081 : #define STEM_BURST SNAPMK_STEM_BURST
2082 : #define STEM_LAZY SNAPMK_STEM_LAZY
2083 : #define STEM_CALLBACK_CONTEXT_TYPE fd_snapmk_t
2084 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_snapmk_t)
2085 0 : #define STEM_CALLBACK_RECV_CREDIT recv_credit
2086 0 : #define STEM_CALLBACK_CHECK_CREDIT check_credit
2087 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
2088 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
2089 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
2090 : #include "../../disco/stem/fd_stem.c"
2091 :
2092 :
2093 : /* snapmk_run contains a bunch of boilerplate to hijack flow control
2094 : away from stem. */
2095 :
2096 : static void
2097 : snapmk_run( fd_topo_t * topo,
2098 0 : fd_topo_tile_t * tile ) {
2099 0 : fd_snapmk_t * ctx = (fd_snapmk_t *)fd_ulong_align_up( (ulong)fd_topo_obj_laddr( topo, tile->tile_obj_id ), alignof(fd_snapmk_t) );
2100 :
2101 0 : fd_frag_meta_t const * in_mcache[ FD_TOPO_MAX_LINKS ];
2102 0 : ulong * in_fseq [ FD_TOPO_MAX_TILE_IN_LINKS ];
2103 :
2104 0 : ulong polled_in_cnt = 0UL;
2105 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
2106 0 : if( FD_UNLIKELY( !tile->in_link_poll[ i ] ) ) continue;
2107 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
2108 0 : in_mcache[ polled_in_cnt ] = link->mcache;
2109 0 : FD_TEST( in_mcache[ polled_in_cnt ] );
2110 : /* Redirect links with custom flow control to local shadows. */
2111 0 : if( 0==strcmp( link->name, "snaprd_out" ) ) {
2112 : /* snaprd_out fseq set using custom logic, disable stem fseq updates */
2113 0 : in_fseq[ polled_in_cnt ] = &ctx->rd_fseq_dummy;
2114 0 : } else {
2115 0 : in_fseq[ polled_in_cnt ] = tile->in_link_fseq[ i ];
2116 0 : }
2117 0 : FD_TEST( in_fseq[ polled_in_cnt ] );
2118 0 : polled_in_cnt += 1UL;
2119 0 : }
2120 :
2121 0 : fd_frag_meta_t * out_mcache[ FD_TOPO_MAX_LINKS ];
2122 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
2123 0 : out_mcache[ i ] = topo->links[ tile->out_link_id[ i ] ].mcache;
2124 0 : FD_TEST( out_mcache[ i ] );
2125 0 : }
2126 :
2127 0 : ulong reliable_cons_cnt = 0UL;
2128 0 : ulong cons_out [ FD_TOPO_MAX_LINKS ];
2129 0 : ulong * cons_fseq[ FD_TOPO_MAX_LINKS ];
2130 0 : volatile ulong * cons_slow[ FD_TOPO_MAX_LINKS ];
2131 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
2132 0 : fd_topo_tile_t * consumer_tile = &topo->tiles[ i ];
2133 0 : ulong polled_in_idx = 0UL;
2134 0 : for( ulong j=0UL; j<consumer_tile->in_cnt; j++ ) {
2135 0 : int is_polled = consumer_tile->in_link_poll[ j ];
2136 0 : for( ulong k=0UL; k<tile->out_cnt; k++ ) {
2137 0 : if( FD_UNLIKELY( consumer_tile->in_link_id[ j ]==tile->out_link_id[ k ] && consumer_tile->in_link_reliable[ j ] ) ) {
2138 0 : cons_out [ reliable_cons_cnt ] = k;
2139 0 : cons_fseq[ reliable_cons_cnt ] = consumer_tile->in_link_fseq[ j ];
2140 0 : FD_TEST( cons_fseq[ reliable_cons_cnt ] );
2141 0 : cons_slow[ reliable_cons_cnt ] = fd_metrics_link_in( consumer_tile->metrics, polled_in_idx ) + FD_METRICS_COUNTER_LINK_BACKPRESSURE_BLAMED_OFF;
2142 0 : reliable_cons_cnt++;
2143 0 : FD_TEST( reliable_cons_cnt<FD_TOPO_MAX_LINKS );
2144 0 : }
2145 0 : }
2146 0 : if( FD_LIKELY( is_polled ) ) polled_in_idx++;
2147 0 : }
2148 0 : }
2149 :
2150 0 : fd_rng_t rng[1];
2151 0 : FD_TEST( fd_rng_join( fd_rng_new( rng, (uint)fd_ulong_hash( (ulong)fd_tickcount() + tile->id ), 0UL ) ) );
2152 :
2153 0 : uchar __attribute__((aligned(FD_STEM_SCRATCH_ALIGN))) stem_scratch[ stem_scratch_footprint( polled_in_cnt, tile->out_cnt, reliable_cons_cnt ) ];
2154 :
2155 0 : fd_stem_sleep_t sleep[ 1 ] = {{ .shmem = NULL }};
2156 0 : if( FD_UNLIKELY( topo->sleep_obj_id!=ULONG_MAX ) ) {
2157 0 : sleep->shmem = fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) );
2158 0 : FD_TEST( sleep->shmem );
2159 0 : sleep->tile_id = tile->id;
2160 0 : sleep->waker_fseq = NULL;
2161 0 : sleep->out_link_id = tile->out_link_id;
2162 :
2163 0 : ulong polled_idx = 0UL;
2164 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
2165 0 : if( FD_LIKELY( !tile->in_link_poll[ i ] ) ) continue;
2166 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
2167 0 : sleep->in_link_id [ polled_idx ] = tile->in_link_id[ i ];
2168 : /* snaprd_out credits go through rd_ack, which rings snaprd itself;
2169 : the stem writes a dummy fseq for it and must not ring */
2170 0 : sleep->in_producer[ polled_idx ] = tile->in_link_reliable[ i ] && strcmp( link->name, "snaprd_out" ) ? fd_topo_find_link_producer( topo, link ) : ULONG_MAX;
2171 0 : polled_idx++;
2172 0 : }
2173 :
2174 0 : ulong pair_cnt = 0UL;
2175 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
2176 0 : sleep->wake_off[ i ] = (ushort)pair_cnt;
2177 0 : pair_cnt += fd_sleep_wake_table( sleep->wake+pair_cnt, topo, tile->out_link_id[ i ] );
2178 0 : }
2179 0 : sleep->wake_off[ tile->out_cnt ] = (ushort)pair_cnt;
2180 0 : }
2181 :
2182 0 : stem_run1( polled_in_cnt, in_mcache, in_fseq,
2183 0 : tile->out_cnt, out_mcache,
2184 0 : reliable_cons_cnt, cons_out, cons_fseq, cons_slow,
2185 0 : SNAPMK_STEM_BURST, SNAPMK_STEM_LAZY,
2186 0 : rng, stem_scratch, ctx, sleep );
2187 0 : }
2188 :
2189 : static ulong
2190 0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
2191 0 : return sizeof(fd_event_snapshot_created_t);
2192 0 : }
2193 :
2194 : fd_topo_run_tile_t fd_tile_snapmk = {
2195 : .name = "snapmk",
2196 : .populate_allowed_fds = populate_allowed_fds,
2197 : .populate_allowed_seccomp = populate_allowed_seccomp,
2198 : .scratch_align = scratch_align,
2199 : .scratch_footprint = scratch_footprint,
2200 : .privileged_init = privileged_init,
2201 : .unprivileged_init = unprivileged_init,
2202 : .run = snapmk_run,
2203 : .max_event_sz = max_event_sz,
2204 : .allow_renameat = 1
2205 : };
|