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