Line data Source code
1 : #include "fd_wksp_private.h"
2 : #include "../tpool/fd_tpool.h"
3 : #include "../checkpt/fd_checkpt.h"
4 :
5 : #include <errno.h>
6 : #include <unistd.h>
7 : #include <fcntl.h>
8 : #include <sys/stat.h>
9 :
10 : /* This is an implementation detail and not strictly part of the v2
11 : specification. */
12 :
13 0 : #define FD_WKSP_CHECKPT_V2_CGROUP_MAX (1024UL)
14 :
15 : int
16 : fd_wksp_private_checkpt_v2( fd_tpool_t * tpool,
17 : ulong t0,
18 : ulong t1,
19 : fd_wksp_t * wksp,
20 : char const * path,
21 : ulong mode,
22 : char const * uinfo,
23 0 : int frame_style_compressed ) {
24 :
25 0 : (void)tpool; (void)t0; (void)t1; /* Thread parallelization not currently implemented */
26 :
27 0 : char const * binfo = fd_log_build_info;
28 :
29 0 : if( FD_UNLIKELY( !fd_checkpt_frame_style_is_supported( frame_style_compressed ) ) ) {
30 0 : FD_LOG_WARNING(( "compressed frames are not supported on this target" ));
31 0 : return FD_WKSP_ERR_INVAL;
32 0 : }
33 :
34 0 : int err_fail;
35 :
36 0 : int locked = 0;
37 0 : int fd = -1;
38 0 : fd_checkpt_t * checkpt = NULL;
39 :
40 0 : fd_wksp_private_pinfo_t * pinfo = fd_wksp_private_pinfo( wksp );
41 :
42 0 : char const * name = wksp->name;
43 0 : ulong name_len = fd_shmem_name_len( name );
44 0 : if( FD_UNLIKELY( !name_len ) ) {
45 0 : FD_LOG_WARNING(( "checkpt wksp to \"%s\" failed due to bad name; attempting to continue", path ));
46 0 : err_fail = FD_WKSP_ERR_CORRUPT;
47 0 : goto fail;
48 0 : }
49 :
50 : /* Lock the wksp */
51 :
52 0 : {
53 0 : int _err = fd_wksp_private_lock( wksp ); /* logs details */
54 0 : if( FD_UNLIKELY( _err ) ) {
55 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed due to being locked; attempting to continue", name, path ));
56 0 : err_fail = _err;
57 0 : goto fail;
58 0 : }
59 0 : locked = 1;
60 0 : }
61 :
62 : /* Determine a reasonable number of cgroups (note: in principle we
63 : could thread parallelize this but it probably isn't worth the extra
64 : complexity). */
65 :
66 0 : ulong cgroup_cnt;
67 0 : ulong alloc_cnt = 0UL;
68 :
69 0 : {
70 0 : # define WKSP_TEST( c ) do { \
71 0 : if( FD_UNLIKELY( !(c) ) ) { \
72 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed due to failing test %s; attempting to continue", \
73 0 : name, path, #c )); \
74 0 : err_fail = FD_WKSP_ERR_CORRUPT; \
75 0 : goto fail; \
76 0 : } \
77 0 : } while(0)
78 :
79 : /* Count the number of allocations by traversing over all partitions
80 : in reverse order by gaddr_lo (same iteration we will do to assign
81 : partitions to cgroups), validating as we go. */
82 :
83 0 : ulong part_max = wksp->part_max;
84 0 : ulong data_lo = wksp->gaddr_lo;
85 0 : ulong data_hi = wksp->gaddr_hi;
86 0 : ulong cycle_tag = wksp->cycle_tag++;
87 :
88 0 : WKSP_TEST( (0UL<data_lo) & (data_lo<=data_hi) ); /* Valid data region */
89 :
90 0 : ulong gaddr_last = data_hi;
91 :
92 0 : ulong part_idx = fd_wksp_private_pinfo_idx( wksp->part_tail_cidx );
93 0 : while( !fd_wksp_private_pinfo_idx_is_null( part_idx ) ) {
94 :
95 : /* Load partition metadata and validate it */
96 :
97 0 : WKSP_TEST( part_idx<part_max ); /* Valid idx */
98 0 : WKSP_TEST( pinfo[ part_idx ].cycle_tag!=cycle_tag ); /* No cycles */
99 0 : pinfo[ part_idx ].cycle_tag = cycle_tag; /* Mark part_idx as visited */
100 :
101 0 : ulong gaddr_lo = pinfo[ part_idx ].gaddr_lo;
102 0 : ulong gaddr_hi = pinfo[ part_idx ].gaddr_hi;
103 0 : ulong tag = pinfo[ part_idx ].tag;
104 :
105 0 : WKSP_TEST( (data_lo<=gaddr_lo) & (gaddr_lo<gaddr_hi) & (gaddr_hi==gaddr_last) ); /* Valid partition range */
106 0 : gaddr_last = gaddr_lo;
107 :
108 : /* If this partition holds an allocation, count it */
109 :
110 0 : alloc_cnt += (ulong)(tag>0UL);
111 :
112 : /* Advance to the previous partition */
113 :
114 0 : part_idx = fd_wksp_private_pinfo_idx( pinfo[ part_idx ].prev_cidx );
115 0 : }
116 :
117 0 : WKSP_TEST( gaddr_last==data_lo ); /* Complete partitioning */
118 :
119 : /* Compute a reasonable cgroup_cnt for alloc_cnt. To do this,
120 : let N be the number of allocations. We assume they have IID
121 : sizes with mean U and standard deviation S. If we assign each
122 : allocation to 1 of M cgroups IID uniform random (we will do
123 : better below but we pessimize here), in the limit of N>>M>>1, a
124 : cgroup's load (total number of allocation bytes assigned to a
125 : cgroup to compress) is Gaussian (by central limit theorem) with
126 : mean (N/M)U with standard deviation sqrt(N/M) sqrt(U^2+S^2).
127 :
128 : That is, we are load balanced on average (yay) but there is some
129 : natural imbalance expected due to statistical fluctuations (boo).
130 : Noting that allocation sizes are positive, if we further assume
131 : that S<~U typically (note that it is theoretically possible to
132 : have a positive valued random variable with S arbitrarily larger
133 : than U), then the cgroup load standard deviation is typically
134 : less than sqrt(2N/M) U.
135 :
136 : The load for each cgroup will be approximately independent of
137 : each other for M>>1. Extremal value statistics for a Gaussian
138 : then implies that the least loaded cgroup is typically likely to
139 : have more than (N/M)U - sqrt(2N/M) U sqrt(2 ln M) load. We want
140 : this to be positive such such that the least loaded cgroup will
141 : typically have some load:
142 :
143 : (N/M)U >> sqrt((4 N ln M)/M))U
144 : -> (N/M) >> 4 ln M
145 :
146 : That is, we want pick the number of cgroups such that the number
147 : of allocations per cgroup on average much greater than a few
148 : times the natural log of the number of cgroups.
149 :
150 : Given the number of cgroups is at most CGROUP_MAX ~ 1024, the
151 : above implies if we target more than ~28 allocations per cgroup
152 : on average, each cgroup is likely to get some load and cgroups
153 : will be reasonably load balanced on average. We use 32 below for
154 : computational convenience. */
155 :
156 0 : cgroup_cnt = fd_ulong_min( (alloc_cnt+31UL)/32UL, FD_WKSP_CHECKPT_V2_CGROUP_MAX );
157 :
158 0 : # undef WKSP_TEST
159 0 : }
160 :
161 : /* Assign allocations to cgroups (note: in principle we could thread
162 : parallelize this but it also probably isn't worth the extra
163 : complexity). */
164 :
165 0 : uint cgroup_head_cidx[ FD_WKSP_CHECKPT_V2_CGROUP_MAX ]; /* Head of a linked list for partitions assigned to each cgroup */
166 0 : ulong cgroup_alloc_cnt[ FD_WKSP_CHECKPT_V2_CGROUP_MAX ]; /* Number of partitions in each cgroup */
167 :
168 0 : {
169 :
170 : /* Initialize the cgroups to empty */
171 :
172 0 : ulong cgroup_load[ FD_WKSP_CHECKPT_V2_CGROUP_MAX ];
173 :
174 0 : uint null_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
175 0 : for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
176 0 : cgroup_head_cidx[ cgroup_idx ] = null_cidx;
177 0 : cgroup_alloc_cnt[ cgroup_idx ] = 0UL;
178 0 : cgroup_load [ cgroup_idx ] = 0UL;
179 0 : }
180 :
181 : /* Configure cgroup sampling */
182 :
183 0 : ulong cgroup_cursor = 0UL;
184 0 : ulong cgroup_idx = 0UL;
185 :
186 : /* For all partitions in reverse order by gaddr_lo */
187 :
188 0 : ulong part_idx = fd_wksp_private_pinfo_idx( wksp->part_tail_cidx );
189 0 : while( !fd_wksp_private_pinfo_idx_is_null( part_idx ) ) {
190 :
191 : /* Load partition metadata */
192 :
193 0 : ulong gaddr_lo = pinfo[ part_idx ].gaddr_lo;
194 0 : ulong gaddr_hi = pinfo[ part_idx ].gaddr_hi;
195 0 : ulong tag = pinfo[ part_idx ].tag;
196 :
197 : /* If this partition holds an allocation, deterministically assign
198 : it to a cgroup in an approximately load balanced way such that
199 : the assignments will be identical for the same set of
200 : allocations and cgroup_cnt. */
201 :
202 0 : if( tag ) { /* ~50/50 */
203 :
204 : /* Sample a handful of cgroups and pick the least loaded to
205 : approximate a greedy load balance method. We consider the
206 : most recently assigned cgroup (which was thought to be
207 : lightly loaded at the previous assignment), a cyclically
208 : sampled cgroup (ala striping) and two pseudo-randomly sampled
209 : cgroups based on the common hash of gaddr_lo (ala random
210 : assignment). We don't care if our samples collide; we are
211 : just trying to improve on load balance over straight striping
212 : and random sampling (both of which are already asymptotically
213 : are load balanced as per the above).
214 :
215 : We could use a min-heap here but that would be
216 : algorithmically more expensive, more complex to implement and
217 : unlikely to improve load balance much futher (it would be
218 : the greedy load balance method, which is also asymptotically
219 : optimal but not perfect ... perfect load balance is a
220 : computationally hard knapsack like problem but pretty good
221 : load balance is easy). */
222 :
223 0 : {
224 0 : ulong h = fd_ulong_hash( gaddr_lo );
225 :
226 0 : ulong i0 = cgroup_idx; ulong l0 = cgroup_load[ i0 ];
227 0 : ulong i1 = cgroup_cursor; ulong l1 = cgroup_load[ i1 ];
228 0 : ulong i2 = h % cgroup_cnt; ulong l2 = cgroup_load[ i2 ];
229 0 : ulong i3 = (h >> 32) % cgroup_cnt; ulong l3 = cgroup_load[ i3 ];
230 :
231 0 : i0 = fd_ulong_if( l0<=l1, i0, i1 ); l0 = fd_ulong_min( l0, l1 );
232 0 : i1 = fd_ulong_if( l2<=l3, i2, i3 ); l1 = fd_ulong_min( l2, l3 );
233 0 : i0 = fd_ulong_if( l0<=l1, i0, i1 ); l0 = fd_ulong_min( l0, l1 );
234 :
235 0 : cgroup_cursor = fd_ulong_if( cgroup_cursor<cgroup_cnt-1UL, cgroup_cursor+1UL, 0UL );
236 0 : cgroup_idx = i0;
237 0 : }
238 :
239 : /* Update this cgroup's partition count and load. The load is
240 : currently the total uncompressed bytes of partition metadata
241 : and data (TODO: consider adding a fixed base cost here to
242 : account for fixed computational overheads too. This would be
243 : an order of magnitude ballpark of the cost of doing 2
244 : fd_checkpt_buf relative to the marginal cost of checkpointing
245 : an additional byte for some representative target ... note
246 : that specific target details should not be incorporated into
247 : this because then specific checkpt byte stream would be
248 : sensitive to who wrote the checkpt and ideally checkpt should
249 : be bit-for-bit identical for identical wksp regardless of the
250 : target details). */
251 :
252 0 : cgroup_alloc_cnt[ cgroup_idx ]++;
253 0 : cgroup_load [ cgroup_idx ] += 3UL*sizeof(ulong) + (gaddr_hi - gaddr_lo);
254 :
255 : /* Push this partition onto the cgroup's stack. Since we are
256 : iterating over partitions in reverse order by gaddr_lo, the
257 : stack for each cgroup can be treated as a linked list in
258 : sorted order by gaddr_lo (helps with metdata
259 : compressibility). */
260 :
261 0 : pinfo[ part_idx ].stack_cidx = cgroup_head_cidx[ cgroup_idx ];
262 0 : cgroup_head_cidx[ cgroup_idx ] = fd_wksp_private_pinfo_cidx( part_idx );
263 0 : }
264 :
265 : /* Advance to the previous partition */
266 :
267 0 : part_idx = fd_wksp_private_pinfo_idx( pinfo[ part_idx ].prev_cidx );
268 0 : }
269 0 : }
270 :
271 : /* At this point, each wksp partitions to checkpt have been assigned
272 : to a cgroup, the cgroups are approximately load balanced and the
273 : partitions for each cgroup are given in a singly linked list sorted
274 : in ascending order by gaddr_lo. */
275 :
276 : /* Create the checkpt file */
277 :
278 0 : {
279 0 : mode_t old_mask = umask( (mode_t)0 );
280 0 : fd = open( path, O_CREAT|O_EXCL|O_WRONLY, (mode_t)mode );
281 0 : umask( old_mask );
282 0 : if( FD_UNLIKELY( fd==-1 ) ) {
283 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed opening file with flags_O_CREAT|O_EXCL|O_WRONLY in mode 0%03lo "
284 0 : "(%i-%s); attempting to continue", name, path, mode, errno, fd_io_strerror( errno ) ));
285 0 : err_fail = FD_WKSP_ERR_FAIL;
286 0 : goto fail;
287 0 : }
288 0 : }
289 :
290 : /* Initialize the checkpt */
291 :
292 0 : ulong frame_off[ FD_WKSP_CHECKPT_V2_CGROUP_MAX+6UL ];
293 0 : ulong frame_cnt = 0UL;
294 :
295 0 : fd_checkpt_t _checkpt[ 1 ];
296 0 : uchar wbuf[ FD_CHECKPT_WBUF_MIN ];
297 :
298 0 : checkpt = fd_checkpt_init_stream( _checkpt, fd, wbuf, FD_CHECKPT_WBUF_MIN ); /* logs details */
299 0 : if( FD_UNLIKELY( !checkpt ) ) {
300 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed when initializing; attempting to continue", name, path ));
301 0 : err_fail = FD_WKSP_ERR_FAIL;
302 0 : goto fail;
303 0 : }
304 :
305 0 : # define CHECKPT_OPEN(frame_style) do { \
306 0 : int _err = fd_checkpt_open_advanced( checkpt, (frame_style), &frame_off[ frame_cnt ] ); \
307 0 : if( FD_UNLIKELY( _err ) ) { \
308 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed when opening a %s frame (%i-%s); attempting to continue", \
309 0 : name, path, #frame_style, _err, fd_checkpt_strerror( _err ) )); \
310 0 : err_fail = FD_WKSP_ERR_FAIL; \
311 0 : goto fail; \
312 0 : } \
313 0 : } while(0)
314 :
315 0 : # define CHECKPT_CLOSE() do { \
316 0 : frame_cnt++; \
317 0 : int _err = fd_checkpt_close_advanced( checkpt, &frame_off[ frame_cnt ] ); /* logs details */ \
318 0 : if( FD_UNLIKELY( _err ) ) { \
319 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed when closing a frame (%i-%s); attempting to continue", \
320 0 : name, path, _err, fd_checkpt_strerror( _err ) )); \
321 0 : err_fail = FD_WKSP_ERR_FAIL; \
322 0 : goto fail; \
323 0 : } \
324 0 : } while(0)
325 :
326 : /* Note: sz must be at most FD_CHECKPT_META_MAX */
327 0 : # define CHECKPT_META( meta, sz ) do { \
328 0 : ulong _sz = (sz); \
329 0 : int _err = fd_checkpt_meta( checkpt, (meta), _sz ); /* logs details */ \
330 0 : if( FD_UNLIKELY( _err ) ) { \
331 0 : FD_LOG_WARNING(( "checkpt to \"%s\" failed when writing %lu bytes metadata %s (%i-%s); attempting to continue", \
332 0 : path, _sz, #meta, _err, fd_checkpt_strerror( _err ) )); \
333 0 : err_fail = FD_WKSP_ERR_FAIL; \
334 0 : goto fail; \
335 0 : } \
336 0 : } while(0)
337 :
338 : /* Note: data must exist and be unchanged until frame close */
339 0 : # define CHECKPT_DATA( data, sz ) do { \
340 0 : ulong _sz = (sz); \
341 0 : int _err = fd_checkpt_data( checkpt, (data), _sz ); /* logs details */ \
342 0 : if( FD_UNLIKELY( _err ) ) { \
343 0 : FD_LOG_WARNING(( "checkpt to \"%s\" failed when writing %lu bytes data %s (%i-%s); attempting to continue", \
344 0 : path, _sz, #data, _err, fd_checkpt_strerror( _err ) )); \
345 0 : err_fail = FD_WKSP_ERR_FAIL; \
346 0 : goto fail; \
347 0 : } \
348 0 : } while(0)
349 :
350 : /* Checkpt the header */
351 :
352 0 : {
353 0 : fd_wksp_checkpt_v2_hdr_t hdr[1];
354 :
355 0 : hdr->magic = wksp->magic;
356 0 : hdr->style = FD_WKSP_CHECKPT_STYLE_V2;
357 0 : hdr->frame_style_compressed = frame_style_compressed;
358 0 : hdr->reserved = 0U;
359 0 : memset( hdr->name, 0, FD_SHMEM_NAME_MAX ); /* Make sure trailing zeros clear */
360 0 : memcpy( hdr->name, name, name_len );
361 0 : hdr->seed = wksp->seed;
362 0 : hdr->part_max = wksp->part_max;
363 0 : hdr->data_max = wksp->data_max;
364 :
365 0 : CHECKPT_OPEN( FD_CHECKPT_FRAME_STYLE_RAW );
366 0 : CHECKPT_DATA( hdr, sizeof(fd_wksp_checkpt_v2_hdr_t) );
367 0 : CHECKPT_CLOSE();
368 0 : }
369 :
370 : /* Checkpt the info */
371 :
372 0 : {
373 0 : fd_wksp_checkpt_v2_info_t info[1];
374 0 : char buf[ 65536 ];
375 0 : char * p = buf;
376 :
377 0 : info->mode = mode;
378 0 : info->wallclock = fd_log_wallclock();
379 0 : info->app_id = fd_log_app_id ();
380 0 : info->thread_id = fd_log_thread_id();
381 0 : info->host_id = fd_log_host_id ();
382 0 : info->cpu_id = fd_log_cpu_id ();
383 0 : info->group_id = fd_log_group_id ();
384 0 : info->tid = fd_log_tid ();
385 0 : info->user_id = fd_log_user_id ();
386 :
387 0 : # define APPEND_CSTR( field, cstr, len ) do { \
388 0 : ulong _len = (len); \
389 0 : memcpy( p, (cstr), _len ); \
390 0 : p[ _len ] = '\0'; \
391 0 : info->sz_##field = _len + 1UL; \
392 0 : p += info->sz_##field; \
393 0 : } while(0)
394 :
395 0 : APPEND_CSTR( app, fd_log_app(), strlen( fd_log_app() ) ); /* appends at most FD_LOG_NAME_MAX ~ 40 B */
396 0 : APPEND_CSTR( thread, fd_log_thread(), strlen( fd_log_thread() ) ); /* " */
397 0 : APPEND_CSTR( host, fd_log_host(), strlen( fd_log_host() ) ); /* " */
398 0 : APPEND_CSTR( cpu, fd_log_cpu(), strlen( fd_log_cpu() ) ); /* " */
399 0 : APPEND_CSTR( group, fd_log_group(), strlen( fd_log_group() ) ); /* " */
400 0 : APPEND_CSTR( user, fd_log_user(), strlen( fd_log_user() ) ); /* " */
401 0 : APPEND_CSTR( path, path, strlen( path ) ); /* appends at most PATH_MAX-1 ~ 4 KiB */
402 0 : APPEND_CSTR( binfo, binfo, fd_cstr_nlen( binfo, FD_WKSP_CHECKPT_V2_BINFO_MAX-1UL ) ); /* appends at most 16 KiB */
403 0 : APPEND_CSTR( uinfo, uinfo, fd_cstr_nlen( uinfo, FD_WKSP_CHECKPT_V2_UINFO_MAX-1UL ) ); /* " */
404 :
405 0 : # undef APPEND_CSTR
406 :
407 : /* Write the info */
408 :
409 0 : CHECKPT_OPEN( frame_style_compressed );
410 0 : CHECKPT_DATA( info, sizeof(fd_wksp_checkpt_v2_info_t) );
411 0 : CHECKPT_DATA( buf, (ulong)(p-buf) );
412 0 : CHECKPT_CLOSE();
413 0 : }
414 :
415 : /* Checkpt the volume cgroups. Note: This implementation just
416 : checkpoints 1 volume with at most CGROUP_MAX cgroup_cnt groups.
417 :
418 : Note: this loop can be parallelized over multiple threads if
419 : willing to leave holes in the file (and then maybe do a second pass
420 : to compact the holes or maybe do a planning pass and then a real
421 : pass or maybe leave the holes and do a second pass of run length
422 : and entropy coding or maybe write to separate files and distribute
423 : as a multiple files or maybe use non-POSIX filesystem mojo to
424 : stitch together the separate files to appear as one file or ...) */
425 :
426 0 : for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
427 :
428 0 : CHECKPT_OPEN( frame_style_compressed );
429 :
430 : /* Write cgroup commands */
431 :
432 0 : fd_wksp_checkpt_v2_cmd_t cmd[1];
433 :
434 0 : ulong part_idx = fd_wksp_private_pinfo_idx( cgroup_head_cidx[ cgroup_idx ] );
435 0 : while( !fd_wksp_private_pinfo_idx_is_null( part_idx ) ) {
436 :
437 : /* Command: "meta (tag,gaddr_lo,gaddr_hi)" */
438 :
439 0 : cmd->meta.tag = pinfo[ part_idx ].tag; /* Note: non-zero */
440 0 : cmd->meta.gaddr_lo = pinfo[ part_idx ].gaddr_lo;
441 0 : cmd->meta.gaddr_hi = pinfo[ part_idx ].gaddr_hi;
442 :
443 0 : CHECKPT_META( cmd, sizeof(fd_wksp_checkpt_v2_cmd_t) );
444 :
445 0 : part_idx = fd_wksp_private_pinfo_idx( pinfo[ part_idx ].stack_cidx );
446 0 : }
447 :
448 : /* Command: "corresponding data follows" */
449 :
450 0 : cmd->data.tag = 0UL;
451 0 : cmd->data.cgroup_cnt = ULONG_MAX;
452 0 : cmd->data.frame_off = ULONG_MAX;
453 :
454 0 : CHECKPT_META( cmd, sizeof(fd_wksp_checkpt_v2_cmd_t) );
455 :
456 : /* Write cgroup partition data */
457 :
458 0 : part_idx = fd_wksp_private_pinfo_idx( cgroup_head_cidx[ cgroup_idx ] );
459 0 : while( !fd_wksp_private_pinfo_idx_is_null( part_idx ) ) {
460 0 : ulong gaddr_lo = pinfo[ part_idx ].gaddr_lo;
461 0 : ulong gaddr_hi = pinfo[ part_idx ].gaddr_hi;
462 :
463 : # if FD_HAS_DEEPASAN
464 : /* We read the whole partition, so unpoison the holes sub-allocators
465 : leave poisoned (permanently discarding that finer poisoning). */
466 : fd_asan_unpoison( fd_wksp_laddr_fast( wksp, gaddr_lo ), gaddr_hi - gaddr_lo );
467 : # endif
468 :
469 0 : CHECKPT_DATA( fd_wksp_laddr_fast( wksp, gaddr_lo ), gaddr_hi - gaddr_lo );
470 :
471 0 : part_idx = fd_wksp_private_pinfo_idx( pinfo[ part_idx ].stack_cidx );
472 0 : }
473 :
474 0 : CHECKPT_CLOSE();
475 :
476 0 : }
477 :
478 : /* Checkpt the volume appendix. This starts with a command that
479 : indicates this frame is an appendix for cgroup_cnt cgroups (this
480 : can be used in a streaming restore to tell when it has reached the
481 : appendix and in a parallel restore of the appendix so a parallel
482 : restore thread knows how much it needs to decompress), the offsets
483 : of each cgroup frame (so parallel restore threads can seek to the
484 : partitions assigned to them) and the number of partitions in each
485 : cgroup frame (so that the pinfo on restore can be partitioned over
486 : parallel restore threads upfront non-atomically and
487 : deterministically). */
488 :
489 0 : {
490 0 : fd_wksp_checkpt_v2_cmd_t cmd[1];
491 :
492 : /* Command: "appendix for a volume with cgroup_cnt frames and no
493 : previous volumes" */
494 :
495 0 : cmd->appendix.tag = 0UL;
496 0 : cmd->appendix.cgroup_cnt = cgroup_cnt;
497 0 : cmd->appendix.frame_off = 0UL;
498 :
499 0 : CHECKPT_OPEN( frame_style_compressed );
500 0 : CHECKPT_META( cmd, sizeof(fd_wksp_checkpt_v2_cmd_t) ); /* Note: must be meta for restore */
501 0 : CHECKPT_DATA( frame_off+2UL, cgroup_cnt*sizeof(ulong) );
502 0 : CHECKPT_DATA( cgroup_alloc_cnt, cgroup_cnt*sizeof(ulong) );
503 0 : CHECKPT_CLOSE();
504 0 : }
505 :
506 : /* Checkpt the volumes frame */
507 :
508 0 : {
509 0 : fd_wksp_checkpt_v2_cmd_t cmd[1];
510 :
511 : /* Command: "no more volumes */
512 :
513 0 : cmd->volumes.tag = 0UL;
514 0 : cmd->volumes.cgroup_cnt = ULONG_MAX;
515 0 : cmd->volumes.frame_off = frame_off[ frame_cnt-1 ];
516 :
517 0 : CHECKPT_OPEN( frame_style_compressed );
518 0 : CHECKPT_META( cmd, sizeof(fd_wksp_checkpt_v2_cmd_t) );
519 0 : CHECKPT_CLOSE();
520 0 : }
521 :
522 : /* Checkpt the footer */
523 :
524 0 : {
525 0 : fd_wksp_checkpt_v2_ftr_t ftr[1];
526 :
527 : /* Command: "footer for a checkpt with cgroup_cnt total cgroup
528 : frames" */
529 :
530 0 : ftr->alloc_cnt = alloc_cnt;
531 0 : ftr->cgroup_cnt = cgroup_cnt;
532 0 : ftr->volume_cnt = 1UL;
533 0 : ftr->frame_off = frame_off[ frame_cnt-1U ];
534 0 : ftr->checkpt_sz = frame_off[ frame_cnt ] + sizeof(fd_wksp_checkpt_v2_ftr_t);
535 0 : ftr->data_max = wksp->data_max;
536 0 : ftr->part_max = wksp->part_max;
537 0 : ftr->seed = wksp->seed;
538 0 : memset( ftr->name, 0, FD_SHMEM_NAME_MAX ); /* Make sure trailing zeros clear */
539 0 : memcpy( ftr->name, name, name_len );
540 0 : ftr->reserved = 0U;
541 0 : ftr->frame_style_compressed = frame_style_compressed;
542 0 : ftr->style = FD_WKSP_CHECKPT_STYLE_V2;
543 0 : ftr->unmagic = ~wksp->magic;
544 :
545 0 : CHECKPT_OPEN( FD_CHECKPT_FRAME_STYLE_RAW );
546 0 : CHECKPT_DATA( ftr, sizeof(fd_wksp_checkpt_v2_ftr_t) );
547 0 : CHECKPT_CLOSE();
548 0 : }
549 :
550 0 : # undef CHECKPT_DATA
551 0 : # undef CHECKPT_META
552 0 : # undef CHECKPT_CLOSE
553 0 : # undef CHECKPT_OPEN
554 :
555 : /* Finalize the checkpt */
556 :
557 0 : if( FD_UNLIKELY( !fd_checkpt_fini( checkpt ) ) ) { /* logs details */
558 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed when finalizing; attempting to continue", name, path ));
559 0 : checkpt = NULL;
560 0 : err_fail = FD_WKSP_ERR_FAIL;
561 0 : goto fail;
562 0 : }
563 :
564 : /* Close the file */
565 :
566 0 : if( FD_UNLIKELY( close( fd ) ) ) {
567 0 : FD_LOG_WARNING(( "checkpt wksp \"%s\" to \"%s\" failed when closing; attempting to continue", name, path ));
568 0 : fd = -1;
569 0 : err_fail = FD_WKSP_ERR_FAIL;
570 0 : goto fail;
571 0 : }
572 :
573 : /* Unlock the wksp */
574 :
575 0 : fd_wksp_private_unlock( wksp );
576 0 : locked = 0;
577 :
578 0 : return FD_WKSP_SUCCESS;
579 :
580 0 : fail:
581 :
582 : /* Release resources that might be reserved */
583 :
584 0 : if( FD_LIKELY( checkpt ) ) {
585 0 : if( FD_UNLIKELY( fd_checkpt_in_frame( checkpt ) ) && FD_UNLIKELY( fd_checkpt_close( checkpt ) ) )
586 0 : FD_LOG_WARNING(( "fd_checkpt_close failed; attempting to continue" ));
587 :
588 0 : if( FD_UNLIKELY( !fd_checkpt_fini( checkpt ) ) ) /* logs details */
589 0 : FD_LOG_WARNING(( "fd_checkpt_fini failed; attempting to continue" ));
590 0 : }
591 :
592 0 : if( FD_LIKELY( fd!=-1 ) && FD_UNLIKELY( close( fd ) ) )
593 0 : FD_LOG_WARNING(( "close(\"%s\") failed (%i-%s); attempting to continue", path, errno, fd_io_strerror( errno ) ));
594 :
595 0 : if( FD_LIKELY( locked ) ) fd_wksp_private_unlock( wksp );
596 :
597 0 : return err_fail;
598 0 : }
|