@@ -42,6 +42,7 @@ fd_forest_new( void * shmem, ulong ele_max, ulong seed ) {
42
42
void * ancestry = FD_SCRATCH_ALLOC_APPEND ( l , fd_forest_ancestry_align (), fd_forest_ancestry_footprint ( ele_max ) );
43
43
void * frontier = FD_SCRATCH_ALLOC_APPEND ( l , fd_forest_frontier_align (), fd_forest_frontier_footprint ( ele_max ) );
44
44
void * orphaned = FD_SCRATCH_ALLOC_APPEND ( l , fd_forest_orphaned_align (), fd_forest_orphaned_footprint ( ele_max ) );
45
+ void * deque = FD_SCRATCH_ALLOC_APPEND ( l , fd_forest_deque_align (), fd_forest_deque_footprint ( ele_max ) );
45
46
FD_TEST ( FD_SCRATCH_ALLOC_FINI ( l , fd_forest_align () ) == (ulong )shmem + footprint );
46
47
47
48
forest -> root = ULONG_MAX ;
@@ -51,6 +52,7 @@ fd_forest_new( void * shmem, ulong ele_max, ulong seed ) {
51
52
forest -> ancestry_gaddr = fd_wksp_gaddr_fast ( wksp , fd_forest_ancestry_join ( fd_forest_ancestry_new ( ancestry , ele_max , seed ) ) );
52
53
forest -> frontier_gaddr = fd_wksp_gaddr_fast ( wksp , fd_forest_frontier_join ( fd_forest_frontier_new ( frontier , ele_max , seed ) ) );
53
54
forest -> orphaned_gaddr = fd_wksp_gaddr_fast ( wksp , fd_forest_orphaned_join ( fd_forest_orphaned_new ( orphaned , ele_max , seed ) ) );
55
+ forest -> deque_gaddr = fd_wksp_gaddr_fast ( wksp , fd_forest_deque_join ( fd_forest_deque_new ( deque , ele_max ) ) );
54
56
55
57
FD_COMPILER_MFENCE ();
56
58
FD_VOLATILE ( forest -> magic ) = FD_FOREST_MAGIC ;
@@ -126,7 +128,6 @@ fd_forest_init( fd_forest_t * forest, ulong root_slot ) {
126
128
127
129
fd_forest_ele_t * root_ele = fd_forest_pool_ele_acquire ( pool );
128
130
root_ele -> slot = root_slot ;
129
- root_ele -> prev = null ;
130
131
root_ele -> parent = null ;
131
132
root_ele -> child = null ;
132
133
root_ele -> sibling = null ;
@@ -188,16 +189,14 @@ fd_forest_verify( fd_forest_t const * forest ) {
188
189
return 0 ;
189
190
}
190
191
191
- /* query queries for a connected ele keyed by slot. does not return
192
- orphaned ele. */
192
+ FD_FN_PURE static inline ulong *
193
+ fd_forest_deque ( fd_forest_t * forest ) {
194
+ return fd_wksp_laddr_fast ( fd_forest_wksp ( forest ), forest -> deque_gaddr );
195
+ }
193
196
194
- static fd_forest_ele_t *
195
- ancestry_frontier_query ( fd_forest_t * forest , ulong slot ) {
196
- fd_forest_ele_t * pool = fd_forest_pool ( forest );
197
- fd_forest_ele_t * ele = NULL ;
198
- ele = fd_forest_ancestry_ele_query ( fd_forest_ancestry ( forest ), & slot , NULL , pool );
199
- ele = fd_ptr_if ( !ele , fd_forest_frontier_ele_query ( fd_forest_frontier ( forest ), & slot , NULL , pool ), ele );
200
- return ele ;
197
+ FD_FN_PURE static inline ulong *
198
+ fd_forest_deque_const ( fd_forest_t const * forest ) {
199
+ return fd_wksp_laddr_fast ( fd_forest_wksp ( forest ), forest -> deque_gaddr );
201
200
}
202
201
203
202
/* remove removes and returns a connected ele from ancestry or frontier
@@ -233,41 +232,6 @@ link( fd_forest_t * forest, fd_forest_ele_t * parent, fd_forest_ele_t * child )
233
232
child -> parent = fd_forest_pool_idx ( pool , parent );
234
233
}
235
234
236
- /* link_orphans performs a BFS beginning from head using BFS. head is
237
- the first element of a linked list representing the BFS queue. If the
238
- starting orphan is connected to the ancestry tree (ie. its parent is
239
- in the map), it is linked to the tree and removed from the orphaned
240
- map, and any of its orphaned children are added to the queue (linking
241
- a parent also links its direct children). Otherwise it remains in the
242
- orphaned map. The BFS continues until the queue is empty. */
243
-
244
- FD_FN_UNUSED static void
245
- link_orphans ( fd_forest_t * forest , fd_forest_ele_t * head ) {
246
- fd_forest_ele_t * pool = fd_forest_pool ( forest );
247
- ulong null = fd_forest_pool_idx_null ( pool );
248
- fd_forest_ancestry_t * ancestry = fd_forest_ancestry ( forest );
249
- fd_forest_orphaned_t * orphaned = fd_forest_orphaned ( forest );
250
- fd_forest_ele_t * tail = head ;
251
- fd_forest_ele_t * prev = NULL ;
252
- while ( FD_LIKELY ( head ) ) { /* while queue is non-empty */
253
- if ( FD_LIKELY ( fd_forest_orphaned_ele_remove ( orphaned , & head -> slot , NULL , pool ) ) ) { /* head is orphan root */
254
- fd_forest_ancestry_ele_insert ( ancestry , head , pool );
255
- fd_forest_ele_t * child = fd_forest_pool_ele ( pool , head -> child );
256
- while ( FD_LIKELY ( child ) ) { /* append children to frontier */
257
- tail -> prev = fd_forest_pool_idx ( pool , child ); /* safe to overwrite prev */
258
- tail = fd_forest_pool_ele ( pool , tail -> prev );
259
- tail -> prev = null ;
260
- ulong sibling = child -> sibling ;
261
- child -> sibling = null ;
262
- child = fd_forest_pool_ele ( pool , sibling );
263
- }
264
- }
265
- prev = head ;
266
- head = fd_forest_pool_ele ( pool , head -> prev );
267
- prev -> prev = null ;
268
- }
269
- }
270
-
271
235
/* advance_frontier attempts to advance the frontier beginning from slot
272
236
using BFS. head is the first element of a linked list representing
273
237
the BFS queue. A slot can be advanced if all shreds for the block
@@ -276,36 +240,36 @@ link_orphans( fd_forest_t * forest, fd_forest_ele_t * head ) {
276
240
static void
277
241
advance_frontier ( fd_forest_t * forest , ulong slot , ushort parent_off ) {
278
242
fd_forest_ele_t * pool = fd_forest_pool ( forest );
279
- ulong null = fd_forest_pool_idx_null ( pool );
280
243
fd_forest_ancestry_t * ancestry = fd_forest_ancestry ( forest );
281
244
fd_forest_frontier_t * frontier = fd_forest_frontier ( forest );
245
+ ulong * queue = fd_forest_deque ( forest );
282
246
283
247
fd_forest_ele_t * ele ;
284
248
ele = fd_forest_frontier_ele_query ( fd_forest_frontier ( forest ), & slot , NULL , pool );
285
249
ulong parent_slot = slot - parent_off ;
286
250
ele = fd_ptr_if ( !ele , fd_forest_frontier_ele_query ( fd_forest_frontier ( forest ), & parent_slot , NULL , pool ), ele );
251
+ if ( FD_UNLIKELY ( !ele ) ) return ;
287
252
288
- fd_forest_ele_t * head = ele ;
289
- fd_forest_ele_t * tail = head ;
290
- fd_forest_ele_t * prev = NULL ;
253
+ #if FD_FOREST_USE_HANDHOLDING
254
+ FD_TEST ( fd_forest_deque_cnt ( queue ) == 0 ) ;
255
+ #endif
291
256
292
- while ( FD_LIKELY ( head ) ) {
257
+ /* BFS elements as pool idxs*/
258
+ fd_forest_deque_push_tail ( queue , fd_forest_pool_idx ( pool , ele ) );
259
+ while ( FD_LIKELY ( fd_forest_deque_cnt ( queue ) ) ) {
260
+ fd_forest_ele_t * head = fd_forest_pool_ele ( pool , fd_forest_deque_pop_head ( queue ) );
293
261
fd_forest_ele_t * child = fd_forest_pool_ele ( pool , head -> child );
294
262
if ( FD_LIKELY ( child && head -> complete_idx != UINT_MAX && head -> buffered_idx == head -> complete_idx ) ) {
295
263
fd_forest_frontier_ele_remove ( frontier , & head -> slot , NULL , pool );
296
264
fd_forest_ancestry_ele_insert ( ancestry , head , pool );
297
265
while ( FD_LIKELY ( child ) ) { /* append children to frontier */
298
266
fd_forest_ancestry_ele_remove ( ancestry , & child -> slot , NULL , pool );
299
267
fd_forest_frontier_ele_insert ( frontier , child , pool );
300
- tail -> prev = fd_forest_pool_idx ( pool , child );
301
- tail = fd_forest_pool_ele ( pool , tail -> prev );
302
- tail -> prev = fd_forest_pool_idx_null ( pool );
303
- child = fd_forest_pool_ele ( pool , child -> sibling );
268
+
269
+ fd_forest_deque_push_tail ( queue , fd_forest_pool_idx ( pool , child ) );
270
+ child = fd_forest_pool_ele ( pool , child -> sibling );
304
271
}
305
272
}
306
- prev = head ;
307
- head = fd_forest_pool_ele ( pool , head -> prev );
308
- prev -> prev = null ;
309
273
}
310
274
}
311
275
@@ -330,7 +294,6 @@ acquire( fd_forest_t * forest, ulong slot ) {
330
294
ulong null = fd_forest_pool_idx_null ( pool );
331
295
332
296
ele -> slot = slot ;
333
- ele -> prev = null ;
334
297
ele -> next = null ;
335
298
ele -> parent = null ;
336
299
ele -> child = null ;
@@ -382,9 +345,6 @@ insert( fd_forest_t * forest, ulong slot, ushort parent_off ) {
382
345
383
346
fd_forest_ele_t *
384
347
fd_forest_query ( fd_forest_t * forest , ulong slot ) {
385
- # if FD_FOREST_USE_HANDHOLDING
386
- FD_TEST ( slot > fd_forest_root_slot ( forest ) ); /* caller error - inval */
387
- # endif
388
348
return query ( forest , slot );
389
349
}
390
350
@@ -433,50 +393,108 @@ fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) {
433
393
434
394
fd_forest_ancestry_t * ancestry = fd_forest_ancestry ( forest );
435
395
fd_forest_frontier_t * frontier = fd_forest_frontier ( forest );
396
+ fd_forest_orphaned_t * orphaned = fd_forest_orphaned ( forest );
436
397
fd_forest_ele_t * pool = fd_forest_pool ( forest );
437
398
ulong null = fd_forest_pool_idx_null ( pool );
399
+ ulong * queue = fd_forest_deque ( forest );
438
400
439
401
fd_forest_ele_t * old_root_ele = fd_forest_pool_ele ( pool , forest -> root );
440
- fd_forest_ele_t * new_root_ele = ancestry_frontier_query ( forest , new_root_slot );
402
+ fd_forest_ele_t * new_root_ele = query ( forest , new_root_slot );
441
403
442
- # if FD_FOREST_USE_HANDHOLDING
443
- FD_TEST ( new_root_ele ); /* caller error - not found */
444
- FD_TEST ( new_root_ele -> slot > old_root_ele -> slot ); /* caller error - inval */
445
- # endif
404
+ #if FD_FOREST_USE_HANDHOLDING
405
+ if ( FD_LIKELY ( new_root_ele ) ) {
406
+ FD_TEST ( new_root_ele -> slot > old_root_ele -> slot ); /* caller error - inval */
407
+ }
408
+ #endif
409
+
410
+ /* Edge case where if we haven't been getting repairs, and we have a
411
+ gap between the root and orphans. we publish forward to a slot that
412
+ we don't have. This only case this should be happening is when we
413
+ load a second incremental and that incremental slot lives in the
414
+ gap. In that case this isn't a bug, but we should be treating this
415
+ new root like the snapshot slot / init root. Should be happening
416
+ very rarely given a well-functioning repair. */
417
+
418
+ if ( FD_UNLIKELY ( !new_root_ele ) ) {
419
+ new_root_ele = acquire ( forest , new_root_slot );
420
+ new_root_ele -> complete_idx = 0 ;
421
+ new_root_ele -> buffered_idx = 0 ;
422
+ fd_forest_frontier_ele_insert ( frontier , new_root_ele , pool );
423
+ }
446
424
447
425
/* First, remove the previous root, and add it to a FIFO prune queue.
448
426
head points to the queue head (initialized with old_root_ele). */
449
-
427
+ #if FD_FOREST_USE_HANDHOLDING
428
+ FD_TEST ( fd_forest_deque_cnt ( queue ) == 0 );
429
+ #endif
450
430
fd_forest_ele_t * head = ancestry_frontier_remove ( forest , old_root_ele -> slot );
451
- head -> next = null ;
452
- fd_forest_ele_t * tail = head ;
431
+ if ( FD_LIKELY ( head ) ) fd_forest_deque_push_tail ( queue , fd_forest_pool_idx ( pool , head ) );
453
432
454
433
/* Second, BFS down the tree, inserting each ele into the prune queue
455
434
except for the new root. Loop invariant: head always descends from
456
435
old_root_ele and never descends from new_root_ele. */
457
436
458
- while ( head ) {
437
+ while ( FD_LIKELY ( fd_forest_deque_cnt ( queue ) ) ) {
438
+ head = fd_forest_pool_ele ( pool , fd_forest_deque_pop_head ( queue ) );
459
439
fd_forest_ele_t * child = fd_forest_pool_ele ( pool , head -> child );
460
440
while ( FD_LIKELY ( child ) ) {
461
441
if ( FD_LIKELY ( child != new_root_ele ) ) { /* do not prune new root or descendants */
462
- ulong idx = fd_forest_ancestry_idx_remove ( ancestry , & child -> slot , null , pool );
463
- idx = fd_ulong_if ( idx != null , idx , fd_forest_frontier_idx_remove ( frontier , & child -> slot , null , pool ) );
464
- tail -> next = idx ; /* insert prune queue */
465
- # if FD_FOREST_USE_HANDHOLDING
466
- FD_TEST ( tail -> next != null ); /* programming error in BFS */
467
- # endif
468
- tail = fd_forest_pool_ele ( pool , tail -> next ); /* advance prune queue */
469
- tail -> next = null ;
442
+ ulong idx = fd_forest_ancestry_idx_remove ( ancestry , & child -> slot , null , pool );
443
+ idx = fd_ulong_if ( idx != null , idx , fd_forest_frontier_idx_remove ( frontier , & child -> slot , null , pool ) );
444
+ fd_forest_deque_push_tail ( queue , idx );
470
445
}
471
446
child = fd_forest_pool_ele ( pool , child -> sibling );
472
447
}
473
- fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> next ); /* FIFO pop */
474
- fd_forest_pool_ele_release ( pool , head ); /* free head */
475
- head = next ;
448
+ fd_forest_pool_ele_release ( pool , head );
449
+ }
450
+
451
+ /* If there is nothing on the frontier, we have hit an edge case
452
+ during catching up where all of our frontiers were < the new root.
453
+ In that case we need to continue repairing from the new root, so
454
+ add it to the frontier. */
455
+
456
+ if ( FD_UNLIKELY ( fd_forest_frontier_iter_done ( fd_forest_frontier_iter_init ( frontier , pool ), frontier , pool ) ) ) {
457
+ fd_forest_ele_t * remove = fd_forest_ancestry_ele_remove ( ancestry , & new_root_ele -> slot , NULL , pool );
458
+ if ( FD_UNLIKELY ( !remove ) ) {
459
+ /* Very rare case where during second incremental load we could publish to an orphaned slot */
460
+ remove = fd_forest_orphaned_ele_remove ( orphaned , & new_root_ele -> slot , NULL , pool );
461
+ }
462
+ FD_TEST ( remove == new_root_ele );
463
+ fd_forest_frontier_ele_insert ( frontier , new_root_ele , pool );
464
+ new_root_ele -> complete_idx = 0 ;
465
+ new_root_ele -> buffered_idx = 0 ;
466
+ advance_frontier ( forest , new_root_ele -> slot , 0 );
476
467
}
477
468
478
469
new_root_ele -> parent = null ; /* unlink new root from parent */
479
- forest -> root = fd_forest_ancestry_idx_query ( ancestry , & new_root_slot , null , pool );
470
+ forest -> root = fd_forest_pool_idx ( pool , new_root_ele );
471
+
472
+ /* Lastly, cleanup orphans if there orphan heads < new_root_slot.
473
+ First, add any relevant orphans to the prune queue. */
474
+
475
+ for ( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init ( orphaned , pool );
476
+ !fd_forest_orphaned_iter_done ( iter , orphaned , pool );
477
+ iter = fd_forest_orphaned_iter_next ( iter , orphaned , pool ) ) {
478
+ fd_forest_ele_t * ele = fd_forest_orphaned_iter_ele ( iter , orphaned , pool );
479
+ if ( FD_UNLIKELY ( ele -> slot < new_root_slot ) ) {
480
+ fd_forest_deque_push_tail ( queue , fd_forest_pool_idx ( pool , ele ) );
481
+ }
482
+ }
483
+
484
+ /* Now BFS and clean up children of these orphan heads */
485
+ while ( FD_LIKELY ( fd_forest_deque_cnt ( queue ) ) ) {
486
+ head = fd_forest_pool_ele ( pool , fd_forest_deque_pop_head ( queue ) );
487
+ fd_forest_ele_t * child = fd_forest_pool_ele ( pool , head -> child );
488
+ while ( FD_LIKELY ( child ) ) {
489
+ if ( FD_LIKELY ( child != new_root_ele ) ) {
490
+ fd_forest_deque_push_tail ( queue , fd_forest_pool_idx ( pool , child ) );
491
+ }
492
+ child = fd_forest_pool_ele ( pool , child -> sibling );
493
+ }
494
+ ulong remove = fd_forest_orphaned_idx_remove ( orphaned , & head -> slot , null , pool ); /* remove myself */
495
+ remove = fd_ulong_if ( remove == null , fd_forest_ancestry_idx_remove ( ancestry , & head -> slot , null , pool ), remove );
496
+ fd_forest_pool_ele_release ( pool , head ); /* free head */
497
+ }
480
498
return new_root_ele ;
481
499
}
482
500
0 commit comments