Line data Source code
1 : #include "fd_wksp_private.h"
2 : #include <sys/mman.h>
3 : #include <errno.h>
4 :
5 : int
6 564333105 : fd_wksp_private_lock( fd_wksp_t * wksp ) {
7 : # if FD_WKSP_LOCK_RECLAIM
8 : int warning = 0;
9 : #endif
10 564333105 : ulong me = fd_log_group_id();
11 :
12 564333105 : ulong * _owner = &wksp->owner;
13 564333105 : for(;;) {
14 :
15 : /* Note that we emulate CAS on platforms without FD_HAS_ATOMIC
16 : to minimize the amount of code differences we have to test. On
17 : platforms without FD_HAS_ATOMIC, a workspace should not be used
18 : concurrently though. */
19 :
20 564333105 : FD_COMPILER_MFENCE();
21 564333105 : # if FD_HAS_ATOMIC
22 564333105 : ulong pid = FD_ATOMIC_CAS( _owner, ULONG_MAX, me );
23 : # else
24 : ulong pid = FD_VOLATILE_CONST( *_owner );
25 : if( pid==ULONG_MAX ) FD_VOLATILE( *_owner ) = me;
26 : # endif
27 564333105 : FD_COMPILER_MFENCE();
28 :
29 564333105 : if( FD_LIKELY( pid==ULONG_MAX ) ) return FD_WKSP_SUCCESS;
30 :
31 : # if FD_WKSP_LOCK_RECLAIM
32 : int status = fd_log_group_id_query( pid );
33 : if( FD_UNLIKELY( status==FD_LOG_GROUP_ID_QUERY_DEAD ) ) { /* A process died while holding the lock, try to recover the lock */
34 :
35 : FD_COMPILER_MFENCE();
36 : # if FD_HAS_ATOMIC
37 : ulong cur = FD_ATOMIC_CAS( _owner, pid, me );
38 : # else
39 : ulong cur = FD_VOLATILE_CONST( *_owner );
40 : if( cur==pid ) FD_VOLATILE( *_owner ) = me;
41 : # endif
42 : FD_COMPILER_MFENCE();
43 :
44 : if( FD_LIKELY( cur==pid ) ) { /* We recovered the lock from the dead pid, try to fix up incomplete ops */
45 :
46 : FD_LOG_WARNING(( "Process %lu died in an operation on wksp %s; verifying", pid, wksp->name ));
47 : if( FD_LIKELY( !fd_wksp_verify( wksp ) ) ) { /* logs details of issues detected */
48 : FD_LOG_NOTICE(( "wksp verified" ));
49 : return FD_WKSP_SUCCESS;
50 : }
51 :
52 : FD_LOG_WARNING(( "Issues detected; rebuilding" ));
53 : if( FD_UNLIKELY( fd_wksp_rebuild( wksp, wksp->seed ) ) ) { /* Rebuild failed (logs details of issues detected) */
54 : /* Return control of the lock to the previous owner */
55 : FD_COMPILER_MFENCE();
56 : FD_VOLATILE( *_owner ) = pid;
57 : FD_COMPILER_MFENCE();
58 : FD_LOG_WARNING(( "corrupt wksp detected" ));
59 : return FD_WKSP_ERR_CORRUPT;
60 : }
61 :
62 : FD_LOG_NOTICE(( "wksp rebuilt" ));
63 : return FD_WKSP_SUCCESS;
64 :
65 : }
66 :
67 : /* Somebody beat us to recovering the lock ... try again */
68 :
69 : } else if( FD_UNLIKELY( status!=FD_LOG_GROUP_ID_QUERY_LIVE ) ) { /* Unclear pid status ... issue a warning and try again */
70 :
71 : if( FD_UNLIKELY( !warning ) ) {
72 : FD_LOG_WARNING(( "wksp %s is owned by unknown pid %li; attempting to recover", wksp->name, pid ));
73 : warning = 1;
74 : }
75 :
76 : }
77 :
78 : /* At this point, either another thread in this process has the
79 : lock, another active thread in another process has the lock,
80 : another unknown status thread in other process has the lock or
81 : another thread beat us to reclaim the lock from a dead process.
82 : In any case, we don't have the lock. Wait a while to limit O/S
83 : contention and try again. */
84 :
85 : FD_YIELD();
86 : # else
87 :
88 : /* If we are running without FD_WKSP_LOCK_RECLAIM then it is assumed
89 : that the contention is caused by a tile pinned to another core,
90 : and that this core is itself pinned so spin locking is best. */
91 0 : FD_SPIN_PAUSE();
92 :
93 0 : #endif
94 0 : }
95 :
96 : /* never get here */
97 564333105 : }
98 :
99 : /* Public APIs ********************************************************/
100 :
101 : ulong
102 : fd_wksp_part_max_est( ulong footprint,
103 543 : ulong sz_typical ) {
104 543 : footprint = fd_ulong_align_dn( footprint, FD_WKSP_ALIGN );
105 543 : ulong data_end = footprint - 1UL;
106 543 : ulong pinfo_off = fd_wksp_private_pinfo_off();
107 543 : ulong consumed = sizeof(fd_wksp_private_pinfo_t) + sz_typical;
108 543 : ulong part_max = (data_end - pinfo_off) / (consumed + (ulong)!consumed); /* avoid div-by-zero */
109 543 : if( FD_UNLIKELY( (!footprint) | (!sz_typical) | (sz_typical>consumed) | (pinfo_off>data_end) ) ) return 0UL;
110 534 : return fd_ulong_min( part_max, FD_WKSP_PRIVATE_PINFO_IDX_NULL );
111 543 : }
112 :
113 : ulong
114 : fd_wksp_data_max_est( ulong footprint,
115 552 : ulong part_max ) {
116 552 : footprint = fd_ulong_align_dn( footprint, FD_WKSP_ALIGN );
117 552 : ulong data_end = footprint - 1UL;
118 552 : ulong data_off = fd_wksp_private_data_off( part_max );
119 552 : if( FD_UNLIKELY( (!part_max) | (part_max>FD_WKSP_PRIVATE_PINFO_IDX_NULL) |
120 552 : (part_max > ((ULONG_MAX - fd_wksp_private_pinfo_off())/sizeof(fd_wksp_private_pinfo_t))) | /* covered above */
121 552 : (!footprint) | (data_off>=data_end) ) ) return 0UL;
122 534 : return data_end - data_off;
123 552 : }
124 :
125 : ulong
126 3 : fd_wksp_align( void ) {
127 3 : return FD_WKSP_ALIGN;
128 3 : }
129 :
130 : ulong
131 : fd_wksp_footprint( ulong part_max,
132 2334 : ulong data_max ) {
133 2334 : ulong data_off = fd_wksp_private_data_off( part_max );
134 2334 : if( FD_UNLIKELY( (!part_max) | (part_max>FD_WKSP_PRIVATE_PINFO_IDX_NULL) | (!data_max) |
135 2334 : (part_max > ((ULONG_MAX - fd_wksp_private_pinfo_off())/sizeof(fd_wksp_private_pinfo_t))) | /* Covered above */
136 2334 : (data_max > (ULONG_MAX - FD_WKSP_ALIGN + 1UL - data_off - 1UL) ) ) ) return 0UL;
137 2310 : return fd_ulong_align_up( data_off + data_max + 1UL, FD_WKSP_ALIGN );
138 2334 : }
139 :
140 : void *
141 : fd_wksp_new( void * shmem,
142 : char const * name,
143 : uint seed,
144 : ulong part_max,
145 546 : ulong data_max ) {
146 546 : fd_wksp_t * wksp = (fd_wksp_t *)shmem;
147 :
148 546 : if( FD_UNLIKELY( !wksp ) ) {
149 3 : FD_LOG_WARNING(( "NULL shmem" ));
150 3 : return NULL;
151 3 : }
152 :
153 543 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)wksp, FD_WKSP_ALIGN ) ) ) {
154 3 : FD_LOG_WARNING(( "bad align" ));
155 3 : return NULL;
156 3 : }
157 :
158 540 : ulong name_len = fd_shmem_name_len( name );
159 540 : if( FD_UNLIKELY( !name_len ) ) {
160 3 : FD_LOG_WARNING(( "bad name" ));
161 3 : return NULL;
162 3 : }
163 :
164 537 : ulong footprint = fd_wksp_footprint( part_max, data_max );
165 537 : if( FD_UNLIKELY( !footprint ) ) {
166 12 : FD_LOG_WARNING(( "bad part_max and/or data_max" ));
167 12 : return NULL;
168 12 : }
169 :
170 525 : fd_memset( wksp, 0, fd_wksp_footprint( part_max, 1UL ) );
171 :
172 525 : wksp->part_max = part_max;
173 525 : wksp->data_max = data_max;
174 525 : wksp->gaddr_lo = fd_wksp_private_data_off( part_max );
175 525 : wksp->gaddr_hi = wksp->gaddr_lo + data_max;
176 525 : fd_memcpy( wksp->name, name, name_len+1UL );
177 525 : wksp->seed = seed;
178 525 : wksp->idle_top_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
179 525 : wksp->part_head_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
180 525 : wksp->part_tail_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
181 525 : wksp->part_used_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
182 525 : wksp->part_free_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
183 525 : wksp->cycle_tag = 4UL; /* Verify uses tags 0-3 */
184 525 : wksp->owner = 0UL; /* Mark as locked and in construction */
185 :
186 : /* Note that wksp->owner was set to zero above, "locking" the wksp by
187 : group_id 0. And the memset above set all the partition tags to
188 : zero such that there are no allocated partitions. So once we set
189 : magic below, we can finish the initialization by rebuilding and
190 : unlocking. Since fd_log_group_id is non-zero, the zero owner
191 : indicates to any remote observer of the shared memory region that
192 : the wksp is being built for the first time. */
193 :
194 525 : FD_COMPILER_MFENCE();
195 525 : FD_VOLATILE( wksp->magic ) = FD_WKSP_MAGIC;
196 525 : FD_COMPILER_MFENCE();
197 :
198 525 : int err = fd_wksp_rebuild( wksp, seed );
199 525 : if( FD_UNLIKELY( err ) ) { /* Should be impossible at this point */
200 :
201 0 : FD_COMPILER_MFENCE();
202 0 : FD_VOLATILE( wksp->magic ) = 0UL;
203 0 : FD_COMPILER_MFENCE();
204 :
205 0 : FD_LOG_WARNING(( "fd_wksp_rebuild failed (%i-%s)", err, fd_wksp_strerror( err ) ));
206 0 : return NULL;
207 0 : }
208 :
209 : #if FD_HAS_DEEPASAN
210 : /* Poison entire workspace except wksp header and the pinfo array. */
211 : void * wksp_data = (void*)((ulong)wksp + fd_wksp_private_pinfo_off());
212 : fd_asan_poison( wksp_data, footprint - fd_wksp_private_pinfo_off() );
213 : fd_wksp_private_pinfo_t * pinfo = fd_wksp_private_pinfo( wksp );
214 : for( ulong i=0; i<part_max; i++ ) {
215 : fd_asan_unpoison( &pinfo[ i ], FD_WKSP_PRIVATE_PINFO_FOOTPRINT );
216 : }
217 : #endif
218 :
219 525 : fd_wksp_private_unlock( wksp );
220 :
221 525 : return wksp;
222 525 : }
223 :
224 : fd_wksp_t *
225 1533 : fd_wksp_join( void * shwksp ) {
226 1533 : fd_wksp_t * wksp = (fd_wksp_t *)shwksp;
227 :
228 1533 : if( FD_UNLIKELY( !wksp ) ) {
229 3 : FD_LOG_WARNING(( "NULL shwksp" ));
230 3 : return NULL;
231 3 : }
232 :
233 1530 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)wksp, FD_WKSP_ALIGN ) ) ) {
234 3 : FD_LOG_WARNING(( "bad align" ));
235 3 : return NULL;
236 3 : }
237 :
238 1527 : if( FD_UNLIKELY( wksp->magic!=FD_WKSP_MAGIC ) ) {
239 3 : FD_LOG_WARNING(( "bad magic" ));
240 3 : return NULL;
241 3 : }
242 :
243 1524 : return wksp;
244 1527 : }
245 :
246 : void *
247 1515 : fd_wksp_leave( fd_wksp_t * wksp ) {
248 1515 : if( FD_UNLIKELY( !wksp ) ) {
249 3 : FD_LOG_WARNING(( "NULL wksp" ));
250 3 : return NULL;
251 3 : }
252 :
253 1512 : return (void *)wksp;
254 1515 : }
255 :
256 : void *
257 222 : fd_wksp_delete( void * shwksp ) {
258 222 : fd_wksp_t * wksp = (fd_wksp_t *)shwksp;
259 :
260 222 : if( FD_UNLIKELY( !wksp ) ) {
261 3 : FD_LOG_WARNING(( "NULL shwksp" ));
262 3 : return NULL;
263 3 : }
264 :
265 219 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)wksp, FD_WKSP_ALIGN ) ) ) {
266 3 : FD_LOG_WARNING(( "bad align" ));
267 3 : return NULL;
268 3 : }
269 :
270 216 : if( FD_UNLIKELY( wksp->magic!=FD_WKSP_MAGIC ) ) {
271 3 : FD_LOG_WARNING(( "bad magic" ));
272 3 : return NULL;
273 3 : }
274 :
275 : /* TODO: consider testing owner */
276 :
277 213 : FD_COMPILER_MFENCE();
278 213 : FD_VOLATILE( wksp->magic ) = 0UL;
279 213 : FD_COMPILER_MFENCE();
280 :
281 : # if FD_HAS_DEEPASAN
282 : /* Unpoison entire wksp region. */
283 : ulong footprint = fd_wksp_footprint( wksp->part_max, wksp->data_max );
284 : void * wksp_data = (void*)((ulong)wksp + fd_wksp_private_pinfo_off());
285 : fd_asan_unpoison( wksp_data, footprint - fd_wksp_private_pinfo_off());
286 : # endif
287 :
288 213 : return wksp;
289 216 : }
290 :
291 3 : char const * fd_wksp_name ( fd_wksp_t const * wksp ) { return wksp->name; }
292 30 : uint fd_wksp_seed ( fd_wksp_t const * wksp ) { return wksp->seed; }
293 3 : ulong fd_wksp_part_max( fd_wksp_t const * wksp ) { return wksp->part_max; }
294 3 : ulong fd_wksp_data_max( fd_wksp_t const * wksp ) { return wksp->data_max; }
295 :
296 : ulong
297 0 : fd_wksp_owner( fd_wksp_t const * wksp ) {
298 0 : FD_COMPILER_MFENCE();
299 0 : ulong owner = FD_VOLATILE_CONST( wksp->owner );
300 0 : FD_COMPILER_MFENCE();
301 0 : return owner;
302 0 : }
303 :
304 : char const *
305 12 : fd_wksp_strerror( int err ) {
306 12 : switch( err ) {
307 3 : case FD_WKSP_SUCCESS: return "success";
308 3 : case FD_WKSP_ERR_INVAL: return "inval";
309 3 : case FD_WKSP_ERR_FAIL: return "fail";
310 3 : case FD_WKSP_ERR_CORRUPT: return "corrupt";
311 0 : default: break;
312 12 : }
313 0 : return "unknown";
314 12 : }
315 :
316 : int
317 42 : fd_wksp_verify( fd_wksp_t * wksp ) {
318 :
319 135735 : # define TEST(c) do { \
320 135735 : if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_WKSP_ERR_CORRUPT; } \
321 135735 : } while(0)
322 :
323 : /* Validate metadata */
324 :
325 42 : TEST( wksp );
326 42 : TEST( wksp->magic==FD_WKSP_MAGIC );
327 :
328 42 : ulong part_max = wksp->part_max;
329 42 : ulong data_max = wksp->data_max;
330 42 : TEST( fd_wksp_footprint( part_max, data_max ) );
331 :
332 42 : ulong gaddr_lo = wksp->gaddr_lo; TEST( gaddr_lo==fd_wksp_private_data_off( part_max ) );
333 42 : ulong gaddr_hi = wksp->gaddr_hi; TEST( gaddr_hi==gaddr_lo+data_max );
334 :
335 42 : TEST( fd_shmem_name_len( wksp->name ) );
336 :
337 : /* seed is arbitrary */
338 :
339 42 : TEST( wksp->cycle_tag >= 4UL );
340 :
341 : /* TODO: consider verifying owner */
342 :
343 42 : fd_wksp_private_pinfo_t * pinfo = fd_wksp_private_pinfo( wksp );
344 :
345 : /* Clear out cycle tags */
346 :
347 54060 : for( ulong i=0UL; i<part_max; i++ ) pinfo[ i ].cycle_tag = 0UL;
348 :
349 : /* Verify the idle stack */
350 :
351 42 : ulong idle_cnt = 0UL;
352 :
353 42 : do {
354 42 : ulong i = fd_wksp_private_pinfo_idx( wksp->idle_top_cidx );
355 51756 : while( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
356 :
357 : /* Visit i. Note that i has not been validated yet. */
358 :
359 51714 : TEST( i<part_max ); /* Validate i */
360 51714 : TEST( !pinfo[ i ].cycle_tag ); /* Make sure not visited before */
361 51714 : pinfo[ i ].cycle_tag = 1UL; /* Mark as visited in idle stack */
362 51714 : idle_cnt++; /* Update the idle cnt */
363 :
364 : /* Advance to the next idle */
365 :
366 51714 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx );
367 51714 : }
368 42 : } while(0);
369 :
370 : /* Idle stack looks intact, verify partitioning */
371 :
372 42 : ulong free_cnt = 0UL;
373 42 : ulong used_cnt = 0UL;
374 :
375 42 : do {
376 42 : ulong j = FD_WKSP_PRIVATE_PINFO_IDX_NULL;
377 42 : ulong i = fd_wksp_private_pinfo_idx( wksp->part_head_cidx );
378 42 : ulong g = gaddr_lo;
379 :
380 42 : int last_free = 0;
381 :
382 2346 : while( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
383 :
384 : /* At this point, we last visited j. Visit i. Note that j has
385 : been validated but i has not. */
386 :
387 2304 : TEST( i<part_max ); /* Validate i */
388 2304 : TEST( pinfo[ i ].gaddr_lo==g ); /* Make sure partition is tightly adjacent to previous */
389 2304 : TEST( pinfo[ i ].gaddr_hi> g ); /* Make sure partition size is non-zero */
390 2304 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].prev_cidx )==j ); /* Make sure correct prev partition */
391 2304 : TEST( !pinfo[ i ].cycle_tag ); /* Make sure not visited before */
392 2304 : pinfo[ i ].cycle_tag = 2UL; /* Mark as visited in partitioning */
393 :
394 2304 : g = pinfo[ i ].gaddr_hi; /* Extract where the next partition should start */
395 2304 : int is_free = !pinfo[ i ].tag; /* Determine if this partition is free or used */
396 2304 : TEST( !(last_free & is_free) ); /* Make sure no adjacent free partitions */
397 2304 : free_cnt += (ulong) is_free; /* Update the free cnt */
398 2304 : used_cnt += (ulong)!is_free; /* Update the used cnt */
399 :
400 : /* Advance to the next partition */
401 :
402 2304 : last_free = is_free;
403 :
404 2304 : j = i;
405 2304 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].next_cidx );
406 2304 : }
407 :
408 42 : TEST( fd_wksp_private_pinfo_idx( wksp->part_tail_cidx )==j ); /* Make sure correct partition tail */
409 42 : TEST( g==gaddr_hi ); /* Make sure complete partitioning */
410 42 : TEST( (idle_cnt + free_cnt + used_cnt)==part_max ); /* Make sure no lost idle partitions */
411 42 : } while(0);
412 :
413 : /* Idle stack and partitioning look intact, validate used treap */
414 :
415 42 : do {
416 42 : ulong visit_cnt = 0UL;
417 :
418 42 : ulong i = fd_wksp_private_pinfo_idx( wksp->part_used_cidx );
419 42 : ulong s = FD_WKSP_PRIVATE_PINFO_IDX_NULL;
420 42 : ulong g = gaddr_lo;
421 :
422 42 : if( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
423 39 : TEST( i<part_max ); /* Validate i */
424 39 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx )==s ); /* Validate parent */
425 39 : }
426 :
427 2748 : for(;;) {
428 :
429 : /* At this point i is and everything on stack is validated */
430 :
431 2748 : if( fd_wksp_private_pinfo_idx_is_null( i ) ) {
432 1395 : if( fd_wksp_private_pinfo_idx_is_null( s ) ) break; /* Done */
433 :
434 : /* Pop stack */
435 :
436 1353 : i = s;
437 1353 : s = fd_wksp_private_pinfo_idx( pinfo[ i ].stack_cidx );
438 :
439 : /* Visit i */
440 :
441 1353 : ulong p = fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx ); /* Extract the parent */
442 :
443 1353 : TEST( pinfo[ i ].gaddr_lo>=g ); /* Make sure this starts after last visited */
444 1353 : TEST( pinfo[ i ].tag ); /* Make sure tagged as a used partition */
445 1353 : TEST( pinfo[ i ].cycle_tag==2UL ); /* Make sure in partitioning and not visited yet this traversal */
446 1353 : if( !fd_wksp_private_pinfo_idx_is_null( p ) ) /* Make sure heap property satisfied */
447 1314 : TEST( pinfo[ p ].heap_prio >= pinfo[ i ].heap_prio );
448 :
449 1353 : TEST( !pinfo[ i ].in_same ); /* Make sure unique */
450 1353 : TEST( fd_wksp_private_pinfo_idx_is_null( fd_wksp_private_pinfo_idx( pinfo[ i ].same_cidx ) ) ); /* " */
451 :
452 1353 : pinfo[ i ].cycle_tag = 3UL; /* Mark as visited this traversal */
453 1353 : visit_cnt++; /* Update the visit cnt */
454 1353 : g = pinfo[ i ].gaddr_hi; /* Get minimum start for next partition */
455 :
456 : /* Traverse the right subtree */
457 :
458 1353 : p = i;
459 1353 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].right_cidx );
460 1353 : if( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
461 660 : TEST( i<part_max ); /* Validate i */
462 660 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx )==p ); /* Validate parent */
463 660 : }
464 :
465 1353 : } else {
466 :
467 : /* At this point i and everything on the stack is validated.
468 : Push i to the stack and recurse on the left subtree. */
469 :
470 1353 : pinfo[ i ].stack_cidx = fd_wksp_private_pinfo_cidx( s );
471 1353 : s = i;
472 1353 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].left_cidx );
473 1353 : if( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
474 654 : TEST( i<part_max ); /* Validate i */
475 654 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx )==s ); /* Validate parent */
476 654 : }
477 :
478 1353 : }
479 2748 : }
480 :
481 42 : TEST( visit_cnt==used_cnt ); /* Make sure all used partitions in used treap */
482 42 : } while(0);
483 :
484 : /* Idle stack, partitioning and used treap look intact, validate the
485 : free treap. */
486 :
487 42 : do {
488 42 : ulong visit_cnt = 0UL;
489 :
490 42 : ulong i = fd_wksp_private_pinfo_idx( wksp->part_free_cidx );
491 42 : ulong s = FD_WKSP_PRIVATE_PINFO_IDX_NULL;
492 42 : ulong sz = 0UL;
493 :
494 42 : if( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
495 42 : TEST( i<part_max ); /* Validate i */
496 42 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx )==s ); /* Validate parent */
497 42 : }
498 :
499 1200 : for(;;) {
500 :
501 : /* At this point i and everything on the stack is validated */
502 :
503 1200 : if( fd_wksp_private_pinfo_idx_is_null( i ) ) {
504 621 : if( fd_wksp_private_pinfo_idx_is_null( s ) ) break; /* Done */
505 :
506 : /* Pop stack */
507 :
508 579 : i = s;
509 579 : s = fd_wksp_private_pinfo_idx( pinfo[ i ].stack_cidx );
510 :
511 : /* Visit i */
512 :
513 579 : ulong p = fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx ); /* Extract the parent */
514 579 : ulong isz = fd_wksp_private_pinfo_sz( pinfo + i ); /* Extract the size */
515 :
516 579 : TEST( isz>sz ); /* Make sure this partition i larger than previous */
517 579 : TEST( !pinfo[ i ].tag ); /* Make sure tagged as a free partition */
518 579 : TEST( !pinfo[ i ].in_same ); /* Make sure marked as not in same */
519 :
520 579 : if( !fd_wksp_private_pinfo_idx_is_null( p ) ) { /* Make sure heap property satisfied */
521 537 : TEST( pinfo[ p ].heap_prio >= pinfo[ i ].heap_prio );
522 537 : }
523 :
524 579 : sz = isz; /* Update largest size partition seen so far */
525 :
526 : /* Traverse all same sized partitions */
527 :
528 579 : ulong j = i;
529 951 : for(;;) {
530 :
531 : /* At this point, j is validated */
532 :
533 951 : TEST( pinfo[ j ].cycle_tag==2UL ); /* Make sure in partitioning and not visited yet this traversal */
534 951 : pinfo[ j ].cycle_tag = 3UL; /* Mark as visited this traversal */
535 951 : visit_cnt++;
536 :
537 951 : ulong k = fd_wksp_private_pinfo_idx( pinfo[ j ].same_cidx ); /* Get the next same sized */
538 951 : if( fd_wksp_private_pinfo_idx_is_null( k ) ) break; /* If no more, we are done with this node */
539 372 : TEST( k<part_max ); /* Make sure valid index */
540 372 : TEST( fd_wksp_private_pinfo_sz( pinfo + k )==sz ); /* Make sure same size */
541 372 : TEST( pinfo[ k ].in_same ); /* Make sure marked as in same */
542 372 : TEST( fd_wksp_private_pinfo_idx_is_null( fd_wksp_private_pinfo_idx( pinfo[ k ].left_cidx ) ) );
543 372 : TEST( fd_wksp_private_pinfo_idx_is_null( fd_wksp_private_pinfo_idx( pinfo[ k ].right_cidx ) ) );
544 372 : TEST( fd_wksp_private_pinfo_idx( pinfo[ k ].parent_cidx )==j ); /* Make sure correct parent */
545 372 : j = k;
546 372 : }
547 :
548 : /* Recurse on the right subtree */
549 :
550 579 : p = i;
551 579 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].right_cidx );
552 579 : if( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
553 270 : TEST( i<part_max ); /* Validate i */
554 270 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx )==p ); /* Validate parent */
555 270 : }
556 :
557 579 : } else {
558 :
559 579 : TEST( i<part_max ); /* Validate i */
560 :
561 : /* At this point i and everything on the stack is validated.
562 : Push i to the stack and recurse on the left subtree. */
563 :
564 579 : pinfo[ i ].stack_cidx = fd_wksp_private_pinfo_cidx( s );
565 579 : s = i;
566 579 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].left_cidx );
567 579 : if( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
568 267 : TEST( i<part_max ); /* Validate i */
569 267 : TEST( fd_wksp_private_pinfo_idx( pinfo[ i ].parent_cidx )==s ); /* Validate parent */
570 267 : }
571 579 : }
572 1200 : }
573 :
574 42 : TEST( visit_cnt==free_cnt ); /* Make sure all free partitions in free treap */
575 :
576 42 : } while(0);
577 :
578 42 : # undef TEST
579 :
580 42 : return FD_WKSP_SUCCESS;
581 42 : }
582 :
583 : int
584 : fd_wksp_rebuild( fd_wksp_t * wksp,
585 1089 : uint seed ) {
586 :
587 : /* Load the wksp metadata, don't rebuild if any of it looks even
588 : slightly off. */
589 :
590 1089 : if( FD_UNLIKELY( !wksp ) ) {
591 0 : FD_LOG_WARNING(( "NULL wksp" ));
592 0 : return FD_WKSP_ERR_CORRUPT;
593 0 : }
594 :
595 1089 : ulong magic = wksp->magic;
596 1089 : ulong part_max = wksp->part_max;
597 1089 : ulong data_max = wksp->data_max;
598 1089 : ulong gaddr_lo = wksp->gaddr_lo;
599 1089 : ulong gaddr_hi = wksp->gaddr_hi;
600 1089 : ulong cycle_tag = wksp->cycle_tag;
601 :
602 : /* TODO: consider verifying owner */
603 :
604 1089 : ulong footprint = fd_wksp_footprint( part_max, data_max );
605 1089 : ulong gaddr_lo_exp = fd_wksp_private_data_off( part_max );
606 1089 : ulong gaddr_hi_exp = gaddr_lo_exp + data_max;
607 1089 : if( FD_UNLIKELY( (magic!=FD_WKSP_MAGIC) | (!footprint) | (!fd_shmem_name_len( wksp->name )) |
608 1089 : (gaddr_lo!=gaddr_lo_exp) | (gaddr_hi!=gaddr_hi_exp) | (cycle_tag<4UL) ) ) {
609 0 : FD_LOG_WARNING(( "bad metadata\n\t"
610 0 : "magic %016lx (exp %016lx)\n\t"
611 0 : "part_max %lu data_max %lu (footprint %lu)\n\t"
612 0 : "gaddr_lo %lu (exp %lu)\n\t"
613 0 : "gaddr_hi %lu (exp %lu)\n\t"
614 0 : "cycle_tag %lu (exp>=4)",
615 0 : magic, FD_WKSP_MAGIC, part_max, data_max, footprint,
616 0 : gaddr_lo, gaddr_lo_exp, gaddr_hi, gaddr_hi_exp, cycle_tag ));
617 0 : return FD_WKSP_ERR_CORRUPT;
618 0 : }
619 :
620 : /* Scan the wksp pinfo and insert any used partitions into the used
621 : treap and put the rest on the idle stack. If there is any sign of
622 : corruption (empty, bad range or overlap between used partitions),
623 : we abort the rebuild (this is almost certainly data corruption of
624 : some form and we don't have enough info to resolve a conflict
625 : without potentially making the situation worse). We do the scan in
626 : reverse order to rebuild the idle stack in forward order.
627 :
628 : Note that we don't ever change the gaddr_lo,gaddr_hi of any tagged
629 : partitions such that operation is guaranteed to never change the
630 : single source of truth. As such, this operation can be interrupted
631 : and restarted arbitrarily safely.*/
632 :
633 1089 : fd_wksp_private_pinfo_t * pinfo = fd_wksp_private_pinfo( wksp );
634 :
635 1089 : do {
636 1089 : wksp->seed = seed;
637 1089 : wksp->idle_top_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL ); /* Flush idle stack */
638 1089 : wksp->part_used_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL ); /* Flush used treap */
639 1089 : wksp->part_free_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL ); /* Flush free treap */
640 :
641 1089 : ulong i = part_max;
642 11805618 : while( i ) {
643 11804529 : i--;
644 :
645 : /* Ideally, heap priorities should just be a shuffling of the
646 : integers [0,part_max). fd_uint_hash will generate such a
647 : shuffling for part_max = 2^32. Using the lower 30 bits
648 : (reserving bit 31 for bulk operations) will yield something
649 : very close. We use seed to mix it up some more. */
650 :
651 11804529 : pinfo[ i ].in_same = 0U;
652 11804529 : pinfo[ i ].heap_prio = fd_uint_hash( seed ^ (uint)i ) & ((1U<<30)-1U);
653 11804529 : pinfo[ i ].stack_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
654 11804529 : pinfo[ i ].cycle_tag = 0U;
655 :
656 11804529 : ulong tag = pinfo[ i ].tag;
657 11804529 : if( !tag ) { /* Not used ... make it available for reuse below */
658 11795073 : fd_wksp_private_idle_stack_push( i, wksp, pinfo );
659 11795073 : continue;
660 11795073 : }
661 :
662 9456 : pinfo[ i ].prev_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
663 9456 : pinfo[ i ].next_cidx = fd_wksp_private_pinfo_cidx( FD_WKSP_PRIVATE_PINFO_IDX_NULL );
664 :
665 9456 : if( FD_UNLIKELY( fd_wksp_private_used_treap_insert( i, wksp, pinfo ) ) ) return FD_WKSP_ERR_CORRUPT; /* Logs details */
666 9456 : }
667 1089 : } while(0);
668 :
669 : /* At this point, a partition is either in the idle stack or used
670 : treap. Further, we have:
671 :
672 : | used | idle
673 : ----------+----------------------------+--------
674 : gaddr_* | non-empty range | 0
675 : | no overlap with other used | 0
676 : tag | non-zero | 0
677 : in_same | 0 | 0
678 : heap_prio | randomized | randomized
679 : prev | NULL | NULL
680 : next | NULL | NULL
681 : left | used treap managed | NULL
682 : right | used treap managed | NULL
683 : same | used treap managed (NULL) | NULL
684 : parent | used treap managed | idle stack managed
685 : stack | wksp managed | wksp managed
686 : cycle_tag | wksp managed | wksp managed
687 :
688 : In-order traverse the used treap to rebuild the partitioning and
689 : the free treap. */
690 :
691 1089 : do {
692 1089 : uint * j_next_cidx_ptr = &wksp->part_head_cidx; /* Location of most recently added partition next link */
693 :
694 1089 : ulong j = FD_WKSP_PRIVATE_PINFO_IDX_NULL; /* Most recently added partition */
695 1089 : ulong g0 = gaddr_lo; /* Most recently added partition end */
696 :
697 1089 : ulong i = fd_wksp_private_pinfo_idx( wksp->part_used_cidx );
698 1089 : ulong s = FD_WKSP_PRIVATE_PINFO_IDX_NULL;
699 20001 : for(;;) {
700 20001 : if( fd_wksp_private_pinfo_idx_is_null( i ) ) {
701 10545 : if( fd_wksp_private_pinfo_idx_is_null( s ) ) break; /* Done */
702 :
703 : /* Pop traversal stack */
704 :
705 9456 : i = s;
706 9456 : s = fd_wksp_private_pinfo_idx( pinfo[ i ].stack_cidx );
707 :
708 : /* Visit i */
709 :
710 9456 : ulong g1 = pinfo[ i ].gaddr_lo;
711 9456 : if( g1 > g0 ) { /* There's a gap between i and the most recently added partition */
712 :
713 : /* Acquire an idle partition to hold the gap */
714 :
715 6471 : if( FD_UNLIKELY( fd_wksp_private_idle_stack_is_empty( wksp ) ) ) {
716 0 : FD_LOG_WARNING(( "part_max (%lu) too small to fill gap before partition %lu (tag %lu gaddr_lo %lu gaddr_hi %lu)",
717 0 : part_max, i, pinfo[i].tag, pinfo[i].gaddr_lo, pinfo[i].gaddr_hi ));
718 0 : return FD_WKSP_ERR_CORRUPT;
719 0 : }
720 6471 : ulong k = fd_wksp_private_idle_stack_pop( wksp, pinfo );
721 :
722 : /* Populate the acquired partition with the gap details,
723 : append it to the wksp partitioning and insert it into the
724 : free treap. Note that stack_push/pop reset gaddr_lo,
725 : gaddr_hi, tag, in_same, {prev, next, left, right, same,
726 : parent}_cidx. It preserved heap_prio from its original
727 : assignment and didn't touch stack_cidx or cycle_tag. */
728 :
729 6471 : pinfo[ k ].gaddr_lo = g0;
730 6471 : pinfo[ k ].gaddr_hi = g1;
731 6471 : pinfo[ k ].prev_cidx = fd_wksp_private_pinfo_cidx( j );
732 6471 : *j_next_cidx_ptr = fd_wksp_private_pinfo_cidx( k );
733 6471 : j_next_cidx_ptr = &pinfo[ k ].next_cidx;
734 6471 : j = k;
735 6471 : g0 = g1;
736 :
737 6471 : fd_wksp_private_free_treap_insert( j, wksp, pinfo );
738 6471 : }
739 :
740 : /* Add i to the partitioning. */
741 :
742 9456 : pinfo[ i ].prev_cidx = fd_wksp_private_pinfo_cidx( j );
743 9456 : *j_next_cidx_ptr = fd_wksp_private_pinfo_cidx( i );
744 9456 : j_next_cidx_ptr = &pinfo[ i ].next_cidx;
745 9456 : j = i;
746 9456 : g0 = pinfo[ i ].gaddr_hi;
747 :
748 : /* Traverse the right subtree */
749 :
750 9456 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].right_cidx );
751 :
752 9456 : } else {
753 :
754 : /* Push i to the stack and recurse on the left subtree. */
755 :
756 9456 : pinfo[ i ].stack_cidx = fd_wksp_private_pinfo_cidx( s );
757 9456 : s = i;
758 9456 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].left_cidx );
759 :
760 9456 : }
761 20001 : }
762 :
763 1089 : if( g0 < gaddr_hi ) { /* Have final gap to fill */
764 :
765 : /* This works the same as the above */
766 :
767 1071 : if( FD_UNLIKELY( fd_wksp_private_idle_stack_is_empty( wksp ) ) ) {
768 0 : FD_LOG_WARNING(( "part_max (%lu) too small to complete partitioning", part_max ));
769 0 : return FD_WKSP_ERR_CORRUPT;
770 0 : }
771 1071 : ulong k = fd_wksp_private_idle_stack_pop( wksp, pinfo );
772 :
773 1071 : pinfo[ k ].gaddr_lo = g0;
774 1071 : pinfo[ k ].gaddr_hi = gaddr_hi;
775 1071 : pinfo[ k ].prev_cidx = fd_wksp_private_pinfo_cidx( j );
776 1071 : *j_next_cidx_ptr = fd_wksp_private_pinfo_cidx( k );
777 1071 : j_next_cidx_ptr = &pinfo[ k ].next_cidx;
778 1071 : j = k;
779 : //g0 = gaddr_hi;
780 :
781 1071 : fd_wksp_private_free_treap_insert( j, wksp, pinfo );
782 1071 : }
783 :
784 1089 : wksp->part_tail_cidx = fd_wksp_private_pinfo_cidx( j );
785 :
786 1089 : } while(0);
787 :
788 1089 : return FD_WKSP_SUCCESS;
789 1089 : }
790 :
791 : void
792 0 : fd_wksp_mprotect( fd_wksp_t * wksp, int flag ) {
793 0 : if( FD_UNLIKELY( !wksp ) ) {
794 0 : FD_LOG_WARNING(( "NULL wksp" ));
795 0 : return;
796 0 : }
797 :
798 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)wksp, FD_WKSP_ALIGN ) ) ) {
799 0 : FD_LOG_WARNING(( "bad align" ));
800 0 : return;
801 0 : }
802 :
803 0 : if( FD_UNLIKELY( wksp->magic!=FD_WKSP_MAGIC ) ) {
804 0 : FD_LOG_WARNING(( "bad magic" ));
805 0 : return;
806 0 : }
807 :
808 0 : ulong wksp_footprint = fd_wksp_footprint( wksp->part_max, wksp->data_max );
809 0 : if( FD_UNLIKELY( mprotect( (void*)wksp, wksp_footprint, ( flag ? PROT_READ : (PROT_READ|PROT_WRITE) ) ) ) ) {
810 0 : FD_LOG_WARNING(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
811 0 : return;
812 0 : }
813 0 : }
|